From bcc5fd09563fde07893cd59b4d07d402727d6b10 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 8 Aug 2024 00:24:40 +0800 Subject: [PATCH 01/42] add hardofork HF_Echidna --- src/Neo.CLI/config.fs.mainnet.json | 3 ++- src/Neo.CLI/config.json | 3 ++- src/Neo.CLI/config.mainnet.json | 3 ++- src/Neo.CLI/config.testnet.json | 3 ++- src/Neo/Hardfork.cs | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index 6879232732..c12372b43b 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -40,7 +40,8 @@ "HF_Aspidochelone": 3000000, "HF_Basilisk": 4500000, "HF_Cockatrice": 5800000, - "HF_Domovoi": 5800000 + "HF_Domovoi": 5800000, + "HF_Echidna": 0 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 772a221714..9e97ec1f00 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, - "HF_Domovoi": 5570000 + "HF_Domovoi": 5570000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 772a221714..9e97ec1f00 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, - "HF_Domovoi": 5570000 + "HF_Domovoi": 5570000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index dc102be54b..88a04794f0 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 210000, "HF_Basilisk": 2680000, "HF_Cockatrice": 3967000, - "HF_Domovoi": 4144000 + "HF_Domovoi": 4144000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index 7bd3cc0aef..9276c9c07d 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -16,6 +16,7 @@ public enum Hardfork : byte HF_Aspidochelone, HF_Basilisk, HF_Cockatrice, - HF_Domovoi + HF_Domovoi, + HF_Echidna } } From e63f10b3ad7d96ab2cf456f7faf101724f406e17 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 8 Aug 2024 01:54:41 -0700 Subject: [PATCH 02/42] Add entries to `Designation` event (#3397) * Add entries to Designation event * Change to HF_Echidna * Add UT * Add count --- .../SmartContract/Native/RoleManagement.cs | 24 +++++++++++++++++-- .../SmartContract/Native/UT_NativeContract.cs | 18 ++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index d0437594c9..a0fc82f126 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -26,7 +26,16 @@ public sealed class RoleManagement : NativeContract { [ContractEvent(0, name: "Designation", "Role", ContractParameterType.Integer, - "BlockIndex", ContractParameterType.Integer)] + "BlockIndex", ContractParameterType.Integer, + Hardfork.HF_Echidna)] + + [ContractEvent(Hardfork.HF_Echidna, 0, name: "Designation", + "Role", ContractParameterType.Integer, + "BlockIndex", ContractParameterType.Integer, + "Old", ContractParameterType.Array, + "New", ContractParameterType.Array + )] + internal RoleManagement() : base() { } /// @@ -69,7 +78,18 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node list.AddRange(nodes); list.Sort(); engine.SnapshotCache.Add(key, new StorageItem(list)); - engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, new StackItem[] { (int)role, engine.PersistingBlock.Index })); + + if (engine.IsHardforkEnabled(Hardfork.HF_Echidna)) + { + var oldNodes = new VM.Types.Array(engine.ReferenceCounter, GetDesignatedByRole(engine.Snapshot, role, index - 1).Select(u => (ByteString)u.EncodePoint(true))); + var newNodes = new VM.Types.Array(engine.ReferenceCounter, nodes.Select(u => (ByteString)u.EncodePoint(true))); + + engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, [(int)role, engine.PersistingBlock.Index, oldNodes, newNodes])); + } + else + { + engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, [(int)role, engine.PersistingBlock.Index])); + } } private class NodeList : InteroperableList diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index f9a3089f9d..627a9fdd3c 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -80,6 +80,24 @@ public void TestActiveDeprecatedIn() Assert.IsFalse(NativeContract.IsActive(new active() { ActiveIn = null, DeprecatedIn = Hardfork.HF_Cockatrice }, settings.IsHardforkEnabled, 20)); } + [TestMethod] + public void TestActiveDeprecatedInRoleManagement() + { + string json = UT_ProtocolSettings.CreateHKSettings("\"HF_Echidna\": 20"); + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + var before = NativeContract.RoleManagement.GetContractState(settings.IsHardforkEnabled, 19); + var after = NativeContract.RoleManagement.GetContractState(settings.IsHardforkEnabled, 20); + + Assert.AreEqual(2, before.Manifest.Abi.Events[0].Parameters.Length); + Assert.AreEqual(1, before.Manifest.Abi.Events.Length); + Assert.AreEqual(4, after.Manifest.Abi.Events[0].Parameters.Length); + Assert.AreEqual(1, after.Manifest.Abi.Events.Length); + } + [TestMethod] public void TestGetContract() { From 9d16c53741e1373cd96226fe98008f9e46400ce6 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 9 Aug 2024 03:42:18 +0800 Subject: [PATCH 03/42] [Neo Core StdLib] Add Base64url (#3453) * add base64url * active in * update placehold hf height * fix hf issue and move methods to proper place. * fix test * use identifymodel instead. --- src/Neo.CLI/config.fs.mainnet.json | 2 +- src/Neo.CLI/config.json | 2 +- src/Neo.CLI/config.mainnet.json | 2 +- src/Neo.CLI/config.testnet.json | 2 +- src/Neo/Neo.csproj | 1 + src/Neo/SmartContract/Native/StdLib.cs | 23 +++++++++++++++++++ .../SmartContract/Native/UT_NativeContract.cs | 2 +- .../SmartContract/Native/UT_StdLib.cs | 20 ++++++++++++++++ 8 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index c12372b43b..bbd17978e3 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -41,7 +41,7 @@ "HF_Basilisk": 4500000, "HF_Cockatrice": 5800000, "HF_Domovoi": 5800000, - "HF_Echidna": 0 + "HF_Echidna": 5800001 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 9e97ec1f00..9590fc60fd 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -39,7 +39,7 @@ "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, "HF_Domovoi": 5570000, - "HF_Echidna": 0 + "HF_Echidna": 5570001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 9e97ec1f00..9590fc60fd 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -39,7 +39,7 @@ "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, "HF_Domovoi": 5570000, - "HF_Echidna": 0 + "HF_Echidna": 5570001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index 88a04794f0..8bcd6d8956 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -39,7 +39,7 @@ "HF_Basilisk": 2680000, "HF_Cockatrice": 3967000, "HF_Domovoi": 4144000, - "HF_Echidna": 0 + "HF_Echidna": 4144001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index 7c6478c33e..f4f2cb36b3 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Neo/SmartContract/Native/StdLib.cs b/src/Neo/SmartContract/Native/StdLib.cs index f8fa9efcc2..1b4030d9f5 100644 --- a/src/Neo/SmartContract/Native/StdLib.cs +++ b/src/Neo/SmartContract/Native/StdLib.cs @@ -11,6 +11,7 @@ #pragma warning disable IDE0051 +using Microsoft.IdentityModel.Tokens; using Neo.Cryptography; using Neo.Json; using Neo.VM.Types; @@ -131,6 +132,28 @@ public static byte[] Base64Decode([MaxLength(MaxInputLength)] string s) return Convert.FromBase64String(s); } + /// + /// Encodes a byte array into a base64Url string. + /// + /// The base64Url to be encoded. + /// The encoded base64Url string. + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)] + public static string Base64UrlEncode([MaxLength(MaxInputLength)] string data) + { + return Base64UrlEncoder.Encode(data); + } + + /// + /// Decodes a byte array from a base64Url string. + /// + /// The base64Url string. + /// The decoded base64Url string. + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)] + public static string Base64UrlDecode([MaxLength(MaxInputLength)] string s) + { + return Base64UrlEncoder.Decode(s); + } + /// /// Encodes a byte array into a base58 . /// diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 627a9fdd3c..26e00a3c2d 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -41,7 +41,7 @@ public void TestSetup() _nativeStates = new Dictionary { {"ContractManagement", """{"id":-1,"updatecounter":0,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getContractById","parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getContractHashes","parameters":[],"returntype":"InteropInterface","offset":35,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":42,"safe":true},{"name":"hasMethod","parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","offset":49,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":63,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":70,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}""" }, - {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":56,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":63,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":70,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":77,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":84,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":91,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":98,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":112,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":119,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":126,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":133,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":140,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2681632925},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"base64UrlDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"base64UrlEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":63,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":70,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":77,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":84,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":91,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":98,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":112,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":126,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":133,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":140,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":147,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":154,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"CryptoLib", """{"id":-3,"updatecounter":0,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"bls12381Add","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","offset":0,"safe":true},{"name":"bls12381Deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","offset":7,"safe":true},{"name":"bls12381Equal","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","offset":14,"safe":true},{"name":"bls12381Mul","parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"bls12381Pairing","parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","offset":28,"safe":true},{"name":"bls12381Serialize","parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","offset":35,"safe":true},{"name":"keccak256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"murmur32","parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","offset":49,"safe":true},{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":63,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","offset":70,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"LedgerContract", """{"id":-4,"updatecounter":0,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"LedgerContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"currentHash","parameters":[],"returntype":"Hash256","offset":0,"safe":true},{"name":"currentIndex","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getBlock","parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","offset":14,"safe":true},{"name":"getTransaction","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":21,"safe":true},{"name":"getTransactionFromBlock","parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getTransactionHeight","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":35,"safe":true},{"name":"getTransactionSigners","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":42,"safe":true},{"name":"getTransactionVMState","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":49,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"NeoToken", """{"id":-5,"updatecounter":0,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getAccountState","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","offset":14,"safe":true},{"name":"getAllCandidates","parameters":[],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"getCandidateVote","parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","offset":28,"safe":true},{"name":"getCandidates","parameters":[],"returntype":"Array","offset":35,"safe":true},{"name":"getCommittee","parameters":[],"returntype":"Array","offset":42,"safe":true},{"name":"getCommitteeAddress","parameters":[],"returntype":"Hash160","offset":49,"safe":true},{"name":"getGasPerBlock","parameters":[],"returntype":"Integer","offset":56,"safe":true},{"name":"getNextBlockValidators","parameters":[],"returntype":"Array","offset":63,"safe":true},{"name":"getRegisterPrice","parameters":[],"returntype":"Integer","offset":70,"safe":true},{"name":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":77,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":84,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":91,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":98,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":105,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":112,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":126,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":133,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs index 1dffb3d384..f9761bee07 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -406,5 +406,25 @@ public void TestRuntime_Deserialize() Assert.AreEqual(engine.ResultStack.Pop().GetInteger(), 100); Assert.AreEqual(engine.ResultStack.Pop().GetString(), "test"); } + + [TestMethod] + public void TestBase64Url() + { + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + using (var script = new ScriptBuilder()) + { + // Test encoding + script.EmitDynamicCall(NativeContract.StdLib.Hash, "base64UrlEncode", "Subject=test@example.com&Issuer=https://example.com"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "base64UrlDecode", "U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t"); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + Assert.AreEqual("Subject=test@example.com&Issuer=https://example.com", engine.ResultStack.Pop()); + Assert.AreEqual("U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t", engine.ResultStack.Pop().GetString()); + } + } } } From 9287c6663012ca4f6c4d2c97f2775872bb151116 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Sat, 28 Sep 2024 05:33:07 -0400 Subject: [PATCH 04/42] `[Move]` Part-5 Classes into Different Library - `Neo.Extensions` (#3409) * Part-1 `Neo.IO` - move * Part-2 * Added `BigInteger` to `Neo.Extensions` * Found more `BigInteger` * Added `ByteArray` to `Neo.Extensions` * Added `DateTime` Extensions to `Neo.Extensions` * Added `HashSetExtensions`, `HashSetExtensions2`, `IpAddressExtensions`, `AssemblyExtensions`, `StringExtensdions` Deleted `Helper.cs` file * Added `ICollection`, `Memory`, `String`, `Unsafe` extensions * Adding `using` * dotnet format * Added Tests * Added `tests` from `Part-2` * Added `tests` for `PART-4` * Added `tests` for `PART-5` * Made changes and fixes * Fixes * Apply suggestions from code review * Update tests/Neo.Extensions.Tests/UT_StringExtensions.cs * @shagron review changes * formating * Moved `UnsafeData` tests to `UT_UnsafeData` * Formating --------- Co-authored-by: Shargon Co-authored-by: Jimmy --- src/Neo.Extensions/StringExtensions.cs | 11 ++ src/Neo.Extensions/UnsafeData.cs | 31 +++ .../Collections/ICollectionExtensions.cs | 74 +++++++ src/Neo/Extensions/MemoryExtensions.cs | 60 ++++++ src/Neo/IO/Caching/ReflectionCache.cs | 1 + src/Neo/IO/Helper.cs | 118 ------------ src/Neo/Network/P2P/Message.cs | 1 + src/Neo/Network/P2P/Payloads/AddrPayload.cs | 1 + src/Neo/Network/P2P/Payloads/Block.cs | 1 + .../P2P/Payloads/Conditions/AndCondition.cs | 1 + .../P2P/Payloads/Conditions/OrCondition.cs | 1 + .../Network/P2P/Payloads/ExtensiblePayload.cs | 1 + .../Network/P2P/Payloads/FilterAddPayload.cs | 1 + .../Network/P2P/Payloads/FilterLoadPayload.cs | 1 + .../Network/P2P/Payloads/HeadersPayload.cs | 1 + src/Neo/Network/P2P/Payloads/InvPayload.cs | 1 + .../P2P/Payloads/MerkleBlockPayload.cs | 1 + .../Network/P2P/Payloads/OracleResponse.cs | 1 + src/Neo/Network/P2P/Payloads/Signer.cs | 1 + src/Neo/Network/P2P/Payloads/Transaction.cs | 1 + src/Neo/Network/P2P/Payloads/Witness.cs | 1 + src/Neo/SmartContract/MethodToken.cs | 1 + .../SmartContract/Native/LedgerContract.cs | 1 + src/Neo/SmartContract/Native/NeoToken.cs | 1 + .../SmartContract/Native/TransactionState.cs | 1 + src/Neo/SmartContract/Native/TrimmedBlock.cs | 1 + src/Neo/SmartContract/NefFile.cs | 1 + src/Neo/Wallets/Helper.cs | 5 +- .../Store/States/BlockLogState.cs | 2 +- .../Store/States/EngineLogState.cs | 1 + .../Store/States/ExecutionLogState.cs | 1 + .../Store/States/NotifyLogState.cs | 2 +- .../Store/States/TransactionEngineLogState.cs | 1 + .../Store/States/TransactionLogState.cs | 1 + .../Consensus/ConsensusContext.Get.cs | 3 +- .../DBFTPlugin/Messages/ConsensusMessage.cs | 1 + .../DBFTPlugin/Messages/PrepareRequest.cs | 1 + ...ecoveryMessage.ChangeViewPayloadCompact.cs | 1 + .../RecoveryMessage.CommitPayloadCompact.cs | 1 + ...coveryMessage.PreparationPayloadCompact.cs | 1 + .../RecoveryMessage/RecoveryMessage.cs | 1 + .../Cryptography/MPTTrie/Node.Extension.cs | 1 + .../MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs | 1 + .../MPTTrie/Cryptography/MPTTrie/Node.cs | 7 +- src/Plugins/OracleService/OracleService.cs | 4 +- .../RpcServer/RpcServer.SmartContract.cs | 3 +- .../SQLiteWallet/VerificationContract.cs | 1 + src/Plugins/StateService/Network/Vote.cs | 1 + .../StateService/Storage/StateStore.cs | 1 + .../Verification/VerificationService.cs | 1 + src/Plugins/TokensTracker/Extensions.cs | 5 +- .../UT_StringExtensions.cs | 7 + tests/Neo.Extensions.Tests/UT_UnsafeData.cs | 163 ++++++++++++++++ tests/Neo.UnitTests/IO/UT_IOHelper.cs | 181 +----------------- .../P2P/Payloads/UT_NetworkAddressWithTime.cs | 1 + .../Network/P2P/Payloads/UT_VersionPayload.cs | 1 + tests/Neo.UnitTests/TestUtils.Transaction.cs | 1 + 57 files changed, 410 insertions(+), 307 deletions(-) create mode 100644 src/Neo.Extensions/UnsafeData.cs create mode 100644 src/Neo/Extensions/Collections/ICollectionExtensions.cs create mode 100644 src/Neo/Extensions/MemoryExtensions.cs create mode 100644 tests/Neo.Extensions.Tests/UT_UnsafeData.cs diff --git a/src/Neo.Extensions/StringExtensions.cs b/src/Neo.Extensions/StringExtensions.cs index 8d851bf905..b84869b2e1 100644 --- a/src/Neo.Extensions/StringExtensions.cs +++ b/src/Neo.Extensions/StringExtensions.cs @@ -32,5 +32,16 @@ public static byte[] HexToBytes(this string value) result[i] = byte.Parse(value.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier); return result; } + + /// + /// Gets the size of the specified encoded in variable-length encoding. + /// + /// The specified . + /// The size of the . + public static int GetVarSize(this string value) + { + var size = Utility.StrictUTF8.GetByteCount(value); + return UnsafeData.GetVarSize(size) + size; + } } } diff --git a/src/Neo.Extensions/UnsafeData.cs b/src/Neo.Extensions/UnsafeData.cs new file mode 100644 index 0000000000..6d4c9a6f8e --- /dev/null +++ b/src/Neo.Extensions/UnsafeData.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UnsafeData.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Extensions +{ + public static class UnsafeData + { + /// + /// Gets the size of variable-length of the data. + /// + /// The length of the data. + /// The size of variable-length of the data. + public static int GetVarSize(int value) + { + if (value < 0xFD) + return sizeof(byte); + else if (value <= 0xFFFF) + return sizeof(byte) + sizeof(ushort); + else + return sizeof(byte) + sizeof(uint); + } + } +} diff --git a/src/Neo/Extensions/Collections/ICollectionExtensions.cs b/src/Neo/Extensions/Collections/ICollectionExtensions.cs new file mode 100644 index 0000000000..724def6d80 --- /dev/null +++ b/src/Neo/Extensions/Collections/ICollectionExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ICollectionExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Neo.Extensions +{ + public static class ICollectionExtensions + { + /// + /// Gets the size of the specified array encoded in variable-length encoding. + /// + /// The type of the array element. + /// The specified array. + /// The size of the array. + public static int GetVarSize(this IReadOnlyCollection value) + { + int value_size; + var t = typeof(T); + if (typeof(ISerializable).IsAssignableFrom(t)) + { + value_size = value.OfType().Sum(p => p.Size); + } + else if (t.GetTypeInfo().IsEnum) + { + int element_size; + var u = t.GetTypeInfo().GetEnumUnderlyingType(); + if (u == typeof(sbyte) || u == typeof(byte)) + element_size = 1; + else if (u == typeof(short) || u == typeof(ushort)) + element_size = 2; + else if (u == typeof(int) || u == typeof(uint)) + element_size = 4; + else //if (u == typeof(long) || u == typeof(ulong)) + element_size = 8; + value_size = value.Count * element_size; + } + else + { + value_size = value.Count * Marshal.SizeOf(); + } + return UnsafeData.GetVarSize(value.Count) + value_size; + } + + /// + /// Converts an array to a byte array. + /// + /// The type of the array element. + /// The array to be converted. + /// The converted byte array. + public static byte[] ToByteArray(this IReadOnlyCollection value) + where T : ISerializable + { + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + writer.Write(value); + writer.Flush(); + return ms.ToArray(); + } + } +} diff --git a/src/Neo/Extensions/MemoryExtensions.cs b/src/Neo/Extensions/MemoryExtensions.cs new file mode 100644 index 0000000000..f6576387de --- /dev/null +++ b/src/Neo/Extensions/MemoryExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.Reflection; + +namespace Neo.Extensions +{ + public static class MemoryExtensions + { + /// + /// Converts a byte array to an object. + /// + /// The type to convert to. + /// The byte array to be converted. + /// The converted object. + public static T AsSerializable(this ReadOnlyMemory value) + where T : ISerializable, new() + { + if (value.IsEmpty) throw new FormatException(); + MemoryReader reader = new(value); + return reader.ReadSerializable(); + } + + /// + /// Converts a byte array to an object. + /// + /// The byte array to be converted. + /// The type to convert to. + /// The converted object. + public static ISerializable AsSerializable(this ReadOnlyMemory value, Type type) + { + if (!typeof(ISerializable).GetTypeInfo().IsAssignableFrom(type)) + throw new InvalidCastException(); + var serializable = (ISerializable)Activator.CreateInstance(type); + MemoryReader reader = new(value); + serializable.Deserialize(ref reader); + return serializable; + } + + /// + /// Gets the size of the specified array encoded in variable-length encoding. + /// + /// The specified array. + /// The size of the array. + public static int GetVarSize(this ReadOnlyMemory value) + { + return UnsafeData.GetVarSize(value.Length) + value.Length; + } + } +} diff --git a/src/Neo/IO/Caching/ReflectionCache.cs b/src/Neo/IO/Caching/ReflectionCache.cs index 5007b67740..f24f4e5ab6 100644 --- a/src/Neo/IO/Caching/ReflectionCache.cs +++ b/src/Neo/IO/Caching/ReflectionCache.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using System; using System.Collections.Generic; using System.Reflection; diff --git a/src/Neo/IO/Helper.cs b/src/Neo/IO/Helper.cs index 2e6d362ee1..d0e5a00ec9 100644 --- a/src/Neo/IO/Helper.cs +++ b/src/Neo/IO/Helper.cs @@ -14,9 +14,6 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; namespace Neo.IO { @@ -38,35 +35,6 @@ public static class Helper return reader.ReadSerializable(); } - /// - /// Converts a byte array to an object. - /// - /// The type to convert to. - /// The byte array to be converted. - /// The converted object. - public static T AsSerializable(this ReadOnlyMemory value) where T : ISerializable, new() - { - if (value.IsEmpty) throw new FormatException(); - MemoryReader reader = new(value); - return reader.ReadSerializable(); - } - - /// - /// Converts a byte array to an object. - /// - /// The byte array to be converted. - /// The type to convert to. - /// The converted object. - public static ISerializable AsSerializable(this ReadOnlyMemory value, Type type) - { - if (!typeof(ISerializable).GetTypeInfo().IsAssignableFrom(type)) - throw new InvalidCastException(); - ISerializable serializable = (ISerializable)Activator.CreateInstance(type); - MemoryReader reader = new(value); - serializable.Deserialize(ref reader); - return serializable; - } - /// /// Converts a byte array to an array. /// @@ -124,77 +92,6 @@ public static byte[] DecompressLz4(this ReadOnlySpan data, int maxOutput) return result; } - /// - /// Gets the size of variable-length of the data. - /// - /// The length of the data. - /// The size of variable-length of the data. - public static int GetVarSize(int value) - { - if (value < 0xFD) - return sizeof(byte); - else if (value <= 0xFFFF) - return sizeof(byte) + sizeof(ushort); - else - return sizeof(byte) + sizeof(uint); - } - - /// - /// Gets the size of the specified array encoded in variable-length encoding. - /// - /// The type of the array element. - /// The specified array. - /// The size of the array. - public static int GetVarSize(this IReadOnlyCollection value) - { - int value_size; - Type t = typeof(T); - if (typeof(ISerializable).IsAssignableFrom(t)) - { - value_size = value.OfType().Sum(p => p.Size); - } - else if (t.GetTypeInfo().IsEnum) - { - int element_size; - Type u = t.GetTypeInfo().GetEnumUnderlyingType(); - if (u == typeof(sbyte) || u == typeof(byte)) - element_size = 1; - else if (u == typeof(short) || u == typeof(ushort)) - element_size = 2; - else if (u == typeof(int) || u == typeof(uint)) - element_size = 4; - else //if (u == typeof(long) || u == typeof(ulong)) - element_size = 8; - value_size = value.Count * element_size; - } - else - { - value_size = value.Count * Marshal.SizeOf(); - } - return GetVarSize(value.Count) + value_size; - } - - /// - /// Gets the size of the specified array encoded in variable-length encoding. - /// - /// The specified array. - /// The size of the array. - public static int GetVarSize(this ReadOnlyMemory value) - { - return GetVarSize(value.Length) + value.Length; - } - - /// - /// Gets the size of the specified encoded in variable-length encoding. - /// - /// The specified . - /// The size of the . - public static int GetVarSize(this string value) - { - int size = Utility.StrictUTF8.GetByteCount(value); - return GetVarSize(size) + size; - } - /// /// Reads a byte array of the specified size from a . /// @@ -315,21 +212,6 @@ public static byte[] ToArray(this ISerializable value) return ms.ToArray(); } - /// - /// Converts an array to a byte array. - /// - /// The type of the array element. - /// The array to be converted. - /// The converted byte array. - public static byte[] ToByteArray(this IReadOnlyCollection value) where T : ISerializable - { - using MemoryStream ms = new(); - using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); - writer.Write(value); - writer.Flush(); - return ms.ToArray(); - } - /// /// Writes an object into a . /// diff --git a/src/Neo/Network/P2P/Message.cs b/src/Neo/Network/P2P/Message.cs index 9d3c63a85b..96c79f2b10 100644 --- a/src/Neo/Network/P2P/Message.cs +++ b/src/Neo/Network/P2P/Message.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.IO; +using Neo.Extensions; using Neo.IO; using Neo.IO.Caching; using System; diff --git a/src/Neo/Network/P2P/Payloads/AddrPayload.cs b/src/Neo/Network/P2P/Payloads/AddrPayload.cs index ebdceb65ec..90958a38ef 100644 --- a/src/Neo/Network/P2P/Payloads/AddrPayload.cs +++ b/src/Neo/Network/P2P/Payloads/AddrPayload.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/Block.cs b/src/Neo/Network/P2P/Payloads/Block.cs index 3c727b9886..ab28c6c363 100644 --- a/src/Neo/Network/P2P/Payloads/Block.cs +++ b/src/Neo/Network/P2P/Payloads/Block.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Ledger; diff --git a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs index e395b10d5d..2fed4d32fb 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.SmartContract; diff --git a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs index b06fc922a8..72b27529c4 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.SmartContract; diff --git a/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs b/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs index 306f6327ab..6e2b94a17d 100644 --- a/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs +++ b/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using Neo.SmartContract; diff --git a/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs b/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs index ff4aa1287b..4f4e9df152 100644 --- a/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs +++ b/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs b/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs index 2608e0e7b0..f0eae27278 100644 --- a/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs +++ b/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/HeadersPayload.cs b/src/Neo/Network/P2P/Payloads/HeadersPayload.cs index 152ba32857..e5800d4544 100644 --- a/src/Neo/Network/P2P/Payloads/HeadersPayload.cs +++ b/src/Neo/Network/P2P/Payloads/HeadersPayload.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/InvPayload.cs b/src/Neo/Network/P2P/Payloads/InvPayload.cs index aa4d340d99..17625f6dc4 100644 --- a/src/Neo/Network/P2P/Payloads/InvPayload.cs +++ b/src/Neo/Network/P2P/Payloads/InvPayload.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.Collections.Generic; diff --git a/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs b/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs index aebb17d5d9..97f9ed0f27 100644 --- a/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs +++ b/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using System; using System.Collections; diff --git a/src/Neo/Network/P2P/Payloads/OracleResponse.cs b/src/Neo/Network/P2P/Payloads/OracleResponse.cs index 2770f556a8..bf3aa70cf4 100644 --- a/src/Neo/Network/P2P/Payloads/OracleResponse.cs +++ b/src/Neo/Network/P2P/Payloads/OracleResponse.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Persistence; diff --git a/src/Neo/Network/P2P/Payloads/Signer.cs b/src/Neo/Network/P2P/Payloads/Signer.cs index 79065408f4..9b5e317abc 100644 --- a/src/Neo/Network/P2P/Payloads/Signer.cs +++ b/src/Neo/Network/P2P/Payloads/Signer.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads.Conditions; diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index b922674d91..6f41b5141b 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -11,6 +11,7 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Ledger; diff --git a/src/Neo/Network/P2P/Payloads/Witness.cs b/src/Neo/Network/P2P/Payloads/Witness.cs index 34932cc7fb..b3da47d2b7 100644 --- a/src/Neo/Network/P2P/Payloads/Witness.cs +++ b/src/Neo/Network/P2P/Payloads/Witness.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.SmartContract; diff --git a/src/Neo/SmartContract/MethodToken.cs b/src/Neo/SmartContract/MethodToken.cs index 1b391edd32..1b8fa00242 100644 --- a/src/Neo/SmartContract/MethodToken.cs +++ b/src/Neo/SmartContract/MethodToken.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Json; using System; diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index 329e0565d5..3d2d16e023 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -11,6 +11,7 @@ #pragma warning disable IDE0051 +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index 40f63355df..d639a55caf 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -12,6 +12,7 @@ #pragma warning disable IDE0051 using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using Neo.SmartContract.Iterators; diff --git a/src/Neo/SmartContract/Native/TransactionState.cs b/src/Neo/SmartContract/Native/TransactionState.cs index b17296b42d..050358f179 100644 --- a/src/Neo/SmartContract/Native/TransactionState.cs +++ b/src/Neo/SmartContract/Native/TransactionState.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.VM; diff --git a/src/Neo/SmartContract/Native/TrimmedBlock.cs b/src/Neo/SmartContract/Native/TrimmedBlock.cs index 4cc4c39c0f..c2bab2567c 100644 --- a/src/Neo/SmartContract/Native/TrimmedBlock.cs +++ b/src/Neo/SmartContract/Native/TrimmedBlock.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.VM; diff --git a/src/Neo/SmartContract/NefFile.cs b/src/Neo/SmartContract/NefFile.cs index 3e343d3be2..5638447688 100644 --- a/src/Neo/SmartContract/NefFile.cs +++ b/src/Neo/SmartContract/NefFile.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.VM; diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 5beae49ab9..9f24e54764 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -93,7 +94,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); // base size for transaction: includes const_header + signers + attributes + script + hashes - int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1; + int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + UnsafeData.GetVarSize(hashes.Length), index = -1; uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); long networkFee = 0; foreach (UInt160 hash in hashes) @@ -152,7 +153,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, else if (IsMultiSigContract(witnessScript, out int m, out int n)) { int size_inv = 66 * m; - size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); + size += UnsafeData.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); } // We can support more contract types in the future. diff --git a/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs b/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs index 54369a672a..ce957b79f6 100644 --- a/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs @@ -9,7 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo; +using Neo.Extensions; using Neo.IO; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs b/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs index 96f9041aaf..4a997757f5 100644 --- a/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs b/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs index 210ac36283..ace7f0e42f 100644 --- a/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.VM; diff --git a/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs b/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs index 70b53268e5..87cc5e60ae 100644 --- a/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs @@ -9,7 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo; +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; diff --git a/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs b/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs index 9417f984e2..80e5be7e97 100644 --- a/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs b/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs index 1667478509..bca012ec50 100644 --- a/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs index edffc1cb09..d055ddbb4a 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Network.P2P.Payloads; using Neo.Plugins.DBFTPlugin.Messages; using Neo.SmartContract; @@ -110,7 +111,7 @@ internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions) sizeof(byte) + // PrimaryIndex UInt160.Length + // NextConsensus 1 + _witnessSize + // Witness - IO.Helper.GetVarSize(expectedTransactions); + UnsafeData.GetVarSize(expectedTransactions); } } } diff --git a/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs b/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs index de030d166d..f8cd8cbc14 100644 --- a/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs +++ b/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Plugins.DBFTPlugin.Types; using System; diff --git a/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs b/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs index 495ccbd726..c5b150b572 100644 --- a/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs +++ b/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Plugins.DBFTPlugin.Types; using System; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs index 2d7283cd22..40528d8aaf 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs index 6d3880f3c0..dc7d532d30 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs index 8fae1596ef..cec8a45a3a 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs index 2de33470ea..2feb919520 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Plugins.DBFTPlugin.Consensus; diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs index 510db49250..0d68564732 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; using System; diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs index 024a07f8c8..b1b7707729 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; using System; diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs index ef45548645..2e20f03a9d 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; @@ -31,11 +32,11 @@ public int Size switch (type) { case NodeType.BranchNode: - return size + BranchSize + IO.Helper.GetVarSize(Reference); + return size + BranchSize + UnsafeData.GetVarSize(Reference); case NodeType.ExtensionNode: - return size + ExtensionSize + IO.Helper.GetVarSize(Reference); + return size + ExtensionSize + UnsafeData.GetVarSize(Reference); case NodeType.LeafNode: - return size + LeafSize + IO.Helper.GetVarSize(Reference); + return size + LeafSize + UnsafeData.GetVarSize(Reference); case NodeType.HashNode: return size + HashSize; case NodeType.Empty: diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs index fbdee148db..f17df8167c 100644 --- a/src/Plugins/OracleService/OracleService.cs +++ b/src/Plugins/OracleService/OracleService.cs @@ -446,8 +446,8 @@ public static Transaction CreateResponseTx(DataCache snapshot, OracleRequest req int size_inv = 66 * m; int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Script.GetVarSize() - + IO.Helper.GetVarSize(hashes.Length) + witnessDict[NativeContract.Oracle.Hash].Size - + IO.Helper.GetVarSize(size_inv) + size_inv + oracleSignContract.Script.GetVarSize(); + + UnsafeData.GetVarSize(hashes.Length) + witnessDict[NativeContract.Oracle.Hash].Size + + UnsafeData.GetVarSize(size_inv) + size_inv + oracleSignContract.Script.GetVarSize(); var feePerByte = NativeContract.Policy.GetFeePerByte(snapshot); if (response.Result.Length > OracleResponse.MaxResultSize) diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index a3de939f4b..11e1218e57 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; @@ -189,7 +190,7 @@ private static Signer[] SignersFromJson(JArray _params, ProtocolSettings setting // Validate format - _ = IO.Helper.ToByteArray(ret).AsSerializableArray(); + _ = ret.ToByteArray().AsSerializableArray(); return ret; } diff --git a/src/Plugins/SQLiteWallet/VerificationContract.cs b/src/Plugins/SQLiteWallet/VerificationContract.cs index e128045adb..2f62a7174b 100644 --- a/src/Plugins/SQLiteWallet/VerificationContract.cs +++ b/src/Plugins/SQLiteWallet/VerificationContract.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; diff --git a/src/Plugins/StateService/Network/Vote.cs b/src/Plugins/StateService/Network/Vote.cs index e6840c7a9f..bba40f6a54 100644 --- a/src/Plugins/StateService/Network/Vote.cs +++ b/src/Plugins/StateService/Network/Vote.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Plugins/StateService/Storage/StateStore.cs b/src/Plugins/StateService/Storage/StateStore.cs index f2ca6d7d05..beb19818bb 100644 --- a/src/Plugins/StateService/Storage/StateStore.cs +++ b/src/Plugins/StateService/Storage/StateStore.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.Actor; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/src/Plugins/StateService/Verification/VerificationService.cs b/src/Plugins/StateService/Verification/VerificationService.cs index a8eb9fd030..3d77bc3501 100644 --- a/src/Plugins/StateService/Verification/VerificationService.cs +++ b/src/Plugins/StateService/Verification/VerificationService.cs @@ -11,6 +11,7 @@ using Akka.Actor; using Akka.Util.Internal; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/src/Plugins/TokensTracker/Extensions.cs b/src/Plugins/TokensTracker/Extensions.cs index 7805056280..83b4371435 100644 --- a/src/Plugins/TokensTracker/Extensions.cs +++ b/src/Plugins/TokensTracker/Extensions.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using Neo.VM.Types; @@ -33,13 +34,13 @@ public static string ToBase64(this ReadOnlySpan item) public static int GetVarSize(this ByteString item) { var length = item.GetSpan().Length; - return IO.Helper.GetVarSize(length) + length; + return UnsafeData.GetVarSize(length) + length; } public static int GetVarSize(this BigInteger item) { var length = item.GetByteCount(); - return IO.Helper.GetVarSize(length) + length; + return UnsafeData.GetVarSize(length) + length; } public static IEnumerable<(TKey, TValue)> FindPrefix(this IStore db, byte[] prefix) diff --git a/tests/Neo.Extensions.Tests/UT_StringExtensions.cs b/tests/Neo.Extensions.Tests/UT_StringExtensions.cs index 9954f24765..6e823d2493 100644 --- a/tests/Neo.Extensions.Tests/UT_StringExtensions.cs +++ b/tests/Neo.Extensions.Tests/UT_StringExtensions.cs @@ -35,5 +35,12 @@ public void TestHexToBytes() byte[] bytes = str2.HexToBytes(); bytes.ToHexString().Should().Be(new byte[] { 0x01, 0x02 }.ToHexString()); } + + [TestMethod] + public void TestGetVarSizeString() + { + int result = "AA".GetVarSize(); + Assert.AreEqual(3, result); + } } } diff --git a/tests/Neo.Extensions.Tests/UT_UnsafeData.cs b/tests/Neo.Extensions.Tests/UT_UnsafeData.cs new file mode 100644 index 0000000000..5c8bb310c3 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_UnsafeData.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_UnsafeData + { + [TestMethod] + public void TestGetVarSizeInt() + { + for (int i = 0; i < 3; i++) + { + if (i == 0) + { + int result = UnsafeData.GetVarSize(1); + Assert.AreEqual(1, result); + } + else if (i == 1) + { + int result = UnsafeData.GetVarSize(0xFFFF); + Assert.AreEqual(3, result); + } + else + { + int result = UnsafeData.GetVarSize(0xFFFFFF); + Assert.AreEqual(5, result); + } + } + } + + [TestMethod] + public void TestGetVarSizeGeneric() + { + for (int i = 0; i < 9; i++) + { + if (i == 0) + { + int result = new UInt160[] { UInt160.Zero }.GetVarSize(); + Assert.AreEqual(21, result); + } + else if (i == 1)//sbyte + { + List initList = new() + { + TestEnum0.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(2, result); + } + else if (i == 2)//byte + { + List initList = new() + { + TestEnum1.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(2, result); + } + else if (i == 3)//short + { + List initList = new() + { + TestEnum2.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(3, result); + } + else if (i == 4)//ushort + { + List initList = new() + { + TestEnum3.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(3, result); + } + else if (i == 5)//int + { + List initList = new() + { + TestEnum4.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(5, result); + } + else if (i == 6)//uint + { + List initList = new() + { + TestEnum5.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(5, result); + } + else if (i == 7)//long + { + List initList = new() + { + TestEnum6.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(9, result); + } + else if (i == 8) + { + List initList = new() + { + 1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(5, result); + } + } + } + + enum TestEnum0 : sbyte + { + case1 = 1, case2 = 2 + } + + enum TestEnum1 : byte + { + case1 = 1, case2 = 2 + } + + enum TestEnum2 : short + { + case1 = 1, case2 = 2 + } + + enum TestEnum3 : ushort + { + case1 = 1, case2 = 2 + } + + enum TestEnum4 : int + { + case1 = 1, case2 = 2 + } + + enum TestEnum5 : uint + { + case1 = 1, case2 = 2 + } + + enum TestEnum6 : long + { + case1 = 1, case2 = 2 + } + } +} diff --git a/tests/Neo.UnitTests/IO/UT_IOHelper.cs b/tests/Neo.UnitTests/IO/UT_IOHelper.cs index 48453c992a..d9d753e292 100644 --- a/tests/Neo.UnitTests/IO/UT_IOHelper.cs +++ b/tests/Neo.UnitTests/IO/UT_IOHelper.cs @@ -110,23 +110,9 @@ public void TestNullableArray() [TestMethod] public void TestAsSerializable() { - for (int i = 0; i < 2; i++) - { - if (i == 0) - { - byte[] caseArray = new byte[] { 0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00 }; - ISerializable result = Neo.IO.Helper.AsSerializable(caseArray, typeof(UInt160)); - Assert.AreEqual(UInt160.Zero, result); - } - else - { - Action action = () => Neo.IO.Helper.AsSerializable(Array.Empty(), typeof(double)); - action.Should().Throw(); - } - } + byte[] caseArray = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + ISerializable result = caseArray.AsSerializable(); + Assert.AreEqual(UInt160.Zero, result); } [TestMethod] @@ -164,169 +150,12 @@ public void TestCompression() [TestMethod] public void TestAsSerializableArray() { - byte[] byteArray = Neo.IO.Helper.ToByteArray(new UInt160[] { UInt160.Zero }); + byte[] byteArray = new UInt160[] { UInt160.Zero }.ToByteArray(); UInt160[] result = Neo.IO.Helper.AsSerializableArray(byteArray); Assert.AreEqual(1, result.Length); Assert.AreEqual(UInt160.Zero, result[0]); } - [TestMethod] - public void TestGetVarSizeInt() - { - for (int i = 0; i < 3; i++) - { - if (i == 0) - { - int result = Neo.IO.Helper.GetVarSize(1); - Assert.AreEqual(1, result); - } - else if (i == 1) - { - int result = Neo.IO.Helper.GetVarSize(0xFFFF); - Assert.AreEqual(3, result); - } - else - { - int result = Neo.IO.Helper.GetVarSize(0xFFFFFF); - Assert.AreEqual(5, result); - } - } - } - enum TestEnum0 : sbyte - { - case1 = 1, case2 = 2 - } - - enum TestEnum1 : byte - { - case1 = 1, case2 = 2 - } - - enum TestEnum2 : short - { - case1 = 1, case2 = 2 - } - - enum TestEnum3 : ushort - { - case1 = 1, case2 = 2 - } - - enum TestEnum4 : int - { - case1 = 1, case2 = 2 - } - - enum TestEnum5 : uint - { - case1 = 1, case2 = 2 - } - - enum TestEnum6 : long - { - case1 = 1, case2 = 2 - } - - [TestMethod] - public void TestGetVarSizeGeneric() - { - for (int i = 0; i < 9; i++) - { - if (i == 0) - { - int result = Neo.IO.Helper.GetVarSize(new UInt160[] { UInt160.Zero }); - Assert.AreEqual(21, result); - } - else if (i == 1)//sbyte - { - List initList = new() - { - TestEnum0.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(2, result); - } - else if (i == 2)//byte - { - List initList = new() - { - TestEnum1.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(2, result); - } - else if (i == 3)//short - { - List initList = new() - { - TestEnum2.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(3, result); - } - else if (i == 4)//ushort - { - List initList = new() - { - TestEnum3.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(3, result); - } - else if (i == 5)//int - { - List initList = new() - { - TestEnum4.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(5, result); - } - else if (i == 6)//uint - { - List initList = new() - { - TestEnum5.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(5, result); - } - else if (i == 7)//long - { - List initList = new() - { - TestEnum6.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(9, result); - } - else if (i == 8) - { - List initList = new() - { - 1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(5, result); - } - } - } - - [TestMethod] - public void TestGetVarSizeString() - { - int result = Neo.IO.Helper.GetVarSize("AA"); - Assert.AreEqual(3, result); - } - [TestMethod] public void TestReadSerializable() { @@ -413,7 +242,7 @@ public void TestToArray() [TestMethod] public void TestToByteArrayGeneric() { - byte[] byteArray = Neo.IO.Helper.ToByteArray(new UInt160[] { UInt160.Zero }); + byte[] byteArray = new UInt160[] { UInt160.Zero }.ToByteArray(); Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs index 2ed86e4da7..3e2c6c336c 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs index a77740a1e5..66bebf68d8 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.UnitTests/TestUtils.Transaction.cs b/tests/Neo.UnitTests/TestUtils.Transaction.cs index 12e9b4ec0e..70ac264a19 100644 --- a/tests/Neo.UnitTests/TestUtils.Transaction.cs +++ b/tests/Neo.UnitTests/TestUtils.Transaction.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; +using Neo.Cryptography.ECC; using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; From 33d0913bc4059a6645bd8fe18101bdb512e332c2 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Sun, 29 Sep 2024 17:14:52 +0800 Subject: [PATCH 05/42] fix: always throw `DivideByZeroException ` when `BloomFilter` is empty (#3502) * fix: always throw divided by zero when BloomFilter is empty * fix: always throw divided by zero when BloomFilter is empty --- src/Neo/Cryptography/BloomFilter.cs | 14 ++++++++------ tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs | 11 +++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Neo/Cryptography/BloomFilter.cs b/src/Neo/Cryptography/BloomFilter.cs index 73141d9e11..30cab45392 100644 --- a/src/Neo/Cryptography/BloomFilter.cs +++ b/src/Neo/Cryptography/BloomFilter.cs @@ -41,12 +41,13 @@ public class BloomFilter /// /// Initializes a new instance of the class. /// - /// The size of the bit array used by the bloom filter. - /// The number of hash functions used by the bloom filter. + /// The size of the bit array used by the bloom filter, and must be greater than 0. + /// The number of hash functions used by the bloom filter, and must be greater than 0. /// Used to generate the seeds of the murmur hash functions. + /// Thrown when or is less than or equal to 0. public BloomFilter(int m, int k, uint nTweak) { - if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); + if (k <= 0 || m <= 0) throw new ArgumentOutOfRangeException(); seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); bits = new BitArray(m) { @@ -58,13 +59,14 @@ public BloomFilter(int m, int k, uint nTweak) /// /// Initializes a new instance of the class. /// - /// The size of the bit array used by the bloom filter. - /// The number of hash functions used by the bloom filter. + /// The size of the bit array used by the bloom filter, and must be greater than 0. + /// The number of hash functions used by the bloom filter, and must be greater than 0. /// Used to generate the seeds of the murmur hash functions. /// The initial elements contained in this object. + /// Thrown when or is less than or equal to 0. public BloomFilter(int m, int k, uint nTweak, ReadOnlyMemory elements) { - if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); + if (k <= 0 || m <= 0) throw new ArgumentOutOfRangeException(); seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); bits = new BitArray(elements.ToArray()) { diff --git a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs index f7bc1789dd..8693157724 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs @@ -78,5 +78,16 @@ public void TestGetBits() foreach (byte value in result) value.Should().Be(0); } + + [TestMethod] + public void TestInvalidArguments() + { + uint nTweak = 123456; + Action action = () => new BloomFilter(0, 3, nTweak); + action.Should().Throw(); + + action = () => new BloomFilter(3, 0, nTweak); + action.Should().Throw(); + } } } From 6e74e33a765e21a8becc556a609c29d15b590e13 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Mon, 30 Sep 2024 16:45:05 +0800 Subject: [PATCH 06/42] fix: concurrency conflict in MemPool.TryRemoveUnVerified (#3500) * fix: concurrency conflict in MemPool.TryRemoveUnVerified * Remove method * Update src/Neo/Ledger/MemoryPool.cs * clean * Apply suggestions from code review * reformat --------- Co-authored-by: Shargon Co-authored-by: Jimmy --- src/Neo/Ledger/MemoryPool.cs | 18 +++++++++++++----- tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs index 8f9437641b..badef9d3a0 100644 --- a/src/Neo/Ledger/MemoryPool.cs +++ b/src/Neo/Ledger/MemoryPool.cs @@ -445,12 +445,20 @@ private void RemoveConflictsOfVerified(PoolItem item) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryRemoveUnVerified(UInt256 hash, [MaybeNullWhen(false)] out PoolItem? item) { - if (!_unverifiedTransactions.TryGetValue(hash, out item)) - return false; + _txRwLock.EnterWriteLock(); + try + { + if (!_unverifiedTransactions.TryGetValue(hash, out item)) + return false; - _unverifiedTransactions.Remove(hash); - _unverifiedSortedTransactions.Remove(item); - return true; + _unverifiedTransactions.Remove(hash); + _unverifiedSortedTransactions.Remove(item); + return true; + } + finally + { + _txRwLock.ExitWriteLock(); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs index ca4e08341e..3226015f18 100644 --- a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -747,6 +747,25 @@ public void TestUpdatePoolForBlockPersisted() _unit.VerifiedCount.Should().Be(0); } + [TestMethod] + public void TestTryRemoveUnVerified() + { + AddTransactions(32); + _unit.SortedTxCount.Should().Be(32); + + var txs = _unit.GetSortedVerifiedTransactions().ToArray(); + _unit.InvalidateVerifiedTransactions(); + + _unit.SortedTxCount.Should().Be(0); + + foreach (var tx in txs) + { + _unit.TryRemoveUnVerified(tx.Hash, out _); + } + + _unit.UnVerifiedCount.Should().Be(0); + } + public static StorageKey CreateStorageKey(int id, byte prefix, byte[] key = null) { byte[] buffer = GC.AllocateUninitializedArray(sizeof(byte) + (key?.Length ?? 0)); From 1688a7a3e24ace2372f839d3efd86c22ff6718dc Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 30 Sep 2024 23:38:59 +0800 Subject: [PATCH 07/42] update rvcount error message (#3504) --- src/Neo.VM/JumpTable/JumpTable.Control.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Neo.VM/JumpTable/JumpTable.Control.cs b/src/Neo.VM/JumpTable/JumpTable.Control.cs index 3c8448c050..1ef74fe4a5 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Control.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Control.cs @@ -539,7 +539,11 @@ public virtual void Ret(ExecutionEngine engine, Instruction instruction) if (context_pop.EvaluationStack != stack_eval) { if (context_pop.RVCount >= 0 && context_pop.EvaluationStack.Count != context_pop.RVCount) - throw new InvalidOperationException("RVCount doesn't match with EvaluationStack"); + // This exception indicates a mismatch between the expected and actual number of stack items. + // It typically occurs due to compilation errors caused by potential issues in the compiler, resulting in either too many or too few + // items left on the stack compared to what was anticipated by the return value count. + // When you run into this problem, try to reach core-devs at https://github.com/neo-project/neo for help. + throw new InvalidOperationException($"Return value count mismatch: expected {context_pop.RVCount}, but got {context_pop.EvaluationStack.Count} items on the evaluation stack"); context_pop.EvaluationStack.CopyTo(stack_eval); } if (engine.InvocationStack.Count == 0) From f746f8de81958560bbd45c8d225931a559fb3ec4 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 8 Aug 2024 00:24:40 +0800 Subject: [PATCH 08/42] add hardofork HF_Echidna --- src/Neo.CLI/config.fs.mainnet.json | 3 ++- src/Neo.CLI/config.json | 3 ++- src/Neo.CLI/config.mainnet.json | 3 ++- src/Neo.CLI/config.testnet.json | 3 ++- src/Neo/Hardfork.cs | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index 6879232732..c12372b43b 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -40,7 +40,8 @@ "HF_Aspidochelone": 3000000, "HF_Basilisk": 4500000, "HF_Cockatrice": 5800000, - "HF_Domovoi": 5800000 + "HF_Domovoi": 5800000, + "HF_Echidna": 0 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 772a221714..9e97ec1f00 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, - "HF_Domovoi": 5570000 + "HF_Domovoi": 5570000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 772a221714..9e97ec1f00 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, - "HF_Domovoi": 5570000 + "HF_Domovoi": 5570000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index dc102be54b..88a04794f0 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 210000, "HF_Basilisk": 2680000, "HF_Cockatrice": 3967000, - "HF_Domovoi": 4144000 + "HF_Domovoi": 4144000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index 7bd3cc0aef..9276c9c07d 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -16,6 +16,7 @@ public enum Hardfork : byte HF_Aspidochelone, HF_Basilisk, HF_Cockatrice, - HF_Domovoi + HF_Domovoi, + HF_Echidna } } From 8d7f9e8ccbd556bf488994a6944f5f3b5a443682 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 8 Aug 2024 01:54:41 -0700 Subject: [PATCH 09/42] Add entries to `Designation` event (#3397) * Add entries to Designation event * Change to HF_Echidna * Add UT * Add count --- .../SmartContract/Native/RoleManagement.cs | 24 +++++++++++++++++-- .../SmartContract/Native/UT_NativeContract.cs | 18 ++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index d0437594c9..a0fc82f126 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -26,7 +26,16 @@ public sealed class RoleManagement : NativeContract { [ContractEvent(0, name: "Designation", "Role", ContractParameterType.Integer, - "BlockIndex", ContractParameterType.Integer)] + "BlockIndex", ContractParameterType.Integer, + Hardfork.HF_Echidna)] + + [ContractEvent(Hardfork.HF_Echidna, 0, name: "Designation", + "Role", ContractParameterType.Integer, + "BlockIndex", ContractParameterType.Integer, + "Old", ContractParameterType.Array, + "New", ContractParameterType.Array + )] + internal RoleManagement() : base() { } /// @@ -69,7 +78,18 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node list.AddRange(nodes); list.Sort(); engine.SnapshotCache.Add(key, new StorageItem(list)); - engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, new StackItem[] { (int)role, engine.PersistingBlock.Index })); + + if (engine.IsHardforkEnabled(Hardfork.HF_Echidna)) + { + var oldNodes = new VM.Types.Array(engine.ReferenceCounter, GetDesignatedByRole(engine.Snapshot, role, index - 1).Select(u => (ByteString)u.EncodePoint(true))); + var newNodes = new VM.Types.Array(engine.ReferenceCounter, nodes.Select(u => (ByteString)u.EncodePoint(true))); + + engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, [(int)role, engine.PersistingBlock.Index, oldNodes, newNodes])); + } + else + { + engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, [(int)role, engine.PersistingBlock.Index])); + } } private class NodeList : InteroperableList diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index f9a3089f9d..627a9fdd3c 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -80,6 +80,24 @@ public void TestActiveDeprecatedIn() Assert.IsFalse(NativeContract.IsActive(new active() { ActiveIn = null, DeprecatedIn = Hardfork.HF_Cockatrice }, settings.IsHardforkEnabled, 20)); } + [TestMethod] + public void TestActiveDeprecatedInRoleManagement() + { + string json = UT_ProtocolSettings.CreateHKSettings("\"HF_Echidna\": 20"); + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + var before = NativeContract.RoleManagement.GetContractState(settings.IsHardforkEnabled, 19); + var after = NativeContract.RoleManagement.GetContractState(settings.IsHardforkEnabled, 20); + + Assert.AreEqual(2, before.Manifest.Abi.Events[0].Parameters.Length); + Assert.AreEqual(1, before.Manifest.Abi.Events.Length); + Assert.AreEqual(4, after.Manifest.Abi.Events[0].Parameters.Length); + Assert.AreEqual(1, after.Manifest.Abi.Events.Length); + } + [TestMethod] public void TestGetContract() { From 3fc8077e17df8112de51dd260395455e27c69004 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 9 Aug 2024 03:42:18 +0800 Subject: [PATCH 10/42] [Neo Core StdLib] Add Base64url (#3453) * add base64url * active in * update placehold hf height * fix hf issue and move methods to proper place. * fix test * use identifymodel instead. --- src/Neo.CLI/config.fs.mainnet.json | 2 +- src/Neo.CLI/config.json | 2 +- src/Neo.CLI/config.mainnet.json | 2 +- src/Neo.CLI/config.testnet.json | 2 +- src/Neo/Neo.csproj | 1 + src/Neo/SmartContract/Native/StdLib.cs | 23 +++++++++++++++++++ .../SmartContract/Native/UT_NativeContract.cs | 2 +- .../SmartContract/Native/UT_StdLib.cs | 20 ++++++++++++++++ 8 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index c12372b43b..bbd17978e3 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -41,7 +41,7 @@ "HF_Basilisk": 4500000, "HF_Cockatrice": 5800000, "HF_Domovoi": 5800000, - "HF_Echidna": 0 + "HF_Echidna": 5800001 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 9e97ec1f00..9590fc60fd 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -39,7 +39,7 @@ "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, "HF_Domovoi": 5570000, - "HF_Echidna": 0 + "HF_Echidna": 5570001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 9e97ec1f00..9590fc60fd 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -39,7 +39,7 @@ "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, "HF_Domovoi": 5570000, - "HF_Echidna": 0 + "HF_Echidna": 5570001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index 88a04794f0..8bcd6d8956 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -39,7 +39,7 @@ "HF_Basilisk": 2680000, "HF_Cockatrice": 3967000, "HF_Domovoi": 4144000, - "HF_Echidna": 0 + "HF_Echidna": 4144001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index 7c6478c33e..f4f2cb36b3 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Neo/SmartContract/Native/StdLib.cs b/src/Neo/SmartContract/Native/StdLib.cs index f8fa9efcc2..1b4030d9f5 100644 --- a/src/Neo/SmartContract/Native/StdLib.cs +++ b/src/Neo/SmartContract/Native/StdLib.cs @@ -11,6 +11,7 @@ #pragma warning disable IDE0051 +using Microsoft.IdentityModel.Tokens; using Neo.Cryptography; using Neo.Json; using Neo.VM.Types; @@ -131,6 +132,28 @@ public static byte[] Base64Decode([MaxLength(MaxInputLength)] string s) return Convert.FromBase64String(s); } + /// + /// Encodes a byte array into a base64Url string. + /// + /// The base64Url to be encoded. + /// The encoded base64Url string. + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)] + public static string Base64UrlEncode([MaxLength(MaxInputLength)] string data) + { + return Base64UrlEncoder.Encode(data); + } + + /// + /// Decodes a byte array from a base64Url string. + /// + /// The base64Url string. + /// The decoded base64Url string. + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)] + public static string Base64UrlDecode([MaxLength(MaxInputLength)] string s) + { + return Base64UrlEncoder.Decode(s); + } + /// /// Encodes a byte array into a base58 . /// diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 627a9fdd3c..26e00a3c2d 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -41,7 +41,7 @@ public void TestSetup() _nativeStates = new Dictionary { {"ContractManagement", """{"id":-1,"updatecounter":0,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getContractById","parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getContractHashes","parameters":[],"returntype":"InteropInterface","offset":35,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":42,"safe":true},{"name":"hasMethod","parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","offset":49,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":63,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":70,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}""" }, - {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":56,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":63,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":70,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":77,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":84,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":91,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":98,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":112,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":119,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":126,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":133,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":140,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2681632925},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"base64UrlDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"base64UrlEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":63,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":70,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":77,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":84,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":91,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":98,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":112,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":126,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":133,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":140,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":147,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":154,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"CryptoLib", """{"id":-3,"updatecounter":0,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"bls12381Add","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","offset":0,"safe":true},{"name":"bls12381Deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","offset":7,"safe":true},{"name":"bls12381Equal","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","offset":14,"safe":true},{"name":"bls12381Mul","parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"bls12381Pairing","parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","offset":28,"safe":true},{"name":"bls12381Serialize","parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","offset":35,"safe":true},{"name":"keccak256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"murmur32","parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","offset":49,"safe":true},{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":63,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","offset":70,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"LedgerContract", """{"id":-4,"updatecounter":0,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"LedgerContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"currentHash","parameters":[],"returntype":"Hash256","offset":0,"safe":true},{"name":"currentIndex","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getBlock","parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","offset":14,"safe":true},{"name":"getTransaction","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":21,"safe":true},{"name":"getTransactionFromBlock","parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getTransactionHeight","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":35,"safe":true},{"name":"getTransactionSigners","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":42,"safe":true},{"name":"getTransactionVMState","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":49,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"NeoToken", """{"id":-5,"updatecounter":0,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getAccountState","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","offset":14,"safe":true},{"name":"getAllCandidates","parameters":[],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"getCandidateVote","parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","offset":28,"safe":true},{"name":"getCandidates","parameters":[],"returntype":"Array","offset":35,"safe":true},{"name":"getCommittee","parameters":[],"returntype":"Array","offset":42,"safe":true},{"name":"getCommitteeAddress","parameters":[],"returntype":"Hash160","offset":49,"safe":true},{"name":"getGasPerBlock","parameters":[],"returntype":"Integer","offset":56,"safe":true},{"name":"getNextBlockValidators","parameters":[],"returntype":"Array","offset":63,"safe":true},{"name":"getRegisterPrice","parameters":[],"returntype":"Integer","offset":70,"safe":true},{"name":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":77,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":84,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":91,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":98,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":105,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":112,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":126,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":133,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs index 1dffb3d384..f9761bee07 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -406,5 +406,25 @@ public void TestRuntime_Deserialize() Assert.AreEqual(engine.ResultStack.Pop().GetInteger(), 100); Assert.AreEqual(engine.ResultStack.Pop().GetString(), "test"); } + + [TestMethod] + public void TestBase64Url() + { + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + using (var script = new ScriptBuilder()) + { + // Test encoding + script.EmitDynamicCall(NativeContract.StdLib.Hash, "base64UrlEncode", "Subject=test@example.com&Issuer=https://example.com"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "base64UrlDecode", "U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t"); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + Assert.AreEqual("Subject=test@example.com&Issuer=https://example.com", engine.ResultStack.Pop()); + Assert.AreEqual("U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t", engine.ResultStack.Pop().GetString()); + } + } } } From a38d618ed8306d27b769fb0c38e4b9f54a567344 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Fri, 4 Oct 2024 06:00:00 -0400 Subject: [PATCH 11/42] Added `CreateStruct` & Fixed `CreateMap` (#3494) * Added `CreateStruct`, Fixed CreateMap * Added test and comments --------- Co-authored-by: Jimmy Co-authored-by: NGD Admin <154295625+NGDAdmin@users.noreply.github.com> Co-authored-by: Shargon --- src/Neo/VM/Helper.cs | 70 ++++++++++++++++++++++++----- tests/Neo.UnitTests/VM/UT_Helper.cs | 24 ++++++++++ 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/src/Neo/VM/Helper.cs b/src/Neo/VM/Helper.cs index c6423ac1f1..26d0fa132a 100644 --- a/src/Neo/VM/Helper.cs +++ b/src/Neo/VM/Helper.cs @@ -54,18 +54,64 @@ public static ScriptBuilder CreateArray(this ScriptBuilder builder, IReadOnly /// The to be used. /// The key/value pairs of the map. /// The same instance as . - public static ScriptBuilder CreateMap(this ScriptBuilder builder, IEnumerable> map = null) + public static ScriptBuilder CreateMap(this ScriptBuilder builder, IEnumerable> map) + where TKey : notnull + where TValue : notnull { - builder.Emit(OpCode.NEWMAP); - if (map != null) - foreach (var p in map) - { - builder.Emit(OpCode.DUP); - builder.EmitPush(p.Key); - builder.EmitPush(p.Value); - builder.Emit(OpCode.SETITEM); - } - return builder; + var count = map.Count(); + + if (count == 0) + return builder.Emit(OpCode.NEWMAP); + + foreach (var (key, value) in map.Reverse()) + { + builder.EmitPush(value); + builder.EmitPush(key); + } + builder.EmitPush(count); + return builder.Emit(OpCode.PACKMAP); + } + + /// + /// Emits the opcodes for creating a map. + /// + /// The type of the key of the map. + /// The type of the value of the map. + /// The to be used. + /// The key/value pairs of the map. + /// The same instance as . + public static ScriptBuilder CreateMap(this ScriptBuilder builder, IReadOnlyDictionary map) + where TKey : notnull + where TValue : notnull + { + if (map.Count == 0) + return builder.Emit(OpCode.NEWMAP); + + foreach (var (key, value) in map.Reverse()) + { + builder.EmitPush(value); + builder.EmitPush(key); + } + builder.EmitPush(map.Count); + return builder.Emit(OpCode.PACKMAP); + } + + /// + /// Emits the opcodes for creating a struct. + /// + /// The type of the property. + /// The to be used. + /// The list of properties. + /// The same instance as . + public static ScriptBuilder CreateStruct(this ScriptBuilder builder, IReadOnlyList array) + where T : notnull + { + if (array.Count == 0) + return builder.Emit(OpCode.NEWSTRUCT0); + for (var i = array.Count - 1; i >= 0; i--) + builder.EmitPush(array[i]); + builder.EmitPush(array.Count); + return builder.Emit(OpCode.PACKSTRUCT); } /// @@ -218,7 +264,7 @@ public static ScriptBuilder EmitPush(this ScriptBuilder builder, object obj) builder.EmitPush(data); break; case char data: - builder.EmitPush((ushort)data); + builder.EmitPush(data); break; case ushort data: builder.EmitPush(data); diff --git a/tests/Neo.UnitTests/VM/UT_Helper.cs b/tests/Neo.UnitTests/VM/UT_Helper.cs index 3e83256669..db47555dbd 100644 --- a/tests/Neo.UnitTests/VM/UT_Helper.cs +++ b/tests/Neo.UnitTests/VM/UT_Helper.cs @@ -106,6 +106,30 @@ public void TestEmitArray() Assert.AreEqual(0, engine2.ResultStack.Pop().Count); } + [TestMethod] + public void TestEmitStruct() + { + var expected = new BigInteger[] { 1, 2, 3 }; + var sb = new ScriptBuilder(); + sb.CreateStruct(expected); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null); + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + + CollectionAssert.AreEqual(expected, engine.ResultStack.Pop().Select(u => u.GetInteger()).ToArray()); + + expected = new BigInteger[] { }; + sb = new ScriptBuilder(); + sb.CreateStruct(expected); + + using var engine2 = ApplicationEngine.Create(TriggerType.Application, null, null); + engine2.LoadScript(sb.ToArray()); + Assert.AreEqual(VMState.HALT, engine2.Execute()); + + Assert.AreEqual(0, engine2.ResultStack.Pop().Count); + } + [TestMethod] public void TestEmitMap() { From bb32a794e6c610de2833fc8c07edea1659bf40a0 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 7 Oct 2024 21:37:55 +0800 Subject: [PATCH 12/42] benchmark convert (#3509) --- benchmarks/Neo.VM.Benchmarks/Program.cs | 2 +- .../VMTypes/Benchmarks_Convert.cs | 217 ++++++++++++++++++ .../Benchmarks_DeepCopy.cs} | 4 +- 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs rename benchmarks/Neo.VM.Benchmarks/{Benchmarks.Types.cs => VMTypes/Benchmarks_DeepCopy.cs} (97%) diff --git a/benchmarks/Neo.VM.Benchmarks/Program.cs b/benchmarks/Neo.VM.Benchmarks/Program.cs index 028dff9460..29b4549674 100644 --- a/benchmarks/Neo.VM.Benchmarks/Program.cs +++ b/benchmarks/Neo.VM.Benchmarks/Program.cs @@ -12,4 +12,4 @@ using BenchmarkDotNet.Running; using Neo.VM.Benchmark; -BenchmarkRunner.Run(); +BenchmarkRunner.Run(); diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs new file mode 100644 index 0000000000..0860749955 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs @@ -0,0 +1,217 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks_Convert.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Attributes; +using Neo.VM.Types; +using System.Collections.Generic; +using Array = Neo.VM.Types.Array; +using Buffer = Neo.VM.Types.Buffer; + +namespace Neo.VM.Benchmark; + +public class Benchmarks_Convert +{ + private Dictionary> testItemsByType; + + [GlobalSetup] + public void Setup() + { + testItemsByType = CreateTestItemsByType(); + } + + [Benchmark] + [ArgumentsSource(nameof(GetTypeConversionPairs))] + public void BenchConvertTo(StackItemType fromType, StackItemType toType) + { + foreach (var item in testItemsByType[fromType]) + { + try + { + _ = item.ConvertTo(toType); + } + catch (Exception) + { + // Ignore invalid casts as they're expected for some conversions + } + } + } + + public IEnumerable GetTypeConversionPairs() + { + var types = (StackItemType[])Enum.GetValues(typeof(StackItemType)); + foreach (var fromType in types) + { + foreach (var toType in types) + { + yield return new object[] { fromType, toType }; + } + } + } + + private Dictionary> CreateTestItemsByType() + { + var referenceCounter = new ReferenceCounter(); + var result = new Dictionary>(); + + foreach (StackItemType type in Enum.GetValues(typeof(StackItemType))) + { + result[type] = new List(); + } + + result[StackItemType.Boolean].Add(StackItem.True); + result[StackItemType.Boolean].Add(StackItem.False); + + result[StackItemType.Integer].Add(new Integer(42)); + result[StackItemType.Integer].Add(new Integer(-1)); + + result[StackItemType.ByteString].Add(new ByteString(new byte[] { 1, 2, 3 })); + result[StackItemType.ByteString].Add(new ByteString(new byte[] { 255, 0, 128 })); + + // Create a 128-byte buffer + var longBuffer = new byte[128]; + for (int i = 0; i < 128; i++) longBuffer[i] = (byte)(i % 256); + result[StackItemType.Buffer].Add(new Buffer(longBuffer)); + result[StackItemType.Buffer].Add(new Buffer(new byte[128])); // Another 128-byte buffer, all zeros + + // Create an array with 10 items + var longArray = new Array(referenceCounter); + for (int i = 0; i < 10; i++) longArray.Add(new Integer(i)); + result[StackItemType.Array].Add(longArray); + result[StackItemType.Array].Add(new Array(referenceCounter) { StackItem.True, new ByteString(new byte[] { 3, 4, 5 }) }); + + // Create a struct with 10 items + var longStruct = new Struct(referenceCounter); + for (int i = 0; i < 10; i++) longStruct.Add(new Integer(i * 10)); + result[StackItemType.Struct].Add(longStruct); + result[StackItemType.Struct].Add(new Struct(referenceCounter) { StackItem.False, new Buffer(new byte[] { 6, 7, 8 }) }); + + // Create a map with 10 items + var longMap = new Map(referenceCounter); + for (int i = 0; i < 10; i++) longMap[new Integer(i)] = new ByteString(new byte[] { (byte)(i * 20) }); + result[StackItemType.Map].Add(longMap); + result[StackItemType.Map].Add(new Map(referenceCounter) { [new ByteString(new byte[] { 9 })] = StackItem.True }); + + result[StackItemType.InteropInterface].Add(new InteropInterface(new object())); + result[StackItemType.InteropInterface].Add(new InteropInterface("test string")); + + return result; + } +} + +// BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.4249/23H2/2023Update/SunValley3) +// Intel Core i9-14900HX, 1 CPU, 32 logical and 24 physical cores +// .NET SDK 8.0.205 +// [Host] : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// DefaultJob : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// +// +// | Method | fromType | toType | Mean | Error | StdDev | +// |--------------- |----------------- |----------------- |-------------:|------------:|------------:| +// | BenchConvertTo | Any | Any | 1.762 ns | 0.0195 ns | 0.0182 ns | +// | BenchConvertTo | Any | Pointer | 1.791 ns | 0.0196 ns | 0.0183 ns | +// | BenchConvertTo | Any | Boolean | 1.774 ns | 0.0245 ns | 0.0229 ns | +// | BenchConvertTo | Any | Integer | 1.781 ns | 0.0236 ns | 0.0220 ns | +// | BenchConvertTo | Any | ByteString | 1.767 ns | 0.0255 ns | 0.0226 ns | +// | BenchConvertTo | Any | Buffer | 1.774 ns | 0.0217 ns | 0.0203 ns | +// | BenchConvertTo | Any | Array | 1.770 ns | 0.0412 ns | 0.0385 ns | +// | BenchConvertTo | Any | Struct | 1.787 ns | 0.0227 ns | 0.0212 ns | +// | BenchConvertTo | Any | Map | 1.796 ns | 0.0292 ns | 0.0273 ns | +// | BenchConvertTo | Any | InteropInterface | 1.820 ns | 0.0549 ns | 0.0675 ns | +// | BenchConvertTo | Pointer | Any | 2.312 ns | 0.0210 ns | 0.0175 ns | +// | BenchConvertTo | Pointer | Pointer | 2.337 ns | 0.0157 ns | 0.0146 ns | +// | BenchConvertTo | Pointer | Boolean | 2.352 ns | 0.0190 ns | 0.0169 ns | +// | BenchConvertTo | Pointer | Integer | 2.334 ns | 0.0231 ns | 0.0216 ns | +// | BenchConvertTo | Pointer | ByteString | 2.317 ns | 0.0298 ns | 0.0279 ns | +// | BenchConvertTo | Pointer | Buffer | 2.329 ns | 0.0274 ns | 0.0256 ns | +// | BenchConvertTo | Pointer | Array | 2.338 ns | 0.0257 ns | 0.0241 ns | +// | BenchConvertTo | Pointer | Struct | 2.336 ns | 0.0318 ns | 0.0298 ns | +// | BenchConvertTo | Pointer | Map | 2.351 ns | 0.0676 ns | 0.0903 ns | +// | BenchConvertTo | Pointer | InteropInterface | 2.281 ns | 0.0133 ns | 0.0125 ns | +// | BenchConvertTo | Boolean | Any | 5,926.451 ns | 118.1195 ns | 136.0266 ns | +// | BenchConvertTo | Boolean | Pointer | 6,001.282 ns | 15.3048 ns | 12.7802 ns | +// | BenchConvertTo | Boolean | Boolean | 4.459 ns | 0.0151 ns | 0.0133 ns | +// | BenchConvertTo | Boolean | Integer | 14.104 ns | 0.1526 ns | 0.1428 ns | +// | BenchConvertTo | Boolean | ByteString | 11.650 ns | 0.0539 ns | 0.0450 ns | +// | BenchConvertTo | Boolean | Buffer | 26.106 ns | 0.1549 ns | 0.1449 ns | +// | BenchConvertTo | Boolean | Array | 5,813.116 ns | 28.1911 ns | 26.3700 ns | +// | BenchConvertTo | Boolean | Struct | 5,809.844 ns | 19.1249 ns | 15.9702 ns | +// | BenchConvertTo | Boolean | Map | 6,061.558 ns | 29.3991 ns | 27.4999 ns | +// | BenchConvertTo | Boolean | InteropInterface | 5,924.682 ns | 80.5533 ns | 75.3496 ns | +// | BenchConvertTo | Integer | Any | 5,240.903 ns | 41.0628 ns | 38.4102 ns | +// | BenchConvertTo | Integer | Pointer | 5,479.116 ns | 75.8232 ns | 70.9251 ns | +// | BenchConvertTo | Integer | Boolean | 5.981 ns | 0.0445 ns | 0.0416 ns | +// | BenchConvertTo | Integer | Integer | 4.277 ns | 0.0177 ns | 0.0166 ns | +// | BenchConvertTo | Integer | ByteString | 19.053 ns | 0.2125 ns | 0.1883 ns | +// | BenchConvertTo | Integer | Buffer | 32.782 ns | 0.1653 ns | 0.1380 ns | +// | BenchConvertTo | Integer | Array | 4,693.207 ns | 14.2446 ns | 12.6275 ns | +// | BenchConvertTo | Integer | Struct | 4,737.341 ns | 60.1813 ns | 56.2936 ns | +// | BenchConvertTo | Integer | Map | 4,808.431 ns | 23.5380 ns | 22.0174 ns | +// | BenchConvertTo | Integer | InteropInterface | 4,684.409 ns | 24.7033 ns | 21.8989 ns | +// | BenchConvertTo | ByteString | Any | 5,833.857 ns | 20.1553 ns | 18.8533 ns | +// | BenchConvertTo | ByteString | Pointer | 5,807.973 ns | 11.7754 ns | 10.4386 ns | +// | BenchConvertTo | ByteString | Boolean | 33.007 ns | 0.1574 ns | 0.1472 ns | +// | BenchConvertTo | ByteString | Integer | 23.622 ns | 0.0755 ns | 0.0669 ns | +// | BenchConvertTo | ByteString | ByteString | 4.288 ns | 0.0152 ns | 0.0142 ns | +// | BenchConvertTo | ByteString | Buffer | 24.881 ns | 0.0889 ns | 0.0788 ns | +// | BenchConvertTo | ByteString | Array | 6,030.813 ns | 19.9562 ns | 18.6670 ns | +// | BenchConvertTo | ByteString | Struct | 5,811.185 ns | 24.0781 ns | 22.5226 ns | +// | BenchConvertTo | ByteString | Map | 5,866.820 ns | 17.0315 ns | 15.0980 ns | +// | BenchConvertTo | ByteString | InteropInterface | 5,757.124 ns | 16.3184 ns | 14.4658 ns | +// | BenchConvertTo | Buffer | Any | 4,886.279 ns | 17.1370 ns | 14.3102 ns | +// | BenchConvertTo | Buffer | Pointer | 4,698.364 ns | 14.5491 ns | 12.1492 ns | +// | BenchConvertTo | Buffer | Boolean | 6.130 ns | 0.0323 ns | 0.0302 ns | +// | BenchConvertTo | Buffer | Integer | 4,645.764 ns | 15.8146 ns | 14.7930 ns | +// | BenchConvertTo | Buffer | ByteString | 29.874 ns | 0.1518 ns | 0.1268 ns | +// | BenchConvertTo | Buffer | Buffer | 4.939 ns | 0.0190 ns | 0.0178 ns | +// | BenchConvertTo | Buffer | Array | 4,683.427 ns | 21.3813 ns | 20.0001 ns | +// | BenchConvertTo | Buffer | Struct | 4,680.762 ns | 15.7220 ns | 13.9371 ns | +// | BenchConvertTo | Buffer | Map | 4,706.510 ns | 14.2061 ns | 12.5934 ns | +// | BenchConvertTo | Buffer | InteropInterface | 4,703.050 ns | 15.8002 ns | 14.0064 ns | +// | BenchConvertTo | Array | Any | 4,652.710 ns | 23.2061 ns | 20.5716 ns | +// | BenchConvertTo | Array | Pointer | 4,625.049 ns | 12.4455 ns | 11.6415 ns | +// | BenchConvertTo | Array | Boolean | 5.568 ns | 0.0181 ns | 0.0169 ns | +// | BenchConvertTo | Array | Integer | 4,659.897 ns | 19.8036 ns | 18.5243 ns | +// | BenchConvertTo | Array | ByteString | 4,663.020 ns | 12.4988 ns | 11.6914 ns | +// | BenchConvertTo | Array | Buffer | 4,680.281 ns | 14.9748 ns | 13.2748 ns | +// | BenchConvertTo | Array | Array | 4.246 ns | 0.0124 ns | 0.0110 ns | +// | BenchConvertTo | Array | Struct | 1,193.106 ns | 98.5374 ns | 285.8748 ns | +// | BenchConvertTo | Array | Map | 4,742.631 ns | 35.5855 ns | 33.2867 ns | +// | BenchConvertTo | Array | InteropInterface | 4,670.743 ns | 9.3547 ns | 7.8116 ns | +// | BenchConvertTo | Struct | Any | 4,643.558 ns | 31.0451 ns | 29.0396 ns | +// | BenchConvertTo | Struct | Pointer | 4,867.925 ns | 22.2347 ns | 19.7105 ns | +// | BenchConvertTo | Struct | Boolean | 5.581 ns | 0.0251 ns | 0.0235 ns | +// | BenchConvertTo | Struct | Integer | 4,653.442 ns | 17.7417 ns | 16.5956 ns | +// | BenchConvertTo | Struct | ByteString | 4,646.242 ns | 13.7830 ns | 12.8926 ns | +// | BenchConvertTo | Struct | Buffer | 4,776.205 ns | 14.1918 ns | 13.2751 ns | +// | BenchConvertTo | Struct | Array | 1,622.573 ns | 144.8116 ns | 398.8532 ns | +// | BenchConvertTo | Struct | Struct | 4.195 ns | 0.0327 ns | 0.0290 ns | +// | BenchConvertTo | Struct | Map | 4,672.579 ns | 17.6257 ns | 16.4871 ns | +// | BenchConvertTo | Struct | InteropInterface | 4,653.476 ns | 8.2047 ns | 7.6747 ns | +// | BenchConvertTo | Map | Any | 4,676.540 ns | 15.2010 ns | 13.4753 ns | +// | BenchConvertTo | Map | Pointer | 4,663.489 ns | 13.7871 ns | 12.2219 ns | +// | BenchConvertTo | Map | Boolean | 5.535 ns | 0.0205 ns | 0.0192 ns | +// | BenchConvertTo | Map | Integer | 4,661.275 ns | 12.4402 ns | 11.6366 ns | +// | BenchConvertTo | Map | ByteString | 4,662.482 ns | 25.7111 ns | 24.0502 ns | +// | BenchConvertTo | Map | Buffer | 4,859.809 ns | 18.2981 ns | 16.2208 ns | +// | BenchConvertTo | Map | Array | 4,627.149 ns | 10.7487 ns | 9.5285 ns | +// | BenchConvertTo | Map | Struct | 4,646.504 ns | 22.4190 ns | 20.9707 ns | +// | BenchConvertTo | Map | Map | 4.160 ns | 0.0180 ns | 0.0169 ns | +// | BenchConvertTo | Map | InteropInterface | 4,667.024 ns | 14.1790 ns | 13.2630 ns | +// | BenchConvertTo | InteropInterface | Any | 4,700.511 ns | 17.4725 ns | 15.4889 ns | +// | BenchConvertTo | InteropInterface | Pointer | 4,705.819 ns | 25.2035 ns | 23.5754 ns | +// | BenchConvertTo | InteropInterface | Boolean | 5.557 ns | 0.0244 ns | 0.0228 ns | +// | BenchConvertTo | InteropInterface | Integer | 4,695.410 ns | 21.8674 ns | 20.4547 ns | +// | BenchConvertTo | InteropInterface | ByteString | 4,674.552 ns | 18.8705 ns | 17.6515 ns | +// | BenchConvertTo | InteropInterface | Buffer | 4,649.237 ns | 23.9084 ns | 22.3639 ns | +// | BenchConvertTo | InteropInterface | Array | 4,827.652 ns | 29.7153 ns | 27.7957 ns | +// | BenchConvertTo | InteropInterface | Struct | 4,624.202 ns | 10.3563 ns | 8.0855 ns | +// | BenchConvertTo | InteropInterface | Map | 4,695.310 ns | 23.1192 ns | 21.6257 ns | +// | BenchConvertTo | InteropInterface | InteropInterface | 4.137 ns | 0.0156 ns | 0.0138 ns | diff --git a/benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs similarity index 97% rename from benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs rename to benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs index 3685e45f12..7991608b8c 100644 --- a/benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// Benchmarks.Types.cs file belongs to the neo project and is free +// Benchmarks_DeepCopy.cs file belongs to the neo project and is free // software distributed under the MIT software license, see the // accompanying file LICENSE in the main directory of the // repository or http://www.opensource.org/licenses/mit-license.php @@ -14,7 +14,7 @@ namespace Neo.VM.Benchmark; -public class Benchmarks_Types +public class Benchmarks_DeepCopy { public IEnumerable<(int Depth, int ElementsPerLevel)> ParamSource() { From 6067f670fedbb0e6830a95db5a79dbfb9d5e1b93 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 8 Oct 2024 11:27:54 +0300 Subject: [PATCH 13/42] OpCodes: extend MODMUL tests with negative base/multiplier/mod (#3513) Allows to avoid bugs like https://github.com/nspcc-dev/neo-go/issues/3598. Signed-off-by: Anna Shaleva Co-authored-by: Jimmy --- .../Tests/OpCodes/Arithmetic/MODMUL.json | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json index e3b93bfb56..1c2a9d9dab 100644 --- a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json @@ -136,6 +136,242 @@ } } ] + }, + { + "name": "Real test (-3 * 4 % 5)", + "script": [ + "PUSH3", + "NEGATE", + "PUSH4", + "PUSH5", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": -3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] + }, + { + "name": "Real test (3 * 4 % -5)", + "script": [ + "PUSH3", + "PUSH4", + "PUSH5", + "NEGATE", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 2 + } + ] + } + } + ] + }, + { + "name": "Real test (-3 * 4 % -5)", + "script": [ + "PUSH3", + "NEGATE", + "PUSH4", + "PUSH5", + "NEGATE", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": -3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] + }, + { + "name": "Real test (3 * -4 % -5)", + "script": [ + "PUSH3", + "PUSH4", + "NEGATE", + "PUSH5", + "NEGATE", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": -4 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] } ] } From 331fa24d754b3eeedea1800e3488c685b66ec211 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 8 Oct 2024 18:03:04 +0800 Subject: [PATCH 14/42] fix: sensitive data compare should use constant time compare to avoid timing attack (#3508) * fix: pass compare should use contant time compare to avoid timing attack * fix: pass compare should use contant time compare to avoid timing attack * Update src/Plugins/RpcServer/RpcServer.cs * Update src/Plugins/RpcServer/RpcServer.cs --------- Co-authored-by: Shargon Co-authored-by: Jimmy --- src/Plugins/RpcServer/RpcServer.cs | 24 ++++++++--- .../UT_RpcServer.cs | 42 +++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index c90bbad539..0dc3d467bf 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.DependencyInjection; +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P; using System; @@ -26,6 +27,7 @@ using System.Linq.Expressions; using System.Net.Security; using System.Reflection; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; @@ -44,10 +46,18 @@ public partial class RpcServer : IDisposable private readonly NeoSystem system; private readonly LocalNode localNode; + // avoid GetBytes every time + private readonly byte[] _rpcUser; + private readonly byte[] _rpcPass; + public RpcServer(NeoSystem system, RpcServerSettings settings) { this.system = system; this.settings = settings; + + _rpcUser = settings.RpcUser is not null ? Encoding.UTF8.GetBytes(settings.RpcUser) : []; + _rpcPass = settings.RpcPass is not null ? Encoding.UTF8.GetBytes(settings.RpcPass) : []; + localNode = system.LocalNode.Ask(new LocalNode.GetInstance()).Result; RegisterMethods(this); Initialize_SmartContract(); @@ -65,21 +75,25 @@ internal bool CheckAuth(HttpContext context) return false; } - string authstring; + byte[] auths; try { - authstring = Encoding.UTF8.GetString(Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim())); + auths = Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim()); } catch { return false; } - string[] authvalues = authstring.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); - if (authvalues.Length < 2) + int colonIndex = Array.IndexOf(auths, (byte)':'); + if (colonIndex == -1) return false; - return authvalues[0] == settings.RpcUser && authvalues[1] == settings.RpcPass; + byte[] user = auths[..colonIndex]; + byte[] pass = auths[(colonIndex + 1)..]; + + // Always execute both checks, but both must evaluate to true + return CryptographicOperations.FixedTimeEquals(user, _rpcUser) & CryptographicOperations.FixedTimeEquals(pass, _rpcPass); } private static JObject CreateErrorResponse(JToken id, RpcError rpcError) diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs index b63380f803..b8425ee0a9 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -87,5 +87,47 @@ public void TestCheckAuth_ValidCredentials_ReturnsTrue() // Assert Assert.IsTrue(result); } + + [TestMethod] + public void TestCheckAuth() + { + var memoryStoreProvider = new TestMemoryStoreProvider(new MemoryStore()); + var neoSystem = new NeoSystem(TestProtocolSettings.SoleNode, memoryStoreProvider); + var rpcServerSettings = RpcServerSettings.Default with + { + SessionEnabled = true, + SessionExpirationTime = TimeSpan.FromSeconds(0.3), + MaxGasInvoke = 1500_0000_0000, + Network = TestProtocolSettings.SoleNode.Network, + RpcUser = "testuser", + RpcPass = "testpass", + }; + var rpcServer = new RpcServer(neoSystem, rpcServerSettings); + + var context = new DefaultHttpContext(); + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:testpass")); + var result = rpcServer.CheckAuth(context); + Assert.IsTrue(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:wrongpass")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("wronguser:testpass")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(":testpass")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + } } } From a817e2918e62cf9d43ae0aaa67addfbafb6f7759 Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 9 Oct 2024 05:58:46 +0200 Subject: [PATCH 15/42] Related to https://github.com/neo-project/neo/pull/3508#discussion_r1788226279 (#3516) Co-authored-by: Jimmy --- .../ConstantTimeUtility.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs index fefde72b67..3bfc1226e6 100644 --- a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs +++ b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace Neo.Cryptography.BLS12_381; @@ -18,16 +19,10 @@ public static class ConstantTimeUtility { public static bool ConstantTimeEq(in T a, in T b) where T : unmanaged { - ReadOnlySpan a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1)); - ReadOnlySpan b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1)); - ReadOnlySpan a_u64 = MemoryMarshal.Cast(a_bytes); - ReadOnlySpan b_u64 = MemoryMarshal.Cast(b_bytes); - ulong f = 0; - for (int i = 0; i < a_u64.Length; i++) - f |= a_u64[i] ^ b_u64[i]; - for (int i = a_u64.Length * sizeof(ulong); i < a_bytes.Length; i++) - f |= (ulong)a_bytes[i] ^ b_bytes[i]; - return f == 0; + var a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1)); + var b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1)); + + return CryptographicOperations.FixedTimeEquals(a_bytes, b_bytes); } public static T ConditionalSelect(in T a, in T b, bool choice) where T : unmanaged From 82906978e4ab18178294a93a4275d51aa88d05e1 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 9 Oct 2024 14:13:53 +0800 Subject: [PATCH 16/42] [Benchmark] this pr adds more pocs to benchmark (#3512) * this pr adds more pocs to benchmark * format --------- Co-authored-by: Fernando Diaz Toledano --- .../Neo.VM.Benchmarks/Benchmarks.POC.cs | 292 +++++++++++++++++- 1 file changed, 288 insertions(+), 4 deletions(-) diff --git a/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs b/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs index 1a5dac4b97..2eede59fc3 100644 --- a/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs +++ b/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs @@ -46,7 +46,7 @@ public void NeoIssue2528() // L24: LDLOC 0 // L25: JMPIF_L L19 // L26: DROP - Run(nameof(NeoIssue2528), "VwEAwkpKAfsHdwARwG8AnXcAbwAl9////xHAzwJwlAAAdwAQzm8AnXcAbwAl9////0U="); + Run("VwEAwkpKAfsHdwARwG8AnXcAbwAl9////xHAzwJwlAAAdwAQzm8AnXcAbwAl9////0U="); } [Benchmark] @@ -81,7 +81,7 @@ public void NeoVMIssue418() // L25: DROP // L26: ROT // L27: DROP - Run(nameof(NeoVMIssue418), "whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUU="); + Run("whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUU="); } [Benchmark] @@ -98,15 +98,299 @@ public void NeoIssue2723() // L08: DUP // L09: STSFLD 0 // L10: JMPIF L03 - Run(nameof(NeoIssue2723), "VgEC0PsBAGcAAgAAEACIRV8AnUpnACTz"); + Run("VgEC0PsBAGcAAgAAEACIRV8AnUpnACTz"); } - private static void Run(string name, string poc) + // Below are PoCs from issue https://github.com/neo-project/neo/issues/2723 by @dusmart + [Benchmark] + public void PoC_NewBuffer() + { + // INITSLOT 0100 + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT32 1048576 + // NEWBUFFER + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f2ffffff + // CLEAR + // RET + Run("VwEAAsDzXgF3AAIAABAAiEVvAJ13AG8AJfL///9JQA=="); + } + + [Benchmark] + public void PoC_Cat() + { + // INITSLOT 0100 + // PUSHINT32 1048575 + // NEWBUFFER + // PUSH1 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // OVER + // OVER + // CAT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // CLEAR + // RET + Run("VwEAAv//DwCIEYgCWYHyB3cAS0uLRW8AnXcAbwAl9f///0lA"); + } + + [Benchmark] + public void PoC_Left() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // DUP + // PUSHINT32 1048576 + // LEFT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f1ffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAlmB8gd3AEoCAAAQAI1FbwCddwBvACXx////SUA="); + } + + [Benchmark] + public void PoC_Right() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // DUP + // PUSHINT32 1048576 + // RIGHT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f1ffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAlmB8gd3AEoCAAAQAI5FbwCddwBvACXx////SUA="); + } + + [Benchmark] + public void PoC_ReverseN() + { + // INITSLOT 0100 + // PUSHINT16 2040 + // STLOC 00 + // PUSHDATA1 aaabbbbbbbbbcccccccdddddddeeeeeeefffffff + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L cfffffff + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT16 2040 + // REVERSEN + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // CLEAR + // RET + Run("VwEAAfgHdwAMKGFhYWJiYmJiYmJiYmNjY2NjY2NkZGRkZGRkZWVlZWVlZWZmZmZmZmZvAJ13AG8AJc////8CwPNeAXcAAfgHVW8AnXcAbwAl9f///0lA"); + } + + [Benchmark] + public void PoC_Substr() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // DUP + // PUSH0 + // PUSHINT32 1048576 + // SUBSTR + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f0ffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAlmB8gd3AEoQAgAAEACMRW8AnXcAbwAl8P///0lA"); + } + + [Benchmark] + public void PoC_NewArray() + { + // INITSLOT 0100 + // PUSHINT32 1333333337 + // STLOC 00 + // PUSHINT16 2040 + // NEWARRAY + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f4ffffff + // RET + Run("VwEAAlkNeU93AAH4B8NFbwCddwBvACX0////QA=="); + } + + [Benchmark] + public void PoC_NewStruct() + { + // INITSLOT 0100 + // PUSHINT32 1333333337 + // STLOC 00 + // PUSHINT16 2040 + // NEWSTRUCT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f4ffffff + // RET + Run("VwEAAlkNeU93AAH4B8ZFbwCddwBvACX0////QA=="); + } + + [Benchmark] + public void PoC_Roll() + { + // INITSLOT 0100 + // PUSHINT16 2040 + // STLOC 00 + // PUSHDATA1 aaabbbbbbbbbcccccccdddddddeeeeeeefffffff + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L cfffffff + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT16 2039 + // ROLL + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // CLEAR + // RET + Run("VwEAAfgHdwAMKGFhYWJiYmJiYmJiYmNjY2NjY2NkZGRkZGRkZWVlZWVlZWZmZmZmZmZvAJ13AG8AJc////8CwPNeAXcAAfcHUm8AnXcAbwAl9f///0lA"); + } + + [Benchmark] + public void PoC_XDrop() + { + // INITSLOT 0100 + // PUSHINT16 2040 + // STLOC 00 + // PUSHDATA1 aaabbbbbbbbbcccccccdddddddeeeeeeefffffff + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L cfffffff + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT16 2039 + // XDROP + // DUP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f4ffffff + // CLEAR + // RET + Run("VwEAAfgHdwAMKGFhYWJiYmJiYmJiYmNjY2NjY2NkZGRkZGRkZWVlZWVlZWZmZmZmZmZvAJ13AG8AJc////8CwPNeAXcAAfcHSEpvAJ13AG8AJfT///9JQA=="); + } + + [Benchmark] + public void PoC_MemCpy() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // OVER + // PUSH0 + // PUSH2 + // PICK + // PUSH0 + // PUSHINT32 1048576 + // MEMCPY + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L eeffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAgAAEACIAlmB8gd3AEsQEk0QAgAAEACJbwCddwBvACXu////SUA="); + } + + [Benchmark] + public void PoC_Unpack() + { + // INITSLOT 0200 + // PUSHINT16 1010 + // NEWARRAY + // STLOC 01 + // PUSHINT32 1333333337 + // STLOC 00 + // LDLOC 01 + // UNPACK + // CLEAR + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // RET + Run("VwIAAfIDw3cBAlkNeU93AG8BwUlvAJ13AG8AJfX///9A"); + } + + [Benchmark] + public void PoC_GetScriptContainer() + { + // SYSCALL System.Runtime.GetScriptContainer + // DROP + // JMP fa + Run("QS1RCDBFIvo="); + } + + private static void Run(string poc) { byte[] script = Convert.FromBase64String(poc); using ExecutionEngine engine = new(); engine.LoadScript(script); engine.Execute(); + Debug.Assert(engine.State == VMState.HALT); } } From 52393be239bce163c14ff09b15dbabd4a6bc3240 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:00:11 -0700 Subject: [PATCH 17/42] Bump System.Text.Json from 8.0.4 to 8.0.5 in /src/Neo.Json (#3519) Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 8.0.4 to 8.0.5. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.4...v8.0.5) --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: NGD Admin <154295625+NGDAdmin@users.noreply.github.com> Co-authored-by: Shargon --- src/Neo.Json/Neo.Json.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo.Json/Neo.Json.csproj b/src/Neo.Json/Neo.Json.csproj index 8d8fd33ac9..0a490a1934 100644 --- a/src/Neo.Json/Neo.Json.csproj +++ b/src/Neo.Json/Neo.Json.csproj @@ -10,7 +10,7 @@ - + From 5d2d8daa73f2609abe489456e22ba375ebb11cf9 Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Thu, 10 Oct 2024 18:13:49 +0800 Subject: [PATCH 18/42] Circular reference error info in ToJson (#3522) Co-authored-by: Shargon --- src/Neo/VM/Helper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Neo/VM/Helper.cs b/src/Neo/VM/Helper.cs index 26d0fa132a..6964747473 100644 --- a/src/Neo/VM/Helper.cs +++ b/src/Neo/VM/Helper.cs @@ -365,7 +365,7 @@ private static JObject ToJson(StackItem item, HashSet context, ref in case Array array: { context ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!context.Add(array)) throw new InvalidOperationException(); + if (!context.Add(array)) throw new InvalidOperationException("Circular reference."); maxSize -= 2/*[]*/+ Math.Max(0, (array.Count - 1))/*,*/; JArray a = new(); foreach (StackItem stackItem in array) @@ -398,7 +398,7 @@ private static JObject ToJson(StackItem item, HashSet context, ref in case Map map: { context ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!context.Add(map)) throw new InvalidOperationException(); + if (!context.Add(map)) throw new InvalidOperationException("Circular reference."); maxSize -= 2/*[]*/+ Math.Max(0, (map.Count - 1))/*,*/; JArray a = new(); foreach (var (k, v) in map) From 20c866174c4364f644b8ca403b97b616a4e798ec Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 10 Oct 2024 22:03:31 +0800 Subject: [PATCH 19/42] [Benchmark] Benchmark OpCode and VM (#3514) * add opcode benchmark system * add opcode benchmark system * update to make the framework easier to work with. * Clean code * filescope namespace * remove uncomplet benchmark * add missing using --------- Co-authored-by: Fernando Diaz Toledano --- .../InstructionBuilder/Helper.cs | 72 ++++++ .../InstructionBuilder/Instruction.cs | 58 +++++ .../InstructionBuilder/InstructionBuilder.cs | 242 ++++++++++++++++++ .../InstructionBuilder/JumpTarget.cs | 17 ++ .../Neo.VM.Benchmarks.csproj | 4 + .../OpCode/Arrays/OpCode.ReverseN.cs | 136 ++++++++++ .../OpCode/Benchmark.Opcode.cs | 234 +++++++++++++++++ .../OpCode/BenchmarkEngine.cs | 190 ++++++++++++++ .../Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs | 19 ++ .../Neo.VM.Benchmarks/OpCode/OpCodeBase.cs | 45 ++++ benchmarks/Neo.VM.Benchmarks/Program.cs | 57 ++++- .../VMTypes/Benchmarks_Convert.cs | 1 - src/Neo.VM/OpCode.cs | 2 +- tests/Neo.VM.Tests/Types/TestEngine.cs | 2 +- 14 files changed, 1075 insertions(+), 4 deletions(-) create mode 100644 benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs create mode 100644 benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs new file mode 100644 index 0000000000..f5ea579e6f --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Buffers.Binary; + +namespace Neo.VM.Benchmark; + +public static class Helper +{ + public static void RebuildOffsets(this IReadOnlyList instructions) + { + var offset = 0; + foreach (var instruction in instructions) + { + instruction._offset = offset; + offset += instruction.Size; + } + } + + public static void RebuildOperands(this IReadOnlyList instructions) + { + foreach (var instruction in instructions) + { + if (instruction._target is null) continue; + bool isLong; + if (instruction._opCode >= VM.OpCode.JMP && instruction._opCode <= VM.OpCode.CALL_L) + isLong = (instruction._opCode - VM.OpCode.JMP) % 2 != 0; + else + isLong = instruction._opCode == VM.OpCode.PUSHA || instruction._opCode == VM.OpCode.CALLA || instruction._opCode == VM.OpCode.TRY_L || instruction._opCode == VM.OpCode.ENDTRY_L; + if (instruction._opCode == VM.OpCode.TRY || instruction._opCode == VM.OpCode.TRY_L) + { + var offset1 = (instruction._target._instruction?._offset - instruction._offset) ?? 0; + var offset2 = (instruction._target2!._instruction?._offset - instruction._offset) ?? 0; + if (isLong) + { + instruction._operand = new byte[sizeof(int) + sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(instruction._operand, offset1); + BinaryPrimitives.WriteInt32LittleEndian(instruction._operand.AsSpan(sizeof(int)), offset2); + } + else + { + instruction._operand = new byte[sizeof(sbyte) + sizeof(sbyte)]; + var sbyte1 = checked((sbyte)offset1); + var sbyte2 = checked((sbyte)offset2); + instruction._operand[0] = unchecked((byte)sbyte1); + instruction._operand[1] = unchecked((byte)sbyte2); + } + } + else + { + int offset = instruction._target._instruction!._offset - instruction._offset; + if (isLong) + { + instruction._operand = BitConverter.GetBytes(offset); + } + else + { + var sbyte1 = checked((sbyte)offset); + instruction._operand = [unchecked((byte)sbyte1)]; + } + } + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs new file mode 100644 index 0000000000..5a30aeec10 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Instruction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.CodeAnalysis; +using System.Diagnostics; +using System.Reflection; + +namespace Neo.VM.Benchmark; + +[DebuggerDisplay("{_opCode}")] +public class Instruction +{ + private static readonly int[] s_operandSizePrefixTable = new int[256]; + private static readonly int[] s_operandSizeTable = new int[256]; + + public VM.OpCode _opCode; + public byte[]? _operand; + public JumpTarget? _target; + public JumpTarget? _target2; + public int _offset; + + public int Size + { + get + { + int prefixSize = s_operandSizePrefixTable[(int)_opCode]; + return prefixSize > 0 + ? sizeof(VM.OpCode) + _operand!.Length + : sizeof(VM.OpCode) + s_operandSizeTable[(int)_opCode]; + } + } + + static Instruction() + { + foreach (var field in typeof(VM.OpCode).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var attribute = field.GetCustomAttribute(); + if (attribute is null) continue; + var index = (int)(VM.OpCode)field.GetValue(null)!; + s_operandSizePrefixTable[index] = attribute.SizePrefix; + s_operandSizeTable[index] = attribute.Size; + } + } + + public byte[] ToArray() + { + if (_operand is null) return [(byte)_opCode]; + return _operand.Prepend((byte)_opCode).ToArray(); + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs new file mode 100644 index 0000000000..21d1b77de2 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs @@ -0,0 +1,242 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InstructionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Buffers.Binary; +using System.Numerics; + +namespace Neo.VM.Benchmark; + +internal class InstructionBuilder +{ + internal readonly List _instructions = new(); + + public InstructionBuilder() { } + + internal Instruction AddInstruction(Instruction instruction) + { + _instructions.Add(instruction); + return instruction; + } + + internal Instruction AddInstruction(VM.OpCode opcode) + { + return AddInstruction(new Instruction + { + _opCode = opcode + }); + } + + internal Instruction Jump(VM.OpCode opcode, JumpTarget target) + { + return AddInstruction(new Instruction + { + _opCode = opcode, + _target = target + }); + } + + internal void Push(bool value) + { + AddInstruction(value ? VM.OpCode.PUSHT : VM.OpCode.PUSHF); + } + + internal Instruction Ret() => AddInstruction(VM.OpCode.RET); + + internal Instruction Push(BigInteger number) + { + if (number >= -1 && number <= 16) return AddInstruction(number == -1 ? VM.OpCode.PUSHM1 : VM.OpCode.PUSH0 + (byte)(int)number); + Span buffer = stackalloc byte[32]; + if (!number.TryWriteBytes(buffer, out var bytesWritten, isUnsigned: false, isBigEndian: false)) + throw new ArgumentOutOfRangeException(nameof(number)); + var instruction = bytesWritten switch + { + 1 => new Instruction + { + _opCode = VM.OpCode.PUSHINT8, + _operand = PadRight(buffer, bytesWritten, 1, number.Sign < 0).ToArray() + }, + 2 => new Instruction + { + _opCode = VM.OpCode.PUSHINT16, + _operand = PadRight(buffer, bytesWritten, 2, number.Sign < 0).ToArray() + }, + <= 4 => new Instruction + { + _opCode = VM.OpCode.PUSHINT32, + _operand = PadRight(buffer, bytesWritten, 4, number.Sign < 0).ToArray() + }, + <= 8 => new Instruction + { + _opCode = VM.OpCode.PUSHINT64, + _operand = PadRight(buffer, bytesWritten, 8, number.Sign < 0).ToArray() + }, + <= 16 => new Instruction + { + _opCode = VM.OpCode.PUSHINT128, + _operand = PadRight(buffer, bytesWritten, 16, number.Sign < 0).ToArray() + }, + <= 32 => new Instruction + { + _opCode = VM.OpCode.PUSHINT256, + _operand = PadRight(buffer, bytesWritten, 32, number.Sign < 0).ToArray() + }, + _ => throw new ArgumentOutOfRangeException($"Number too large: {bytesWritten}") + }; + AddInstruction(instruction); + return instruction; + } + + internal Instruction Push(string s) + { + return Push(Utility.StrictUTF8.GetBytes(s)); + } + + internal Instruction Push(byte[] data) + { + VM.OpCode opcode; + byte[] buffer; + switch (data.Length) + { + case <= byte.MaxValue: + opcode = VM.OpCode.PUSHDATA1; + buffer = new byte[sizeof(byte) + data.Length]; + buffer[0] = (byte)data.Length; + Buffer.BlockCopy(data, 0, buffer, sizeof(byte), data.Length); + break; + case <= ushort.MaxValue: + opcode = VM.OpCode.PUSHDATA2; + buffer = new byte[sizeof(ushort) + data.Length]; + BinaryPrimitives.WriteUInt16LittleEndian(buffer, (ushort)data.Length); + Buffer.BlockCopy(data, 0, buffer, sizeof(ushort), data.Length); + break; + default: + opcode = VM.OpCode.PUSHDATA4; + buffer = new byte[sizeof(uint) + data.Length]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)data.Length); + Buffer.BlockCopy(data, 0, buffer, sizeof(uint), data.Length); + break; + } + return AddInstruction(new Instruction + { + _opCode = opcode, + _operand = buffer + }); + } + + internal void Push(object? obj) + { + switch (obj) + { + case bool data: + Push(data); + break; + case byte[] data: + Push(data); + break; + case string data: + Push(data); + break; + case BigInteger data: + Push(data); + break; + case char data: + Push((ushort)data); + break; + case sbyte data: + Push(data); + break; + case byte data: + Push(data); + break; + case short data: + Push(data); + break; + case ushort data: + Push(data); + break; + case int data: + Push(data); + break; + case uint data: + Push(data); + break; + case long data: + Push(data); + break; + case ulong data: + Push(data); + break; + case Enum data: + Push(BigInteger.Parse(data.ToString("d"))); + break; + case null: + AddInstruction(VM.OpCode.PUSHNULL); + break; + default: + throw new NotSupportedException($"Unsupported constant value: {obj}"); + } + } + + // Helper method to reverse stack items + internal void ReverseStackItems(int count) + { + switch (count) + { + case 2: + AddInstruction(VM.OpCode.SWAP); + break; + case 3: + AddInstruction(VM.OpCode.REVERSE3); + break; + case 4: + AddInstruction(VM.OpCode.REVERSE4); + break; + default: + Push(count); + AddInstruction(VM.OpCode.REVERSEN); + break; + } + } + + internal static ReadOnlySpan PadRight(Span buffer, int dataLength, int padLength, bool negative) + { + byte pad = negative ? (byte)0xff : (byte)0; + for (int x = dataLength; x < padLength; x++) + buffer[x] = pad; + return buffer[..padLength]; + } + + internal Instruction IsType(VM.Types.StackItemType type) + { + return AddInstruction(new Instruction + { + _opCode = VM.OpCode.ISTYPE, + _operand = [(byte)type] + }); + } + + internal Instruction ChangeType(VM.Types.StackItemType type) + { + return AddInstruction(new Instruction + { + _opCode = VM.OpCode.CONVERT, + _operand = [(byte)type] + }); + } + + internal byte[] ToArray() + { + var instructions = _instructions.ToArray(); + instructions.RebuildOffsets(); + instructions.RebuildOperands(); + return instructions.Select(p => p.ToArray()).SelectMany(p => p).ToArray(); + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs new file mode 100644 index 0000000000..246b0e5884 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs @@ -0,0 +1,17 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTarget.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark; + +public class JumpTarget +{ + public Instruction? _instruction; +} diff --git a/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj index ae717e8254..d5a7909a4b 100644 --- a/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj +++ b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj @@ -9,8 +9,12 @@ + + + + diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs new file mode 100644 index 0000000000..515125ddc0 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs @@ -0,0 +1,136 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OpCode.ReverseN.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark.OpCode; + +public class OpCode_ReverseN : OpCodeBase +{ + protected override byte[] CreateScript(BenchmarkMode benchmarkMode) + { + var builder = new InstructionBuilder(); + var initBegin = new JumpTarget(); + builder.AddInstruction(new Instruction { _opCode = VM.OpCode.INITSLOT, _operand = [1, 0] }); + builder.Push(ItemCount); + builder.AddInstruction(VM.OpCode.STLOC0); + initBegin._instruction = builder.AddInstruction(VM.OpCode.NOP); + builder.Push(0); + builder.AddInstruction(VM.OpCode.LDLOC0); + builder.AddInstruction(VM.OpCode.DEC); + builder.AddInstruction(VM.OpCode.STLOC0); + builder.AddInstruction(VM.OpCode.LDLOC0); + builder.Jump(VM.OpCode.JMPIF, initBegin); + if (benchmarkMode == BenchmarkMode.BaseLine) + { + return builder.ToArray(); + } + builder.Push(ItemCount); + builder.AddInstruction(VM.OpCode.REVERSEN); + if (benchmarkMode == BenchmarkMode.OneGAS) + { + // just keep running until GAS is exhausted + var loopStart = new JumpTarget { _instruction = builder.AddInstruction(VM.OpCode.NOP) }; + builder.Push(ItemCount); + builder.AddInstruction(VM.OpCode.REVERSEN); + builder.Jump(VM.OpCode.JMP, loopStart); + } + + return builder.ToArray(); + } +} + +// for 0 + +// BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.4249/23H2/2023Update/SunValley3) +// Intel Core i9-14900HX, 1 CPU, 32 logical and 24 physical cores +// .NET SDK 8.0.205 +// [Host] : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// DefaultJob : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// +// +// | Method | ItemCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +// |--------------------- |---------- |-----------------:|--------------:|---------------:|-----------------:|----------:|---------:| +// | Bench_ReverseN | 1 | 63.43 us | 0.466 us | 0.518 us | 63.45 us | 0.99 | 0.01 | +// | Bench_OneGasReverseN | 1 | 403,904.11 us | 6,492.511 us | 6,073.099 us | 402,932.40 us | 6,315.67 | 89.44 | +// | Bench_BaseLine | 1 | 63.96 us | 0.763 us | 0.714 us | 63.92 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 2 | 62.55 us | 0.988 us | 0.924 us | 62.46 us | 0.95 | 0.02 | +// | Bench_OneGasReverseN | 2 | 424,297.10 us | 8,453.137 us | 7,493.486 us | 423,912.90 us | 6,446.21 | 118.35 | +// | Bench_BaseLine | 2 | 65.83 us | 0.845 us | 0.749 us | 65.95 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 4 | 63.93 us | 0.418 us | 0.371 us | 63.89 us | 0.95 | 0.03 | +// | Bench_OneGasReverseN | 4 | 443,708.92 us | 6,689.013 us | 6,256.907 us | 444,636.60 us | 6,560.69 | 229.86 | +// | Bench_BaseLine | 4 | 67.64 us | 1.281 us | 1.524 us | 67.79 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 8 | 66.69 us | 0.757 us | 0.671 us | 66.69 us | 1.00 | 0.02 | +// | Bench_OneGasReverseN | 8 | 463,571.38 us | 6,614.687 us | 6,187.382 us | 465,568.00 us | 6,963.59 | 85.80 | +// | Bench_BaseLine | 8 | 66.64 us | 0.870 us | 0.771 us | 66.68 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 16 | 65.85 us | 0.994 us | 0.929 us | 65.61 us | 0.94 | 0.02 | +// | Bench_OneGasReverseN | 16 | 740,905.55 us | 71,090.901 us | 209,613.127 us | 653,644.75 us | 9,341.86 | 3,092.85 | +// | Bench_BaseLine | 16 | 70.08 us | 1.376 us | 1.638 us | 70.15 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 32 | 66.47 us | 0.928 us | 2.187 us | 65.77 us | 1.02 | 0.04 | +// | Bench_OneGasReverseN | 32 | 631,596.65 us | 11,060.847 us | 10,346.323 us | 629,654.10 us | 9,360.06 | 221.36 | +// | Bench_BaseLine | 32 | 67.49 us | 0.900 us | 0.842 us | 67.56 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 64 | 72.21 us | 0.921 us | 0.862 us | 72.05 us | 1.02 | 0.02 | +// | Bench_OneGasReverseN | 64 | 787,570.95 us | 6,915.746 us | 6,468.994 us | 786,778.70 us | 11,090.76 | 177.74 | +// | Bench_BaseLine | 64 | 71.02 us | 0.946 us | 0.884 us | 71.06 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 128 | 80.17 us | 0.723 us | 0.676 us | 80.19 us | 0.98 | 0.01 | +// | Bench_OneGasReverseN | 128 | 1,134,510.61 us | 14,991.714 us | 14,023.259 us | 1,133,177.90 us | 13,828.61 | 184.58 | +// | Bench_BaseLine | 128 | 81.90 us | 0.623 us | 0.553 us | 81.77 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 256 | 98.24 us | 1.140 us | 1.067 us | 98.05 us | 0.99 | 0.01 | +// | Bench_OneGasReverseN | 256 | 1,785,906.33 us | 13,785.746 us | 12,895.195 us | 1,788,819.30 us | 18,067.20 | 198.87 | +// | Bench_BaseLine | 256 | 98.85 us | 0.961 us | 0.899 us | 98.95 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 512 | 136.19 us | 1.614 us | 1.510 us | 136.34 us | 1.02 | 0.02 | +// | Bench_OneGasReverseN | 512 | 3,100,087.41 us | 16,564.249 us | 15,494.209 us | 3,097,066.60 us | 23,209.57 | 381.50 | +// | Bench_BaseLine | 512 | 133.60 us | 2.144 us | 2.006 us | 132.73 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 1024 | 207.06 us | 2.213 us | 2.070 us | 206.76 us | 1.01 | 0.01 | +// | Bench_OneGasReverseN | 1024 | 5,762,294.72 us | 20,289.404 us | 16,942.572 us | 5,764,133.80 us | 28,109.14 | 349.87 | +// | Bench_BaseLine | 1024 | 205.07 us | 2.360 us | 2.208 us | 205.07 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 2040 | 345.09 us | 4.271 us | 3.995 us | 345.40 us | 0.97 | 0.01 | +// | Bench_OneGasReverseN | 2040 | 11,005,147.03 us | 37,306.455 us | 33,071.200 us | 11,003,479.70 us | 31,019.36 | 356.02 | +// | Bench_BaseLine | 2040 | 354.62 us | 4.623 us | 4.325 us | 353.32 us | 1.00 | 0.00 | + + + +// for StackItems that has a size of maximum stack size. +// | Method | ItemCount | Mean | Error | StdDev | +// |--------------------- |---------- |----------------:|--------------:|--------------:| +// | Bench_ReverseN | 1 | 104.0 us | 0.77 us | 0.72 us | +// | Bench_OneGasReverseN | 1 | 389,585.4 us | 4,740.18 us | 4,433.96 us | +// | Bench_ReverseN | 2 | 148.3 us | 2.25 us | 2.10 us | +// | Bench_OneGasReverseN | 2 | 417,831.5 us | 6,651.20 us | 6,221.53 us | +// | Bench_ReverseN | 4 | 231.8 us | 3.92 us | 3.67 us | +// | Bench_OneGasReverseN | 4 | 428,442.6 us | 8,034.41 us | 7,515.39 us | +// | Bench_ReverseN | 8 | 387.8 us | 5.23 us | 4.89 us | +// | Bench_OneGasReverseN | 8 | 448,046.9 us | 6,270.18 us | 5,235.89 us | +// | Bench_ReverseN | 16 | 240.0 us | 7.30 us | 21.53 us | +// | Bench_OneGasReverseN | 16 | 522,904.3 us | 7,157.93 us | 6,695.54 us | +// | Bench_ReverseN | 32 | 302.4 us | 9.53 us | 27.79 us | +// | Bench_OneGasReverseN | 32 | 626,536.6 us | 6,629.69 us | 6,201.42 us | +// | Bench_ReverseN | 64 | 1,728.3 us | 34.27 us | 58.19 us | +// | Bench_OneGasReverseN | 64 | 827,284.5 us | 15,943.00 us | 14,913.09 us | +// | Bench_ReverseN | 128 | 3,704.5 us | 73.98 us | 175.82 us | +// | Bench_OneGasReverseN | 128 | 1,125,104.6 us | 10,629.65 us | 9,942.98 us | +// | Bench_ReverseN | 256 | 6,381.1 us | 127.42 us | 290.21 us | +// | Bench_OneGasReverseN | 256 | 1,804,355.7 us | 9,690.50 us | 8,590.37 us | +// | Bench_ReverseN | 512 | 9,485.9 us | 184.52 us | 492.52 us | +// | Bench_OneGasReverseN | 512 | 3,159,411.1 us | 28,901.54 us | 27,034.52 us | +// | Bench_ReverseN | 1024 | 14,125.6 us | 282.51 us | 577.08 us | +// | Bench_OneGasReverseN | 1024 | 5,799,154.5 us | 33,817.93 us | 31,633.31 us | +// | Bench_ReverseN | 2040 | 22,868.0 us | 449.84 us | 929.00 us | +// | Bench_OneGasReverseN | 2040 | 11,100,853.9 us | 159,980.97 us | 141,818.97 us | diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs new file mode 100644 index 0000000000..f55abae7ee --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs @@ -0,0 +1,234 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmark.Opcode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark.OpCode; + +public class Benchmark_Opcode +{ + internal static readonly long OneGasDatoshi = 1_0000_0000; + + public static readonly IReadOnlyDictionary OpCodePrices = new Dictionary + { + [VM.OpCode.PUSHINT8] = 1 << 0, + [VM.OpCode.PUSHINT16] = 1 << 0, + [VM.OpCode.PUSHINT32] = 1 << 0, + [VM.OpCode.PUSHINT64] = 1 << 0, + [VM.OpCode.PUSHINT128] = 1 << 2, + [VM.OpCode.PUSHINT256] = 1 << 2, + [VM.OpCode.PUSHT] = 1 << 0, + [VM.OpCode.PUSHF] = 1 << 0, + [VM.OpCode.PUSHA] = 1 << 2, + [VM.OpCode.PUSHNULL] = 1 << 0, + [VM.OpCode.PUSHDATA1] = 1 << 3, + [VM.OpCode.PUSHDATA2] = 1 << 9, + [VM.OpCode.PUSHDATA4] = 1 << 12, + [VM.OpCode.PUSHM1] = 1 << 0, + [VM.OpCode.PUSH0] = 1 << 0, + [VM.OpCode.PUSH1] = 1 << 0, + [VM.OpCode.PUSH2] = 1 << 0, + [VM.OpCode.PUSH3] = 1 << 0, + [VM.OpCode.PUSH4] = 1 << 0, + [VM.OpCode.PUSH5] = 1 << 0, + [VM.OpCode.PUSH6] = 1 << 0, + [VM.OpCode.PUSH7] = 1 << 0, + [VM.OpCode.PUSH8] = 1 << 0, + [VM.OpCode.PUSH9] = 1 << 0, + [VM.OpCode.PUSH10] = 1 << 0, + [VM.OpCode.PUSH11] = 1 << 0, + [VM.OpCode.PUSH12] = 1 << 0, + [VM.OpCode.PUSH13] = 1 << 0, + [VM.OpCode.PUSH14] = 1 << 0, + [VM.OpCode.PUSH15] = 1 << 0, + [VM.OpCode.PUSH16] = 1 << 0, + [VM.OpCode.NOP] = 1 << 0, + [VM.OpCode.JMP] = 1 << 1, + [VM.OpCode.JMP_L] = 1 << 1, + [VM.OpCode.JMPIF] = 1 << 1, + [VM.OpCode.JMPIF_L] = 1 << 1, + [VM.OpCode.JMPIFNOT] = 1 << 1, + [VM.OpCode.JMPIFNOT_L] = 1 << 1, + [VM.OpCode.JMPEQ] = 1 << 1, + [VM.OpCode.JMPEQ_L] = 1 << 1, + [VM.OpCode.JMPNE] = 1 << 1, + [VM.OpCode.JMPNE_L] = 1 << 1, + [VM.OpCode.JMPGT] = 1 << 1, + [VM.OpCode.JMPGT_L] = 1 << 1, + [VM.OpCode.JMPGE] = 1 << 1, + [VM.OpCode.JMPGE_L] = 1 << 1, + [VM.OpCode.JMPLT] = 1 << 1, + [VM.OpCode.JMPLT_L] = 1 << 1, + [VM.OpCode.JMPLE] = 1 << 1, + [VM.OpCode.JMPLE_L] = 1 << 1, + [VM.OpCode.CALL] = 1 << 9, + [VM.OpCode.CALL_L] = 1 << 9, + [VM.OpCode.CALLA] = 1 << 9, + [VM.OpCode.CALLT] = 1 << 15, + [VM.OpCode.ABORT] = 0, + [VM.OpCode.ABORTMSG] = 0, + [VM.OpCode.ASSERT] = 1 << 0, + [VM.OpCode.ASSERTMSG] = 1 << 0, + [VM.OpCode.THROW] = 1 << 9, + [VM.OpCode.TRY] = 1 << 2, + [VM.OpCode.TRY_L] = 1 << 2, + [VM.OpCode.ENDTRY] = 1 << 2, + [VM.OpCode.ENDTRY_L] = 1 << 2, + [VM.OpCode.ENDFINALLY] = 1 << 2, + [VM.OpCode.RET] = 0, + [VM.OpCode.SYSCALL] = 0, + [VM.OpCode.DEPTH] = 1 << 1, + [VM.OpCode.DROP] = 1 << 1, + [VM.OpCode.NIP] = 1 << 1, + [VM.OpCode.XDROP] = 1 << 4, + [VM.OpCode.CLEAR] = 1 << 4, + [VM.OpCode.DUP] = 1 << 1, + [VM.OpCode.OVER] = 1 << 1, + [VM.OpCode.PICK] = 1 << 1, + [VM.OpCode.TUCK] = 1 << 1, + [VM.OpCode.SWAP] = 1 << 1, + [VM.OpCode.ROT] = 1 << 1, + [VM.OpCode.ROLL] = 1 << 4, + [VM.OpCode.REVERSE3] = 1 << 1, + [VM.OpCode.REVERSE4] = 1 << 1, + [VM.OpCode.REVERSEN] = 1 << 4, + [VM.OpCode.INITSSLOT] = 1 << 4, + [VM.OpCode.INITSLOT] = 1 << 6, + [VM.OpCode.LDSFLD0] = 1 << 1, + [VM.OpCode.LDSFLD1] = 1 << 1, + [VM.OpCode.LDSFLD2] = 1 << 1, + [VM.OpCode.LDSFLD3] = 1 << 1, + [VM.OpCode.LDSFLD4] = 1 << 1, + [VM.OpCode.LDSFLD5] = 1 << 1, + [VM.OpCode.LDSFLD6] = 1 << 1, + [VM.OpCode.LDSFLD] = 1 << 1, + [VM.OpCode.STSFLD0] = 1 << 1, + [VM.OpCode.STSFLD1] = 1 << 1, + [VM.OpCode.STSFLD2] = 1 << 1, + [VM.OpCode.STSFLD3] = 1 << 1, + [VM.OpCode.STSFLD4] = 1 << 1, + [VM.OpCode.STSFLD5] = 1 << 1, + [VM.OpCode.STSFLD6] = 1 << 1, + [VM.OpCode.STSFLD] = 1 << 1, + [VM.OpCode.LDLOC0] = 1 << 1, + [VM.OpCode.LDLOC1] = 1 << 1, + [VM.OpCode.LDLOC2] = 1 << 1, + [VM.OpCode.LDLOC3] = 1 << 1, + [VM.OpCode.LDLOC4] = 1 << 1, + [VM.OpCode.LDLOC5] = 1 << 1, + [VM.OpCode.LDLOC6] = 1 << 1, + [VM.OpCode.LDLOC] = 1 << 1, + [VM.OpCode.STLOC0] = 1 << 1, + [VM.OpCode.STLOC1] = 1 << 1, + [VM.OpCode.STLOC2] = 1 << 1, + [VM.OpCode.STLOC3] = 1 << 1, + [VM.OpCode.STLOC4] = 1 << 1, + [VM.OpCode.STLOC5] = 1 << 1, + [VM.OpCode.STLOC6] = 1 << 1, + [VM.OpCode.STLOC] = 1 << 1, + [VM.OpCode.LDARG0] = 1 << 1, + [VM.OpCode.LDARG1] = 1 << 1, + [VM.OpCode.LDARG2] = 1 << 1, + [VM.OpCode.LDARG3] = 1 << 1, + [VM.OpCode.LDARG4] = 1 << 1, + [VM.OpCode.LDARG5] = 1 << 1, + [VM.OpCode.LDARG6] = 1 << 1, + [VM.OpCode.LDARG] = 1 << 1, + [VM.OpCode.STARG0] = 1 << 1, + [VM.OpCode.STARG1] = 1 << 1, + [VM.OpCode.STARG2] = 1 << 1, + [VM.OpCode.STARG3] = 1 << 1, + [VM.OpCode.STARG4] = 1 << 1, + [VM.OpCode.STARG5] = 1 << 1, + [VM.OpCode.STARG6] = 1 << 1, + [VM.OpCode.STARG] = 1 << 1, + [VM.OpCode.NEWBUFFER] = 1 << 8, + [VM.OpCode.MEMCPY] = 1 << 11, + [VM.OpCode.CAT] = 1 << 11, + [VM.OpCode.SUBSTR] = 1 << 11, + [VM.OpCode.LEFT] = 1 << 11, + [VM.OpCode.RIGHT] = 1 << 11, + [VM.OpCode.INVERT] = 1 << 2, + [VM.OpCode.AND] = 1 << 3, + [VM.OpCode.OR] = 1 << 3, + [VM.OpCode.XOR] = 1 << 3, + [VM.OpCode.EQUAL] = 1 << 5, + [VM.OpCode.NOTEQUAL] = 1 << 5, + [VM.OpCode.SIGN] = 1 << 2, + [VM.OpCode.ABS] = 1 << 2, + [VM.OpCode.NEGATE] = 1 << 2, + [VM.OpCode.INC] = 1 << 2, + [VM.OpCode.DEC] = 1 << 2, + [VM.OpCode.ADD] = 1 << 3, + [VM.OpCode.SUB] = 1 << 3, + [VM.OpCode.MUL] = 1 << 3, + [VM.OpCode.DIV] = 1 << 3, + [VM.OpCode.MOD] = 1 << 3, + [VM.OpCode.POW] = 1 << 6, + [VM.OpCode.SQRT] = 1 << 6, + [VM.OpCode.MODMUL] = 1 << 5, + [VM.OpCode.MODPOW] = 1 << 11, + [VM.OpCode.SHL] = 1 << 3, + [VM.OpCode.SHR] = 1 << 3, + [VM.OpCode.NOT] = 1 << 2, + [VM.OpCode.BOOLAND] = 1 << 3, + [VM.OpCode.BOOLOR] = 1 << 3, + [VM.OpCode.NZ] = 1 << 2, + [VM.OpCode.NUMEQUAL] = 1 << 3, + [VM.OpCode.NUMNOTEQUAL] = 1 << 3, + [VM.OpCode.LT] = 1 << 3, + [VM.OpCode.LE] = 1 << 3, + [VM.OpCode.GT] = 1 << 3, + [VM.OpCode.GE] = 1 << 3, + [VM.OpCode.MIN] = 1 << 3, + [VM.OpCode.MAX] = 1 << 3, + [VM.OpCode.WITHIN] = 1 << 3, + [VM.OpCode.PACKMAP] = 1 << 11, + [VM.OpCode.PACKSTRUCT] = 1 << 11, + [VM.OpCode.PACK] = 1 << 11, + [VM.OpCode.UNPACK] = 1 << 11, + [VM.OpCode.NEWARRAY0] = 1 << 4, + [VM.OpCode.NEWARRAY] = 1 << 9, + [VM.OpCode.NEWARRAY_T] = 1 << 9, + [VM.OpCode.NEWSTRUCT0] = 1 << 4, + [VM.OpCode.NEWSTRUCT] = 1 << 9, + [VM.OpCode.NEWMAP] = 1 << 3, + [VM.OpCode.SIZE] = 1 << 2, + [VM.OpCode.HASKEY] = 1 << 6, + [VM.OpCode.KEYS] = 1 << 4, + [VM.OpCode.VALUES] = 1 << 13, + [VM.OpCode.PICKITEM] = 1 << 6, + [VM.OpCode.APPEND] = 1 << 13, + [VM.OpCode.SETITEM] = 1 << 13, + [VM.OpCode.REVERSEITEMS] = 1 << 13, + [VM.OpCode.REMOVE] = 1 << 4, + [VM.OpCode.CLEARITEMS] = 1 << 4, + [VM.OpCode.POPITEM] = 1 << 4, + [VM.OpCode.ISNULL] = 1 << 1, + [VM.OpCode.ISTYPE] = 1 << 1, + [VM.OpCode.CONVERT] = 1 << 13, + }; + + internal static void RunScript(byte[] script) + { + LoadScript(script).ExecuteBenchmark(); + } + + internal static BenchmarkEngine RunScriptUntil(byte[] script, VM.OpCode opCode) + { + return LoadScript(script).ExecuteUntil(opCode); + } + + internal static BenchmarkEngine LoadScript(byte[] script) + { + var engine = new BenchmarkEngine(); + engine.LoadScript(script); + return engine; + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs new file mode 100644 index 0000000000..50f80c57e9 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs @@ -0,0 +1,190 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BenchmarkEngine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Test.Types; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Benchmark.OpCode; + +/// +/// A simple benchmark engine for . +/// +public class BenchmarkEngine : TestEngine +{ + private readonly Dictionary _opcodeStats = new(); + private readonly Dictionary> _breakPoints = new(); + private long _gasConsumed = 0; + + /// + /// Add a breakpoint at the specified position of the specified script. The VM will break the execution when it reaches the breakpoint. + /// + /// The script to add the breakpoint. + /// The position of the breakpoint in the script. + public void AddBreakPoint(Script script, uint position) + { + if (!_breakPoints.TryGetValue(script, out var hashset)) + { + hashset = []; + _breakPoints.Add(script, hashset); + } + hashset.Add(position); + } + + /// + /// Start or continue execution of the VM. + /// + /// Returns the state of the VM after the execution. + public BenchmarkEngine ExecuteUntil(VM.OpCode opCode) + { + if (State == VMState.BREAK) + State = VMState.NONE; + while (State == VMState.NONE) + { + ExecuteNext(); + try + { + var instruction = CurrentContext!.CurrentInstruction!.OpCode; + if (instruction == opCode) break; + } + catch + { + break; + } + } + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(CurrentContext!.CurrentInstruction!.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteOneGASBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { + var instruction = CurrentContext!.CurrentInstruction ?? VM.Instruction.RET; + _gasConsumed += Benchmark_Opcode.OpCodePrices[instruction.OpCode]; + if (_gasConsumed >= Benchmark_Opcode.OneGasDatoshi) + { + State = VMState.HALT; + } +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(instruction.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteTwentyGASBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { + var instruction = CurrentContext!.CurrentInstruction ?? VM.Instruction.RET; + _gasConsumed += Benchmark_Opcode.OpCodePrices[instruction.OpCode]; + if (_gasConsumed >= 20 * Benchmark_Opcode.OneGasDatoshi) + { + State = VMState.HALT; + } +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(instruction.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteOpCodesBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { + var instruction = CurrentContext!.CurrentInstruction ?? VM.Instruction.RET; + _gasConsumed += Benchmark_Opcode.OpCodePrices[instruction.OpCode]; + if (_gasConsumed >= Benchmark_Opcode.OneGasDatoshi) + { + State = VMState.HALT; + } +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(instruction.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + protected override void OnFault(Exception ex) + { + base.OnFault(ex); + // throw ex; + } + + private void UpdateOpcodeStats(VM.OpCode opcode, TimeSpan elapsed) + { + if (!_opcodeStats.TryGetValue(opcode, out var value)) + { + _opcodeStats[opcode] = (1, elapsed); + } + else + { + var (count, totalTime) = value; + _opcodeStats[opcode] = (count + 1, totalTime + elapsed); + } + } + + private void PrintOpcodeStats() + { + Console.WriteLine("Opcode Statistics:"); + foreach (var kvp in _opcodeStats) + { + Console.WriteLine($"{kvp.Key,-15} " + + $"Count: {kvp.Value.Count,8} " + + $"Total Time: {kvp.Value.TotalTime.TotalMilliseconds * 1000,10:F2} μs " + + $"Avg Time: {kvp.Value.TotalTime.TotalMilliseconds * 1000 / kvp.Value.Count,10:F2} μs"); + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs new file mode 100644 index 0000000000..792ff04f16 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BenchmarkMode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark.OpCode; + +public enum BenchmarkMode +{ + SimpleOpCode, + OneGAS, + BaseLine +} diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs new file mode 100644 index 0000000000..78aa0dd24c --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OpCodeBase.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Attributes; + +namespace Neo.VM.Benchmark.OpCode; + +public abstract class OpCodeBase +{ + [Params(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2040)] + public int ItemCount { get; set; } = 10; + protected byte[] baseLineScript; + protected byte[] script; + protected byte[] multiScript; + + [GlobalSetup] + public void Setup() + { + script = CreateScript(BenchmarkMode.SimpleOpCode); + multiScript = CreateScript(BenchmarkMode.OneGAS); + baseLineScript = CreateScript(BenchmarkMode.BaseLine); + } + + [Benchmark(Baseline = true)] + public void Bench_BaseLine() => Benchmark_Opcode.RunScript(baseLineScript); + + [Benchmark] + public void Bench_OneOpCode() => Benchmark_Opcode.RunScript(script); + + /// + /// Benchmark how long 1 GAS can run. + /// + [Benchmark] + public void Bench_OneGAS() => Benchmark_Opcode.LoadScript(multiScript).ExecuteOneGASBenchmark(); + + protected abstract byte[] CreateScript(BenchmarkMode benchmarkMode); +} diff --git a/benchmarks/Neo.VM.Benchmarks/Program.cs b/benchmarks/Neo.VM.Benchmarks/Program.cs index 29b4549674..09523ecd7c 100644 --- a/benchmarks/Neo.VM.Benchmarks/Program.cs +++ b/benchmarks/Neo.VM.Benchmarks/Program.cs @@ -9,7 +9,62 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using Neo.VM.Benchmark; +using System.Reflection; -BenchmarkRunner.Run(); +// Flag to determine if running benchmark or running methods +// If `NEO_VM_BENCHMARK` environment variable is set, run benchmark no matter. +var runBenchmark = true; + +// Define the benchmark or execute class +var benchmarkType = typeof(Benchmarks_PoCs); + +/* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | DO NOT MODIFY THE CODE BELOW | + | | + | All configuration should be done above this line | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// Explanation: +// Benchmark methods must contain no parameters to be valid. +// This is because we need to be able to invoke these methods repeatedly +// without any external input. All necessary data should be set up in the Setup method +// or as properties of the benchmark class. + +// Example: + +// [Benchmark] +// public void BenchmarkMethod() +// { +// // Benchmark code here +// } +if (Environment.GetEnvironmentVariable("NEO_VM_BENCHMARK") != null || runBenchmark) +{ + BenchmarkRunner.Run(benchmarkType); +} +else +{ + var instance = Activator.CreateInstance(benchmarkType); + var setupMethod = benchmarkType.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .FirstOrDefault(m => m.GetCustomAttribute() != null); + if (setupMethod != null) + { + setupMethod.Invoke(instance, null); + } + + var methods = benchmarkType.GetMethods(BindingFlags.Public | BindingFlags.Instance); + + foreach (var method in methods) + { + if (method.DeclaringType == benchmarkType && !method.GetCustomAttributes().Any()) + { + method.Invoke(instance, null); + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs index 0860749955..91c660ff48 100644 --- a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs @@ -11,7 +11,6 @@ using BenchmarkDotNet.Attributes; using Neo.VM.Types; -using System.Collections.Generic; using Array = Neo.VM.Types.Array; using Buffer = Neo.VM.Types.Buffer; diff --git a/src/Neo.VM/OpCode.cs b/src/Neo.VM/OpCode.cs index 6af023cb6a..8a2878473c 100644 --- a/src/Neo.VM/OpCode.cs +++ b/src/Neo.VM/OpCode.cs @@ -1330,7 +1330,7 @@ public enum OpCode : byte /// /// /// Push: 1 item(s) - /// Pop: 0 item(s) + /// Pop: 1 item(s) /// /// NEWBUFFER = 0x88, diff --git a/tests/Neo.VM.Tests/Types/TestEngine.cs b/tests/Neo.VM.Tests/Types/TestEngine.cs index cf99314892..2602163315 100644 --- a/tests/Neo.VM.Tests/Types/TestEngine.cs +++ b/tests/Neo.VM.Tests/Types/TestEngine.cs @@ -15,7 +15,7 @@ namespace Neo.Test.Types { - class TestEngine : ExecutionEngine + public class TestEngine : ExecutionEngine { public Exception FaultException { get; private set; } From 018e17b2498b20fed953461ccf36e2a0ab34f8e9 Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 11 Oct 2024 09:29:30 +0200 Subject: [PATCH 20/42] Move `ReferenceCounter` to an interface (#3524) * Reference counter V2 * Remove warning * Interface * Change to interface * Update * Remove V2 --- benchmarks/Neo.VM.Benchmarks/TestArray.cs | 2 +- benchmarks/Neo.VM.Benchmarks/TestStruct.cs | 2 +- .../VMTypes/Benchmarks_DeepCopy.cs | 4 +- src/Neo.VM/EvaluationStack.cs | 8 +- src/Neo.VM/ExecutionContext.SharedStates.cs | 2 +- src/Neo.VM/ExecutionContext.cs | 2 +- src/Neo.VM/ExecutionEngine.cs | 2 +- src/Neo.VM/IReferenceCounter.cs | 91 +++++++++++++++++++ src/Neo.VM/ReferenceCounter.cs | 72 +++------------ src/Neo.VM/Slot.cs | 6 +- src/Neo.VM/Types/Array.cs | 2 +- src/Neo.VM/Types/CompoundType.cs | 4 +- src/Neo.VM/Types/Map.cs | 2 +- src/Neo.VM/Types/Struct.cs | 2 +- .../P2P/Payloads/Conditions/AndCondition.cs | 2 +- .../Payloads/Conditions/BooleanCondition.cs | 2 +- .../Conditions/CalledByContractCondition.cs | 2 +- .../Conditions/CalledByGroupCondition.cs | 2 +- .../P2P/Payloads/Conditions/GroupCondition.cs | 2 +- .../P2P/Payloads/Conditions/NotCondition.cs | 2 +- .../P2P/Payloads/Conditions/OrCondition.cs | 2 +- .../Conditions/ScriptHashCondition.cs | 2 +- .../Payloads/Conditions/WitnessCondition.cs | 2 +- src/Neo/Network/P2P/Payloads/Signer.cs | 2 +- src/Neo/Network/P2P/Payloads/Transaction.cs | 2 +- src/Neo/Network/P2P/Payloads/WitnessRule.cs | 2 +- src/Neo/SmartContract/BinarySerializer.cs | 12 +-- src/Neo/SmartContract/ContractState.cs | 2 +- src/Neo/SmartContract/IInteroperable.cs | 4 +- src/Neo/SmartContract/Iterators/IIterator.cs | 2 +- .../Iterators/StorageIterator.cs | 2 +- src/Neo/SmartContract/JsonSerializer.cs | 6 +- src/Neo/SmartContract/Manifest/ContractAbi.cs | 2 +- .../Manifest/ContractEventDescriptor.cs | 2 +- .../SmartContract/Manifest/ContractGroup.cs | 2 +- .../Manifest/ContractManifest.cs | 2 +- .../Manifest/ContractMethodDescriptor.cs | 2 +- .../Manifest/ContractParameterDefinition.cs | 2 +- .../Manifest/ContractPermission.cs | 2 +- src/Neo/SmartContract/Native/AccountState.cs | 2 +- .../SmartContract/Native/HashIndexState.cs | 2 +- .../SmartContract/Native/InteroperableList.cs | 4 +- src/Neo/SmartContract/Native/NeoToken.cs | 6 +- .../SmartContract/Native/OracleContract.cs | 2 +- src/Neo/SmartContract/Native/OracleRequest.cs | 2 +- .../SmartContract/Native/RoleManagement.cs | 2 +- .../SmartContract/Native/TransactionState.cs | 2 +- src/Neo/SmartContract/Native/TrimmedBlock.cs | 2 +- src/Neo/SmartContract/NotifyEventArgs.cs | 4 +- 49 files changed, 170 insertions(+), 125 deletions(-) create mode 100644 src/Neo.VM/IReferenceCounter.cs diff --git a/benchmarks/Neo.VM.Benchmarks/TestArray.cs b/benchmarks/Neo.VM.Benchmarks/TestArray.cs index 62fedfed11..6a7dbcabc1 100644 --- a/benchmarks/Neo.VM.Benchmarks/TestArray.cs +++ b/benchmarks/Neo.VM.Benchmarks/TestArray.cs @@ -57,7 +57,7 @@ public TestArray(IEnumerable? items = null) /// /// The to be used by this array. /// The items to be included in the array. - public TestArray(ReferenceCounter? referenceCounter, IEnumerable? items = null) + public TestArray(IReferenceCounter? referenceCounter, IEnumerable? items = null) : base(referenceCounter) { _array = items switch diff --git a/benchmarks/Neo.VM.Benchmarks/TestStruct.cs b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs index 5a9541f1e0..8f77d680c5 100644 --- a/benchmarks/Neo.VM.Benchmarks/TestStruct.cs +++ b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs @@ -31,7 +31,7 @@ public TestStruct(IEnumerable? fields = null) /// /// The to be used by this structure. /// The fields to be included in the structure. - public TestStruct(ReferenceCounter? referenceCounter, IEnumerable? fields = null) + public TestStruct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) { } diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs index 7991608b8c..1d9c267f5f 100644 --- a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs @@ -70,7 +70,7 @@ public void BenchNestedTestArrayDeepCopyWithReferenceCounter() _ = root.DeepCopy(); } - private static void CreateNestedArray(Array? rootArray, int depth, int elementsPerLevel = 1, ReferenceCounter? referenceCounter = null) + private static void CreateNestedArray(Array? rootArray, int depth, int elementsPerLevel = 1, IReferenceCounter? referenceCounter = null) { if (depth < 0) { @@ -95,7 +95,7 @@ private static void CreateNestedArray(Array? rootArray, int depth, int elementsP } } - private static void CreateNestedTestArray(TestArray rootArray, int depth, int elementsPerLevel = 1, ReferenceCounter referenceCounter = null) + private static void CreateNestedTestArray(TestArray rootArray, int depth, int elementsPerLevel = 1, IReferenceCounter referenceCounter = null) { if (depth < 0) { diff --git a/src/Neo.VM/EvaluationStack.cs b/src/Neo.VM/EvaluationStack.cs index 571cb6b2db..463df8d12a 100644 --- a/src/Neo.VM/EvaluationStack.cs +++ b/src/Neo.VM/EvaluationStack.cs @@ -23,12 +23,12 @@ namespace Neo.VM /// public sealed class EvaluationStack : IReadOnlyList { - private readonly List innerList = new(); - private readonly ReferenceCounter referenceCounter; + private readonly List innerList = []; + private readonly IReferenceCounter referenceCounter; - internal ReferenceCounter ReferenceCounter => referenceCounter; + internal IReferenceCounter ReferenceCounter => referenceCounter; - internal EvaluationStack(ReferenceCounter referenceCounter) + internal EvaluationStack(IReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; } diff --git a/src/Neo.VM/ExecutionContext.SharedStates.cs b/src/Neo.VM/ExecutionContext.SharedStates.cs index afa01d7995..2e9a702b9d 100644 --- a/src/Neo.VM/ExecutionContext.SharedStates.cs +++ b/src/Neo.VM/ExecutionContext.SharedStates.cs @@ -23,7 +23,7 @@ private class SharedStates public Slot? StaticFields; public readonly Dictionary States; - public SharedStates(Script script, ReferenceCounter referenceCounter) + public SharedStates(Script script, IReferenceCounter referenceCounter) { Script = script; EvaluationStack = new EvaluationStack(referenceCounter); diff --git a/src/Neo.VM/ExecutionContext.cs b/src/Neo.VM/ExecutionContext.cs index d1f9a54b1d..67e0889208 100644 --- a/src/Neo.VM/ExecutionContext.cs +++ b/src/Neo.VM/ExecutionContext.cs @@ -107,7 +107,7 @@ public Instruction? NextInstruction } } - internal ExecutionContext(Script script, int rvcount, ReferenceCounter referenceCounter) + internal ExecutionContext(Script script, int rvcount, IReferenceCounter referenceCounter) : this(new SharedStates(script, referenceCounter), rvcount, 0) { } diff --git a/src/Neo.VM/ExecutionEngine.cs b/src/Neo.VM/ExecutionEngine.cs index bec60c4348..b2c04bdc93 100644 --- a/src/Neo.VM/ExecutionEngine.cs +++ b/src/Neo.VM/ExecutionEngine.cs @@ -34,7 +34,7 @@ public class ExecutionEngine : IDisposable /// /// Used for reference counting of objects in the VM. /// - public ReferenceCounter ReferenceCounter { get; } + public IReferenceCounter ReferenceCounter { get; } /// /// The invocation stack of the VM. diff --git a/src/Neo.VM/IReferenceCounter.cs b/src/Neo.VM/IReferenceCounter.cs new file mode 100644 index 0000000000..f451c0e316 --- /dev/null +++ b/src/Neo.VM/IReferenceCounter.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IReferenceCounter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; + +namespace Neo.VM +{ + /// + /// Used for reference counting of objects in the VM. + /// + public interface IReferenceCounter + { + /// + /// Gets the count of references. + /// + int Count { get; } + + /// + /// Adds an item to the zero-referred list. + /// + /// This method is used when an item has no remaining references. + /// It adds the item to the zero-referred list to be checked for cleanup later. + /// + /// Use this method when you detect that an item has zero references and may need to be cleaned up. + /// + /// The item to add. + void AddZeroReferred(StackItem item); + + /// + /// Adds a reference to a specified item with a parent compound type. + /// + /// This method is used when an item gains a new reference through a parent compound type. + /// It increments the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to add a reference from a compound type to a stack item. + /// + /// The item to add a reference to. + /// The parent compound type. + void AddReference(StackItem item, CompoundType parent); + + /// + /// Adds a stack reference to a specified item with a count. + /// + /// This method is used when an item gains a new stack reference, usually due to being pushed onto the evaluation stack. + /// It increments the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to add one or more stack references to a stack item. + /// + /// The item to add a stack reference to. + /// The number of references to add. + void AddStackReference(StackItem item, int count = 1); + + /// + /// Removes a reference from a specified item with a parent compound type. + /// + /// This method is used when an item loses a reference from a parent compound type. + /// It decrements the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to remove a reference from a compound type to a stack item. + /// + /// The item to remove a reference from. + /// The parent compound type. + void RemoveReference(StackItem item, CompoundType parent); + + /// + /// Removes a stack reference from a specified item. + /// + /// This method is used when an item loses a stack reference, usually due to being popped off the evaluation stack. + /// It decrements the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to remove one or more stack references from a stack item. + /// + /// The item to remove a stack reference from. + void RemoveStackReference(StackItem item); + + /// + /// Checks and processes items that have zero references. + /// This method is used to check items in the zero-referred list and clean up those that are no longer needed. + /// + /// The current reference count. + int CheckZeroReferred(); + } +} diff --git a/src/Neo.VM/ReferenceCounter.cs b/src/Neo.VM/ReferenceCounter.cs index f9ea08a2e2..2ba7d74063 100644 --- a/src/Neo.VM/ReferenceCounter.cs +++ b/src/Neo.VM/ReferenceCounter.cs @@ -20,7 +20,7 @@ namespace Neo.VM /// /// Used for reference counting of objects in the VM. /// - public sealed class ReferenceCounter + public sealed class ReferenceCounter : IReferenceCounter { // If set to true, all items will be tracked regardless of their type. private const bool TrackAllItems = false; @@ -38,9 +38,7 @@ public sealed class ReferenceCounter // Keeps the total count of references. private int _referencesCount = 0; - /// - /// Gets the count of references. - /// + /// public int Count => _referencesCount; /// @@ -61,17 +59,8 @@ private static bool NeedTrack(StackItem item) return false; } - /// - /// Adds a reference to a specified item with a parent compound type. - /// - /// This method is used when an item gains a new reference through a parent compound type. - /// It increments the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to add a reference from a compound type to a stack item. - /// - /// The item to add a reference to. - /// The parent compound type. - internal void AddReference(StackItem item, CompoundType parent) + /// + public void AddReference(StackItem item, CompoundType parent) { // Increment the reference count. _referencesCount++; @@ -98,17 +87,8 @@ internal void AddReference(StackItem item, CompoundType parent) pEntry.References++; } - /// - /// Adds a stack reference to a specified item with a count. - /// - /// This method is used when an item gains a new stack reference, usually due to being pushed onto the evaluation stack. - /// It increments the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to add one or more stack references to a stack item. - /// - /// The item to add a stack reference to. - /// The number of references to add. - internal void AddStackReference(StackItem item, int count = 1) + /// + public void AddStackReference(StackItem item, int count = 1) { // Increment the reference count by the specified count. _referencesCount += count; @@ -127,16 +107,8 @@ internal void AddStackReference(StackItem item, int count = 1) _zeroReferred.Remove(item); } - /// - /// Adds an item to the zero-referred list. - /// - /// This method is used when an item has no remaining references. - /// It adds the item to the zero-referred list to be checked for cleanup later. - /// - /// Use this method when you detect that an item has zero references and may need to be cleaned up. - /// - /// The item to add. - internal void AddZeroReferred(StackItem item) + /// + public void AddZeroReferred(StackItem item) { // Add the item to the _zeroReferred set. _zeroReferred.Add(item); @@ -158,7 +130,7 @@ internal void AddZeroReferred(StackItem item) /// Use this method periodically to clean up items with zero references and free up memory. /// /// The current reference count. - internal int CheckZeroReferred() + public int CheckZeroReferred() { // If there are items with zero references, process them. if (_zeroReferred.Count > 0) @@ -241,18 +213,8 @@ internal int CheckZeroReferred() return _referencesCount; } - - /// - /// Removes a reference from a specified item with a parent compound type. - /// - /// This method is used when an item loses a reference from a parent compound type. - /// It decrements the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to remove a reference from a compound type to a stack item. - /// - /// The item to remove a reference from. - /// The parent compound type. - internal void RemoveReference(StackItem item, CompoundType parent) + /// + public void RemoveReference(StackItem item, CompoundType parent) { // Decrement the reference count. _referencesCount--; @@ -271,16 +233,8 @@ internal void RemoveReference(StackItem item, CompoundType parent) _zeroReferred.Add(item); } - /// - /// Removes a stack reference from a specified item. - /// - /// This method is used when an item loses a stack reference, usually due to being popped off the evaluation stack. - /// It decrements the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to remove one or more stack references from a stack item. - /// - /// The item to remove a stack reference from. - internal void RemoveStackReference(StackItem item) + /// + public void RemoveStackReference(StackItem item) { // Decrement the reference count. _referencesCount--; diff --git a/src/Neo.VM/Slot.cs b/src/Neo.VM/Slot.cs index 7812694ce6..30a1cb9a44 100644 --- a/src/Neo.VM/Slot.cs +++ b/src/Neo.VM/Slot.cs @@ -20,7 +20,7 @@ namespace Neo.VM /// public class Slot : IReadOnlyList { - private readonly ReferenceCounter referenceCounter; + private readonly IReferenceCounter referenceCounter; private readonly StackItem[] items; /// @@ -53,7 +53,7 @@ internal set /// /// The items to be contained. /// The reference counter to be used. - public Slot(StackItem[] items, ReferenceCounter referenceCounter) + public Slot(StackItem[] items, IReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; this.items = items; @@ -66,7 +66,7 @@ public Slot(StackItem[] items, ReferenceCounter referenceCounter) /// /// Indicates the number of items contained in the slot. /// The reference counter to be used. - public Slot(int count, ReferenceCounter referenceCounter) + public Slot(int count, IReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; items = new StackItem[count]; diff --git a/src/Neo.VM/Types/Array.cs b/src/Neo.VM/Types/Array.cs index 76c1486778..831bc830e6 100644 --- a/src/Neo.VM/Types/Array.cs +++ b/src/Neo.VM/Types/Array.cs @@ -66,7 +66,7 @@ public Array(IEnumerable? items = null) /// /// The to be used by this array. /// The items to be included in the array. - public Array(ReferenceCounter? referenceCounter, IEnumerable? items = null) + public Array(IReferenceCounter? referenceCounter, IEnumerable? items = null) : base(referenceCounter) { _array = items switch diff --git a/src/Neo.VM/Types/CompoundType.cs b/src/Neo.VM/Types/CompoundType.cs index daa9b05be6..6b0da819ec 100644 --- a/src/Neo.VM/Types/CompoundType.cs +++ b/src/Neo.VM/Types/CompoundType.cs @@ -24,13 +24,13 @@ public abstract class CompoundType : StackItem /// /// The reference counter used to count the items in the VM object. /// - protected internal readonly ReferenceCounter? ReferenceCounter; + protected internal readonly IReferenceCounter? ReferenceCounter; /// /// Create a new with the specified reference counter. /// /// The reference counter to be used. - protected CompoundType(ReferenceCounter? referenceCounter) + protected CompoundType(IReferenceCounter? referenceCounter) { ReferenceCounter = referenceCounter; referenceCounter?.AddZeroReferred(this); diff --git a/src/Neo.VM/Types/Map.cs b/src/Neo.VM/Types/Map.cs index 48155b5207..2986399d59 100644 --- a/src/Neo.VM/Types/Map.cs +++ b/src/Neo.VM/Types/Map.cs @@ -86,7 +86,7 @@ public StackItem this[PrimitiveType key] /// Create a new map with the specified reference counter. /// /// The reference counter to be used. - public Map(ReferenceCounter? referenceCounter = null) + public Map(IReferenceCounter? referenceCounter = null) : base(referenceCounter) { } diff --git a/src/Neo.VM/Types/Struct.cs b/src/Neo.VM/Types/Struct.cs index 8170414363..f2ae144d3e 100644 --- a/src/Neo.VM/Types/Struct.cs +++ b/src/Neo.VM/Types/Struct.cs @@ -35,7 +35,7 @@ public Struct(IEnumerable? fields = null) /// /// The to be used by this structure. /// The fields to be included in the structure. - public Struct(ReferenceCounter? referenceCounter, IEnumerable? fields = null) + public Struct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) { } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs index 2fed4d32fb..5fc1ba4381 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs @@ -67,7 +67,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(new VM.Types.Array(referenceCounter, Expressions.Select(p => p.ToStackItem(referenceCounter)))); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs index e7609fcbaf..011a7466fe 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs @@ -55,7 +55,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Expression); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs index f853743b72..b27c66931d 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs @@ -55,7 +55,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Hash.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs index d28cd9f8bf..652eabd105 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs @@ -60,7 +60,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Group.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs index 3187b62988..be26fc0863 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs @@ -60,7 +60,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Group.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs index 83ecd4d973..74ab01d21b 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs @@ -61,7 +61,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Expression.ToStackItem(referenceCounter)); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs index 72b27529c4..c28d3dbb94 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs @@ -67,7 +67,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(new VM.Types.Array(referenceCounter, Expressions.Select(p => p.ToStackItem(referenceCounter)))); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs index 9199e1a9a2..5d648efbc0 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs @@ -55,7 +55,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Hash.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs index 7738a6d4a8..dd955fce42 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs @@ -127,7 +127,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) + public virtual StackItem ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, new StackItem[] { (byte)Type }); } diff --git a/src/Neo/Network/P2P/Payloads/Signer.cs b/src/Neo/Network/P2P/Payloads/Signer.cs index 9b5e317abc..40496dfe74 100644 --- a/src/Neo/Network/P2P/Payloads/Signer.cs +++ b/src/Neo/Network/P2P/Payloads/Signer.cs @@ -191,7 +191,7 @@ void IInteroperable.FromStackItem(VM.Types.StackItem stackItem) throw new NotSupportedException(); } - VM.Types.StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + VM.Types.StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, [ diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index 6f41b5141b..b5139f1c7c 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -459,7 +459,7 @@ public virtual VerifyResult VerifyStateIndependent(ProtocolSettings settings) return VerifyResult.Succeed; } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { if (_signers == null || _signers.Length == 0) throw new ArgumentException("Sender is not specified in the transaction."); return new Array(referenceCounter, new StackItem[] diff --git a/src/Neo/Network/P2P/Payloads/WitnessRule.cs b/src/Neo/Network/P2P/Payloads/WitnessRule.cs index 3bac09e7f5..8552f0798e 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessRule.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessRule.cs @@ -88,7 +88,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, new StackItem[] { diff --git a/src/Neo/SmartContract/BinarySerializer.cs b/src/Neo/SmartContract/BinarySerializer.cs index 2bdeced678..b77a998d6e 100644 --- a/src/Neo/SmartContract/BinarySerializer.cs +++ b/src/Neo/SmartContract/BinarySerializer.cs @@ -51,9 +51,9 @@ public ContainerPlaceholder(StackItemType type, int count) /// /// The byte array to parse. /// The limits for the deserialization. - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLimits limits, IReferenceCounter referenceCounter = null) { MemoryReader reader = new(data); return Deserialize(ref reader, (uint)Math.Min(data.Length, limits.MaxItemSize), limits.MaxStackSize, referenceCounter); @@ -64,9 +64,9 @@ public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLi /// /// The for reading data. /// The limits for the deserialization. - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimits limits, IReferenceCounter referenceCounter = null) { return Deserialize(ref reader, limits.MaxItemSize, limits.MaxStackSize, referenceCounter); } @@ -77,9 +77,9 @@ public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimi /// The for reading data. /// The maximum size of the result. /// The max of items to serialize - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint maxItems, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint maxItems, IReferenceCounter referenceCounter = null) { Stack deserialized = new(); int undeserialized = 1; diff --git a/src/Neo/SmartContract/ContractState.cs b/src/Neo/SmartContract/ContractState.cs index 5b413c41a4..cd065cb8c3 100644 --- a/src/Neo/SmartContract/ContractState.cs +++ b/src/Neo/SmartContract/ContractState.cs @@ -119,7 +119,7 @@ public JObject ToJson() }; } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter, new StackItem[] { Id, (int)UpdateCounter, Hash.ToArray(), Nef.ToArray(), Manifest.ToStackItem(referenceCounter) }); } diff --git a/src/Neo/SmartContract/IInteroperable.cs b/src/Neo/SmartContract/IInteroperable.cs index 2810d40d01..f177b3f76f 100644 --- a/src/Neo/SmartContract/IInteroperable.cs +++ b/src/Neo/SmartContract/IInteroperable.cs @@ -29,9 +29,9 @@ public interface IInteroperable /// /// Convert the current object to a . /// - /// The used by the . + /// The used by the . /// The converted . - StackItem ToStackItem(ReferenceCounter referenceCounter); + StackItem ToStackItem(IReferenceCounter referenceCounter); public IInteroperable Clone() { diff --git a/src/Neo/SmartContract/Iterators/IIterator.cs b/src/Neo/SmartContract/Iterators/IIterator.cs index 78c42f1abd..4f06182fe1 100644 --- a/src/Neo/SmartContract/Iterators/IIterator.cs +++ b/src/Neo/SmartContract/Iterators/IIterator.cs @@ -30,6 +30,6 @@ public interface IIterator : IDisposable /// Gets the element in the collection at the current position of the iterator. /// /// The element in the collection at the current position of the iterator. - StackItem Value(ReferenceCounter referenceCounter); + StackItem Value(IReferenceCounter referenceCounter); } } diff --git a/src/Neo/SmartContract/Iterators/StorageIterator.cs b/src/Neo/SmartContract/Iterators/StorageIterator.cs index 397333ad7c..b66998c9dc 100644 --- a/src/Neo/SmartContract/Iterators/StorageIterator.cs +++ b/src/Neo/SmartContract/Iterators/StorageIterator.cs @@ -39,7 +39,7 @@ public bool Next() return enumerator.MoveNext(); } - public StackItem Value(ReferenceCounter referenceCounter) + public StackItem Value(IReferenceCounter referenceCounter) { ReadOnlyMemory key = enumerator.Current.Key.Key; ReadOnlyMemory value = enumerator.Current.Value.Value; diff --git a/src/Neo/SmartContract/JsonSerializer.cs b/src/Neo/SmartContract/JsonSerializer.cs index 4d77104a6e..d2055be7d8 100644 --- a/src/Neo/SmartContract/JsonSerializer.cs +++ b/src/Neo/SmartContract/JsonSerializer.cs @@ -163,15 +163,15 @@ public static byte[] SerializeToByteArray(StackItem item, uint maxSize) /// The used. /// The to deserialize. /// The limits for the deserialization. - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ApplicationEngine engine, JToken json, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ApplicationEngine engine, JToken json, ExecutionEngineLimits limits, IReferenceCounter referenceCounter = null) { uint maxStackSize = limits.MaxStackSize; return Deserialize(engine, json, ref maxStackSize, referenceCounter); } - private static StackItem Deserialize(ApplicationEngine engine, JToken json, ref uint maxStackSize, ReferenceCounter referenceCounter) + private static StackItem Deserialize(ApplicationEngine engine, JToken json, ref uint maxStackSize, IReferenceCounter referenceCounter) { if (maxStackSize-- == 0) throw new FormatException(); switch (json) diff --git a/src/Neo/SmartContract/Manifest/ContractAbi.cs b/src/Neo/SmartContract/Manifest/ContractAbi.cs index cedd431d62..4517ffeed2 100644 --- a/src/Neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/Neo/SmartContract/Manifest/ContractAbi.cs @@ -44,7 +44,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Events = ((Array)@struct[1]).Select(p => p.ToInteroperable()).ToArray(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { diff --git a/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs index 90227dd78d..de77597655 100644 --- a/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs @@ -40,7 +40,7 @@ public virtual void FromStackItem(StackItem stackItem) Parameters = ((Array)@struct[1]).Select(p => p.ToInteroperable()).ToArray(); } - public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) + public virtual StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { diff --git a/src/Neo/SmartContract/Manifest/ContractGroup.cs b/src/Neo/SmartContract/Manifest/ContractGroup.cs index 231354327c..8796c0f07a 100644 --- a/src/Neo/SmartContract/Manifest/ContractGroup.cs +++ b/src/Neo/SmartContract/Manifest/ContractGroup.cs @@ -43,7 +43,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Signature = @struct[1].GetSpan().ToArray(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { PubKey.ToArray(), Signature }; } diff --git a/src/Neo/SmartContract/Manifest/ContractManifest.cs b/src/Neo/SmartContract/Manifest/ContractManifest.cs index b1bd317e41..17722eb48a 100644 --- a/src/Neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/Neo/SmartContract/Manifest/ContractManifest.cs @@ -88,7 +88,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Extra = (JObject)JToken.Parse(@struct[7].GetSpan()); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { diff --git a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs index cc8d0a16d5..56b36163de 100644 --- a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -47,7 +47,7 @@ public override void FromStackItem(StackItem stackItem) Safe = @struct[4].GetBoolean(); } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add((byte)ReturnType); diff --git a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs index 61906558d0..b36fa353fe 100644 --- a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs +++ b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -38,7 +38,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Type = (ContractParameterType)(byte)@struct[1].GetInteger(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Name, (byte)Type }; } diff --git a/src/Neo/SmartContract/Manifest/ContractPermission.cs b/src/Neo/SmartContract/Manifest/ContractPermission.cs index 44c67fd9a1..cf4d5078c5 100644 --- a/src/Neo/SmartContract/Manifest/ContractPermission.cs +++ b/src/Neo/SmartContract/Manifest/ContractPermission.cs @@ -68,7 +68,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) }; } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { diff --git a/src/Neo/SmartContract/Native/AccountState.cs b/src/Neo/SmartContract/Native/AccountState.cs index 031b37f8eb..4e8944ab73 100644 --- a/src/Neo/SmartContract/Native/AccountState.cs +++ b/src/Neo/SmartContract/Native/AccountState.cs @@ -30,7 +30,7 @@ public virtual void FromStackItem(StackItem stackItem) Balance = ((Struct)stackItem)[0].GetInteger(); } - public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) + public virtual StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Balance }; } diff --git a/src/Neo/SmartContract/Native/HashIndexState.cs b/src/Neo/SmartContract/Native/HashIndexState.cs index 229458ffdd..4ab7a991cf 100644 --- a/src/Neo/SmartContract/Native/HashIndexState.cs +++ b/src/Neo/SmartContract/Native/HashIndexState.cs @@ -27,7 +27,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Index = (uint)@struct[1].GetInteger(); } - StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Hash.ToArray(), Index }; } diff --git a/src/Neo/SmartContract/Native/InteroperableList.cs b/src/Neo/SmartContract/Native/InteroperableList.cs index e118db7621..09088f6156 100644 --- a/src/Neo/SmartContract/Native/InteroperableList.cs +++ b/src/Neo/SmartContract/Native/InteroperableList.cs @@ -40,7 +40,7 @@ abstract class InteroperableList : IList, IInteroperable public void Sort() => List.Sort(); protected abstract T ElementFromStackItem(StackItem item); - protected abstract StackItem ElementToStackItem(T element, ReferenceCounter referenceCounter); + protected abstract StackItem ElementToStackItem(T element, IReferenceCounter referenceCounter); public void FromStackItem(StackItem stackItem) { @@ -51,7 +51,7 @@ public void FromStackItem(StackItem stackItem) } } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter, this.Select(p => ElementToStackItem(p, referenceCounter))); } diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index d639a55caf..85309b4246 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -581,7 +581,7 @@ public override void FromStackItem(StackItem stackItem) LastGasPerVote = @struct[3].GetInteger(); } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add(BalanceHeight); @@ -603,7 +603,7 @@ public void FromStackItem(StackItem stackItem) Votes = @struct[1].GetInteger(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Registered, Votes }; } @@ -620,7 +620,7 @@ protected override (ECPoint, BigInteger) ElementFromStackItem(StackItem item) return (ECPoint.DecodePoint(@struct[0].GetSpan(), ECCurve.Secp256r1), @struct[1].GetInteger()); } - protected override StackItem ElementToStackItem((ECPoint PublicKey, BigInteger Votes) element, ReferenceCounter referenceCounter) + protected override StackItem ElementToStackItem((ECPoint PublicKey, BigInteger Votes) element, IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { element.PublicKey.ToArray(), element.Votes }; } diff --git a/src/Neo/SmartContract/Native/OracleContract.cs b/src/Neo/SmartContract/Native/OracleContract.cs index be4765581a..54156a0029 100644 --- a/src/Neo/SmartContract/Native/OracleContract.cs +++ b/src/Neo/SmartContract/Native/OracleContract.cs @@ -241,7 +241,7 @@ protected override ulong ElementFromStackItem(StackItem item) return (ulong)item.GetInteger(); } - protected override StackItem ElementToStackItem(ulong element, ReferenceCounter referenceCounter) + protected override StackItem ElementToStackItem(ulong element, IReferenceCounter referenceCounter) { return element; } diff --git a/src/Neo/SmartContract/Native/OracleRequest.cs b/src/Neo/SmartContract/Native/OracleRequest.cs index d18968ef00..cd4962b1cb 100644 --- a/src/Neo/SmartContract/Native/OracleRequest.cs +++ b/src/Neo/SmartContract/Native/OracleRequest.cs @@ -68,7 +68,7 @@ public void FromStackItem(StackItem stackItem) UserData = array[6].GetSpan().ToArray(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter) { diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index d0437594c9..e57f3f3e83 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -79,7 +79,7 @@ protected override ECPoint ElementFromStackItem(StackItem item) return ECPoint.DecodePoint(item.GetSpan(), ECCurve.Secp256r1); } - protected override StackItem ElementToStackItem(ECPoint element, ReferenceCounter referenceCounter) + protected override StackItem ElementToStackItem(ECPoint element, IReferenceCounter referenceCounter) { return element.ToArray(); } diff --git a/src/Neo/SmartContract/Native/TransactionState.cs b/src/Neo/SmartContract/Native/TransactionState.cs index 050358f179..9e3eb2527a 100644 --- a/src/Neo/SmartContract/Native/TransactionState.cs +++ b/src/Neo/SmartContract/Native/TransactionState.cs @@ -76,7 +76,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) State = (VMState)(byte)@struct[2].GetInteger(); } - StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { if (Transaction is null) return new Struct(referenceCounter) { BlockIndex }; diff --git a/src/Neo/SmartContract/Native/TrimmedBlock.cs b/src/Neo/SmartContract/Native/TrimmedBlock.cs index c2bab2567c..69dd55fdbb 100644 --- a/src/Neo/SmartContract/Native/TrimmedBlock.cs +++ b/src/Neo/SmartContract/Native/TrimmedBlock.cs @@ -80,7 +80,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, new StackItem[] { diff --git a/src/Neo/SmartContract/NotifyEventArgs.cs b/src/Neo/SmartContract/NotifyEventArgs.cs index 257efb3a66..8d8542bca9 100644 --- a/src/Neo/SmartContract/NotifyEventArgs.cs +++ b/src/Neo/SmartContract/NotifyEventArgs.cs @@ -63,7 +63,7 @@ public void FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter) { @@ -73,7 +73,7 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) }; } - public StackItem ToStackItem(ReferenceCounter referenceCounter, ApplicationEngine engine) + public StackItem ToStackItem(IReferenceCounter referenceCounter, ApplicationEngine engine) { if (engine.IsHardforkEnabled(Hardfork.HF_Domovoi)) { From a32693924288a606e93d9a1f1f1fb19698bd292e Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sat, 12 Oct 2024 02:16:57 +0800 Subject: [PATCH 21/42] [Neo VM] optimize newstruct (#3525) * optimize newstruct * use Array.Fill * Update src/Neo.VM/JumpTable/JumpTable.Compound.cs --------- Co-authored-by: Shargon --- src/Neo.VM/JumpTable/JumpTable.Compound.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Neo.VM/JumpTable/JumpTable.Compound.cs b/src/Neo.VM/JumpTable/JumpTable.Compound.cs index 817b460cf9..2a81b213fe 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Compound.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Compound.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; +using Array = System.Array; using VMArray = Neo.VM.Types.Array; namespace Neo.VM @@ -151,8 +152,9 @@ public virtual void NewArray(ExecutionEngine engine, Instruction instruction) var n = (int)engine.Pop().GetInteger(); if (n < 0 || n > engine.Limits.MaxStackSize) throw new InvalidOperationException($"MaxStackSize exceed: {n}"); - - engine.Push(new VMArray(engine.ReferenceCounter, Enumerable.Repeat(StackItem.Null, n))); + var nullArray = new StackItem[n]; + Array.Fill(nullArray, StackItem.Null); + engine.Push(new VMArray(engine.ReferenceCounter, nullArray)); } /// @@ -180,8 +182,9 @@ public virtual void NewArray_T(ExecutionEngine engine, Instruction instruction) (byte)StackItemType.ByteString => ByteString.Empty, _ => StackItem.Null }; - - engine.Push(new VMArray(engine.ReferenceCounter, Enumerable.Repeat(item, n))); + var itemArray = new StackItem[n]; + Array.Fill(itemArray, item); + engine.Push(new VMArray(engine.ReferenceCounter, itemArray)); } /// @@ -210,10 +213,10 @@ public virtual void NewStruct(ExecutionEngine engine, Instruction instruction) var n = (int)engine.Pop().GetInteger(); if (n < 0 || n > engine.Limits.MaxStackSize) throw new InvalidOperationException($"MaxStackSize exceed: {n}"); - Struct result = new(engine.ReferenceCounter); - for (var i = 0; i < n; i++) - result.Add(StackItem.Null); - engine.Push(result); + + var nullArray = new StackItem[n]; + Array.Fill(nullArray, StackItem.Null); + engine.Push(new Struct(engine.ReferenceCounter, nullArray)); } /// From b752efe926dd0eae136ad4694e22daa057a03790 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Mon, 14 Oct 2024 15:49:50 +0800 Subject: [PATCH 22/42] fea: use canonical TryGet style in IReadOnlyStore (#3533) --- src/Neo/Persistence/IReadOnlyStore.cs | 10 ++++++++++ src/Neo/Persistence/MemorySnapshot.cs | 5 +++++ src/Neo/Persistence/MemoryStore.cs | 6 ++++++ .../LevelDBStore/Plugins/Storage/Snapshot.cs | 6 ++++++ .../LevelDBStore/Plugins/Storage/Store.cs | 6 ++++++ .../RocksDBStore/Plugins/Storage/Snapshot.cs | 6 ++++++ .../RocksDBStore/Plugins/Storage/Store.cs | 6 ++++++ .../Cryptography/MPTTrie/UT_Trie.cs | 5 +++++ tests/Neo.Plugins.Storage.Tests/StoreTest.cs | 19 +++++++++++++++++++ .../Persistence/UT_MemorySnapshot.cs | 8 ++++++++ .../Persistence/UT_MemoryStore.cs | 10 +++++++++- 11 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Neo/Persistence/IReadOnlyStore.cs b/src/Neo/Persistence/IReadOnlyStore.cs index 4b6c3fe0ec..e52ccbb7c3 100644 --- a/src/Neo/Persistence/IReadOnlyStore.cs +++ b/src/Neo/Persistence/IReadOnlyStore.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; using System.Collections.Generic; namespace Neo.Persistence @@ -31,8 +32,17 @@ public interface IReadOnlyStore /// /// The key of the entry. /// The data of the entry. Or if it doesn't exist. + /// . Obsolete it later for avoiding complier warning. byte[] TryGet(byte[] key); + /// + /// Reads a specified entry from the database. + /// + /// The key of the entry. + /// The data of the entry. + /// if the entry exists; otherwise, . + bool TryGet(byte[] key, out byte[] value); + /// /// Determines whether the database contains the specified entry. /// diff --git a/src/Neo/Persistence/MemorySnapshot.cs b/src/Neo/Persistence/MemorySnapshot.cs index 096431552c..b3d06798d4 100644 --- a/src/Neo/Persistence/MemorySnapshot.cs +++ b/src/Neo/Persistence/MemorySnapshot.cs @@ -69,6 +69,11 @@ public byte[] TryGet(byte[] key) return value?[..]; } + public bool TryGet(byte[] key, out byte[] value) + { + return immutableData.TryGetValue(key, out value); + } + public bool Contains(byte[] key) { return immutableData.ContainsKey(key); diff --git a/src/Neo/Persistence/MemoryStore.cs b/src/Neo/Persistence/MemoryStore.cs index 191b89ea4f..378d5374f1 100644 --- a/src/Neo/Persistence/MemoryStore.cs +++ b/src/Neo/Persistence/MemoryStore.cs @@ -66,6 +66,12 @@ public byte[] TryGet(byte[] key) return value[..]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGet(byte[] key, out byte[] value) + { + return _innerData.TryGetValue(key, out value); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(byte[] key) { diff --git a/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs b/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs index 0b0a63b885..f66164285b 100644 --- a/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs +++ b/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs @@ -65,5 +65,11 @@ public byte[] TryGet(byte[] key) { return db.Get(options, key); } + + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(options, key); + return value != null; + } } } diff --git a/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs b/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs index 27b12a8b64..175b71a8fe 100644 --- a/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs +++ b/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs @@ -63,5 +63,11 @@ public byte[] TryGet(byte[] key) { return db.Get(ReadOptions.Default, key); } + + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(ReadOptions.Default, key); + return value != null; + } } } diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs index 7423f6ae4a..4da29e41fe 100644 --- a/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs @@ -73,6 +73,12 @@ public byte[] TryGet(byte[] key) return db.Get(key, readOptions: options); } + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(key, readOptions: options); + return value != null; + } + public void Dispose() { snapshot.Dispose(); diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs index ebf160dab0..3f8121ca07 100644 --- a/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs @@ -59,6 +59,12 @@ public byte[] TryGet(byte[] key) return db.Get(key); } + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(key); + return value != null; + } + public void Delete(byte[] key) { db.Remove(key); diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs index 2e53456545..d7fcb92775 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs @@ -52,6 +52,11 @@ public byte[] TryGet(byte[] key) return null; } + public bool TryGet(byte[] key, out byte[] value) + { + return store.TryGetValue(StoreKey(key), out value); + } + public void Dispose() { throw new System.NotImplementedException(); } public int Size => store.Count; diff --git a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs index 189e212bc2..ed44faab53 100644 --- a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs +++ b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs @@ -85,6 +85,8 @@ public void TestLevelDbSnapshot() snapshot.Put(testKey, testValue); // Data saved to the leveldb snapshot shall not be visible to the store Assert.IsNull(snapshot.TryGet(testKey)); + Assert.IsFalse(snapshot.TryGet(testKey, out var got)); + Assert.IsNull(got); // Value is in the write batch, not visible to the store and snapshot Assert.AreEqual(false, snapshot.Contains(testKey)); @@ -94,7 +96,13 @@ public void TestLevelDbSnapshot() // After commit, the data shall be visible to the store but not to the snapshot Assert.IsNull(snapshot.TryGet(testKey)); + Assert.IsFalse(snapshot.TryGet(testKey, out got)); + Assert.IsNull(got); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.IsTrue(store.TryGet(testKey, out got)); + CollectionAssert.AreEqual(testValue, got); + Assert.AreEqual(false, snapshot.Contains(testKey)); Assert.AreEqual(true, store.Contains(testKey)); @@ -154,7 +162,12 @@ public void TestRocksDbSnapshot() snapshot.Put(testKey, testValue); // Data saved to the leveldb snapshot shall not be visible Assert.IsNull(snapshot.TryGet(testKey)); + Assert.IsFalse(snapshot.TryGet(testKey, out var got)); + Assert.IsNull(got); + Assert.IsNull(store.TryGet(testKey)); + Assert.IsFalse(store.TryGet(testKey, out got)); + Assert.IsNull(got); // Value is in the write batch, not visible to the store and snapshot Assert.AreEqual(false, snapshot.Contains(testKey)); @@ -164,7 +177,13 @@ public void TestRocksDbSnapshot() // After commit, the data shall be visible to the store but not to the snapshot Assert.IsNull(snapshot.TryGet(testKey)); + Assert.IsFalse(snapshot.TryGet(testKey, out got)); + Assert.IsNull(got); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.IsTrue(store.TryGet(testKey, out got)); + CollectionAssert.AreEqual(testValue, got); + Assert.AreEqual(false, snapshot.Contains(testKey)); Assert.AreEqual(true, store.Contains(testKey)); diff --git a/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs index 629e2bc374..8eea33c9a7 100644 --- a/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs +++ b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs @@ -97,6 +97,14 @@ public void MultiSnapshotTest() Assert.IsNull(_snapshot.TryGet(key1)); CollectionAssert.AreEqual(value1, snapshot2.TryGet(key1)); + Assert.IsFalse(_snapshot.TryGet(key1, out var value2)); + + Assert.IsTrue(snapshot2.TryGet(key1, out value2)); + CollectionAssert.AreEqual(value1, value2); + + Assert.IsTrue(_memoryStore.TryGet(key1, out value2)); + CollectionAssert.AreEqual(value1, value2); + _snapshot.Delete(key1); // Deleted value can not being found from the snapshot but can still get from the store and snapshot2 diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs index 133d9eb66c..4473ae63a8 100644 --- a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs @@ -51,6 +51,9 @@ public void StoreTest() store.Delete([1]); Assert.AreEqual(null, store.TryGet([1])); + Assert.IsFalse(store.TryGet([1], out var got)); + Assert.AreEqual(null, got); + store.Put([1], [1, 2, 3]); CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, store.TryGet([1])); @@ -79,13 +82,18 @@ public void NeoSystemStoreViewTest() var store = _neoSystem.StoreView; var key = new StorageKey(Encoding.UTF8.GetBytes("testKey")); var value = new StorageItem(Encoding.UTF8.GetBytes("testValue")); + store.Add(key, value); store.Commit(); + var result = store.TryGet(key); // The StoreView is a readonly view of the store, here it will have value in the cache Assert.AreEqual("testValue", Encoding.UTF8.GetString(result.Value.ToArray())); - // But the value will not be written to the underlying store even its committed. + + // But the value will not be written to the underlying store even its committed. Assert.IsNull(_memoryStore.TryGet(key.ToArray())); + Assert.IsFalse(_memoryStore.TryGet(key.ToArray(), out var got)); + Assert.AreEqual(null, got); } [TestMethod] From 3f2f78c1fcf71ea51f2c40c3a0e67062ac390aed Mon Sep 17 00:00:00 2001 From: Shargon Date: Mon, 14 Oct 2024 10:23:05 +0200 Subject: [PATCH 23/42] Add references (#3529) Co-authored-by: Jimmy --- benchmarks/Neo.VM.Benchmarks/TestArray.cs | 4 ++-- benchmarks/Neo.VM.Benchmarks/TestStruct.cs | 4 ++-- src/Neo.VM/ExecutionEngine.cs | 5 +++-- src/Neo.VM/Types/Array.cs | 4 ++-- src/Neo.VM/Types/Struct.cs | 4 ++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/benchmarks/Neo.VM.Benchmarks/TestArray.cs b/benchmarks/Neo.VM.Benchmarks/TestArray.cs index 6a7dbcabc1..0799040e52 100644 --- a/benchmarks/Neo.VM.Benchmarks/TestArray.cs +++ b/benchmarks/Neo.VM.Benchmarks/TestArray.cs @@ -53,9 +53,9 @@ public TestArray(IEnumerable? items = null) } /// - /// Create an array containing the specified items. And make the array use the specified . + /// Create an array containing the specified items. And make the array use the specified . /// - /// The to be used by this array. + /// The to be used by this array. /// The items to be included in the array. public TestArray(IReferenceCounter? referenceCounter, IEnumerable? items = null) : base(referenceCounter) diff --git a/benchmarks/Neo.VM.Benchmarks/TestStruct.cs b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs index 8f77d680c5..f7dc2fcb64 100644 --- a/benchmarks/Neo.VM.Benchmarks/TestStruct.cs +++ b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs @@ -27,9 +27,9 @@ public TestStruct(IEnumerable? fields = null) } /// - /// Create a structure with the specified fields. And make the structure use the specified . + /// Create a structure with the specified fields. And make the structure use the specified . /// - /// The to be used by this structure. + /// The to be used by this structure. /// The fields to be included in the structure. public TestStruct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) diff --git a/src/Neo.VM/ExecutionEngine.cs b/src/Neo.VM/ExecutionEngine.cs index b2c04bdc93..8a3167b4c8 100644 --- a/src/Neo.VM/ExecutionEngine.cs +++ b/src/Neo.VM/ExecutionEngine.cs @@ -83,17 +83,18 @@ protected internal set /// /// Initializes a new instance of the class. /// + /// The jump table to be used. public ExecutionEngine(JumpTable? jumpTable = null) : this(jumpTable, new ReferenceCounter(), ExecutionEngineLimits.Default) { } /// - /// Initializes a new instance of the class with the specified and . + /// Initializes a new instance of the class with the specified and . /// /// The jump table to be used. /// The reference counter to be used. /// Restrictions on the VM. - protected ExecutionEngine(JumpTable? jumpTable, ReferenceCounter referenceCounter, ExecutionEngineLimits limits) + protected ExecutionEngine(JumpTable? jumpTable, IReferenceCounter referenceCounter, ExecutionEngineLimits limits) { JumpTable = jumpTable ?? JumpTable.Default; Limits = limits; diff --git a/src/Neo.VM/Types/Array.cs b/src/Neo.VM/Types/Array.cs index 831bc830e6..903613c228 100644 --- a/src/Neo.VM/Types/Array.cs +++ b/src/Neo.VM/Types/Array.cs @@ -62,9 +62,9 @@ public Array(IEnumerable? items = null) } /// - /// Create an array containing the specified items. And make the array use the specified . + /// Create an array containing the specified items. And make the array use the specified . /// - /// The to be used by this array. + /// The to be used by this array. /// The items to be included in the array. public Array(IReferenceCounter? referenceCounter, IEnumerable? items = null) : base(referenceCounter) diff --git a/src/Neo.VM/Types/Struct.cs b/src/Neo.VM/Types/Struct.cs index f2ae144d3e..344147b5ed 100644 --- a/src/Neo.VM/Types/Struct.cs +++ b/src/Neo.VM/Types/Struct.cs @@ -31,9 +31,9 @@ public Struct(IEnumerable? fields = null) } /// - /// Create a structure with the specified fields. And make the structure use the specified . + /// Create a structure with the specified fields. And make the structure use the specified . /// - /// The to be used by this structure. + /// The to be used by this structure. /// The fields to be included in the structure. public Struct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) From bb71676fd9013e8cda8499ef181c59e7514e7f8c Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 15 Oct 2024 01:50:38 +0800 Subject: [PATCH 24/42] fix: concurrency conflict in NEP6Wallet.ToJson (#3527) * fix: concurrency conflict in NEP6Wallet.ToJson * Update also ChangePasssword * Reduce lock time --------- Co-authored-by: Shargon Co-authored-by: Jimmy --- src/Neo/Wallets/NEP6/NEP6Wallet.cs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Neo/Wallets/NEP6/NEP6Wallet.cs b/src/Neo/Wallets/NEP6/NEP6Wallet.cs index 3edb41c5aa..c205637a97 100644 --- a/src/Neo/Wallets/NEP6/NEP6Wallet.cs +++ b/src/Neo/Wallets/NEP6/NEP6Wallet.cs @@ -293,12 +293,18 @@ public override WalletAccount Import(string nep2, string passphrase, int N = 163 /// public JObject ToJson() { + NEP6Account[] accountValues; + lock (accounts) + { + accountValues = accounts.Values.ToArray(); + } + return new() { ["name"] = name, ["version"] = version.ToString(), ["scrypt"] = Scrypt.ToJson(), - ["accounts"] = accounts.Values.Select(p => p.ToJson()).ToArray(), + ["accounts"] = accountValues.Select(p => p.ToJson()).ToArray(), ["extra"] = extra }; } @@ -345,26 +351,28 @@ private bool VerifyPasswordInternal(string password) public override bool ChangePassword(string oldPassword, string newPassword) { bool succeed = true; + NEP6Account[] accountsValues; lock (accounts) { - Parallel.ForEach(accounts.Values, (account, state) => - { - if (!account.ChangePasswordPrepare(oldPassword, newPassword)) - { - state.Stop(); - succeed = false; - } - }); + accountsValues = accounts.Values.ToArray(); } + Parallel.ForEach(accountsValues, (account, state) => + { + if (!account.ChangePasswordPrepare(oldPassword, newPassword)) + { + state.Stop(); + succeed = false; + } + }); if (succeed) { - foreach (NEP6Account account in accounts.Values) + foreach (NEP6Account account in accountsValues) account.ChangePasswordCommit(); password = newPassword.ToSecureString(); } else { - foreach (NEP6Account account in accounts.Values) + foreach (NEP6Account account in accountsValues) account.ChangePasswordRollback(); } return succeed; From 16bde11a3abcafd2b6ad745103ab42726a49643f Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Mon, 21 Oct 2024 23:47:03 +0800 Subject: [PATCH 25/42] fix cref "OpCode.SUBSTR" in comment (#3542) --- src/Neo.VM/JumpTable/JumpTable.Splice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo.VM/JumpTable/JumpTable.Splice.cs b/src/Neo.VM/JumpTable/JumpTable.Splice.cs index f04d045988..eaa979a8b8 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Splice.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Splice.cs @@ -85,7 +85,7 @@ public virtual void Cat(ExecutionEngine engine, Instruction instruction) /// /// Extracts a substring from the specified buffer and pushes it onto the evaluation stack. - /// + /// /// /// The execution engine. /// The instruction being executed. From 6d7ea43b2ea6bc3cffb8a417755c482eaaa66241 Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Wed, 23 Oct 2024 17:16:32 +0800 Subject: [PATCH 26/42] ApplicationEngine helper to get engine error info (#3541) * helper to get engine error info * cancel try * Update src/Neo/SmartContract/ApplicationEngine.Helper.cs Co-authored-by: Shargon * standalone method to get exception stack trace and message * always return string --------- Co-authored-by: Jimmy Co-authored-by: Shargon --- .../SmartContract/ApplicationEngine.Helper.cs | 56 +++++++++++++++++++ .../SmartContract/UT_ApplicationEngine.cs | 7 +++ 2 files changed, 63 insertions(+) create mode 100644 src/Neo/SmartContract/ApplicationEngine.Helper.cs diff --git a/src/Neo/SmartContract/ApplicationEngine.Helper.cs b/src/Neo/SmartContract/ApplicationEngine.Helper.cs new file mode 100644 index 0000000000..5d2976c288 --- /dev/null +++ b/src/Neo/SmartContract/ApplicationEngine.Helper.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Linq; +using System.Text; + +namespace Neo.SmartContract +{ + public partial class ApplicationEngine : ExecutionEngine + { + public string GetEngineStackInfoOnFault(bool exceptionStackTrace = true, bool exceptionMessage = true) + { + if (State != VMState.FAULT || FaultException == null) + return ""; + StringBuilder traceback = new(); + if (CallingScriptHash != null) + traceback.AppendLine($"CallingScriptHash={CallingScriptHash}[{NativeContract.ContractManagement.GetContract(SnapshotCache, CallingScriptHash)?.Manifest.Name}]"); + traceback.AppendLine($"CurrentScriptHash={CurrentScriptHash}[{NativeContract.ContractManagement.GetContract(SnapshotCache, CurrentScriptHash)?.Manifest.Name}]"); + traceback.AppendLine($"EntryScriptHash={EntryScriptHash}"); + + foreach (ExecutionContext context in InvocationStack.Reverse()) + { + UInt160 contextScriptHash = context.GetScriptHash(); + string contextContractName = NativeContract.ContractManagement.GetContract(SnapshotCache, contextScriptHash)?.Manifest.Name; + traceback.AppendLine($"\tInstructionPointer={context.InstructionPointer}, OpCode {context.CurrentInstruction?.OpCode}, Script Length={context.Script.Length} {contextScriptHash}[{contextContractName}]"); + } + traceback.Append(GetEngineExceptionInfo(exceptionStackTrace: exceptionStackTrace, exceptionMessage: exceptionMessage)); + + return traceback.ToString(); + } + + public string GetEngineExceptionInfo(bool exceptionStackTrace = true, bool exceptionMessage = true) + { + if (State != VMState.FAULT || FaultException == null) + return ""; + StringBuilder traceback = new(); + Exception baseException = FaultException.GetBaseException(); + if (exceptionStackTrace) + traceback.AppendLine(baseException.StackTrace); + if (exceptionMessage) + traceback.AppendLine(baseException.Message); + return traceback.ToString(); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index a670e3b4b7..def978596e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -167,8 +167,15 @@ public void TestSystem_Contract_Call_Permissions() }; var currentScriptHash = engine.EntryScriptHash; + Assert.AreEqual("", engine.GetEngineStackInfoOnFault()); Assert.AreEqual(VMState.FAULT, engine.Execute()); Assert.IsTrue(engine.FaultException.ToString().Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + string traceback = engine.GetEngineStackInfoOnFault(); + Assert.IsTrue(traceback.Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + Assert.IsTrue(traceback.Contains("CurrentScriptHash")); + Assert.IsTrue(traceback.Contains("EntryScriptHash")); + Assert.IsTrue(traceback.Contains("InstructionPointer")); + Assert.IsTrue(traceback.Contains("OpCode SYSCALL, Script Length=")); } // Allowed method call. From 85f52f5c132cd6585815a8b0fee8030d98a6970b Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Wed, 23 Oct 2024 17:57:08 +0800 Subject: [PATCH 27/42] stack opcode example comments (#3546) * stack opcode example comments * move index into example --------- Co-authored-by: Jimmy --- src/Neo.VM/OpCode.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Neo.VM/OpCode.cs b/src/Neo.VM/OpCode.cs index 8a2878473c..4a705a556c 100644 --- a/src/Neo.VM/OpCode.cs +++ b/src/Neo.VM/OpCode.cs @@ -725,6 +725,10 @@ public enum OpCode : byte /// /// The item n back in the stack is copied to the top. /// + /// a b c d 2 -> a b c d b + /// index => 3[2]1 0 + /// + /// /// /// Push: 1 item(s) /// Pop: 0 item(s) @@ -735,6 +739,8 @@ public enum OpCode : byte /// /// The item at the top of the stack is copied and inserted before the second-to-top item. /// + /// a b c -> a c b c + /// /// /// Push: 1 item(s) /// Pop: 0 item(s) @@ -769,6 +775,10 @@ public enum OpCode : byte /// /// The item n back in the stack is moved to the top. /// + /// a b c d 2 -> a c d b + /// index => 3[2]1 0 + /// + /// /// /// Push: 0 item(s) /// Pop: 1 item(s) @@ -793,7 +803,6 @@ public enum OpCode : byte /// /// a b c d -> d c b a /// - /// /// /// Push: 0 item(s) /// Pop: 0 item(s) @@ -804,7 +813,8 @@ public enum OpCode : byte /// /// Pop the number N on the stack, and reverse the order of the top N items on the stack. /// - /// + /// a b c d 3 -> a d c b + /// /// /// Push: 0 item(s) /// Pop: 1 item(s) From 9ed6cac3786ef598715537f650018c7a7da35cec Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 23 Oct 2024 12:08:51 +0200 Subject: [PATCH 28/42] Expose `GetInteropDescriptor` (#3545) * Expose GetInteropDescriptor * Update src/Neo/SmartContract/ApplicationEngine.cs --------- Co-authored-by: Jimmy Co-authored-by: NGD Admin <154295625+NGDAdmin@users.noreply.github.com> --- src/Neo/SmartContract/ApplicationEngine.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index 38430f3632..7808baae5e 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -22,6 +22,7 @@ using System.Linq; using System.Numerics; using System.Reflection; +using System.Runtime.CompilerServices; using Array = System.Array; using VMArray = Neo.VM.Types.Array; @@ -241,9 +242,7 @@ protected static void OnSysCall(ExecutionEngine engine, Instruction instruction) { if (engine is ApplicationEngine app) { - uint method = instruction.TokenU32; - - app.OnSysCall(services[method]); + app.OnSysCall(GetInteropDescriptor(instruction.TokenU32)); } else { @@ -641,6 +640,17 @@ private static InteropDescriptor Register(string name, string handler, long fixe return descriptor; } + /// + /// Get Interop Descriptor + /// + /// Method Hash + /// InteropDescriptor + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static InteropDescriptor GetInteropDescriptor(uint methodHash) + { + return services[methodHash]; + } + /// /// Creates a new instance of the class, and use it to run the specified script. /// From 7f5504090420baf18175a485e959f268b0d11712 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 29 Oct 2024 16:05:12 +0800 Subject: [PATCH 29/42] fix: unexpected InvalidOperationException with CyclicReference in ContractParamter.ToJson (#3555) Co-authored-by: Christopher Schuchardt --- src/Neo/SmartContract/ContractParameter.cs | 2 ++ .../SmartContract/UT_ContractParameter.cs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Neo/SmartContract/ContractParameter.cs b/src/Neo/SmartContract/ContractParameter.cs index f1b12c6c0a..3a325d4520 100644 --- a/src/Neo/SmartContract/ContractParameter.cs +++ b/src/Neo/SmartContract/ContractParameter.cs @@ -168,6 +168,7 @@ private static JObject ToJson(ContractParameter parameter, HashSet)parameter.Value).Select(p => ToJson(p, context))); + context.Remove(parameter); break; case ContractParameterType.Map: if (context is null) @@ -182,6 +183,7 @@ private static JObject ToJson(ContractParameter parameter, HashSet(); } + [TestMethod] + public void TestContractParameterCyclicReference() + { + var map = new ContractParameter + { + Type = ContractParameterType.Map, + Value = new List> + { + new( + new ContractParameter { Type = ContractParameterType.Integer, Value = 1 }, + new ContractParameter { Type = ContractParameterType.Integer, Value = 2 } + ) + } + }; + + var value = new List { map, map }; + var item = new ContractParameter { Type = ContractParameterType.Array, Value = value }; + + // just check there is no exception + var json = item.ToJson(); + Assert.AreEqual(json.ToString(), ContractParameter.FromJson(json).ToJson().ToString()); + + // check cyclic reference + value.Add(item); + Action action = () => item.ToJson(); + action.Should().Throw(); + } + [TestMethod] public void TestSetValue() { From 03ba1dc1dc77c296b6e0b9dfe10a862f1d8659a3 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 30 Oct 2024 00:03:34 +0300 Subject: [PATCH 30/42] OpCodes: extend MODPOW tests with negative base/exp/mod (#3557) We need to ensure that NeoGo VM behaviour is compatible, ref. https://github.com/nspcc-dev/neo-go/pull/3649. Signed-off-by: Anna Shaleva Co-authored-by: Shargon --- .../Tests/OpCodes/Arithmetic/MODPOW.json | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json index 68efa97efa..4551bb99b8 100644 --- a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json @@ -140,6 +140,350 @@ } } ] + }, + { + "name": "(3 ^ 4) % 5 == 1", + "script": [ + "PUSH3", + "PUSH4", + "PUSH5", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "(-1 ^ 3) % 3 == -1", + "script": [ + "PUSHM1", + "PUSH3", + "PUSH3", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -1 + } + ] + } + } + ] + }, + { + "name": "(-1 ^ 3) % -3 == -1", + "script": [ + "PUSHM1", + "PUSH3", + "PUSH3", + "NEGATE", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": -3 + }, + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -1 + } + ] + } + } + ] + }, + { + "name": "(-3 ^ 5) % -5 == -3", + "script": [ + "PUSH3", + "NEGATE", + "PUSH5", + "PUSH5", + "NEGATE", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": -3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -3 + } + ] + } + } + ] + }, + { + "name": "(3 ^ 4) % -5 == 1", + "script": [ + "PUSH3", + "PUSH4", + "PUSH5", + "NEGATE", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "(5 ^ -1) % 4 == 1", + "script": [ + "PUSH5", + "PUSHM1", + "PUSH4", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": -1 + }, + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] } ] } From a42bc8d1470fb06686b8dfa555901023f80b3124 Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 30 Oct 2024 21:58:59 +0100 Subject: [PATCH 31/42] Fix InvalidOperationException (#3558) * Fix InvalidOperationException * use ! * Unify * fix --- src/Neo/SmartContract/ContractParameter.cs | 18 ++++++---------- src/Neo/VM/Helper.cs | 6 ++++-- tests/Neo.UnitTests/VM/UT_Helper.cs | 24 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Neo/SmartContract/ContractParameter.cs b/src/Neo/SmartContract/ContractParameter.cs index 3a325d4520..001abeb663 100644 --- a/src/Neo/SmartContract/ContractParameter.cs +++ b/src/Neo/SmartContract/ContractParameter.cs @@ -162,20 +162,14 @@ private static JObject ToJson(ContractParameter parameter, HashSet(); - else if (context.Contains(parameter)) - throw new InvalidOperationException(); - context.Add(parameter); + context ??= []; + if (!context.Add(parameter)) throw new InvalidOperationException("Circular reference."); json["value"] = new JArray(((IList)parameter.Value).Select(p => ToJson(p, context))); - context.Remove(parameter); + if (!context.Remove(parameter)) throw new InvalidOperationException("Circular reference."); break; case ContractParameterType.Map: - if (context is null) - context = new HashSet(); - else if (context.Contains(parameter)) - throw new InvalidOperationException(); - context.Add(parameter); + context ??= []; + if (!context.Add(parameter)) throw new InvalidOperationException("Circular reference."); json["value"] = new JArray(((IList>)parameter.Value).Select(p => { JObject item = new(); @@ -183,7 +177,7 @@ private static JObject ToJson(ContractParameter parameter, HashSet context, ref in { case Array array: { - context ??= new HashSet(ReferenceEqualityComparer.Instance); + context ??= new(ReferenceEqualityComparer.Instance); if (!context.Add(array)) throw new InvalidOperationException("Circular reference."); maxSize -= 2/*[]*/+ Math.Max(0, (array.Count - 1))/*,*/; JArray a = new(); foreach (StackItem stackItem in array) a.Add(ToJson(stackItem, context, ref maxSize)); value = a; + if (!context.Remove(array)) throw new InvalidOperationException("Circular reference."); break; } case Boolean boolean: @@ -397,7 +398,7 @@ private static JObject ToJson(StackItem item, HashSet context, ref in } case Map map: { - context ??= new HashSet(ReferenceEqualityComparer.Instance); + context ??= new(ReferenceEqualityComparer.Instance); if (!context.Add(map)) throw new InvalidOperationException("Circular reference."); maxSize -= 2/*[]*/+ Math.Max(0, (map.Count - 1))/*,*/; JArray a = new(); @@ -412,6 +413,7 @@ private static JObject ToJson(StackItem item, HashSet context, ref in a.Add(i); } value = a; + if (!context.Remove(map)) throw new InvalidOperationException("Circular reference."); break; } case Pointer pointer: diff --git a/tests/Neo.UnitTests/VM/UT_Helper.cs b/tests/Neo.UnitTests/VM/UT_Helper.cs index db47555dbd..cabe0c7a02 100644 --- a/tests/Neo.UnitTests/VM/UT_Helper.cs +++ b/tests/Neo.UnitTests/VM/UT_Helper.cs @@ -689,5 +689,29 @@ public void TestCharAsUInt16() CollectionAssert.AreEqual(sbUInt16.ToArray(), sbChar.ToArray()); } } + + [TestMethod] + public void TestCyclicReference() + { + var map = new VM.Types.Map + { + [1] = 2, + }; + + var item = new VM.Types.Array + { + map, + map + }; + + // just check there is no exception + var json = item.ToJson(); + Assert.AreEqual(json.ToString(), @"{""type"":""Array"",""value"":[{""type"":""Map"",""value"":[{""key"":{""type"":""Integer"",""value"":""1""},""value"":{""type"":""Integer"",""value"":""2""}}]},{""type"":""Map"",""value"":[{""key"":{""type"":""Integer"",""value"":""1""},""value"":{""type"":""Integer"",""value"":""2""}}]}]}"); + + // check cyclic reference + map[2] = item; + var action = () => item.ToJson(); + action.Should().Throw(); + } } } From 37a7a399b70de90e31363ae7ca11328f9aee40b7 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Sat, 2 Nov 2024 20:19:15 +0800 Subject: [PATCH 32/42] fix some compiler warnings (#3564) --- benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs | 6 +++--- benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs | 5 ++++- benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs index 78aa0dd24c..46d5be624f 100644 --- a/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs @@ -17,9 +17,9 @@ public abstract class OpCodeBase { [Params(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2040)] public int ItemCount { get; set; } = 10; - protected byte[] baseLineScript; - protected byte[] script; - protected byte[] multiScript; + protected byte[] baseLineScript = Array.Empty(); + protected byte[] script = Array.Empty(); + protected byte[] multiScript = Array.Empty(); [GlobalSetup] public void Setup() diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs index 91c660ff48..f2d78fda6c 100644 --- a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs @@ -18,7 +18,7 @@ namespace Neo.VM.Benchmark; public class Benchmarks_Convert { - private Dictionary> testItemsByType; + private Dictionary>? testItemsByType; [GlobalSetup] public void Setup() @@ -30,6 +30,9 @@ public void Setup() [ArgumentsSource(nameof(GetTypeConversionPairs))] public void BenchConvertTo(StackItemType fromType, StackItemType toType) { + if (testItemsByType is null) + throw new InvalidOperationException($"{nameof(testItemsByType)} not initialized"); + foreach (var item in testItemsByType[fromType]) { try diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs index 1d9c267f5f..64cc3d6988 100644 --- a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs @@ -95,7 +95,7 @@ private static void CreateNestedArray(Array? rootArray, int depth, int elementsP } } - private static void CreateNestedTestArray(TestArray rootArray, int depth, int elementsPerLevel = 1, IReferenceCounter referenceCounter = null) + private static void CreateNestedTestArray(TestArray rootArray, int depth, int elementsPerLevel = 1, IReferenceCounter? referenceCounter = null) { if (depth < 0) { From a856982dd89c7a5393609f7cd206733e70408249 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Mon, 4 Nov 2024 03:17:37 -0500 Subject: [PATCH 33/42] Fixed hashcode for StackItem [Remove Murmur32 and add XxHash3] (#3531) * Fixed HashCodes * Added test item * Added simple hasing for `GetHashCode` for `ByteString` and `Buffer` * Refactor * Cache hashcode * remove ToArray * Fix pointer * Cache hashcode script * Use Xxhash3 * Rename to InvalidateHashCode * Change to internal * Remove Murmur32 * Fixed `GetHashCode` --------- Co-authored-by: Jimmy Co-authored-by: Fernando Diaz Toledano Co-authored-by: NGD Admin <154295625+NGDAdmin@users.noreply.github.com> --- src/Neo.VM/Cryptography/Murmur32.cs | 98 ---------------------- src/Neo.VM/JumpTable/JumpTable.Compound.cs | 2 + src/Neo.VM/JumpTable/JumpTable.Splice.cs | 1 + src/Neo.VM/Neo.VM.csproj | 4 + src/Neo.VM/Script.cs | 11 +++ src/Neo.VM/Types/Buffer.cs | 10 +++ src/Neo.VM/Types/ByteString.cs | 15 ---- src/Neo.VM/Types/Pointer.cs | 2 +- src/Neo.VM/Types/PrimitiveType.cs | 6 -- src/Neo.VM/Types/StackItem.Vertex.cs | 16 +++- src/Neo.VM/Unsafe.cs | 14 ++++ tests/Neo.VM.Tests/UT_StackItem.cs | 13 ++- 12 files changed, 69 insertions(+), 123 deletions(-) delete mode 100644 src/Neo.VM/Cryptography/Murmur32.cs diff --git a/src/Neo.VM/Cryptography/Murmur32.cs b/src/Neo.VM/Cryptography/Murmur32.cs deleted file mode 100644 index cfb6f8ccd1..0000000000 --- a/src/Neo.VM/Cryptography/Murmur32.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// Murmur32.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using System; -using System.Buffers.Binary; -using System.Numerics; -using System.Security.Cryptography; - -namespace Neo.VM.Cryptography -{ - /// - /// Computes the murmur hash for the input data. - /// - sealed class Murmur32 : HashAlgorithm - { - private const uint c1 = 0xcc9e2d51; - private const uint c2 = 0x1b873593; - private const int r1 = 15; - private const int r2 = 13; - private const uint m = 5; - private const uint n = 0xe6546b64; - - private readonly uint seed; - private uint hash; - private int length; - - public override int HashSize => 32; - - /// - /// Initializes a new instance of the class with the specified seed. - /// - /// The seed to be used. - public Murmur32(uint seed) - { - this.seed = seed; - Initialize(); - } - - protected override void HashCore(byte[] array, int ibStart, int cbSize) - { - length += cbSize; - int remainder = cbSize & 3; - int alignedLength = ibStart + (cbSize - remainder); - for (int i = ibStart; i < alignedLength; i += 4) - { - uint k = BinaryPrimitives.ReadUInt32LittleEndian(array.AsSpan(i)); - k *= c1; - k = BitOperations.RotateLeft(k, r1); - k *= c2; - hash ^= k; - hash = BitOperations.RotateLeft(hash, r2); - hash = hash * m + n; - } - if (remainder > 0) - { - uint remainingBytes = 0; - switch (remainder) - { - case 3: remainingBytes ^= (uint)array[alignedLength + 2] << 16; goto case 2; - case 2: remainingBytes ^= (uint)array[alignedLength + 1] << 8; goto case 1; - case 1: remainingBytes ^= array[alignedLength]; break; - } - remainingBytes *= c1; - remainingBytes = BitOperations.RotateLeft(remainingBytes, r1); - remainingBytes *= c2; - hash ^= remainingBytes; - } - } - - protected override byte[] HashFinal() - { - hash ^= (uint)length; - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; - - byte[] buffer = new byte[sizeof(uint)]; - BinaryPrimitives.WriteUInt32LittleEndian(buffer, hash); - return buffer; - } - - public override void Initialize() - { - hash = seed; - length = 0; - } - } -} diff --git a/src/Neo.VM/JumpTable/JumpTable.Compound.cs b/src/Neo.VM/JumpTable/JumpTable.Compound.cs index 2a81b213fe..2447edfa35 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Compound.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Compound.cs @@ -464,6 +464,7 @@ public virtual void SetItem(ExecutionEngine engine, Instruction instruction) if (b < sbyte.MinValue || b > byte.MaxValue) throw new InvalidOperationException($"Overflow in {instruction.OpCode}, {b} is not a byte type."); buffer.InnerBuffer.Span[index] = (byte)b; + buffer.InvalidateHashCode(); break; } default: @@ -489,6 +490,7 @@ public virtual void ReverseItems(ExecutionEngine engine, Instruction instruction break; case Types.Buffer buffer: buffer.InnerBuffer.Span.Reverse(); + buffer.InvalidateHashCode(); break; default: throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); diff --git a/src/Neo.VM/JumpTable/JumpTable.Splice.cs b/src/Neo.VM/JumpTable/JumpTable.Splice.cs index eaa979a8b8..82e7f5750c 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Splice.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Splice.cs @@ -61,6 +61,7 @@ public virtual void Memcpy(ExecutionEngine engine, Instruction instruction) throw new InvalidOperationException($"The value {count} is out of range."); // TODO: check if we can optimize the memcpy by using peek instead of dup then pop src.Slice(si, count).CopyTo(dst.InnerBuffer.Span[di..]); + dst.InvalidateHashCode(); } /// diff --git a/src/Neo.VM/Neo.VM.csproj b/src/Neo.VM/Neo.VM.csproj index cc6fb4a3a9..eb7f9e7407 100644 --- a/src/Neo.VM/Neo.VM.csproj +++ b/src/Neo.VM/Neo.VM.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/src/Neo.VM/Script.cs b/src/Neo.VM/Script.cs index 95307c94f4..1721b6e104 100644 --- a/src/Neo.VM/Script.cs +++ b/src/Neo.VM/Script.cs @@ -23,6 +23,7 @@ namespace Neo.VM [DebuggerDisplay("Length={Length}")] public class Script { + private int _hashCode = 0; private readonly ReadOnlyMemory _value; private readonly bool strictMode; private readonly Dictionary _instructions = new(); @@ -157,5 +158,15 @@ public Instruction GetInstruction(int ip) public static implicit operator ReadOnlyMemory(Script script) => script._value; public static implicit operator Script(ReadOnlyMemory script) => new(script); public static implicit operator Script(byte[] script) => new(script); + + public override int GetHashCode() + { + if (_hashCode == 0) + { + return _hashCode = HashCode.Combine(Unsafe.HashBytes(_value.Span)); + } + + return _hashCode; + } } } diff --git a/src/Neo.VM/Types/Buffer.cs b/src/Neo.VM/Types/Buffer.cs index 8d170577e6..8ee38a816d 100644 --- a/src/Neo.VM/Types/Buffer.cs +++ b/src/Neo.VM/Types/Buffer.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Numerics; +using System.Runtime.CompilerServices; namespace Neo.VM.Types { @@ -112,5 +113,14 @@ public override string ToString() { return GetSpan().TryGetString(out var str) ? $"(\"{str}\")" : $"(\"Base64: {Convert.ToBase64String(GetSpan())}\")"; } + + /// + /// Invalidate HashCode + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void InvalidateHashCode() + { + _hashCode = 0; + } } } diff --git a/src/Neo.VM/Types/ByteString.cs b/src/Neo.VM/Types/ByteString.cs index 2d2df2d862..8869092b03 100644 --- a/src/Neo.VM/Types/ByteString.cs +++ b/src/Neo.VM/Types/ByteString.cs @@ -9,9 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo.VM.Cryptography; using System; -using System.Buffers.Binary; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; @@ -29,9 +27,6 @@ public class ByteString : PrimitiveType /// public static readonly ByteString Empty = ReadOnlyMemory.Empty; - private static readonly uint s_seed = unchecked((uint)new Random().Next()); - private int _hashCode = 0; - public override ReadOnlyMemory Memory { get; } public override StackItemType Type => StackItemType.ByteString; @@ -88,16 +83,6 @@ public override bool GetBoolean() return Unsafe.NotZero(GetSpan()); } - public override int GetHashCode() - { - if (_hashCode == 0) - { - using Murmur32 murmur = new(s_seed); - _hashCode = BinaryPrimitives.ReadInt32LittleEndian(murmur.ComputeHash(GetSpan().ToArray())); - } - return _hashCode; - } - public override BigInteger GetInteger() { if (Size > Integer.MaxSize) throw new InvalidCastException($"MaxSize exceed: {Size}"); diff --git a/src/Neo.VM/Types/Pointer.cs b/src/Neo.VM/Types/Pointer.cs index 1f729c812f..1fe5d339df 100644 --- a/src/Neo.VM/Types/Pointer.cs +++ b/src/Neo.VM/Types/Pointer.cs @@ -57,7 +57,7 @@ public override bool GetBoolean() public override int GetHashCode() { - return HashCode.Combine(Script, Position); + return HashCode.Combine(Script.GetHashCode(), Position); } public override string ToString() diff --git a/src/Neo.VM/Types/PrimitiveType.cs b/src/Neo.VM/Types/PrimitiveType.cs index 8415b8a45c..49fa0c6ebc 100644 --- a/src/Neo.VM/Types/PrimitiveType.cs +++ b/src/Neo.VM/Types/PrimitiveType.cs @@ -47,12 +47,6 @@ internal sealed override StackItem DeepCopy(Dictionary ref public abstract override bool Equals(StackItem? other); - /// - /// Get the hash code of the VM object, which is used for key comparison in the . - /// - /// The hash code of this VM object. - public abstract override int GetHashCode(); - public sealed override ReadOnlySpan GetSpan() { return Memory.Span; diff --git a/src/Neo.VM/Types/StackItem.Vertex.cs b/src/Neo.VM/Types/StackItem.Vertex.cs index d3e9ed4dbd..c14a08600a 100644 --- a/src/Neo.VM/Types/StackItem.Vertex.cs +++ b/src/Neo.VM/Types/StackItem.Vertex.cs @@ -80,6 +80,11 @@ internal class ObjectReferenceEntry /// internal int LowLink = 0; + /// + /// Stack Item hashcode + /// + protected int _hashCode = 0; + /// /// Indicates whether the item is currently on the stack for Tarjan's algorithm. /// @@ -120,7 +125,14 @@ internal class ObjectReferenceEntry /// Use this method when you need a hash code for a StackItem. /// /// The hash code for the StackItem. - public override int GetHashCode() => - HashCode.Combine(GetSpan().ToArray()); + public override int GetHashCode() + { + if (_hashCode == 0) + { + return _hashCode = HashCode.Combine(Type, Unsafe.HashBytes(GetSpan())); + } + + return _hashCode; + } } } diff --git a/src/Neo.VM/Unsafe.cs b/src/Neo.VM/Unsafe.cs index e3f4366be2..ae46ef8e82 100644 --- a/src/Neo.VM/Unsafe.cs +++ b/src/Neo.VM/Unsafe.cs @@ -10,12 +10,15 @@ // modifications are permitted. using System; +using System.IO.Hashing; using System.Runtime.CompilerServices; namespace Neo.VM { unsafe internal static class Unsafe { + const long HashMagicNumber = 40343; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool NotZero(ReadOnlySpan x) { @@ -38,5 +41,16 @@ public static bool NotZero(ReadOnlySpan x) } return false; } + + /// + /// Get 64-bit hash code for a byte array + /// + /// Span + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong HashBytes(ReadOnlySpan span) + { + return XxHash3.HashToUInt64(span, HashMagicNumber); + } } } diff --git a/tests/Neo.VM.Tests/UT_StackItem.cs b/tests/Neo.VM.Tests/UT_StackItem.cs index 210fa0d433..6982f2463a 100644 --- a/tests/Neo.VM.Tests/UT_StackItem.cs +++ b/tests/Neo.VM.Tests/UT_StackItem.cs @@ -31,8 +31,17 @@ public void TestHashCode() itemA = new VM.Types.Buffer(1); itemB = new VM.Types.Buffer(1); + itemC = new VM.Types.Buffer(2); - Assert.IsTrue(itemA.GetHashCode() != itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); + + itemA = new byte[] { 1, 2, 3 }; + itemB = new byte[] { 1, 2, 3 }; + itemC = new byte[] { 5, 6 }; + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); itemA = true; itemB = true; @@ -67,8 +76,10 @@ public void TestHashCode() itemA = new InteropInterface(123); itemB = new InteropInterface(123); + itemC = new InteropInterface(124); Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); var script = new Script(System.Array.Empty()); itemA = new Pointer(script, 123); From 2f4717a5d10759ba5604a007bc4baeac69454451 Mon Sep 17 00:00:00 2001 From: Shargon Date: Tue, 5 Nov 2024 03:15:36 +0100 Subject: [PATCH 34/42] Extend `CalculateNetworkFee` with contract verification (#3385) * Calculate fee * change values * remove using * fix using * Remove comments * Remove map * Anna's feedback * ensure only one result * Update src/Neo/Wallets/Helper.cs * Remove error * Fix compilation --------- Co-authored-by: Christopher Schuchardt Co-authored-by: Jimmy --- src/Neo/Wallets/Helper.cs | 73 ++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 9f24e54764..576660d61e 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -18,6 +18,7 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; +using Neo.VM.Types; using System; using static Neo.SmartContract.Helper; @@ -118,6 +119,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, if (witnessScript is null || witnessScript.Length == 0) { + // Contract-based verification var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); if (contract is null) throw new ArgumentException($"The smart contract or address {hash} ({hash.ToAddress(settings.AddressVersion)}) is not found. " + @@ -128,35 +130,74 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, if (md.ReturnType != ContractParameterType.Boolean) throw new ArgumentException("The verify method doesn't return boolean value."); if (md.Parameters.Length > 0 && invocationScript is null) - throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script."); + { + var script = new ScriptBuilder(); + foreach (var par in md.Parameters) + { + switch (par.Type) + { + case ContractParameterType.Any: + case ContractParameterType.Signature: + case ContractParameterType.String: + case ContractParameterType.ByteArray: + script.EmitPush(new byte[64]); + break; + case ContractParameterType.Boolean: + script.EmitPush(true); + break; + case ContractParameterType.Integer: + script.Emit(OpCode.PUSHINT256, new byte[Integer.MaxSize]); + break; + case ContractParameterType.Hash160: + script.EmitPush(new byte[UInt160.Length]); + break; + case ContractParameterType.Hash256: + script.EmitPush(new byte[UInt256.Length]); + break; + case ContractParameterType.PublicKey: + script.EmitPush(new byte[33]); + break; + case ContractParameterType.Array: + script.Emit(OpCode.NEWARRAY0); + break; + } + } + invocationScript = script.ToArray(); + } // Empty verification and non-empty invocation scripts - var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize(); - size += Array.Empty().GetVarSize() + invSize; + var invSize = invocationScript?.GetVarSize() ?? System.Array.Empty().GetVarSize(); + size += System.Array.Empty().GetVarSize() + invSize; // Check verify cost using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: settings, gas: maxExecutionCost); engine.LoadContract(contract, md, CallFlags.ReadOnly); if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); - if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); - if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false."); - + if (engine.Execute() == VMState.HALT) + { + // https://github.com/neo-project/neo/issues/2805 + if (engine.ResultStack.Count != 1) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); + _ = engine.ResultStack.Pop().GetBoolean(); // Ensure that the result is boolean + } maxExecutionCost -= engine.FeeConsumed; if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS."); networkFee += engine.FeeConsumed; } - else if (IsSignatureContract(witnessScript)) + else { - size += 67 + witnessScript.GetVarSize(); - networkFee += exec_fee_factor * SignatureContractCost(); - } - else if (IsMultiSigContract(witnessScript, out int m, out int n)) - { - int size_inv = 66 * m; - size += UnsafeData.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); - networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); + // Regular signature verification. + if (IsSignatureContract(witnessScript)) + { + size += 67 + witnessScript.GetVarSize(); + networkFee += exec_fee_factor * SignatureContractCost(); + } + else if (IsMultiSigContract(witnessScript, out int m, out int n)) + { + int size_inv = 66 * m; + size += UnsafeData.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); + networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); + } } - // We can support more contract types in the future. } networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); foreach (TransactionAttribute attr in tx.Attributes) From 08ecb7edb2019c59ae2725cb34fe3b21945164a7 Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Tue, 5 Nov 2024 16:46:12 +0800 Subject: [PATCH 35/42] support file path to base64 in parse (#3487) * support path input in parse * FilePathToContentBase64 * allow only .nef file * Update src/Neo.CLI/CLI/MainService.Tools.cs Co-authored-by: Shargon --------- Co-authored-by: Jimmy Co-authored-by: Christopher Schuchardt Co-authored-by: Shargon --- src/Neo.CLI/CLI/MainService.Tools.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Neo.CLI/CLI/MainService.Tools.cs b/src/Neo.CLI/CLI/MainService.Tools.cs index c000b48655..c6951f7cba 100644 --- a/src/Neo.CLI/CLI/MainService.Tools.cs +++ b/src/Neo.CLI/CLI/MainService.Tools.cs @@ -70,6 +70,17 @@ private void OnParseCommand(string value) } } + /// + /// Read .nef file from path and print its content in base64 + /// + [ParseFunction(".nef file path to content base64")] + private string? NefFileToBase64(string path) + { + if (Path.GetExtension(path).ToLower() != ".nef") return null; + if (!File.Exists(path)) return null; + return Convert.ToBase64String(File.ReadAllBytes(path)); + } + /// /// Little-endian to Big-endian /// input: ce616f7f74617e0fc4b805583af2602a238df63f From a021ebe5581d88289751be83e78c14a788f2410c Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 5 Nov 2024 16:57:52 +0800 Subject: [PATCH 36/42] A simpler and better performance impl for `ByteArrayComparer` (#3563) * A better and simpler impl for ByteArrayComparer * add benchmark * optimization when length not equal * update bench * remove redundant --------- Co-authored-by: Jimmy Co-authored-by: Shargon --- .../Benchmark.ByteArrayComparer.cs | 165 ++++++++++++++++++ .../Neo.Extensions.Benchmarks.csproj | 17 ++ .../OldByteArrayComparer.cs | 18 +- .../Neo.Extensions.Benchmarks/Program.cs | 15 ++ neo.sln | 7 + src/Neo.Extensions/ByteArrayComparer.cs | 46 +++++ src/Neo/Persistence/DataCache.cs | 2 +- src/Neo/Persistence/MemorySnapshot.cs | 1 + src/Neo/Persistence/MemoryStore.cs | 1 + .../UT_ByteArrayComparer.cs | 40 ++++- 10 files changed, 293 insertions(+), 19 deletions(-) create mode 100644 benchmarks/Neo.Extensions.Benchmarks/Benchmark.ByteArrayComparer.cs create mode 100644 benchmarks/Neo.Extensions.Benchmarks/Neo.Extensions.Benchmarks.csproj rename src/Neo.IO/ByteArrayComparer.cs => benchmarks/Neo.Extensions.Benchmarks/OldByteArrayComparer.cs (73%) create mode 100644 benchmarks/Neo.Extensions.Benchmarks/Program.cs create mode 100644 src/Neo.Extensions/ByteArrayComparer.cs rename tests/{Neo.UnitTests/IO => Neo.Extensions.Tests}/UT_ByteArrayComparer.cs (52%) diff --git a/benchmarks/Neo.Extensions.Benchmarks/Benchmark.ByteArrayComparer.cs b/benchmarks/Neo.Extensions.Benchmarks/Benchmark.ByteArrayComparer.cs new file mode 100644 index 0000000000..b41d1b54c4 --- /dev/null +++ b/benchmarks/Neo.Extensions.Benchmarks/Benchmark.ByteArrayComparer.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmark.ByteArrayComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Attributes; + +namespace Neo.Extensions +{ + public class Benchmark_ByteArrayComparer + { + private ByteArrayComparer comparer = ByteArrayComparer.Default; + private ByteArrayComparerV0 _oldComparer = ByteArrayComparerV0.Default; + private byte[]? x, y; + + [GlobalSetup] + public void Setup() + { + comparer = ByteArrayComparer.Default; + _oldComparer = ByteArrayComparerV0.Default; + } + + [GlobalSetup(Target = nameof(NewCompare_50Bytes))] + public void SetupNew50Bytes() + { + comparer = ByteArrayComparer.Default; + + x = new byte[50]; // 50 bytes + y = new byte[50]; // 50 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void NewCompare_50Bytes() + { + comparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(OldCompare_50Bytes))] + public void SetupOld50Bytes() + { + _oldComparer = ByteArrayComparerV0.Default; + + x = new byte[50]; // 50 bytes + y = new byte[50]; // 50 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void OldCompare_50Bytes() + { + _oldComparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(NewCompare_500Bytes))] + public void SetupNew500Bytes() + { + comparer = ByteArrayComparer.Default; + + x = new byte[500]; // 500 bytes + y = new byte[500]; // 500 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void NewCompare_500Bytes() + { + comparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(OldCompare_500Bytes))] + public void SetupOld500Bytes() + { + _oldComparer = ByteArrayComparerV0.Default; + + x = new byte[500]; // 500 bytes + y = new byte[500]; // 500 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void OldCompare_500Bytes() + { + _oldComparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(NewCompare_5000Bytes))] + public void SetupNew5000Bytes() + { + comparer = ByteArrayComparer.Default; + + x = new byte[5000]; // 5000 bytes + y = new byte[5000]; // 5000 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void NewCompare_5000Bytes() + { + comparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(OldCompare_5000Bytes))] + public void SetupOld5000Bytes() + { + _oldComparer = ByteArrayComparerV0.Default; + + x = new byte[5000]; // 5000 bytes + y = new byte[5000]; // 5000 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void OldCompare_5000Bytes() + { + _oldComparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(NewCompare_50000Bytes))] + public void SetupNew50000Bytes() + { + comparer = ByteArrayComparer.Default; + + x = new byte[50000]; // 50000 bytes + y = new byte[50000]; // 50000 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void NewCompare_50000Bytes() + { + comparer.Compare(x, y); + } + + [GlobalSetup(Target = nameof(OldCompare_50000Bytes))] + public void SetupOld50000Bytes() + { + _oldComparer = ByteArrayComparerV0.Default; + + x = new byte[50000]; // 50000 bytes + y = new byte[50000]; // 50000 bytes + Array.Fill(x, (byte)0xCC); + Array.Copy(x, y, x.Length); + } + + [Benchmark] + public void OldCompare_50000Bytes() + { + _oldComparer.Compare(x, y); + } + } +} diff --git a/benchmarks/Neo.Extensions.Benchmarks/Neo.Extensions.Benchmarks.csproj b/benchmarks/Neo.Extensions.Benchmarks/Neo.Extensions.Benchmarks.csproj new file mode 100644 index 0000000000..d87bccaee5 --- /dev/null +++ b/benchmarks/Neo.Extensions.Benchmarks/Neo.Extensions.Benchmarks.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + Neo.Extensions + enable + enable + + + + + + + + + diff --git a/src/Neo.IO/ByteArrayComparer.cs b/benchmarks/Neo.Extensions.Benchmarks/OldByteArrayComparer.cs similarity index 73% rename from src/Neo.IO/ByteArrayComparer.cs rename to benchmarks/Neo.Extensions.Benchmarks/OldByteArrayComparer.cs index f9d44e24d6..6c9716403c 100644 --- a/src/Neo.IO/ByteArrayComparer.cs +++ b/benchmarks/Neo.Extensions.Benchmarks/OldByteArrayComparer.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// ByteArrayComparer.cs file belongs to the neo project and is free +// OldByteArrayComparer.cs file belongs to the neo project and is free // software distributed under the MIT software license, see the // accompanying file LICENSE in the main directory of the // repository or http://www.opensource.org/licenses/mit-license.php @@ -9,20 +9,18 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Neo.IO +namespace Neo.Extensions { - internal class ByteArrayComparer : IComparer + public class ByteArrayComparerV0 : IComparer { - public static readonly ByteArrayComparer Default = new(1); - public static readonly ByteArrayComparer Reverse = new(-1); + public static readonly ByteArrayComparerV0 Default = new(1); + public static readonly ByteArrayComparerV0 Reverse = new(-1); private readonly int _direction; - private ByteArrayComparer(int direction) + internal ByteArrayComparerV0(int direction) { _direction = direction; } @@ -35,8 +33,8 @@ public int Compare(byte[]? x, byte[]? y) if (y is null && x is not null) return _direction > 0 ? x.Length : -x.Length; return _direction > 0 ? - CompareInternal(x!, y!) : - -CompareInternal(x!, y!); + CompareInternal(x!, y!) : + -CompareInternal(x!, y!); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/benchmarks/Neo.Extensions.Benchmarks/Program.cs b/benchmarks/Neo.Extensions.Benchmarks/Program.cs new file mode 100644 index 0000000000..c3c9be7cc7 --- /dev/null +++ b/benchmarks/Neo.Extensions.Benchmarks/Program.cs @@ -0,0 +1,15 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Program.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Running; +using Neo.Extensions; + +BenchmarkRunner.Run(typeof(Benchmark_ByteArrayComparer)); diff --git a/neo.sln b/neo.sln index 5e7f8c7dda..e746e07e7f 100644 --- a/neo.sln +++ b/neo.sln @@ -82,6 +82,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.ApplicationLogs EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Benchmarks", "benchmarks\Neo.Extensions.Benchmarks\Neo.Extensions.Benchmarks.csproj", "{B6CB2559-10F9-41AC-8D58-364BFEF9688B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -228,6 +230,10 @@ Global {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Debug|Any CPU.Build.0 = Debug|Any CPU {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Release|Any CPU.ActiveCfg = Release|Any CPU {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Release|Any CPU.Build.0 = Release|Any CPU + {B6CB2559-10F9-41AC-8D58-364BFEF9688B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6CB2559-10F9-41AC-8D58-364BFEF9688B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6CB2559-10F9-41AC-8D58-364BFEF9688B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6CB2559-10F9-41AC-8D58-364BFEF9688B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -269,6 +275,7 @@ Global {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0} = {C2DC830A-327A-42A7-807D-295216D30DBB} {8C866DC8-2E55-4399-9563-2F47FD4602EC} = {7F257712-D033-47FF-B439-9D4320D06599} {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} + {B6CB2559-10F9-41AC-8D58-364BFEF9688B} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC} diff --git a/src/Neo.Extensions/ByteArrayComparer.cs b/src/Neo.Extensions/ByteArrayComparer.cs new file mode 100644 index 0000000000..661b76d903 --- /dev/null +++ b/src/Neo.Extensions/ByteArrayComparer.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ByteArrayComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.Extensions +{ + public class ByteArrayComparer : IComparer + { + public static readonly ByteArrayComparer Default = new(1); + public static readonly ByteArrayComparer Reverse = new(-1); + + private readonly int _direction; + + private ByteArrayComparer(int direction) + { + _direction = direction; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(byte[]? x, byte[]? y) + { + if (x == y) return 0; + + if (x is null) // y must not be null + return -y!.Length * _direction; + + if (y is null) // x must not be null + return x.Length * _direction; + + if (_direction < 0) + return y.AsSpan().SequenceCompareTo(x.AsSpan()); + return x.AsSpan().SequenceCompareTo(y.AsSpan()); + } + } +} diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index 29c610c6c8..37664bfa67 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -9,7 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo.IO; +using Neo.Extensions; using Neo.SmartContract; using System; using System.Collections.Generic; diff --git a/src/Neo/Persistence/MemorySnapshot.cs b/src/Neo/Persistence/MemorySnapshot.cs index b3d06798d4..4817cc963e 100644 --- a/src/Neo/Persistence/MemorySnapshot.cs +++ b/src/Neo/Persistence/MemorySnapshot.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/src/Neo/Persistence/MemoryStore.cs b/src/Neo/Persistence/MemoryStore.cs index 378d5374f1..3107ebdddd 100644 --- a/src/Neo/Persistence/MemoryStore.cs +++ b/src/Neo/Persistence/MemoryStore.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs b/tests/Neo.Extensions.Tests/UT_ByteArrayComparer.cs similarity index 52% rename from tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs rename to tests/Neo.Extensions.Tests/UT_ByteArrayComparer.cs index 19c3fafcd6..27dbe3f52c 100644 --- a/tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs +++ b/tests/Neo.Extensions.Tests/UT_ByteArrayComparer.cs @@ -11,10 +11,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO; using System; -namespace Neo.UnitTests.IO +namespace Neo.Extensions.Tests { [TestClass] public class UT_ByteArrayComparer @@ -32,31 +31,56 @@ public void TestCompare() comparer.Compare(x, x).Should().Be(0); y = null; - comparer.Compare(x, y).Should().Be(5); + comparer.Compare(x, y).Should().BeGreaterThan(0); y = x; x = null; - comparer.Compare(x, y).Should().Be(-5); + comparer.Compare(x, y).Should().BeLessThan(0); x = new byte[] { 1 }; y = Array.Empty(); - comparer.Compare(x, y).Should().Be(1); + comparer.Compare(x, y).Should().BeGreaterThan(0); y = x; comparer.Compare(x, y).Should().Be(0); x = new byte[] { 1 }; y = new byte[] { 2 }; - comparer.Compare(x, y).Should().Be(-1); + comparer.Compare(x, y).Should().BeLessThan(0); + comparer.Compare(null, Array.Empty()).Should().Be(0); + comparer.Compare(Array.Empty(), null).Should().Be(0); + + x = new byte[] { 1, 2, 3, 4, 5 }; + y = new byte[] { 1, 2, 3 }; + comparer.Compare(x, y).Should().BeGreaterThan(0); + + x = new byte[] { 1, 2, 3, 4, 5 }; + y = new byte[] { 1, 2, 3, 4, 5, 6 }; + comparer.Compare(x, y).Should().BeLessThan(0); + + // cases for reverse comparer comparer = ByteArrayComparer.Reverse; + x = new byte[] { 3 }; - comparer.Compare(x, y).Should().Be(-1); + comparer.Compare(x, y).Should().BeLessThan(0); + y = x; comparer.Compare(x, y).Should().Be(0); x = new byte[] { 1 }; y = new byte[] { 2 }; - comparer.Compare(x, y).Should().Be(1); + comparer.Compare(x, y).Should().BeGreaterThan(0); + + comparer.Compare(null, Array.Empty()).Should().Be(0); + comparer.Compare(Array.Empty(), null).Should().Be(0); + + x = new byte[] { 1, 2, 3, 4, 5 }; + y = new byte[] { 1, 2, 3 }; + comparer.Compare(x, y).Should().BeLessThan(0); + + x = new byte[] { 1, 2, 3, 4, 5 }; + y = new byte[] { 1, 2, 3, 4, 5, 6 }; + comparer.Compare(x, y).Should().BeGreaterThan(0); } } } From 25554dfe2532a19b15be95258247a028eba3eabb Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 6 Nov 2024 10:48:18 +0800 Subject: [PATCH 37/42] use `Xxhash3` for StorageKey.GetHashCode (#3559) * fea: StorageKey.GetHashCode uses XxHash3 for better performance * fea: StorageKey.GetHashCode uses XxHash3 for better performance * fea: StorageKey.GetHashCode uses XxHash3 for better performance * use HashCode.Combine * Unify seed * fix ut * remove unused --------- Co-authored-by: Shargon --- .gitignore | 3 + benchmarks/Neo.Benchmarks/Benchmarks.Hash.cs | 56 +++++++++++++++++++ .../Neo.Benchmarks/Neo.Benchmarks.csproj | 1 + benchmarks/Neo.Benchmarks/Program.cs | 1 + src/Neo.VM/Unsafe.cs | 9 +-- src/Neo/Cryptography/Helper.cs | 27 +++++++++ src/Neo/Neo.csproj | 1 + src/Neo/SmartContract/StorageKey.cs | 11 +++- .../Cryptography/UT_Cryptography_Helper.cs | 15 +++-- tests/Neo.UnitTests/Ledger/UT_StorageKey.cs | 7 ++- 10 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 benchmarks/Neo.Benchmarks/Benchmarks.Hash.cs diff --git a/.gitignore b/.gitignore index 1358af3bde..45ccb8c40a 100644 --- a/.gitignore +++ b/.gitignore @@ -257,3 +257,6 @@ paket-files/ PublishProfiles /.vscode launchSettings.json + +# Benchmarks +**/BenchmarkDotNet.Artifacts/ \ No newline at end of file diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.Hash.cs b/benchmarks/Neo.Benchmarks/Benchmarks.Hash.cs new file mode 100644 index 0000000000..a63542bace --- /dev/null +++ b/benchmarks/Neo.Benchmarks/Benchmarks.Hash.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.Hash.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Attributes; +using Neo.Cryptography; +using Neo.Extensions; +using System.Diagnostics; +using System.IO.Hashing; +using System.Text; + +namespace Neo.Benchmark; + +public class Benchmarks_Hash +{ + // 256 KiB + static readonly byte[] data = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("Hello, World!^_^", 16 * 1024))); + + static readonly byte[] hash = "9182abedfbb9b18d81a05d8bcb45489e7daa2858".HexToBytes(); + + [Benchmark] + public void RIPEMD160_ComputeHash() + { + using var ripemd160 = new RIPEMD160Managed(); + var result = ripemd160.ComputeHash(data); + Debug.Assert(result.SequenceEqual(hash)); + } + + [Benchmark] + public void XxHash32_HashToUInt32() + { + var result = XxHash32.HashToUInt32(data); + Debug.Assert(result == 682967318u); + } + + [Benchmark] + public void XxHash3_HashToUInt64() + { + var result = (uint)XxHash3.HashToUInt64(data); + Debug.Assert(result == 1389469485u); + } + + [Benchmark] + public void Murmur32_HashToUInt32() + { + var result = data.Murmur32(0); + Debug.Assert(result == 3731881930u); + } +} diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index a59fc6e728..6a4bd93264 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -9,6 +9,7 @@ + diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index c44b76f839..a64a1ca981 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -14,3 +14,4 @@ // BenchmarkRunner.Run(); BenchmarkRunner.Run(); +BenchmarkRunner.Run(); diff --git a/src/Neo.VM/Unsafe.cs b/src/Neo.VM/Unsafe.cs index ae46ef8e82..86380722b6 100644 --- a/src/Neo.VM/Unsafe.cs +++ b/src/Neo.VM/Unsafe.cs @@ -17,7 +17,7 @@ namespace Neo.VM { unsafe internal static class Unsafe { - const long HashMagicNumber = 40343; + private const long DefaultXxHash3Seed = 40343; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool NotZero(ReadOnlySpan x) @@ -46,11 +46,12 @@ public static bool NotZero(ReadOnlySpan x) /// Get 64-bit hash code for a byte array /// /// Span - /// + /// The seed used by the xxhash3 algorithm. + /// The computed hash code. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong HashBytes(ReadOnlySpan span) + public static ulong HashBytes(ReadOnlySpan span, long seed = DefaultXxHash3Seed) { - return XxHash3.HashToUInt64(span, HashMagicNumber); + return XxHash3.HashToUInt64(span, seed); } } } diff --git a/src/Neo/Cryptography/Helper.cs b/src/Neo/Cryptography/Helper.cs index 41e89f5a49..761073463b 100644 --- a/src/Neo/Cryptography/Helper.cs +++ b/src/Neo/Cryptography/Helper.cs @@ -18,6 +18,7 @@ using Org.BouncyCastle.Crypto.Parameters; using System; using System.Buffers.Binary; +using System.IO.Hashing; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -31,7 +32,9 @@ namespace Neo.Cryptography /// public static class Helper { + private const int DefaultXxHash3Seed = 40343; private static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + /// /// Computes the hash value for the specified byte array using the ripemd160 algorithm. /// @@ -117,6 +120,30 @@ public static byte[] Sha256(this byte[] value) return sha256.ComputeHash(value); } + /// + /// Computes the 32-bit hash value for the specified byte array using the xxhash3 algorithm. + /// + /// The input to compute the hash code for. + /// The seed used by the xxhash3 algorithm. + /// The computed hash code. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int XxHash3_32(this ReadOnlySpan value, long seed = DefaultXxHash3Seed) + { + return HashCode.Combine(XxHash3.HashToUInt64(value, seed)); + } + + /// + /// Computes the 32-bit hash value for the specified byte array using the xxhash3 algorithm. + /// + /// The input to compute the hash code for. + /// The seed used by the xxhash3 algorithm. + /// The computed hash code. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int XxHash3_32(this byte[] value, long seed = DefaultXxHash3Seed) + { + return XxHash3_32(value.AsSpan(), seed); + } + /// /// Computes the hash value for the specified region of the specified byte array using the sha256 algorithm. /// diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index 7c6478c33e..89c200e31b 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Neo/SmartContract/StorageKey.cs b/src/Neo/SmartContract/StorageKey.cs index 9c5e37827f..7a643a2737 100644 --- a/src/Neo/SmartContract/StorageKey.cs +++ b/src/Neo/SmartContract/StorageKey.cs @@ -33,8 +33,15 @@ public sealed record StorageKey private byte[] cache = null; + // NOTE: StorageKey is readonly, so we can cache the hash code. + private int _hashCode = 0; + public StorageKey() { } + /// + /// Initializes a new instance of the class. + /// + /// The cached byte array. NOTE: It must be read-only and can be modified by the caller. internal StorageKey(byte[] cache) { this.cache = cache; @@ -67,7 +74,9 @@ public bool Equals(StorageKey other) public override int GetHashCode() { - return Id + (int)Key.Span.Murmur32(0); + if (_hashCode == 0) + _hashCode = HashCode.Combine(Id, Key.Span.XxHash3_32()); + return _hashCode; } public byte[] ToArray() diff --git a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs index 8cddc26d16..194d65c35b 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs @@ -19,6 +19,8 @@ using Neo.Wallets; using Neo.Wallets.NEP6; using System; +using System.Collections.Generic; +using System.IO.Hashing; using System.Linq; using System.Text; @@ -55,13 +57,19 @@ public void TestMurmurReadOnlySpan() input.Murmur128(0).Should().Equal(input2.Murmur128(0)); } + [TestMethod] + public void TestXxHash3() + { + byte[] data = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("Hello, World!^_^", 16 * 1024))); + data.XxHash3_32().Should().Be(HashCode.Combine(XxHash3.HashToUInt64(data, 40343))); + } + [TestMethod] public void TestSha256() { byte[] value = Encoding.ASCII.GetBytes("hello world"); byte[] result = value.Sha256(0, value.Length); - string resultStr = result.ToHexString(); - resultStr.Should().Be("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"); + result.ToHexString().Should().Be("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"); value.Sha256().Should().Equal(result); ((Span)value).Sha256().Should().Equal(result); ((ReadOnlySpan)value).Sha256().Should().Equal(result); @@ -82,8 +90,7 @@ public void TestRIPEMD160() { ReadOnlySpan value = Encoding.ASCII.GetBytes("hello world"); byte[] result = value.RIPEMD160(); - string resultStr = result.ToHexString(); - resultStr.Should().Be("98c615784ccb5fe5936fbc0cbe9dfdb408d92f0f"); + result.ToHexString().Should().Be("98c615784ccb5fe5936fbc0cbe9dfdb408d92f0f"); } [TestMethod] diff --git a/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs b/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs index f2d9300fee..a3bcefb37c 100644 --- a/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs +++ b/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs @@ -11,7 +11,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; using Neo.SmartContract; +using System; namespace Neo.UnitTests.Ledger { @@ -102,8 +104,9 @@ public void Equals_SameHash_DiffKey() [TestMethod] public void GetHashCode_Get() { - StorageKey uut = new() { Id = 0x42000000, Key = TestUtils.GetByteArray(10, 0x42) }; - uut.GetHashCode().Should().Be(1374529787); + var data = TestUtils.GetByteArray(10, 0x42); + StorageKey uut = new() { Id = 0x42000000, Key = data }; + uut.GetHashCode().Should().Be(HashCode.Combine(0x42000000, data.XxHash3_32())); } [TestMethod] From 7e31d0caacfadb7634614d629c88b9244b434501 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 8 Aug 2024 00:24:40 +0800 Subject: [PATCH 38/42] add hardofork HF_Echidna --- src/Neo.CLI/config.fs.mainnet.json | 3 ++- src/Neo.CLI/config.json | 3 ++- src/Neo.CLI/config.mainnet.json | 3 ++- src/Neo.CLI/config.testnet.json | 3 ++- src/Neo/Hardfork.cs | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index 6879232732..c12372b43b 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -40,7 +40,8 @@ "HF_Aspidochelone": 3000000, "HF_Basilisk": 4500000, "HF_Cockatrice": 5800000, - "HF_Domovoi": 5800000 + "HF_Domovoi": 5800000, + "HF_Echidna": 0 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 772a221714..9e97ec1f00 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, - "HF_Domovoi": 5570000 + "HF_Domovoi": 5570000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 772a221714..9e97ec1f00 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, - "HF_Domovoi": 5570000 + "HF_Domovoi": 5570000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index dc102be54b..88a04794f0 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -38,7 +38,8 @@ "HF_Aspidochelone": 210000, "HF_Basilisk": 2680000, "HF_Cockatrice": 3967000, - "HF_Domovoi": 4144000 + "HF_Domovoi": 4144000, + "HF_Echidna": 0 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index 7bd3cc0aef..9276c9c07d 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -16,6 +16,7 @@ public enum Hardfork : byte HF_Aspidochelone, HF_Basilisk, HF_Cockatrice, - HF_Domovoi + HF_Domovoi, + HF_Echidna } } From 993e3fe9dd4485387f8dd244885f616b4dd78de5 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 8 Aug 2024 01:54:41 -0700 Subject: [PATCH 39/42] Add entries to `Designation` event (#3397) * Add entries to Designation event * Change to HF_Echidna * Add UT * Add count --- .../SmartContract/Native/RoleManagement.cs | 24 +++++++++++++++++-- .../SmartContract/Native/UT_NativeContract.cs | 18 ++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index e57f3f3e83..ecedba4c86 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -26,7 +26,16 @@ public sealed class RoleManagement : NativeContract { [ContractEvent(0, name: "Designation", "Role", ContractParameterType.Integer, - "BlockIndex", ContractParameterType.Integer)] + "BlockIndex", ContractParameterType.Integer, + Hardfork.HF_Echidna)] + + [ContractEvent(Hardfork.HF_Echidna, 0, name: "Designation", + "Role", ContractParameterType.Integer, + "BlockIndex", ContractParameterType.Integer, + "Old", ContractParameterType.Array, + "New", ContractParameterType.Array + )] + internal RoleManagement() : base() { } /// @@ -69,7 +78,18 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node list.AddRange(nodes); list.Sort(); engine.SnapshotCache.Add(key, new StorageItem(list)); - engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, new StackItem[] { (int)role, engine.PersistingBlock.Index })); + + if (engine.IsHardforkEnabled(Hardfork.HF_Echidna)) + { + var oldNodes = new VM.Types.Array(engine.ReferenceCounter, GetDesignatedByRole(engine.Snapshot, role, index - 1).Select(u => (ByteString)u.EncodePoint(true))); + var newNodes = new VM.Types.Array(engine.ReferenceCounter, nodes.Select(u => (ByteString)u.EncodePoint(true))); + + engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, [(int)role, engine.PersistingBlock.Index, oldNodes, newNodes])); + } + else + { + engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, [(int)role, engine.PersistingBlock.Index])); + } } private class NodeList : InteroperableList diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index f9a3089f9d..627a9fdd3c 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -80,6 +80,24 @@ public void TestActiveDeprecatedIn() Assert.IsFalse(NativeContract.IsActive(new active() { ActiveIn = null, DeprecatedIn = Hardfork.HF_Cockatrice }, settings.IsHardforkEnabled, 20)); } + [TestMethod] + public void TestActiveDeprecatedInRoleManagement() + { + string json = UT_ProtocolSettings.CreateHKSettings("\"HF_Echidna\": 20"); + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + var before = NativeContract.RoleManagement.GetContractState(settings.IsHardforkEnabled, 19); + var after = NativeContract.RoleManagement.GetContractState(settings.IsHardforkEnabled, 20); + + Assert.AreEqual(2, before.Manifest.Abi.Events[0].Parameters.Length); + Assert.AreEqual(1, before.Manifest.Abi.Events.Length); + Assert.AreEqual(4, after.Manifest.Abi.Events[0].Parameters.Length); + Assert.AreEqual(1, after.Manifest.Abi.Events.Length); + } + [TestMethod] public void TestGetContract() { From 37bf0cb81a64661e2939af0f2c7d8f5af0c610af Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 9 Aug 2024 03:42:18 +0800 Subject: [PATCH 40/42] [Neo Core StdLib] Add Base64url (#3453) * add base64url * active in * update placehold hf height * fix hf issue and move methods to proper place. * fix test * use identifymodel instead. --- src/Neo.CLI/config.fs.mainnet.json | 2 +- src/Neo.CLI/config.json | 2 +- src/Neo.CLI/config.mainnet.json | 2 +- src/Neo.CLI/config.testnet.json | 2 +- src/Neo/Neo.csproj | 1 + src/Neo/SmartContract/Native/StdLib.cs | 23 +++++++++++++++++++ .../SmartContract/Native/UT_NativeContract.cs | 2 +- .../SmartContract/Native/UT_StdLib.cs | 20 ++++++++++++++++ 8 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index c12372b43b..bbd17978e3 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -41,7 +41,7 @@ "HF_Basilisk": 4500000, "HF_Cockatrice": 5800000, "HF_Domovoi": 5800000, - "HF_Echidna": 0 + "HF_Echidna": 5800001 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 9e97ec1f00..9590fc60fd 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -39,7 +39,7 @@ "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, "HF_Domovoi": 5570000, - "HF_Echidna": 0 + "HF_Echidna": 5570001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 9e97ec1f00..9590fc60fd 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -39,7 +39,7 @@ "HF_Basilisk": 4120000, "HF_Cockatrice": 5450000, "HF_Domovoi": 5570000, - "HF_Echidna": 0 + "HF_Echidna": 5570001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index 88a04794f0..8bcd6d8956 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -39,7 +39,7 @@ "HF_Basilisk": 2680000, "HF_Cockatrice": 3967000, "HF_Domovoi": 4144000, - "HF_Echidna": 0 + "HF_Echidna": 4144001 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index 89c200e31b..25d80ba91f 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Neo/SmartContract/Native/StdLib.cs b/src/Neo/SmartContract/Native/StdLib.cs index f8fa9efcc2..1b4030d9f5 100644 --- a/src/Neo/SmartContract/Native/StdLib.cs +++ b/src/Neo/SmartContract/Native/StdLib.cs @@ -11,6 +11,7 @@ #pragma warning disable IDE0051 +using Microsoft.IdentityModel.Tokens; using Neo.Cryptography; using Neo.Json; using Neo.VM.Types; @@ -131,6 +132,28 @@ public static byte[] Base64Decode([MaxLength(MaxInputLength)] string s) return Convert.FromBase64String(s); } + /// + /// Encodes a byte array into a base64Url string. + /// + /// The base64Url to be encoded. + /// The encoded base64Url string. + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)] + public static string Base64UrlEncode([MaxLength(MaxInputLength)] string data) + { + return Base64UrlEncoder.Encode(data); + } + + /// + /// Decodes a byte array from a base64Url string. + /// + /// The base64Url string. + /// The decoded base64Url string. + [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)] + public static string Base64UrlDecode([MaxLength(MaxInputLength)] string s) + { + return Base64UrlEncoder.Decode(s); + } + /// /// Encodes a byte array into a base58 . /// diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 627a9fdd3c..26e00a3c2d 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -41,7 +41,7 @@ public void TestSetup() _nativeStates = new Dictionary { {"ContractManagement", """{"id":-1,"updatecounter":0,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getContractById","parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getContractHashes","parameters":[],"returntype":"InteropInterface","offset":35,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":42,"safe":true},{"name":"hasMethod","parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","offset":49,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":63,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":70,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}""" }, - {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":56,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":63,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":70,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":77,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":84,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":91,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":98,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":112,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":119,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":126,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":133,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":140,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2681632925},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"base64UrlDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"base64UrlEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":63,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":70,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":77,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":84,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":91,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":98,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":112,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":126,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":133,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":140,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":147,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":154,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"CryptoLib", """{"id":-3,"updatecounter":0,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"bls12381Add","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","offset":0,"safe":true},{"name":"bls12381Deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","offset":7,"safe":true},{"name":"bls12381Equal","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","offset":14,"safe":true},{"name":"bls12381Mul","parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"bls12381Pairing","parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","offset":28,"safe":true},{"name":"bls12381Serialize","parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","offset":35,"safe":true},{"name":"keccak256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"murmur32","parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","offset":49,"safe":true},{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":63,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","offset":70,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"LedgerContract", """{"id":-4,"updatecounter":0,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"LedgerContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"currentHash","parameters":[],"returntype":"Hash256","offset":0,"safe":true},{"name":"currentIndex","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getBlock","parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","offset":14,"safe":true},{"name":"getTransaction","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":21,"safe":true},{"name":"getTransactionFromBlock","parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getTransactionHeight","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":35,"safe":true},{"name":"getTransactionSigners","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":42,"safe":true},{"name":"getTransactionVMState","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":49,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, {"NeoToken", """{"id":-5,"updatecounter":0,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getAccountState","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","offset":14,"safe":true},{"name":"getAllCandidates","parameters":[],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"getCandidateVote","parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","offset":28,"safe":true},{"name":"getCandidates","parameters":[],"returntype":"Array","offset":35,"safe":true},{"name":"getCommittee","parameters":[],"returntype":"Array","offset":42,"safe":true},{"name":"getCommitteeAddress","parameters":[],"returntype":"Hash160","offset":49,"safe":true},{"name":"getGasPerBlock","parameters":[],"returntype":"Integer","offset":56,"safe":true},{"name":"getNextBlockValidators","parameters":[],"returntype":"Array","offset":63,"safe":true},{"name":"getRegisterPrice","parameters":[],"returntype":"Integer","offset":70,"safe":true},{"name":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":77,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":84,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":91,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":98,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":105,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":112,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":126,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":133,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs index 1dffb3d384..f9761bee07 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -406,5 +406,25 @@ public void TestRuntime_Deserialize() Assert.AreEqual(engine.ResultStack.Pop().GetInteger(), 100); Assert.AreEqual(engine.ResultStack.Pop().GetString(), "test"); } + + [TestMethod] + public void TestBase64Url() + { + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + using (var script = new ScriptBuilder()) + { + // Test encoding + script.EmitDynamicCall(NativeContract.StdLib.Hash, "base64UrlEncode", "Subject=test@example.com&Issuer=https://example.com"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "base64UrlDecode", "U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t"); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + Assert.AreEqual("Subject=test@example.com&Issuer=https://example.com", engine.ResultStack.Pop()); + Assert.AreEqual("U3ViamVjdD10ZXN0QGV4YW1wbGUuY29tJklzc3Vlcj1odHRwczovL2V4YW1wbGUuY29t", engine.ResultStack.Pop().GetString()); + } + } } } From 7dba1303c9b3d7b7f8b6a470e7d185d8d0903302 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 6 Nov 2024 12:42:42 +0800 Subject: [PATCH 41/42] format --- src/Neo/SmartContract/Native/RoleManagement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index ecedba4c86..084d6fdd36 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -78,7 +78,7 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node list.AddRange(nodes); list.Sort(); engine.SnapshotCache.Add(key, new StorageItem(list)); - + if (engine.IsHardforkEnabled(Hardfork.HF_Echidna)) { var oldNodes = new VM.Types.Array(engine.ReferenceCounter, GetDesignatedByRole(engine.Snapshot, role, index - 1).Select(u => (ByteString)u.EncodePoint(true))); From 42f0efdd090ba925251dc5098044aa42490a613e Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Wed, 6 Nov 2024 02:35:32 -0500 Subject: [PATCH 42/42] [`Fix`] StackItem.GetHashCode for CompoundType (#3549) * Fixed #3544 * Fixed SubItems of CompoundType * Reused `Unsafe.HashBytes` for hashing function in compoundtype * Fixed test * simple hashcode for compoundtype * Test circular reference * Fixed compoundType * Add one more test case * Fixed test * changed to use subclass * add comment * Add more to GetHashCode for CompoundType * Clean code --------- Co-authored-by: Jimmy Co-authored-by: Fernando Diaz Toledano Co-authored-by: NGD Admin <154295625+NGDAdmin@users.noreply.github.com> --- src/Neo.VM/Types/CompoundType.cs | 26 +++++++++++++++-- tests/Neo.VM.Tests/UT_StackItem.cs | 45 ++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/Neo.VM/Types/CompoundType.cs b/src/Neo.VM/Types/CompoundType.cs index 6b0da819ec..33f670bfcb 100644 --- a/src/Neo.VM/Types/CompoundType.cs +++ b/src/Neo.VM/Types/CompoundType.cs @@ -60,12 +60,32 @@ public sealed override bool GetBoolean() } /// - /// The operation is not supported. Always throw . + /// + /// This method provides a hash code for the based on its item's span. + /// It is used for efficient storage and retrieval in hash-based collections. + /// + /// Use this method when you need a hash code for a . /// - /// This method always throws the exception. + /// The hash code for the . public override int GetHashCode() { - throw new NotSupportedException(); + var h = new HashCode(); + h.Add(Count); + h.Add(Type); + foreach (var item in SubItems) + { + // This isn't prefect and leaves somethings unsolved. + if (item is CompoundType cItem) + { + h.Add(cItem.Count); + h.Add(cItem.Type); + } + else + { + h.Add(item.GetHashCode()); + } + } + return h.ToHashCode(); } public override string ToString() diff --git a/tests/Neo.VM.Tests/UT_StackItem.cs b/tests/Neo.VM.Tests/UT_StackItem.cs index 6982f2463a..61f6de74a5 100644 --- a/tests/Neo.VM.Tests/UT_StackItem.cs +++ b/tests/Neo.VM.Tests/UT_StackItem.cs @@ -19,6 +19,21 @@ namespace Neo.Test [TestClass] public class UT_StackItem { + [TestMethod] + public void TestCircularReference() + { + var itemA = new Struct { true, false }; + var itemB = new Struct { true, false }; + var itemC = new Struct { false, false }; + + itemA[1] = itemA; + itemB[1] = itemB; + itemC[1] = itemC; + + Assert.AreEqual(itemA.GetHashCode(), itemB.GetHashCode()); + Assert.AreNotEqual(itemA.GetHashCode(), itemC.GetHashCode()); + } + [TestMethod] public void TestHashCode() { @@ -62,17 +77,35 @@ public void TestHashCode() Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); - itemA = new VM.Types.Array(); + itemA = new Array { true, false, 0 }; + itemB = new Array { true, false, 0 }; + itemC = new Array { true, false, 1 }; + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); + + itemA = new Struct { true, false, 0 }; + itemB = new Struct { true, false, 0 }; + itemC = new Struct { true, false, 1 }; - Assert.ThrowsException(() => itemA.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); - itemA = new Struct(); + itemA = new Map { [true] = false, [0] = 1 }; + itemB = new Map { [true] = false, [0] = 1 }; + itemC = new Map { [true] = false, [0] = 2 }; - Assert.ThrowsException(() => itemA.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); - itemA = new Map(); + // Test CompoundType GetHashCode for subitems + var junk = new Array { true, false, 0 }; + itemA = new Map { [true] = junk, [0] = junk }; + itemB = new Map { [true] = junk, [0] = junk }; + itemC = new Map { [true] = junk, [0] = 2 }; - Assert.ThrowsException(() => itemA.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); itemA = new InteropInterface(123); itemB = new InteropInterface(123);