diff --git a/.editorconfig b/.editorconfig index c8247e5496..ccbd29fd03 100644 --- a/.editorconfig +++ b/.editorconfig @@ -48,6 +48,11 @@ indent_size = 2 end_of_line = lf indent_size = 2 +# YAML files +[*.yml] +end_of_line = lf +indent_size = 2 + # Dotnet code style settings: [*.{cs,vb}] # Member can be made 'readonly' diff --git a/.github/workflows/pkgs-delete.yml b/.github/workflows/pkgs-delete.yml index 2dc580953d..75bbd9455e 100644 --- a/.github/workflows/pkgs-delete.yml +++ b/.github/workflows/pkgs-delete.yml @@ -61,78 +61,53 @@ jobs: shell: python - delete-git-pkgs: - name: Delete Old Nuget Packages + delete-git-docker-pkgs: + name: Delete Old Docker Images runs-on: ubuntu-latest steps: - - name: Delete Neo.Cryptography.BLS12_381 Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.Cryptography.BLS12_381 - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Delete Neo.VM Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.VM - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Delete Neo.Json Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.Json - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Delete Neo.IO Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.IO - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Delete Neo Package (nuget) - uses: actions/delete-package-versions@v4 - with: - package-name: Neo - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - name: Delete Neo Package (docker) uses: actions/delete-package-versions@v4 + continue-on-error: true with: package-name: Neo package-type: docker min-versions-to-keep: 1 - delete-only-pre-release-versions: "true" token: "${{ secrets.GITHUB_TOKEN }}" - - name: Delete Neo.ConsoleService Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.ConsoleService - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" + delete-git-nuget-pkgs: + name: Delete Old Nuget Packages + strategy: + matrix: + pkgs: + - "Neo.Plugins.StatesDumper" + - "Neo.Plugins.StateService" + - "Neo.Plugins.Storage.LevelDBStore" + - "Neo.Plugins.Storage.RocksDBStore" + - "Neo.Plugins.StorageDumper" + - "Neo.Plugins.TokensTracker" + - "Neo.Wallets.SQLite" + - "Neo.Consensus.DBFT" + - "Neo.ConsoleService" + - "Neo.Cryptography.MPT" + - "Neo.Extensions" + - "Neo.Network.RPC.RpcClient" + - "Neo.Plugins.ApplicationLogs" + - "Neo.Plugins.OracleService" + - "Neo.Plugins.RpcServer" + - "Neo.Cryptography.BLS12_381" + - "Neo.VM" + - "Neo.Json" + - "Neo.IO" + - "Neo" + runs-on: ubuntu-latest - - name: Delete Neo.Extensions Package + steps: + - name: Delete ${{ matrix.pkgs }} Package uses: actions/delete-package-versions@v4 + continue-on-error: true with: - package-name: Neo.Extensions + package-name: ${{ matrix.pkgs }} package-type: nuget min-versions-to-keep: 3 delete-only-pre-release-versions: "true" 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); } } 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 028dff9460..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/TestArray.cs b/benchmarks/Neo.VM.Benchmarks/TestArray.cs index 62fedfed11..0799040e52 100644 --- a/benchmarks/Neo.VM.Benchmarks/TestArray.cs +++ b/benchmarks/Neo.VM.Benchmarks/TestArray.cs @@ -53,11 +53,11 @@ 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(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..f7dc2fcb64 100644 --- a/benchmarks/Neo.VM.Benchmarks/TestStruct.cs +++ b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs @@ -27,11 +27,11 @@ 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(ReferenceCounter? referenceCounter, IEnumerable? fields = null) + public TestStruct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) { } 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..91c660ff48 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs @@ -0,0 +1,216 @@ +// 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 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 92% rename from benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs rename to benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs index 3685e45f12..1d9c267f5f 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() { @@ -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.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 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.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 @@ - + 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..8a3167b4c8 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. @@ -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/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/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)); } /// 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) 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/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..903613c228 100644 --- a/src/Neo.VM/Types/Array.cs +++ b/src/Neo.VM/Types/Array.cs @@ -62,11 +62,11 @@ 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(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..344147b5ed 100644 --- a/src/Neo.VM/Types/Struct.cs +++ b/src/Neo.VM/Types/Struct.cs @@ -31,11 +31,11 @@ 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(ReferenceCounter? referenceCounter, IEnumerable? fields = null) + public Struct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) { } diff --git a/src/Neo/Builders/AndConditionBuilder.cs b/src/Neo/Builders/AndConditionBuilder.cs new file mode 100644 index 0000000000..a75a52227a --- /dev/null +++ b/src/Neo/Builders/AndConditionBuilder.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AndConditionBuilder.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.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class AndConditionBuilder + { + private readonly AndCondition _condition = new() { Expressions = [] }; + + private AndConditionBuilder() { } + + public static AndConditionBuilder CreateEmpty() + { + return new AndConditionBuilder(); + } + + public AndConditionBuilder And(Action config) + { + var acb = new AndConditionBuilder(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public AndConditionBuilder Or(Action config) + { + var ocb = OrConditionBuilder.CreateEmpty(); + config(ocb); + + _condition.Expressions = [.. _condition.Expressions, ocb.Build()]; + + return this; + } + + public AndConditionBuilder Boolean(bool expression) + { + _condition.Expressions = [.. _condition.Expressions, new BooleanCondition { Expression = expression }]; + return this; + } + + public AndConditionBuilder CalledByContract(UInt160 hash) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByContractCondition { Hash = hash }]; + return this; + } + + public AndConditionBuilder CalledByEntry() + { + _condition.Expressions = [.. _condition.Expressions, new CalledByEntryCondition()]; + return this; + } + + public AndConditionBuilder CalledByGroup(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByGroupCondition { Group = publicKey }]; + return this; + } + + public AndConditionBuilder Group(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new GroupCondition() { Group = publicKey }]; + return this; + } + + public AndConditionBuilder ScriptHash(UInt160 scriptHash) + { + _condition.Expressions = [.. _condition.Expressions, new ScriptHashCondition() { Hash = scriptHash }]; + return this; + } + + public AndCondition Build() + { + return _condition; + } + } +} diff --git a/src/Neo/Builders/OrConditionBuilder.cs b/src/Neo/Builders/OrConditionBuilder.cs new file mode 100644 index 0000000000..6b8ca2a0e1 --- /dev/null +++ b/src/Neo/Builders/OrConditionBuilder.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OrConditionBuilder.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.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class OrConditionBuilder + { + private readonly OrCondition _condition = new() { Expressions = [] }; + + private OrConditionBuilder() { } + + public static OrConditionBuilder CreateEmpty() + { + return new OrConditionBuilder(); + } + + public OrConditionBuilder And(Action config) + { + var acb = AndConditionBuilder.CreateEmpty(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public OrConditionBuilder Or(Action config) + { + var acb = new OrConditionBuilder(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public OrConditionBuilder Boolean(bool expression) + { + _condition.Expressions = [.. _condition.Expressions, new BooleanCondition { Expression = expression }]; + return this; + } + + public OrConditionBuilder CalledByContract(UInt160 hash) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByContractCondition { Hash = hash }]; + return this; + } + + public OrConditionBuilder CalledByEntry() + { + _condition.Expressions = [.. _condition.Expressions, new CalledByEntryCondition()]; + return this; + } + + public OrConditionBuilder CalledByGroup(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByGroupCondition { Group = publicKey }]; + return this; + } + + public OrConditionBuilder Group(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new GroupCondition() { Group = publicKey }]; + return this; + } + + public OrConditionBuilder ScriptHash(UInt160 scriptHash) + { + _condition.Expressions = [.. _condition.Expressions, new ScriptHashCondition() { Hash = scriptHash }]; + return this; + } + + public OrCondition Build() + { + return _condition; + } + } +} diff --git a/src/Neo/Builders/SignerBuilder.cs b/src/Neo/Builders/SignerBuilder.cs new file mode 100644 index 0000000000..76b70f1efe --- /dev/null +++ b/src/Neo/Builders/SignerBuilder.cs @@ -0,0 +1,73 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SignerBuilder.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.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.Builders +{ + public sealed class SignerBuilder + { + private readonly Signer _signer = new Signer() + { + Account = UInt160.Zero, + AllowedContracts = [], + AllowedGroups = [], + Rules = [], + Scopes = WitnessScope.None, + }; + + private SignerBuilder() { } + + public static SignerBuilder CreateEmpty() + { + return new SignerBuilder(); + } + + public SignerBuilder Account(UInt160 scriptHash) + { + _signer.Account = scriptHash; + return this; + } + + public SignerBuilder AllowContract(UInt160 contractHash) + { + _signer.AllowedContracts = [.. _signer.AllowedContracts, contractHash]; + return this; + } + + public SignerBuilder AllowGroup(ECPoint publicKey) + { + _signer.AllowedGroups = [.. _signer.AllowedGroups, publicKey]; + return this; + } + + public SignerBuilder AddWitnessScope(WitnessScope scope) + { + _signer.Scopes |= scope; + return this; + } + + public SignerBuilder AddWitnessRule(WitnessRuleAction action, Action config) + { + var rb = WitnessRuleBuilder.Create(action); + config(rb); + _signer.Rules = [.. _signer.Rules, rb.Build()]; + return this; + } + + public Signer Build() + { + return _signer; + } + } +} diff --git a/src/Neo/Builders/TransactionAttributesBuilder.cs b/src/Neo/Builders/TransactionAttributesBuilder.cs new file mode 100644 index 0000000000..9eb4ae99e3 --- /dev/null +++ b/src/Neo/Builders/TransactionAttributesBuilder.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttributesBuilder.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.Network.P2P.Payloads; +using System; +using System.Linq; + +namespace Neo.Builders +{ + public sealed class TransactionAttributesBuilder + { + private TransactionAttribute[] _attributes = []; + + private TransactionAttributesBuilder() { } + + public static TransactionAttributesBuilder CreateEmpty() + { + return new TransactionAttributesBuilder(); + } + + public TransactionAttributesBuilder AddConflict(Action config) + { + var conflicts = new Conflicts(); + config(conflicts); + _attributes = [.. _attributes, conflicts]; + return this; + } + + public TransactionAttributesBuilder AddOracleResponse(Action config) + { + var oracleResponse = new OracleResponse(); + config(oracleResponse); + _attributes = [.. _attributes, oracleResponse]; + return this; + } + + public TransactionAttributesBuilder AddHighPriority() + { + if (_attributes.Any(a => a is HighPriorityAttribute)) + throw new InvalidOperationException("HighPriority already exists in the attributes."); + + var highPriority = new HighPriorityAttribute(); + _attributes = [.. _attributes, highPriority]; + return this; + } + + public TransactionAttributesBuilder AddNotValidBefore(uint block) + { + if (_attributes.Any(a => a is NotValidBefore b && b.Height == block)) + throw new InvalidOperationException($"Block {block} already exists in the attributes."); + + var validUntilBlock = new NotValidBefore() + { + Height = block + }; + + _attributes = [.. _attributes, validUntilBlock]; + return this; + } + + public TransactionAttribute[] Build() + { + return _attributes; + } + } +} diff --git a/src/Neo/Builders/TransactionBuilder.cs b/src/Neo/Builders/TransactionBuilder.cs new file mode 100644 index 0000000000..2a1a7a9424 --- /dev/null +++ b/src/Neo/Builders/TransactionBuilder.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionBuilder.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.Network.P2P.Payloads; +using Neo.VM; +using System; + +namespace Neo.Builders +{ + public sealed class TransactionBuilder + { + private readonly Transaction _tx = new() + { + Script = new[] { (byte)OpCode.RET }, + Attributes = [], + Signers = [], + Witnesses = [], + }; + + private TransactionBuilder() { } + + public static TransactionBuilder CreateEmpty() + { + return new TransactionBuilder(); + } + + public TransactionBuilder Version(byte version) + { + _tx.Version = version; + return this; + } + + public TransactionBuilder Nonce(uint nonce) + { + _tx.Nonce = nonce; + return this; + } + + public TransactionBuilder SystemFee(uint systemFee) + { + _tx.SystemFee = systemFee; + return this; + } + + public TransactionBuilder NetworkFee(uint networkFee) + { + _tx.NetworkFee = networkFee; + return this; + } + + public TransactionBuilder ValidUntil(uint blockIndex) + { + _tx.ValidUntilBlock = blockIndex; + return this; + } + + public TransactionBuilder AttachSystem(Action config) + { + using var sb = new ScriptBuilder(); + config(sb); + _tx.Script = sb.ToArray(); + return this; + } + + public TransactionBuilder AttachSystem(byte[] script) + { + _tx.Script = script; + return this; + } + + public TransactionBuilder AddAttributes(Action config) + { + var ab = TransactionAttributesBuilder.CreateEmpty(); + config(ab); + _tx.Attributes = ab.Build(); + return this; + } + + public TransactionBuilder AddWitness(Action config) + { + var wb = WitnessBuilder.CreateEmpty(); + config(wb); + _tx.Witnesses = [.. _tx.Witnesses, wb.Build()]; + return this; + } + + public TransactionBuilder AddWitness(Action config) + { + var wb = WitnessBuilder.CreateEmpty(); + config(wb, _tx); + _tx.Witnesses = [.. _tx.Witnesses, wb.Build()]; + return this; + } + + public TransactionBuilder AddSigner(Action config) + { + var wb = SignerBuilder.CreateEmpty(); + config(wb, _tx); + _tx.Signers = [.. _tx.Signers, wb.Build()]; + return this; + } + + public Transaction Build() + { + return _tx; + } + } +} diff --git a/src/Neo/Builders/WitnessBuilder.cs b/src/Neo/Builders/WitnessBuilder.cs new file mode 100644 index 0000000000..e555195688 --- /dev/null +++ b/src/Neo/Builders/WitnessBuilder.cs @@ -0,0 +1,79 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessBuilder.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.Network.P2P.Payloads; +using Neo.VM; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessBuilder + { + private byte[] _invocationScript = []; + private byte[] _verificationScript = []; + + private WitnessBuilder() { } + + public static WitnessBuilder CreateEmpty() + { + return new WitnessBuilder(); + } + + public WitnessBuilder AddInvocation(Action config) + { + if (_invocationScript.Length > 0) + throw new InvalidOperationException("Invocation script already exists."); + + using var sb = new ScriptBuilder(); + config(sb); + _invocationScript = sb.ToArray(); + return this; + } + + public WitnessBuilder AddInvocation(byte[] bytes) + { + if (_invocationScript.Length > 0) + throw new InvalidOperationException("Invocation script already exists."); + + _invocationScript = bytes; + return this; + } + + public WitnessBuilder AddVerification(Action config) + { + if (_verificationScript.Length > 0) + throw new InvalidOperationException("Verification script already exists."); + + using var sb = new ScriptBuilder(); + config(sb); + _verificationScript = sb.ToArray(); + return this; + } + + public WitnessBuilder AddVerification(byte[] bytes) + { + if (_verificationScript.Length > 0) + throw new InvalidOperationException("Verification script already exists."); + + _verificationScript = bytes; + return this; + } + + public Witness Build() + { + return new Witness() + { + InvocationScript = _invocationScript, + VerificationScript = _verificationScript, + }; + } + } +} diff --git a/src/Neo/Builders/WitnessConditionBuilder.cs b/src/Neo/Builders/WitnessConditionBuilder.cs new file mode 100644 index 0000000000..41cfc05163 --- /dev/null +++ b/src/Neo/Builders/WitnessConditionBuilder.cs @@ -0,0 +1,131 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessConditionBuilder.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.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessConditionBuilder + { + WitnessCondition _condition; + + private WitnessConditionBuilder() { } + + private WitnessConditionBuilder(WitnessCondition condition) + { + _condition = condition; + } + + public static WitnessConditionBuilder Create() + { + return new WitnessConditionBuilder(); + } + + public WitnessConditionBuilder And(Action config) + { + var acb = AndConditionBuilder.CreateEmpty(); + config(acb); + + _condition = acb.Build(); + + return this; + } + + public WitnessConditionBuilder Boolean(bool expression) + { + var condition = new BooleanCondition() { Expression = expression }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByContract(UInt160 hash) + { + var condition = new CalledByContractCondition() { Hash = hash }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByEntry() + { + var condition = new CalledByEntryCondition(); + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByGroup(ECPoint publicKey) + { + var condition = new CalledByGroupCondition() { Group = publicKey }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Group(ECPoint publicKey) + { + var condition = new GroupCondition() { Group = publicKey }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Not(Action config) + { + var wcb = new WitnessConditionBuilder(); + config(wcb); + + var condition = new NotCondition() + { + Expression = wcb.Build() + }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Or(Action config) + { + var ocb = OrConditionBuilder.CreateEmpty(); + config(ocb); + + _condition = ocb.Build(); + + return this; + } + + public WitnessConditionBuilder ScriptHash(UInt160 scriptHash) + { + var condition = new ScriptHashCondition() { Hash = scriptHash }; + + _condition = condition; + + return this; + } + + public WitnessCondition Build() + { + if (_condition is null) + return new BooleanCondition() { Expression = true }; + + return _condition; + } + } +} diff --git a/src/Neo/Builders/WitnessRuleBuilder.cs b/src/Neo/Builders/WitnessRuleBuilder.cs new file mode 100644 index 0000000000..7c05a524d6 --- /dev/null +++ b/src/Neo/Builders/WitnessRuleBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessRuleBuilder.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.Network.P2P.Payloads; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessRuleBuilder + { + private readonly WitnessRule _rule = new(); + + private WitnessRuleBuilder(WitnessRuleAction action) + { + _rule.Action = action; + } + + public static WitnessRuleBuilder Create(WitnessRuleAction action) + { + return new WitnessRuleBuilder(action); + } + + public WitnessRuleBuilder AddCondition(Action config) + { + var cb = WitnessConditionBuilder.Create(); + config(cb); + _rule.Condition = cb.Build(); + return this; + } + + public WitnessRule Build() + { + return _rule; + } + } +} 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/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/Ledger/HeaderCache.cs b/src/Neo/Ledger/HeaderCache.cs index ac2208011f..6cc8acf6a7 100644 --- a/src/Neo/Ledger/HeaderCache.cs +++ b/src/Neo/Ledger/HeaderCache.cs @@ -55,12 +55,26 @@ public Header this[uint index] /// /// Gets the number of elements in the cache. /// - public int Count => headers.Count; + public int Count + { + get + { + readerWriterLock.EnterReadLock(); + try + { + return headers.Count; + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + } /// /// Indicates whether the cache is full. /// - public bool Full => headers.Count >= 10000; + public bool Full => Count >= 10000; /// /// Gets the last in the cache. Or if the cache is empty. 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/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..5fc1ba4381 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; @@ -66,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 b06fc922a8..c28d3dbb94 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; @@ -66,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/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..40496dfe74 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; @@ -190,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 b922674d91..b5139f1c7c 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; @@ -458,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/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/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/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/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/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/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/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..85309b4246 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; @@ -580,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); @@ -602,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 }; } @@ -619,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 b17296b42d..9e3eb2527a 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; @@ -75,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 4cc4c39c0f..69dd55fdbb 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; @@ -79,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/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/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)) { diff --git a/src/Neo/VM/Helper.cs b/src/Neo/VM/Helper.cs index c6423ac1f1..6964747473 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); @@ -319,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) @@ -352,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) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 181fc7506a..e7e0d6748e 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; @@ -94,7 +95,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) 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/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/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/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/src/Plugins/RpcServer/RpcError.cs b/src/Plugins/RpcServer/RpcError.cs index 667a3906d4..7f383025a5 100644 --- a/src/Plugins/RpcServer/RpcError.cs +++ b/src/Plugins/RpcServer/RpcError.cs @@ -55,7 +55,7 @@ public class RpcError public static readonly RpcError AlreadyInPool = new(-503, "Already in pool"); public static readonly RpcError InsufficientNetworkFee = new(-504, "Insufficient network fee"); public static readonly RpcError PolicyFailed = new(-505, "Policy check failed"); - public static readonly RpcError InvalidScript = new(-509, "Invalid transaction script"); + public static readonly RpcError InvalidScript = new(-506, "Invalid transaction script"); public static readonly RpcError InvalidAttribute = new(-507, "Invalid transaction attribute"); public static readonly RpcError InvalidSignature = new(-508, "Invalid signature"); public static readonly RpcError InvalidSize = new(-509, "Invalid inventory size"); 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/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/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.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.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.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); + } } } 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/Builders/UT_SignerBuilder.cs b/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs new file mode 100644 index 0000000000..7d79f7428c --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_SignerBuilder.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.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_SignerBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var sb = SignerBuilder.CreateEmpty(); + Assert.IsNotNull(sb); + } + + [TestMethod] + public void TestAccount() + { + var signer = SignerBuilder.CreateEmpty() + .Account(UInt160.Zero) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(UInt160.Zero, signer.Account); + } + + [TestMethod] + public void TestAllowContract() + { + var signer = SignerBuilder.CreateEmpty() + .AllowContract(UInt160.Zero) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.AllowedContracts.Length); + Assert.AreEqual(UInt160.Zero, signer.AllowedContracts[0]); + } + + [TestMethod] + public void TestAllowGroup() + { + var myPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var signer = SignerBuilder.CreateEmpty() + .AllowGroup(myPublicKey) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.AllowedGroups.Length); + Assert.AreEqual(myPublicKey, signer.AllowedGroups[0]); + } + + [TestMethod] + public void TestAddWitnessScope() + { + var signer = SignerBuilder.CreateEmpty() + .AddWitnessScope(WitnessScope.Global) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(WitnessScope.Global, signer.Scopes); + } + + [TestMethod] + public void TestAddWitnessRule() + { + var signer = SignerBuilder.CreateEmpty() + .AddWitnessRule(WitnessRuleAction.Allow, rb => + { + rb.AddCondition(cb => + { + cb.ScriptHash(UInt160.Zero); + }); + }) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.Rules.Length); + Assert.AreEqual(WitnessRuleAction.Allow, signer.Rules[0].Action); + Assert.IsInstanceOfType(signer.Rules[0].Condition); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs b/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs new file mode 100644 index 0000000000..d9ade210ea --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionAttributesBuilder.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.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_TransactionAttributesBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var builder = TransactionAttributesBuilder.CreateEmpty(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestConflict() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddConflict(c => c.Hash = UInt256.Zero) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(UInt256.Zero, ((Conflicts)attr[0]).Hash); + } + + [TestMethod] + public void TestOracleResponse() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddOracleResponse(c => + { + c.Id = 1ul; + c.Code = OracleResponseCode.Success; + c.Result = new byte[] { 0x01, 0x02, 0x03 }; + }) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(1ul, ((OracleResponse)attr[0]).Id); + Assert.AreEqual(OracleResponseCode.Success, ((OracleResponse)attr[0]).Code); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, ((OracleResponse)attr[0]).Result.ToArray()); + } + + [TestMethod] + public void TestHighPriority() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddHighPriority() + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + } + + [TestMethod] + public void TestNotValidBefore() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddNotValidBefore(10u) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(10u, ((NotValidBefore)attr[0]).Height); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs b/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs new file mode 100644 index 0000000000..17eb5796ad --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs @@ -0,0 +1,201 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionBuilder.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.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_TransactionBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var builder = TransactionBuilder.CreateEmpty(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestEmptyTransaction() + { + var tx = TransactionBuilder.CreateEmpty() + .Build(); + + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestVersion() + { + byte expectedVersion = 1; + var tx = TransactionBuilder.CreateEmpty() + .Version(expectedVersion) + .Build(); + + Assert.AreEqual(expectedVersion, tx.Version); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestNonce() + { + var expectedNonce = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .Nonce(expectedNonce) + .Build(); + + Assert.AreEqual(expectedNonce, tx.Nonce); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestSystemFee() + { + var expectedSystemFee = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .SystemFee(expectedSystemFee) + .Build(); + + Assert.AreEqual(expectedSystemFee, tx.SystemFee); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestNetworkFee() + { + var expectedNetworkFee = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .NetworkFee(expectedNetworkFee) + .Build(); + + Assert.AreEqual(expectedNetworkFee, tx.NetworkFee); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestValidUntilBlock() + { + var expectedValidUntilBlock = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .ValidUntil(expectedValidUntilBlock) + .Build(); + + Assert.AreEqual(expectedValidUntilBlock, tx.ValidUntilBlock); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestAttachScript() + { + byte[] expectedScript = [(byte)OpCode.NOP]; + var tx = TransactionBuilder.CreateEmpty() + .AttachSystem(sb => sb.Emit(OpCode.NOP)) + .Build(); + + CollectionAssert.AreEqual(expectedScript, tx.Script.ToArray()); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestTransactionAttributes() + { + var tx = TransactionBuilder.CreateEmpty() + .AddAttributes(ab => ab.AddHighPriority()) + .Build(); + + Assert.AreEqual(1, tx.Attributes.Length); + Assert.IsInstanceOfType(tx.Attributes[0]); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestWitness() + { + var tx = TransactionBuilder.CreateEmpty() + .AddWitness(wb => + { + // Contract signature + wb.AddInvocation([]); + wb.AddVerification([]); + }) + .Build(); + + Assert.AreEqual(1, tx.Witnesses.Length); + Assert.AreEqual(0, tx.Witnesses[0].InvocationScript.Length); + Assert.AreEqual(0, tx.Witnesses[0].VerificationScript.Length); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestWitnessWithTransactionParameter() + { + var tx = TransactionBuilder.CreateEmpty() + .AddWitness((wb, tx) => + { + // Checks to make sure the transaction is hash able + // NOTE: transaction can be used for signing here + Assert.IsNotNull(tx.Hash); + }) + .Build(); + } + + [TestMethod] + public void TestSigner() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + + var tx = TransactionBuilder.CreateEmpty() + .AddSigner((sb, tx) => + { + sb.Account(expectedContractHash); + sb.AllowContract(expectedContractHash); + sb.AllowGroup(expectedPublicKey); + sb.AddWitnessScope(WitnessScope.WitnessRules); + sb.AddWitnessRule(WitnessRuleAction.Deny, wrb => + { + wrb.AddCondition(cb => + { + cb.ScriptHash(expectedContractHash); + }); + }); + }) + .Build(); + + Assert.IsNotNull(tx.Hash); + Assert.AreEqual(1, tx.Signers.Length); + Assert.AreEqual(expectedContractHash, tx.Signers[0].Account); + Assert.AreEqual(1, tx.Signers[0].AllowedContracts.Length); + Assert.AreEqual(expectedContractHash, tx.Signers[0].AllowedContracts[0]); + Assert.AreEqual(1, tx.Signers[0].AllowedGroups.Length); + Assert.AreEqual(expectedPublicKey, tx.Signers[0].AllowedGroups[0]); + Assert.AreEqual(WitnessScope.WitnessRules, tx.Signers[0].Scopes); + Assert.AreEqual(1, tx.Signers[0].Rules.Length); + Assert.AreEqual(WitnessRuleAction.Deny, tx.Signers[0].Rules[0].Action); + Assert.IsNotNull(tx.Signers[0].Rules[0].Condition); + Assert.IsInstanceOfType(tx.Signers[0].Rules[0].Condition); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs new file mode 100644 index 0000000000..1b73a69124 --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessBuilder.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.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var wb = WitnessBuilder.CreateEmpty(); + Assert.IsNotNull(wb); + } + + [TestMethod] + public void TestAddInvocationWithScriptBuilder() + { + var witness = WitnessBuilder.CreateEmpty() + .AddInvocation(sb => + { + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.InvocationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x21, 0x21 }, witness.InvocationScript.ToArray()); + } + + [TestMethod] + public void TestAddInvocation() + { + var witness = WitnessBuilder.CreateEmpty() + .AddInvocation(new byte[] { 0x01, 0x02, 0x03 }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.InvocationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, witness.InvocationScript.ToArray()); + } + + [TestMethod] + public void TestAddVerificationWithScriptBuilder() + { + var witness = WitnessBuilder.CreateEmpty() + .AddVerification(sb => + { + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.VerificationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x21, 0x21 }, witness.VerificationScript.ToArray()); + } + + [TestMethod] + public void TestAddVerification() + { + var witness = WitnessBuilder.CreateEmpty() + .AddVerification(new byte[] { 0x01, 0x02, 0x03 }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.VerificationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, witness.VerificationScript.ToArray()); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs new file mode 100644 index 0000000000..4378aa2bfa --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs @@ -0,0 +1,220 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessConditionBuilder.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.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessConditionBuilder + { + [TestMethod] + public void TestAndCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .And(and => + { + and.CalledByContract(expectedContractHash); + and.CalledByGroup(expectedPublicKey); + }) + .Build(); + + var actual = condition as AndCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(2, actual.Expressions.Length); + Assert.IsInstanceOfType(actual.Expressions[0]); + Assert.IsInstanceOfType(actual.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actual.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actual.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestOrCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Or(or => + { + or.CalledByContract(expectedContractHash); + or.CalledByGroup(expectedPublicKey); + }) + .Build(); + + var actual = condition as OrCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(2, actual.Expressions.Length); + Assert.IsInstanceOfType(actual.Expressions[0]); + Assert.IsInstanceOfType(actual.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actual.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actual.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestBoolean() + { + var condition = WitnessConditionBuilder.Create() + .Boolean(true) + .Build(); + + var actual = condition as BooleanCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsTrue(actual.Expression); + } + + [TestMethod] + public void TestCalledByContract() + { + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .CalledByContract(expectedContractHash) + .Build(); + + var actual = condition as CalledByContractCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedContractHash, actual.Hash); + } + + [TestMethod] + public void TestCalledByEntry() + { + var condition = WitnessConditionBuilder.Create() + .CalledByEntry() + .Build(); + + var actual = condition as CalledByEntryCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + } + + [TestMethod] + public void TestCalledByGroup() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var condition = WitnessConditionBuilder.Create() + .CalledByGroup(expectedPublicKey) + .Build(); + + var actual = condition as CalledByGroupCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedPublicKey, actual.Group); + } + + [TestMethod] + public void TestGroup() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var condition = WitnessConditionBuilder.Create() + .Group(expectedPublicKey) + .Build(); + + var actual = condition as GroupCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedPublicKey, actual.Group); + } + + [TestMethod] + public void TestScriptHash() + { + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .ScriptHash(expectedContractHash) + .Build(); + + var actual = condition as ScriptHashCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedContractHash, actual.Hash); + } + + [TestMethod] + public void TestNotConditionWithAndCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Not(not => + { + not.And(and => + { + and.CalledByContract(expectedContractHash); + and.CalledByGroup(expectedPublicKey); + }); + }) + .Build(); + + var actual = condition as NotCondition; + var actualAndCondition = actual.Expression as AndCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsInstanceOfType(actual.Expression); + Assert.AreEqual(2, actualAndCondition.Expressions.Length); + Assert.IsInstanceOfType(actualAndCondition.Expressions[0]); + Assert.IsInstanceOfType(actualAndCondition.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actualAndCondition.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actualAndCondition.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestNotConditionWithOrCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Not(not => + { + not.Or(or => + { + or.CalledByContract(expectedContractHash); + or.CalledByGroup(expectedPublicKey); + }); + }) + .Build(); + + var actual = condition as NotCondition; + var actualOrCondition = actual.Expression as OrCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsInstanceOfType(actual.Expression); + Assert.AreEqual(2, actualOrCondition.Expressions.Length); + Assert.IsInstanceOfType(actualOrCondition.Expressions[0]); + Assert.IsInstanceOfType(actualOrCondition.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actualOrCondition.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actualOrCondition.Expressions[1] as CalledByGroupCondition).Group); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs new file mode 100644 index 0000000000..1b87e2c52f --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessRuleBuilder.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.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessRuleBuilder + { + [TestMethod] + public void TestCreate() + { + var builder = WitnessRuleBuilder.Create(WitnessRuleAction.Allow); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestCondition() + { + var rule = WitnessRuleBuilder.Create(WitnessRuleAction.Allow) + .AddCondition(wcb => + { + wcb.ScriptHash(UInt160.Zero); + }).Build(); + + Assert.IsNotNull(rule.Condition); + Assert.AreEqual(WitnessRuleAction.Allow, rule.Action); + Assert.IsInstanceOfType(rule.Condition); + Assert.AreEqual(UInt160.Zero, ((ScriptHashCondition)rule.Condition).Hash); + } + + [TestMethod] + public void TestCondition2() + { + var rule = WitnessRuleBuilder.Create(WitnessRuleAction.Allow) + .AddCondition(wcb => + { + wcb.And(and => + { + and.ScriptHash(UInt160.Zero); + }); + }).Build(); + + Assert.IsNotNull(rule.Condition); + Assert.AreEqual(WitnessRuleAction.Allow, rule.Action); + Assert.IsInstanceOfType(rule.Condition); + Assert.IsInstanceOfType((rule.Condition as AndCondition).Expressions[0]); + Assert.AreEqual(UInt160.Zero, ((rule.Condition as AndCondition).Expressions[0] as ScriptHashCondition).Hash); + } + } +} 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(); + } } } 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/Ledger/UT_HeaderCache.cs b/tests/Neo.UnitTests/Ledger/UT_HeaderCache.cs new file mode 100644 index 0000000000..71706fda32 --- /dev/null +++ b/tests/Neo.UnitTests/Ledger/UT_HeaderCache.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HeaderCache.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 FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.UnitTests.Ledger +{ + [TestClass] + public class UT_HeaderCache + { + [TestMethod] + public void TestHeaderCache() + { + var cache = new HeaderCache(); + var header = new Header(); + header.Index = 1; + cache.Add(header); + + var got = cache[1]; + got.Should().NotBeNull(); + got.Index.Should().Be(1); + + var count = cache.Count; + count.Should().Be(1); + + var full = cache.Full; + full.Should().BeFalse(); + + var last = cache.Last; + last.Should().NotBeNull(); + last.Index.Should().Be(1); + + got = cache[2]; + got.Should().BeNull(); + + // enumerate + var enumerator = cache.GetEnumerator(); + enumerator.MoveNext().Should().BeTrue(); + enumerator.Current.Index.Should().Be(1); + enumerator.MoveNext().Should().BeFalse(); + + var removed = cache.TryRemoveFirst(out header); + removed.Should().BeTrue(); + + count = cache.Count; + count.Should().Be(0); + + full = cache.Full; + full.Should().BeFalse(); + + last = cache.Last; + last.Should().BeNull(); + + got = cache[1]; + got.Should().BeNull(); + } + } +} 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)); 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/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] 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; 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() { 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 + } + ] + } + } + ] } ] } 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; }