From 9ce9c6fe1cdce05ef20fff0d6b3ae4b25811054f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E5=90=8C?= Date: Tue, 27 Aug 2024 06:21:18 +0800 Subject: [PATCH 1/5] Update UT RpcServer (#3460) * Update UT_RpcServer.Blockchain.cs * Update UT_RpcServer.Blockchain.cs * update * fixed bug * format * update * Update NativeContractExtensions.cs * update * Remove conflicting files * update * format --- src/Neo/Network/P2P/Peer.cs | 2 +- src/Plugins/RpcServer/RpcServer.Node.cs | 4 +- .../UT_RpcServer.Blockchain.cs | 61 +++++++++++++++++-- .../UT_RpcServer.Node.cs | 32 ++++++++++ .../Extensions/NativeContractExtensions.cs | 14 +++++ 5 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/Neo/Network/P2P/Peer.cs b/src/Neo/Network/P2P/Peer.cs index 074c5cf3ba..bf6b23901f 100644 --- a/src/Neo/Network/P2P/Peer.cs +++ b/src/Neo/Network/P2P/Peer.cs @@ -146,7 +146,7 @@ static Peer() /// Tries to add a set of peers to the immutable ImmutableHashSet of UnconnectedPeers. /// /// Peers that the method will try to add (union) to (with) UnconnectedPeers. - protected void AddPeers(IEnumerable peers) + protected internal void AddPeers(IEnumerable peers) { if (UnconnectedPeers.Count < UnconnectedMax) { diff --git a/src/Plugins/RpcServer/RpcServer.Node.cs b/src/Plugins/RpcServer/RpcServer.Node.cs index 716125e2cf..c1b5397c5b 100644 --- a/src/Plugins/RpcServer/RpcServer.Node.cs +++ b/src/Plugins/RpcServer/RpcServer.Node.cs @@ -24,13 +24,13 @@ namespace Neo.Plugins.RpcServer partial class RpcServer { [RpcMethod] - protected virtual JToken GetConnectionCount(JArray _params) + protected internal virtual JToken GetConnectionCount(JArray _params) { return localNode.ConnectedCount; } [RpcMethod] - protected virtual JToken GetPeers(JArray _params) + protected internal virtual JToken GetPeers(JArray _params) { JObject json = new(); json["unconnected"] = new JArray(localNode.GetUnconnectedPeers().Select(p => diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs index 2fbb2f6f3f..b04b5e33d4 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -11,7 +11,9 @@ using Akka.Actor; using Akka.Util.Internal; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -23,6 +25,8 @@ using Neo.UnitTests.Extensions; using System; using System.Linq; +using System.Security.Policy; +using static Neo.SmartContract.Native.NeoToken; namespace Neo.Plugins.RpcServer.Tests { @@ -61,6 +65,11 @@ public void TestGetBlockByHash() { Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); }); + + result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Hash), true); + var block3 = block.ToJson(UnitTests.TestProtocolSettings.Default); + block3["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + result.ToString().Should().Be(block3.ToString()); } [TestMethod] @@ -78,6 +87,11 @@ public void TestGetBlockByIndex() { Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); }); + + result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Index), true); + var block3 = block.ToJson(UnitTests.TestProtocolSettings.Default); + block3["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + result.ToString().Should().Be(block3.ToString()); } [TestMethod] @@ -120,6 +134,11 @@ public void TestGetBlockHeader() var header = block.Header.ToJson(_neoSystem.Settings); header["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; Assert.AreEqual(header.ToString(), result.ToString()); + + result = _rpcServer.GetBlockHeader(new BlockHashOrIndex(block.Hash), false); + var headerArr = Convert.FromBase64String(result.AsString()); + var header2 = headerArr.AsSerializable
(); + header2.ToJson(_neoSystem.Settings).ToString().Should().Be(block.Header.ToJson(_neoSystem.Settings).ToString()); } [TestMethod] @@ -129,9 +148,23 @@ public void TestGetContractState() var contractState = TestUtils.GetContract(); snapshot.AddContract(contractState.Hash, contractState); snapshot.Commit(); + var result = _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Hash)); + Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + result = _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Id)); Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + + var byId = _rpcServer.GetContractState(new ContractNameOrHashOrId(-1)); + var byName = _rpcServer.GetContractState(new ContractNameOrHashOrId("ContractManagement")); + byId.ToString().Should().Be(byName.ToString()); + + snapshot.DeleteContract(contractState.Hash); + snapshot.Commit(); + Action act = () => _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Hash)); + act.Should().Throw().WithMessage(RpcError.UnknownContract.Message); + act = () => _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Id)); + act.Should().Throw().WithMessage(RpcError.UnknownContract.Message); } [TestMethod] @@ -143,8 +176,10 @@ public void TestGetRawMemPool() _neoSystem.MemPool.TryAdd(tx, snapshot); var result = _rpcServer.GetRawMemPool(); - Assert.IsTrue(((JArray)result).Any(p => p.AsString() == tx.Hash.ToString())); + + result = _rpcServer.GetRawMemPool(true); + Assert.IsTrue(((JArray)result["verified"]).Any(p => p.AsString() == tx.Hash.ToString())); } [TestMethod] @@ -154,10 +189,14 @@ public void TestGetRawTransaction() var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); _neoSystem.MemPool.TryAdd(tx, snapshot); snapshot.Commit(); - var result = _rpcServer.GetRawTransaction(tx.Hash, true); + var result = _rpcServer.GetRawTransaction(tx.Hash, true); var json = Utility.TransactionToJson(tx, _neoSystem.Settings); Assert.AreEqual(json.ToString(), result.ToString()); + + result = _rpcServer.GetRawTransaction(tx.Hash, false); + var tx2 = Convert.FromBase64String(result.AsString()).AsSerializable(); + tx2.ToJson(_neoSystem.Settings).ToString().Should().Be(tx.ToJson(_neoSystem.Settings).ToString()); } [TestMethod] @@ -197,6 +236,15 @@ public void TestFindStorage() json["next"] = 1; json["results"] = jarr; Assert.AreEqual(json.ToString(), result.ToString()); + + var result2 = _rpcServer.FindStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key)); + result2.ToString().Should().Be(result.ToString()); + + Enumerable.Range(0, 51).ToList().ForEach(i => TestUtils.StorageItemAdd(snapshot, contractState.Id, new byte[] { 0x01, (byte)i }, new byte[] { 0x02 })); + snapshot.Commit(); + var result4 = _rpcServer.FindStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(new byte[] { 0x01 }), 0); + result4["next"].Should().Be(RpcServerSettings.Default.FindStoragePageSize); + (result4["truncated"]).AsBoolean().Should().Be(true); } [TestMethod] @@ -232,12 +280,16 @@ public void TestGetNextBlockValidators() public void TestGetCandidates() { var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetCandidates(); var json = new JArray(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + + var key = new KeyBuilder(NativeContract.NEO.Id, 33).Add(ECPoint.Parse("02237309a0633ff930d51856db01d17c829a5b2e5cc2638e9c03b4cfa8e9c9f971", ECCurve.Secp256r1)); + snapshot.Add(key, new StorageItem(new CandidateState() { Registered = true, Votes = 10000 })); snapshot.Commit(); var candidates = NativeContract.NEO.GetCandidates(_neoSystem.GetSnapshotCache()); - + result = _rpcServer.GetCandidates(); foreach (var candidate in candidates) { var item = new JObject(); @@ -350,7 +402,8 @@ public void TestGetBlockHashInvalidIndex() var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - Assert.ThrowsException(() => _rpcServer.GetBlockHash(block.Index + 1)); + Action act = () => _rpcServer.GetBlockHash(block.Index + 1); + act.Should().Throw(); } [TestMethod] diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs index c8b655024c..cf674a449c 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs @@ -9,18 +9,50 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Akka.Actor; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.Json; +using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using Neo.UnitTests; using System; +using System.Collections.Generic; +using System.Net; namespace Neo.Plugins.RpcServer.Tests { partial class UT_RpcServer { + [TestMethod] + public void TestGetConnectionCount() + { + var result = _rpcServer.GetConnectionCount(new JArray()); + result.GetType().Should().Be(typeof(JNumber)); + } + + [TestMethod] + public void TestGetPeers() + { + var settings = TestProtocolSettings.SoleNode; + var neoSystem = new NeoSystem(settings, _memoryStoreProvider); + var localNode = neoSystem.LocalNode.Ask(new LocalNode.GetInstance()).Result; + localNode.AddPeers(new List() { new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 11332) }); + localNode.AddPeers(new List() { new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 12332) }); + localNode.AddPeers(new List() { new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 13332) }); + var rpcServer = new RpcServer(neoSystem, RpcServerSettings.Default); + + var result = rpcServer.GetPeers(new JArray()); + Assert.IsInstanceOfType(result, typeof(JObject)); + var json = (JObject)result; + json.ContainsProperty("unconnected").Should().BeTrue(); + (json["unconnected"] as JArray).Count.Should().Be(3); + json.ContainsProperty("bad").Should().BeTrue(); + json.ContainsProperty("connected").Should().BeTrue(); + } + [TestMethod] public void TestGetVersion() { diff --git a/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs b/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs index 9a282759a9..c205a7e4bd 100644 --- a/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs +++ b/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs @@ -9,6 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; +using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -100,14 +102,26 @@ public static void DestroyContract(this DataCache snapshot, UInt160 callingScrip public static void AddContract(this DataCache snapshot, UInt160 hash, ContractState state) { + //key: hash, value: ContractState var key = new KeyBuilder(NativeContract.ContractManagement.Id, 8).Add(hash); snapshot.Add(key, new StorageItem(state)); + //key: id, value: hash + var key2 = new KeyBuilder(NativeContract.ContractManagement.Id, 12).AddBigEndian(state.Id); + if (!snapshot.Contains(key2)) snapshot.Add(key2, new StorageItem(hash.ToArray())); } public static void DeleteContract(this DataCache snapshot, UInt160 hash) { + //key: hash, value: ContractState var key = new KeyBuilder(NativeContract.ContractManagement.Id, 8).Add(hash); + var value = snapshot.TryGet(key)?.GetInteroperable(); snapshot.Delete(key); + if (value != null) + { + //key: id, value: hash + var key2 = new KeyBuilder(NativeContract.ContractManagement.Id, 12).AddBigEndian(value.Id); + snapshot.Delete(key2); + } } public static StackItem Call(this NativeContract contract, DataCache snapshot, string method, params ContractParameter[] args) From 5fe5462b778797c7ec7a9b8779b5aa25a0d74166 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Thu, 29 Aug 2024 01:11:10 -0400 Subject: [PATCH 2/5] [`fixes`] UInt160 Class (#3422) * Fixed `UInt160` and expanded class * Cleaned up code for `TryParse` * Fixed `TryParse` * Fixed small bug with `TryParse` * Change `UInt160.Zero` to `static readonly` * benchmark UInt160 * Fix benchmark * Fixed bugs and added features for `UInt160` class * Revert and just keep bug fixes * Made @shargon changes * Set `InvariantCultureIgnoreCase` back for `0x` and `0X` --------- Co-authored-by: Shargon Co-authored-by: Jimmy Co-authored-by: Jimmy --- .../Neo.Benchmarks/Benchmarks.UInt160.cs | 156 +++++++++++++++ .../Neo.Benchmarks/Neo.Benchmarks.csproj | 1 + benchmarks/Neo.Benchmarks/OldUInt160.cs | 184 ++++++++++++++++++ benchmarks/Neo.Benchmarks/Program.cs | 3 +- src/Neo/UInt160.cs | 110 ++++++----- tests/Neo.UnitTests/UT_UInt160.cs | 27 ++- 6 files changed, 430 insertions(+), 51 deletions(-) create mode 100644 benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs create mode 100644 benchmarks/Neo.Benchmarks/OldUInt160.cs diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs b/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs new file mode 100644 index 0000000000..37fa701ff9 --- /dev/null +++ b/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.UInt160.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.Benchmark; + +public class Benchmarks_UInt160 +{ + static readonly OldUInt160 s_oldUInt160 = new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + static readonly UInt160 s_newUInt160 = new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + + [Benchmark] + public void TestOldUInt160Gernerator1() + { + _ = new OldUInt160(); + } + + [Benchmark] + public void TestOldUInt160Gernerator2() + { + _ = new OldUInt160(new byte[20]); + } + + [Benchmark] + public void TestOldUInt160CompareTo() + { + OldUInt160.Zero.CompareTo(OldUInt160.Zero); + OldUInt160.Zero.CompareTo(s_oldUInt160); + s_oldUInt160.CompareTo(OldUInt160.Zero); + } + + [Benchmark] + public void TestOldUInt160Equals() + { + OldUInt160.Zero.Equals(OldUInt160.Zero); + OldUInt160.Zero.Equals(s_oldUInt160); + s_oldUInt160.Equals(null); + } + + [Benchmark] + public void TestOldUInt160Parse() + { + _ = OldUInt160.Parse("0x0000000000000000000000000000000000000000"); + _ = OldUInt160.Parse("0000000000000000000000000000000000000000"); + } + + [Benchmark] + public void TestOldUInt160TryParse() + { + OldUInt160.TryParse(null, out _); + OldUInt160.TryParse("0x0000000000000000000000000000000000000000", out var temp); + OldUInt160.TryParse("0x1230000000000000000000000000000000000000", out temp); + OldUInt160.TryParse("000000000000000000000000000000000000000", out _); + } + + [Benchmark] + public void TestOldUInt160OperatorLarger() + { + _ = s_oldUInt160 > OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorLargerAndEqual() + { + _ = s_oldUInt160 >= OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorSmaller() + { + _ = s_oldUInt160 < OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorSmallerAndEqual() + { + _ = s_oldUInt160 <= OldUInt160.Zero; + } + + [Benchmark] + public void TestGernerator1() + { + _ = new UInt160(); + } + + [Benchmark] + public void TestGernerator2() + { + _ = new UInt160(new byte[20]); + } + + [Benchmark] + public void TestCompareTo() + { + UInt160.Zero.CompareTo(UInt160.Zero); + UInt160.Zero.CompareTo(s_newUInt160); + s_newUInt160.CompareTo(UInt160.Zero); + } + + [Benchmark] + public void TestEquals() + { + UInt160.Zero.Equals(UInt160.Zero); + UInt160.Zero.Equals(s_newUInt160); + s_newUInt160.Equals(null); + } + + [Benchmark] + public void TestParse() + { + _ = UInt160.Parse("0x0000000000000000000000000000000000000000"); + _ = UInt160.Parse("0000000000000000000000000000000000000000"); + } + + [Benchmark] + public void TestTryParse() + { + UInt160.TryParse(null, out _); + UInt160.TryParse("0x0000000000000000000000000000000000000000", out var temp); + UInt160.TryParse("0x1230000000000000000000000000000000000000", out temp); + UInt160.TryParse("000000000000000000000000000000000000000", out _); + } + + [Benchmark] + public void TestOperatorLarger() + { + _ = s_newUInt160 > UInt160.Zero; + } + + [Benchmark] + public void TestOperatorLargerAndEqual() + { + _ = s_newUInt160 >= UInt160.Zero; + } + + [Benchmark] + public void TestOperatorSmaller() + { + _ = s_newUInt160 < UInt160.Zero; + } + + [Benchmark] + public void TestOperatorSmallerAndEqual() + { + _ = s_newUInt160 <= UInt160.Zero; + } +} diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index c4b1a35da9..a59fc6e728 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -5,6 +5,7 @@ net8.0 Neo enable + true diff --git a/benchmarks/Neo.Benchmarks/OldUInt160.cs b/benchmarks/Neo.Benchmarks/OldUInt160.cs new file mode 100644 index 0000000000..965ac985ea --- /dev/null +++ b/benchmarks/Neo.Benchmarks/OldUInt160.cs @@ -0,0 +1,184 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OldUInt160.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.Extensions; +using Neo.IO; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Neo +{ + /// + /// Represents a 160-bit unsigned integer. + /// + [StructLayout(LayoutKind.Explicit, Size = 20)] + public class OldUInt160 : IComparable, IEquatable, ISerializable + { + /// + /// The length of values. + /// + public const int Length = 20; + + /// + /// Represents 0. + /// + public static readonly OldUInt160 Zero = new(); + + [FieldOffset(0)] private ulong value1; + [FieldOffset(8)] private ulong value2; + [FieldOffset(16)] private uint value3; + + public int Size => Length; + + /// + /// Initializes a new instance of the class. + /// + public OldUInt160() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The value of the . + public unsafe OldUInt160(ReadOnlySpan value) + { + if (value.Length != Length) throw new FormatException(); + fixed (ulong* p = &value1) + { + Span dst = new(p, Length); + value[..Length].CopyTo(dst); + } + } + + public int CompareTo(OldUInt160 other) + { + int result = value3.CompareTo(other.value3); + if (result != 0) return result; + result = value2.CompareTo(other.value2); + if (result != 0) return result; + return value1.CompareTo(other.value1); + } + + public void Deserialize(ref MemoryReader reader) + { + value1 = reader.ReadUInt64(); + value2 = reader.ReadUInt64(); + value3 = reader.ReadUInt32(); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, this)) return true; + return Equals(obj as OldUInt160); + } + + public bool Equals(OldUInt160 other) + { + if (other is null) return false; + return value1 == other.value1 + && value2 == other.value2 + && value3 == other.value3; + } + + public override int GetHashCode() + { + return (int)value1; + } + + /// + /// Parses an from the specified . + /// + /// An represented by a . + /// The parsed . + /// is not in the correct format. + public static OldUInt160 Parse(string value) + { + if (!TryParse(value, out var result)) throw new FormatException(); + return result; + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(value1); + writer.Write(value2); + writer.Write(value3); + } + + public override string ToString() + { + return "0x" + this.ToArray().ToHexString(reverse: true); + } + + /// + /// Parses an from the specified . + /// + /// An represented by a . + /// The parsed . + /// if an is successfully parsed; otherwise, . + public static bool TryParse(string s, out OldUInt160 result) + { + if (s == null) + { + result = null; + return false; + } + if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + s = s[2..]; + if (s.Length != Length * 2) + { + result = null; + return false; + } + byte[] data = new byte[Length]; + for (int i = 0; i < Length; i++) + if (!byte.TryParse(s.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1])) + { + result = null; + return false; + } + result = new OldUInt160(data); + return true; + } + + public static bool operator ==(OldUInt160 left, OldUInt160 right) + { + if (ReferenceEquals(left, right)) return true; + if (left is null || right is null) return false; + return left.Equals(right); + } + + public static bool operator !=(OldUInt160 left, OldUInt160 right) + { + return !(left == right); + } + + public static bool operator >(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) >= 0; + } + + public static bool operator <(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) <= 0; + } + } +} diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index e39ef23ff7..c44b76f839 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -12,4 +12,5 @@ using BenchmarkDotNet.Running; using Neo.Benchmark; -BenchmarkRunner.Run(); +// BenchmarkRunner.Run(); +BenchmarkRunner.Run(); diff --git a/src/Neo/UInt160.cs b/src/Neo/UInt160.cs index db317ef7e5..da4c920cbe 100644 --- a/src/Neo/UInt160.cs +++ b/src/Neo/UInt160.cs @@ -14,6 +14,8 @@ using System; using System.Globalization; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Neo @@ -32,20 +34,18 @@ public class UInt160 : IComparable, IEquatable, ISerializable /// /// Represents 0. /// - public static readonly UInt160 Zero = new(); + public readonly static UInt160 Zero = new(); - [FieldOffset(0)] private ulong value1; - [FieldOffset(8)] private ulong value2; - [FieldOffset(16)] private uint value3; + [FieldOffset(0)] private ulong _value1; + [FieldOffset(8)] private ulong _value2; + [FieldOffset(16)] private uint _value3; public int Size => Length; /// /// Initializes a new instance of the class. /// - public UInt160() - { - } + public UInt160() { } /// /// Initializes a new instance of the class. @@ -53,47 +53,52 @@ public UInt160() /// The value of the . public unsafe UInt160(ReadOnlySpan value) { - if (value.Length != Length) throw new FormatException(); - fixed (ulong* p = &value1) + if (value.Length != Length) + throw new FormatException(); + + fixed (void* p = &_value1) { Span dst = new(p, Length); value[..Length].CopyTo(dst); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(UInt160 other) { - int result = value3.CompareTo(other.value3); + var result = _value3.CompareTo(other._value3); if (result != 0) return result; - result = value2.CompareTo(other.value2); + result = _value2.CompareTo(other._value2); if (result != 0) return result; - return value1.CompareTo(other.value1); + return _value1.CompareTo(other._value1); } public void Deserialize(ref MemoryReader reader) { - value1 = reader.ReadUInt64(); - value2 = reader.ReadUInt64(); - value3 = reader.ReadUInt32(); + _value1 = reader.ReadUInt64(); + _value2 = reader.ReadUInt64(); + _value3 = reader.ReadUInt32(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; return Equals(obj as UInt160); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(UInt160 other) { - if (other is null) return false; - return value1 == other.value1 - && value2 == other.value2 - && value3 == other.value3; + if (other == null) return false; + return _value1 == other._value1 && + _value2 == other._value2 && + _value3 == other._value3; } public override int GetHashCode() { - return (int)value1; + return HashCode.Combine(_value1, _value2, _value3); } /// @@ -110,9 +115,9 @@ public static UInt160 Parse(string value) public void Serialize(BinaryWriter writer) { - writer.Write(value1); - writer.Write(value2); - writer.Write(value3); + writer.Write(_value1); + writer.Write(_value2); + writer.Write(_value3); } public override string ToString() @@ -123,44 +128,61 @@ public override string ToString() /// /// Parses an from the specified . /// - /// An represented by a . + /// An represented by a . /// The parsed . /// if an is successfully parsed; otherwise, . - public static bool TryParse(string s, out UInt160 result) + public static bool TryParse(string str, out UInt160 result) { - if (s == null) + var startIndex = 0; + + result = null; + + if (string.IsNullOrWhiteSpace(str)) return false; + + if (str.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + startIndex = 2; + + if ((str.Length - startIndex) != Length * 2) return false; + + try { - result = null; - return false; + var data = new byte[Length]; + for (var i = 0; i < Length; i++) + { + if (!byte.TryParse(str.AsSpan(i * 2 + startIndex, 2), NumberStyles.HexNumber, null, out data[Length - i - 1])) + return false; + } + result = new(data); + return true; } - if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) - s = s[2..]; - if (s.Length != Length * 2) + catch { - result = null; return false; } - byte[] data = new byte[Length]; - for (int i = 0; i < Length; i++) - if (!byte.TryParse(s.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1])) - { - result = null; - return false; - } - result = new UInt160(data); - return true; + } + + public static implicit operator UInt160(string s) + { + return Parse(s); + } + + public static implicit operator UInt160(byte[] b) + { + return new UInt160(b); } public static bool operator ==(UInt160 left, UInt160 right) { - if (ReferenceEquals(left, right)) return true; - if (left is null || right is null) return false; + if (left is null || right is null) + return Equals(left, right); return left.Equals(right); } public static bool operator !=(UInt160 left, UInt160 right) { - return !(left == right); + if (left is null || right is null) + return !Equals(left, right); + return !left.Equals(right); } public static bool operator >(UInt160 left, UInt160 right) diff --git a/tests/Neo.UnitTests/UT_UInt160.cs b/tests/Neo.UnitTests/UT_UInt160.cs index 4799a80d19..3502c7cf90 100644 --- a/tests/Neo.UnitTests/UT_UInt160.cs +++ b/tests/Neo.UnitTests/UT_UInt160.cs @@ -9,11 +9,10 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -#pragma warning disable CS1718 - using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Security.Cryptography; namespace Neo.UnitTests.IO { @@ -36,8 +35,16 @@ public void TestGernerator1() [TestMethod] public void TestGernerator2() { - UInt160 uInt160 = new UInt160(new byte[20]); + UInt160 uInt160 = new byte[20]; + Assert.IsNotNull(uInt160); + } + + [TestMethod] + public void TestGernerator3() + { + UInt160 uInt160 = "0xff00000000000000000000000000000000000001"; Assert.IsNotNull(uInt160); + Assert.IsTrue(uInt160.ToString() == "0xff00000000000000000000000000000000000001"); } [TestMethod] @@ -57,9 +64,13 @@ public void TestEquals() byte[] temp = new byte[20]; temp[19] = 0x01; UInt160 result = new UInt160(temp); - Assert.AreEqual(true, UInt160.Zero.Equals(UInt160.Zero)); - Assert.AreEqual(false, UInt160.Zero.Equals(result)); - Assert.AreEqual(false, result.Equals(null)); + Assert.IsTrue(UInt160.Zero.Equals(UInt160.Zero)); + Assert.IsFalse(UInt160.Zero.Equals(result)); + Assert.IsFalse(result.Equals(null)); + Assert.IsTrue(UInt160.Zero == UInt160.Zero); + Assert.IsFalse(UInt160.Zero != UInt160.Zero); + Assert.IsTrue(UInt160.Zero == "0x0000000000000000000000000000000000000000"); + Assert.IsFalse(UInt160.Zero == "0x0000000000000000000000000000000000000001"); } [TestMethod] @@ -92,24 +103,28 @@ public void TestTryParse() public void TestOperatorLarger() { Assert.AreEqual(false, UInt160.Zero > UInt160.Zero); + Assert.IsFalse(UInt160.Zero > "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorLargerAndEqual() { Assert.AreEqual(true, UInt160.Zero >= UInt160.Zero); + Assert.IsTrue(UInt160.Zero >= "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorSmaller() { Assert.AreEqual(false, UInt160.Zero < UInt160.Zero); + Assert.IsFalse(UInt160.Zero < "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorSmallerAndEqual() { Assert.AreEqual(true, UInt160.Zero <= UInt160.Zero); + Assert.IsTrue(UInt160.Zero >= "0x0000000000000000000000000000000000000000"); } } } From c30a0db3f5cca0c82e9ac32497accdc3f128dc54 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Aug 2024 17:20:25 +1200 Subject: [PATCH 3/5] Apply suggestions from code review --- src/Neo.Extensions/StringExtensions.cs | 1 - tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Neo.Extensions/StringExtensions.cs b/src/Neo.Extensions/StringExtensions.cs index abd4d07189..b84869b2e1 100644 --- a/src/Neo.Extensions/StringExtensions.cs +++ b/src/Neo.Extensions/StringExtensions.cs @@ -43,6 +43,5 @@ public static int GetVarSize(this string value) var size = Utility.StrictUTF8.GetByteCount(value); return UnsafeData.GetVarSize(size) + size; } - } } diff --git a/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs index 574dd87653..b887d1c973 100644 --- a/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs +++ b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs @@ -18,7 +18,6 @@ namespace Neo.Extensions.Tests [TestClass] public class UT_BigIntegerExtensions { - [TestMethod] public void TestGetLowestSetBit() { From 9b009dd3134e367268e5c9c084159e511f03549c Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Aug 2024 17:21:56 +1200 Subject: [PATCH 4/5] Update tests/Neo.Extensions.Tests/UT_StringExtensions.cs --- tests/Neo.Extensions.Tests/UT_StringExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Neo.Extensions.Tests/UT_StringExtensions.cs b/tests/Neo.Extensions.Tests/UT_StringExtensions.cs index 9c8ea927da..05e5388dfa 100644 --- a/tests/Neo.Extensions.Tests/UT_StringExtensions.cs +++ b/tests/Neo.Extensions.Tests/UT_StringExtensions.cs @@ -66,7 +66,6 @@ public void TestGetVarSizeInt() } } - [TestMethod] public void TestGetVarSizeGeneric() { From e018bc3520c48a11f402c40287020da70f30293d Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Thu, 29 Aug 2024 13:51:56 +0800 Subject: [PATCH 5/5] test GetApplicationLog (#3470) * test GetApplicationLog * filter execution type * Test_Commands; refactor * apply review suggestions --------- Co-authored-by: Shargon --- src/Plugins/ApplicationLogs/LogReader.cs | 8 +- .../Neo.Plugins.ApplicationLogs.Tests.csproj | 7 +- .../UT_LogReader.cs | 213 ++++++++++++++++++ 3 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs diff --git a/src/Plugins/ApplicationLogs/LogReader.cs b/src/Plugins/ApplicationLogs/LogReader.cs index b3f767fecb..6ef4fcec32 100644 --- a/src/Plugins/ApplicationLogs/LogReader.cs +++ b/src/Plugins/ApplicationLogs/LogReader.cs @@ -30,7 +30,7 @@ public class LogReader : Plugin, ICommittingHandler, ICommittedHandler, ILogHand { #region Globals - private NeoStore _neostore; + internal NeoStore _neostore; private NeoSystem _neosystem; private readonly List _logEvents; @@ -123,7 +123,7 @@ public JToken GetApplicationLog(JArray _params) #region Console Commands [ConsoleCommand("log block", Category = "ApplicationLog Commands")] - private void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) + internal void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) { UInt256 blockhash; if (uint.TryParse(blockHashOrIndex, out var blockIndex)) @@ -154,7 +154,7 @@ private void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) } [ConsoleCommand("log tx", Category = "ApplicationLog Commands")] - private void OnGetTransactionCommand(UInt256 txhash, string eventName = null) + internal void OnGetTransactionCommand(UInt256 txhash, string eventName = null) { var txApplication = string.IsNullOrEmpty(eventName) ? _neostore.GetTransactionLog(txhash) : @@ -167,7 +167,7 @@ private void OnGetTransactionCommand(UInt256 txhash, string eventName = null) } [ConsoleCommand("log contract", Category = "ApplicationLog Commands")] - private void OnGetContractCommand(UInt160 scripthash, uint page = 1, uint pageSize = 1, string eventName = null) + internal void OnGetContractCommand(UInt160 scripthash, uint page = 1, uint pageSize = 1, string eventName = null) { if (page == 0) { diff --git a/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj b/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj index e8a46d3022..92951a59dd 100644 --- a/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj @@ -6,14 +6,15 @@ - - - + + + + diff --git a/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs b/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs new file mode 100644 index 0000000000..f7e2e2bdad --- /dev/null +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs @@ -0,0 +1,213 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ApplicationLogs.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 Akka.Actor; +using Akka.Util; +using Microsoft.AspNetCore.Authorization; +using Neo.Cryptography; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs; +using Neo.Plugins.ApplicationLogs.Store; +using Neo.Plugins.ApplicationLogs.Store.Models; +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.Plugins.ApplicationsLogs.Tests.Setup; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Policy; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static Neo.Plugins.ApplicationsLogs.Tests.UT_LogReader; + +namespace Neo.Plugins.ApplicationsLogs.Tests +{ + public class UT_LogReader : IClassFixture + { + static readonly string NeoTransferScript = "CxEMFPlu76Cuc\u002BbgteStE4ozsOWTNUdrDBQtYNweHko3YcnMFOes3ceblcI/lRTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1I="; + static readonly byte[] ValidatorScript = Contract.CreateSignatureRedeemScript(TestProtocolSettings.SoleNode.StandbyCommittee[0]); + static readonly UInt160 ValidatorScriptHash = ValidatorScript.ToScriptHash(); + static readonly string ValidatorAddress = ValidatorScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + static readonly byte[] MultisigScript = Contract.CreateMultiSigRedeemScript(1, TestProtocolSettings.SoleNode.StandbyCommittee); + static readonly UInt160 MultisigScriptHash = MultisigScript.ToScriptHash(); + static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + + public class TestMemoryStoreProvider(MemoryStore memoryStore) : IStoreProvider + { + public MemoryStore MemoryStore { get; init; } = memoryStore; + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => MemoryStore; + } + + public class NeoSystemFixture : IDisposable + { + public NeoSystem _neoSystem; + public TestMemoryStoreProvider _memoryStoreProvider; + public MemoryStore _memoryStore; + public readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); + public WalletAccount _walletAccount; + public Transaction[] txs; + public Block block; + public LogReader logReader; + + public NeoSystemFixture() + { + _memoryStore = new MemoryStore(); + _memoryStoreProvider = new TestMemoryStoreProvider(_memoryStore); + logReader = new LogReader(); + Plugin.Plugins.Add(logReader); // initialize before NeoSystem to let NeoSystem load the plugin + _neoSystem = new NeoSystem(TestProtocolSettings.SoleNode with { Network = ApplicationLogs.Settings.Default.Network }, _memoryStoreProvider); + _walletAccount = _wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); + + NeoSystem system = _neoSystem; + txs = [ + new Transaction + { + Nonce = 233, + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(system.GetSnapshotCache()) + system.Settings.MaxValidUntilBlockIncrement, + Signers = [new Signer() { Account = MultisigScriptHash, Scopes = WitnessScope.CalledByEntry }], + Attributes = Array.Empty(), + Script = Convert.FromBase64String(NeoTransferScript), + NetworkFee = 1000_0000, + SystemFee = 1000_0000, + } + ]; + byte[] signature = txs[0].Sign(_walletAccount.GetKey(), ApplicationLogs.Settings.Default.Network); + txs[0].Witnesses = [new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, (byte)signature.Length }.Concat(signature).ToArray(), + VerificationScript = MultisigScript, + }]; + block = new Block + { + Header = new Header + { + Version = 0, + PrevHash = _neoSystem.GenesisBlock.Hash, + MerkleRoot = new UInt256(), + Timestamp = _neoSystem.GenesisBlock.Timestamp + 15_000, + Index = 1, + NextConsensus = _neoSystem.GenesisBlock.NextConsensus, + }, + Transactions = txs, + }; + block.Header.MerkleRoot ??= MerkleTree.ComputeRoot(block.Transactions.Select(t => t.Hash).ToArray()); + signature = block.Sign(_walletAccount.GetKey(), ApplicationLogs.Settings.Default.Network); + block.Header.Witness = new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, (byte)signature.Length }.Concat(signature).ToArray(), + VerificationScript = MultisigScript, + }; + } + + public void Dispose() + { + logReader.Dispose(); + _neoSystem.Dispose(); + _memoryStore.Dispose(); + } + } + + private readonly NeoSystemFixture _neoSystemFixture; + + public UT_LogReader(NeoSystemFixture neoSystemFixture) + { + _neoSystemFixture = neoSystemFixture; + } + + [Fact] + public async Task Test_GetApplicationLog() + { + NeoSystem system = _neoSystemFixture._neoSystem; + Block block = _neoSystemFixture.block; + await system.Blockchain.Ask(block); // persist the block + + JObject blockJson = (JObject)_neoSystemFixture.logReader.GetApplicationLog([block.Hash.ToString()]); + Assert.Equal(blockJson["blockhash"], block.Hash.ToString()); + JArray executions = (JArray)blockJson["executions"]; + Assert.Equal(executions.Count, 2); + Assert.Equal(executions[0]["trigger"], "OnPersist"); + Assert.Equal(executions[1]["trigger"], "PostPersist"); + JArray notifications = (JArray)executions[1]["notifications"]; + Assert.Equal(notifications.Count, 1); + Assert.Equal(notifications[0]["contract"], GasToken.GAS.Hash.ToString()); + Assert.Equal(notifications[0]["eventname"], "Transfer"); // from null to Validator + Assert.Equal(notifications[0]["state"]["value"][0]["type"], nameof(ContractParameterType.Any)); + Assert.Equal(Convert.FromBase64String(notifications[0]["state"]["value"][1]["value"].AsString()), ValidatorScriptHash.ToArray()); + Assert.Equal(notifications[0]["state"]["value"][2]["value"], "50000000"); + + blockJson = (JObject)_neoSystemFixture.logReader.GetApplicationLog([block.Hash.ToString(), "PostPersist"]); + executions = (JArray)blockJson["executions"]; + Assert.Equal(executions.Count, 1); + Assert.Equal(executions[0]["trigger"], "PostPersist"); + + JObject transactionJson = (JObject)_neoSystemFixture.logReader.GetApplicationLog([_neoSystemFixture.txs[0].Hash.ToString(), true]); // "true" is invalid but still works + executions = (JArray)transactionJson["executions"]; + Assert.Equal(executions.Count, 1); + Assert.Equal(executions[0]["vmstate"], nameof(VMState.HALT)); + Assert.Equal(executions[0]["stack"][0]["value"], true); + notifications = (JArray)executions[0]["notifications"]; + Assert.Equal(notifications.Count, 2); + Assert.Equal(notifications[0]["eventname"].AsString(), "Transfer"); + Assert.Equal(notifications[0]["contract"].AsString(), NeoToken.NEO.Hash.ToString()); + Assert.Equal(notifications[0]["state"]["value"][2]["value"], "1"); + Assert.Equal(notifications[1]["eventname"].AsString(), "Transfer"); + Assert.Equal(notifications[1]["contract"].AsString(), GasToken.GAS.Hash.ToString()); + Assert.Equal(notifications[1]["state"]["value"][2]["value"], "50000000"); + } + + [Fact] + public async Task Test_Commands() + { + NeoSystem system = _neoSystemFixture._neoSystem; + Block block = _neoSystemFixture.block; + await system.Blockchain.Ask(block); // persist the block + + _neoSystemFixture.logReader.OnGetBlockCommand("1"); + _neoSystemFixture.logReader.OnGetBlockCommand(block.Hash.ToString()); + _neoSystemFixture.logReader.OnGetContractCommand(NeoToken.NEO.Hash); + _neoSystemFixture.logReader.OnGetTransactionCommand(_neoSystemFixture.txs[0].Hash); + + BlockchainExecutionModel blockLog = _neoSystemFixture.logReader._neostore.GetBlockLog(block.Hash, TriggerType.Application); + BlockchainExecutionModel transactionLog = _neoSystemFixture.logReader._neostore.GetTransactionLog(_neoSystemFixture.txs[0].Hash); + foreach (BlockchainExecutionModel log in new BlockchainExecutionModel[] { blockLog, transactionLog }) + { + Assert.Equal(log.VmState, VMState.HALT); + Assert.Equal(log.Stack[0].GetBoolean(), true); + Assert.Equal(log.Notifications.Count(), 2); + Assert.Equal(log.Notifications[0].EventName, "Transfer"); + Assert.Equal(log.Notifications[0].ScriptHash, NeoToken.NEO.Hash); + Assert.Equal(log.Notifications[0].State[2], 1); + Assert.Equal(log.Notifications[1].EventName, "Transfer"); + Assert.Equal(log.Notifications[1].ScriptHash, GasToken.GAS.Hash); + Assert.Equal(log.Notifications[1].State[2], 50000000); + } + + List<(BlockchainEventModel eventLog, UInt256 txHash)> neoLogs = _neoSystemFixture.logReader._neostore.GetContractLog(NeoToken.NEO.Hash, TriggerType.Application).ToList(); + Assert.Equal(neoLogs.Count, 1); + Assert.Equal(neoLogs[0].txHash, _neoSystemFixture.txs[0].Hash); + Assert.Equal(neoLogs[0].eventLog.EventName, "Transfer"); + Assert.Equal(neoLogs[0].eventLog.ScriptHash, NeoToken.NEO.Hash); + Assert.Equal(neoLogs[0].eventLog.State[2], 1); + } + } +}