From 07fb5ce167aaac60fa0d30edc1942386157f250b Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 28 Jun 2024 13:35:06 +0800 Subject: [PATCH] [Neo Plugins UT] Add RpcServer Unit Tests Part Blockchain. (#3339) * try mock * not use mock * test * fix test * use neo testutils * complete rpcserver blockchain tests. * revert change to ByteArrayComparer * revert cache change * add more detail to comments * add more exception test cases * fix warning * Apply suggestions from code review * update TODO mark * Update src/Plugins/RpcServer/RpcServer.Blockchain.cs Co-authored-by: Anna Shaleva * Update src/Plugins/RpcServer/RpcServer.Blockchain.cs Co-authored-by: Anna Shaleva * Update src/Plugins/RpcServer/RpcServer.Blockchain.cs Co-authored-by: Anna Shaleva * Update src/Plugins/RpcServer/RpcServer.Blockchain.cs Co-authored-by: Anna Shaleva * Update src/Plugins/RpcServer/RpcServer.Blockchain.cs Co-authored-by: Anna Shaleva --------- Co-authored-by: Shargon Co-authored-by: Anna Shaleva Co-authored-by: Christopher Schuchardt --- src/Neo/SmartContract/Native/NeoToken.cs | 2 +- src/Plugins/RpcServer/RpcServer.Blockchain.cs | 127 +++- .../MockNeoSystem.cs | 34 - .../Neo.Plugins.RpcServer.Tests.csproj | 2 + .../TestMemoryStoreProvider.cs | 21 + .../UT_RpcServer.Blockchain.cs | 620 ++++++++++++++++++ .../UT_RpcServer.cs | 55 +- tests/Neo.UnitTests/Ledger/UT_Blockchain.cs | 32 +- tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs | 4 +- .../Network/P2P/Payloads/UT_Header.cs | 2 +- .../SmartContract/UT_InteropService.cs | 2 +- .../SmartContract/UT_SmartContractHelper.cs | 31 +- .../SmartContract/UT_Syscalls.cs | 2 +- tests/Neo.UnitTests/TestUtils.Block.cs | 144 ++++ tests/Neo.UnitTests/TestUtils.cs | 100 +-- 15 files changed, 1003 insertions(+), 175 deletions(-) delete mode 100644 tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs create mode 100644 tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs create mode 100644 tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs create mode 100644 tests/Neo.UnitTests/TestUtils.Block.cs diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index 99ecd41e29..f43a0d6079 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -421,7 +421,7 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, /// The snapshot used to read data. /// All the registered candidates. [ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)] - private (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) + internal (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) { return GetCandidatesInternal(snapshot) .Select(p => (p.PublicKey, p.State.Votes)) diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 7898dd9d9b..4abddbc183 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -24,14 +24,28 @@ namespace Neo.Plugins.RpcServer { partial class RpcServer { + /// + /// Gets the hash of the best (most recent) block. + /// + /// An empty array; no parameters are required. + /// The hash of the best block as a . [RpcMethod] - protected virtual JToken GetBestBlockHash(JArray _params) + protected internal virtual JToken GetBestBlockHash(JArray _params) { return NativeContract.Ledger.CurrentHash(system.StoreView).ToString(); } + /// + /// Gets a block by its hash or index. + /// + /// + /// An array containing the block hash or index as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The block data as a . If the second item of _params is true, then + /// block data is json format, otherwise, the return type is Base64-encoded byte array. [RpcMethod] - protected virtual JToken GetBlock(JArray _params) + protected internal virtual JToken GetBlock(JArray _params) { JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -60,20 +74,35 @@ protected virtual JToken GetBlock(JArray _params) return Convert.ToBase64String(block.ToArray()); } + /// + /// Gets the number of block headers in the blockchain. + /// + /// An empty array; no parameters are required. + /// The count of block headers as a . [RpcMethod] internal virtual JToken GetBlockHeaderCount(JArray _params) { return (system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(system.StoreView)) + 1; } + /// + /// Gets the number of blocks in the blockchain. + /// + /// An empty array; no parameters are required. + /// The count of blocks as a . [RpcMethod] - protected virtual JToken GetBlockCount(JArray _params) + protected internal virtual JToken GetBlockCount(JArray _params) { return NativeContract.Ledger.CurrentIndex(system.StoreView) + 1; } + /// + /// Gets the hash of the block at the specified height. + /// + /// An array containing the block height as the first element. + /// The hash of the block at the specified height as a . [RpcMethod] - protected virtual JToken GetBlockHash(JArray _params) + protected internal virtual JToken GetBlockHash(JArray _params) { uint height = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Height: {_params[0]}")); var snapshot = system.StoreView; @@ -84,8 +113,16 @@ protected virtual JToken GetBlockHash(JArray _params) throw new RpcException(RpcError.UnknownHeight); } + /// + /// Gets a block header by its hash or index. + /// + /// + /// An array containing the block header hash or index as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The block header data as a . In json format if the second item of _params is true, otherwise Base64-encoded byte array. [RpcMethod] - protected virtual JToken GetBlockHeader(JArray _params) + protected internal virtual JToken GetBlockHeader(JArray _params) { JToken key = _params[0]; bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -114,8 +151,13 @@ protected virtual JToken GetBlockHeader(JArray _params) return Convert.ToBase64String(header.ToArray()); } + /// + /// Gets the state of a contract by its ID or script hash or (only for native contracts) by case-insensitive name. + /// + /// An array containing the contract ID or script hash or case-insensitive native contract name as the first element. + /// The contract state in json format as a . [RpcMethod] - protected virtual JToken GetContractState(JArray _params) + protected internal virtual JToken GetContractState(JArray _params) { if (int.TryParse(_params[0].AsString(), out int contractId)) { @@ -139,8 +181,13 @@ private static UInt160 ToScriptHash(string keyword) return UInt160.Parse(keyword); } + /// + /// Gets the current memory pool transactions. + /// + /// An array containing an optional boolean indicating whether to include unverified transactions. + /// The memory pool transactions in json format as a . [RpcMethod] - protected virtual JToken GetRawMemPool(JArray _params) + protected internal virtual JToken GetRawMemPool(JArray _params) { bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); if (!shouldGetUnverified) @@ -156,8 +203,16 @@ protected virtual JToken GetRawMemPool(JArray _params) return json; } + /// + /// Gets a transaction by its hash. + /// + /// + /// An array containing the transaction hash as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The transaction data as a . In json format if the second item of _params is true, otherwise base64string. [RpcMethod] - protected virtual JToken GetRawTransaction(JArray _params) + protected internal virtual JToken GetRawTransaction(JArray _params) { UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -179,8 +234,16 @@ protected virtual JToken GetRawTransaction(JArray _params) return json; } + /// + /// Gets the storage item by contract ID or script hash and key. + /// + /// + /// An array containing the contract ID or script hash as the first element, + /// and the storage key as the second element. + /// + /// The storage item as a . [RpcMethod] - protected virtual JToken GetStorage(JArray _params) + protected internal virtual JToken GetStorage(JArray _params) { using var snapshot = system.GetSnapshot(); if (!int.TryParse(_params[0].AsString(), out int id)) @@ -198,8 +261,17 @@ protected virtual JToken GetStorage(JArray _params) return Convert.ToBase64String(item.Value.Span); } + /// + /// Finds storage items by contract ID or script hash and prefix. + /// + /// + /// An array containing the contract ID or script hash as the first element, + /// the Base64-encoded storage key prefix as the second element, + /// and an optional start index as the third element. + /// + /// The found storage items as a . [RpcMethod] - protected virtual JToken FindStorage(JArray _params) + protected internal virtual JToken FindStorage(JArray _params) { using var snapshot = system.GetSnapshot(); if (!int.TryParse(_params[0].AsString(), out int id)) @@ -247,8 +319,13 @@ protected virtual JToken FindStorage(JArray _params) return json; } + /// + /// Gets the height of a transaction by its hash. + /// + /// An array containing the transaction hash as the first element. + /// The height of the transaction as a . [RpcMethod] - protected virtual JToken GetTransactionHeight(JArray _params) + protected internal virtual JToken GetTransactionHeight(JArray _params) { UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); uint? height = NativeContract.Ledger.GetTransactionState(system.StoreView, hash)?.BlockIndex; @@ -256,8 +333,13 @@ protected virtual JToken GetTransactionHeight(JArray _params) throw new RpcException(RpcError.UnknownTransaction); } + /// + /// Gets the next block validators. + /// + /// An empty array; no parameters are required. + /// The next block validators as a . [RpcMethod] - protected virtual JToken GetNextBlockValidators(JArray _params) + protected internal virtual JToken GetNextBlockValidators(JArray _params) { using var snapshot = system.GetSnapshot(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); @@ -270,8 +352,13 @@ protected virtual JToken GetNextBlockValidators(JArray _params) }).ToArray(); } + /// + /// Gets the list of candidates for the next block validators. + /// + /// An empty array; no parameters are required. + /// The candidates public key list as a JToken. [RpcMethod] - protected virtual JToken GetCandidates(JArray _params) + protected internal virtual JToken GetCandidates(JArray _params) { using var snapshot = system.GetSnapshot(); byte[] script; @@ -322,14 +409,24 @@ protected virtual JToken GetCandidates(JArray _params) return json; } + /// + /// Gets the list of committee members. + /// + /// An empty array; no parameters are required. + /// The committee members publickeys as a . [RpcMethod] - protected virtual JToken GetCommittee(JArray _params) + protected internal virtual JToken GetCommittee(JArray _params) { return new JArray(NativeContract.NEO.GetCommittee(system.StoreView).Select(p => (JToken)p.ToString())); } + /// + /// Gets the list of native contracts. + /// + /// An empty array; no parameters are required. + /// The native contract states as a . [RpcMethod] - protected virtual JToken GetNativeContracts(JArray _params) + protected internal virtual JToken GetNativeContracts(JArray _params) { return new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(system.StoreView, p.Hash).ToJson())); } diff --git a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs b/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs deleted file mode 100644 index 0f45a63d98..0000000000 --- a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// MockNeoSystem.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.Ledger; -using Neo.Persistence; - -namespace Neo.Plugins.RpcServer.Tests -{ - public class MockNeoSystem : NeoSystem - { - public SnapshotCache SnapshotCache { get; } - public MemoryPool MemoryPool { get; } - - public MockNeoSystem(SnapshotCache snapshotCache, MemoryPool memoryPool) - : base(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()) - { - SnapshotCache = snapshotCache; - MemoryPool = memoryPool; - } - - public SnapshotCache GetSnapshot() - { - return SnapshotCache; - } - } -} diff --git a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj index 5b693c96b8..1586b9753d 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj +++ b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj @@ -4,6 +4,7 @@ net8.0 Neo.Plugins.RpcServer.Tests Neo.Plugins.RpcServer.Tests + true @@ -16,6 +17,7 @@ + \ No newline at end of file diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs b/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs new file mode 100644 index 0000000000..b39b4aeae5 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestMemoryStoreProvider.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.Persistence; + +namespace Neo.Plugins.RpcServer.Tests; + +public class TestMemoryStoreProvider(MemoryStore memoryStore) : IStoreProvider +{ + public MemoryStore MemoryStore { get; init; } = memoryStore; + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => MemoryStore; +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs new file mode 100644 index 0000000000..5fd37b2bc3 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -0,0 +1,620 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Blockchain.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.Util.Internal; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using System; +using System.Linq; + +namespace Neo.Plugins.RpcServer.Tests +{ + public partial class UT_RpcServer + { + + [TestMethod] + public void TestGetBestBlockHash() + { + var key = NativeContract.Ledger.CreateStorageKey(12); + var expectedHash = UInt256.Zero; + + var snapshot = _neoSystem.GetSnapshot(); + var b = snapshot.GetAndChange(key, () => new StorageItem(new HashIndexState())).GetInteroperable(); + b.Hash = UInt256.Zero; + b.Index = 100; + snapshot.Commit(); + + var result = _rpcServer.GetBestBlockHash([]); + // Assert + Assert.AreEqual(expectedHash.ToString(), result.AsString()); + } + + [TestMethod] + public void TestGetBlockByHash() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(block.Hash.ToString(), false); + var result = _rpcServer.GetBlock(parameters); + var blockArr = Convert.FromBase64String(result.AsString()); + var block2 = blockArr.AsSerializable(); + block2.Transactions.ForEach(tx => + { + Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); + }); + } + + [TestMethod] + public void TestGetBlockByIndex() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(block.Index, false); + var result = _rpcServer.GetBlock(parameters); + var blockArr = Convert.FromBase64String(result.AsString()); + var block2 = blockArr.AsSerializable(); + block2.Transactions.ForEach(tx => + { + Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); + }); + } + + [TestMethod] + public void TestGetBlockCount() + { + var expectedCount = 1; + var result = _rpcServer.GetBlockCount(new JArray()); + Assert.AreEqual(expectedCount, result.AsNumber()); + } + + [TestMethod] + public void TestGetBlockHeaderCount() + { + var expectedCount = 1; + var result = _rpcServer.GetBlockHeaderCount(new JArray()); + Assert.AreEqual(expectedCount, result.AsNumber()); + } + + [TestMethod] + public void TestGetBlockHash() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var expectedHash = block.Hash.ToString(); + var result = _rpcServer.GetBlockHash(new JArray(block.Index)); + Assert.AreEqual(expectedHash, result.AsString()); + } + + [TestMethod] + public void TestGetBlockHeader() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var parameters = new JArray(block.Hash.ToString(), true); + var result = _rpcServer.GetBlockHeader(parameters); + var header = block.Header.ToJson(_neoSystem.Settings); + header["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + Assert.AreEqual(header.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetContractState() + { + var snapshot = _neoSystem.GetSnapshot(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + snapshot.Commit(); + var result = _rpcServer.GetContractState(new JArray(contractState.Hash.ToString())); + + Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetRawMemPool() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + snapshot.Commit(); + _neoSystem.MemPool.TryAdd(tx, snapshot); + + var result = _rpcServer.GetRawMemPool(new JArray()); + + Assert.IsTrue(((JArray)result).Any(p => p.AsString() == tx.Hash.ToString())); + } + + [TestMethod] + public void TestGetRawTransaction() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + _neoSystem.MemPool.TryAdd(tx, snapshot); + var parameters = new JArray(tx.Hash.ToString(), true); + snapshot.Commit(); + var result = _rpcServer.GetRawTransaction(parameters); + + var json = Utility.TransactionToJson(tx, _neoSystem.Settings); + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetStorage() + { + var snapshot = _neoSystem.GetSnapshot(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + var key = new byte[] { 0x01 }; + var value = new byte[] { 0x02 }; + TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); + snapshot.Commit(); + + var result = _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + Assert.AreEqual(Convert.ToBase64String(value), result.AsString()); + } + + [TestMethod] + public void TestFindStorage() + { + var snapshot = _neoSystem.GetSnapshot(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + var key = new byte[] { 0x01 }; + var value = new byte[] { 0x02 }; + TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); + snapshot.Commit(); + var result = _rpcServer.FindStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key), 0)); + + var json = new JObject(); + var jarr = new JArray(); + var j = new JObject(); + j["key"] = Convert.ToBase64String(key); + j["value"] = Convert.ToBase64String(value); + jarr.Add(j); + json["truncated"] = false; + json["next"] = 1; + json["results"] = jarr; + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetTransactionHeight() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var tx = block.Transactions[0]; + var result = _rpcServer.GetTransactionHeight(new JArray(tx.Hash.ToString())); + Assert.AreEqual(block.Index, result.AsNumber()); + } + + [TestMethod] + public void TestGetNextBlockValidators() + { + var snapshot = _neoSystem.GetSnapshot(); + var result = _rpcServer.GetNextBlockValidators(new JArray()); + + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + var expected = validators.Select(p => + { + var validator = new JObject(); + validator["publickey"] = p.ToString(); + validator["votes"] = (int)NativeContract.NEO.GetCandidateVote(snapshot, p); + return validator; + }).ToArray(); + Assert.AreEqual(new JArray(expected).ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetCandidates() + { + var snapshot = _neoSystem.GetSnapshot(); + var result = _rpcServer.GetCandidates(new JArray()); + var json = new JArray(); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + snapshot.Commit(); + var candidates = NativeContract.NEO.GetCandidates(_neoSystem.GetSnapshot()); + + foreach (var candidate in candidates) + { + var item = new JObject(); + item["publickey"] = candidate.PublicKey.ToString(); + item["votes"] = candidate.Votes.ToString(); + item["active"] = validators.Contains(candidate.PublicKey); + json.Add(item); + } + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetCommittee() + { + var snapshot = _neoSystem.GetSnapshot(); + var result = _rpcServer.GetCommittee(new JArray()); + var committee = NativeContract.NEO.GetCommittee(snapshot); + var expected = new JArray(committee.Select(p => (JToken)p.ToString())); + Assert.AreEqual(expected.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetNativeContracts() + { + var result = _rpcServer.GetNativeContracts(new JArray()); + var contracts = new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(_neoSystem.GetSnapshot(), p.Hash).ToJson())); + Assert.AreEqual(contracts.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetBlockByUnknownIndex() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(int.MaxValue, false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnknownHash() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnKnownIndex() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(int.MaxValue, false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnKnownHash() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockHashInvalidIndex() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + Assert.ThrowsException(() => _rpcServer.GetBlockHash(new JArray(block.Index + 1))); + } + + [TestMethod] + public void TestGetContractStateUnknownContract() + { + var snapshot = _neoSystem.GetSnapshot(); + var randomHash = TestUtils.RandomUInt160(); + try + { + _rpcServer.GetContractState(new JArray(randomHash.ToString())); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownContract.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetStorageUnknownContract() + { + var snapshot = _neoSystem.GetSnapshot(); + var randomHash = TestUtils.RandomUInt160(); + var key = new byte[] { 0x01 }; + try + { + _rpcServer.GetStorage(new JArray(randomHash.ToString(), Convert.ToBase64String(key))); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownContract.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetStorageUnknownStorageItem() + { + var snapshot = _neoSystem.GetSnapshot(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + snapshot.Commit(); + + var key = new byte[] { 0x01 }; + try + { + _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownStorageItem.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetTransactionHeightUnknownTransaction() + { + var randomHash = TestUtils.RandomUInt256(); + try + { + _rpcServer.GetTransactionHeight(new JArray(randomHash.ToString())); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownTransaction.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetRawTransactionUnknownTransaction() + { + var randomHash = TestUtils.RandomUInt256(); + try + { + _rpcServer.GetRawTransaction(new JArray(randomHash.ToString(), true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownTransaction.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockInvalidParams() + { + try + { + _rpcServer.GetBlock(new JArray("invalid_hash", false)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetBlockHashInvalidParams() + { + try + { + _rpcServer.GetBlockHash(new JArray("invalid_index")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockHeaderInvalidParams() + { + try + { + _rpcServer.GetBlockHeader(new JArray("invalid_hash", true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetContractStateInvalidParams() + { + try + { + _rpcServer.GetContractState(new JArray("invalid_hash")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetStorageInvalidParams() + { + try + { + _rpcServer.GetStorage(new JArray("invalid_hash", "invalid_key")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestFindStorageInvalidParams() + { + try + { + _rpcServer.FindStorage(new JArray("invalid_hash", "invalid_prefix", "invalid_start")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetTransactionHeightInvalidParams() + { + try + { + _rpcServer.GetTransactionHeight(new JArray("invalid_hash")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetRawTransactionInvalidParams() + { + try + { + _rpcServer.GetRawTransaction(new JArray("invalid_hash", true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestInternalServerError() + { + _memoryStore.Reset(); + try + { + _rpcServer.GetCandidates(new JArray()); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InternalServerError.Code, ex.HResult); + } + } + + [TestMethod] + public void TestUnknownHeight() + { + try + { + _rpcServer.GetBlockHash(new JArray(int.MaxValue)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownHeight.Code, ex.HResult); + } + } + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs index 7bca550b83..4ba95b7798 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -11,9 +11,12 @@ using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Neo.Ledger; using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.Wallets; +using Neo.Wallets.NEP6; using System; using System.Text; @@ -22,39 +25,49 @@ namespace Neo.Plugins.RpcServer.Tests [TestClass] public partial class UT_RpcServer { - private Mock _systemMock; - private SnapshotCache _snapshotCache; - private MemoryPool _memoryPool; - private RpcServerSettings _settings; + private NeoSystem _neoSystem; private RpcServer _rpcServer; + private TestMemoryStoreProvider _memoryStoreProvider; + private MemoryStore _memoryStore; + private readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); + private WalletAccount _walletAccount; [TestInitialize] public void TestSetup() { - // Mock IReadOnlyStore - var mockStore = new Mock(); - - // Initialize SnapshotCache with the mock IReadOnlyStore - _snapshotCache = new SnapshotCache(mockStore.Object); - - // Initialize NeoSystem - var neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()); - - // Initialize MemoryPool with the NeoSystem - _memoryPool = new MemoryPool(neoSystem); - - // Set up the mock system with the correct constructor arguments - _systemMock = new Mock(_snapshotCache, _memoryPool); + _memoryStore = new MemoryStore(); + _memoryStoreProvider = new TestMemoryStoreProvider(_memoryStore); + var protocolSettings = TestProtocolSettings.Default; + _neoSystem = new NeoSystem(protocolSettings, _memoryStoreProvider); + _rpcServer = new RpcServer(_neoSystem, RpcServerSettings.Default); + _walletAccount = _wallet.CreateAccount(); + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); + var snapshot = _neoSystem.GetSnapshot(); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); + } - _rpcServer = new RpcServer(_systemMock.Object, RpcServerSettings.Default); + [TestCleanup] + public void TestCleanup() + { + _memoryStore.Reset(); + var snapshot = _neoSystem.GetSnapshot(); + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); } [TestMethod] public void TestCheckAuth_ValidCredentials_ReturnsTrue() { + // Arrange var context = new DefaultHttpContext(); context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:testpass")); + // Act var result = _rpcServer.CheckAuth(context); + // Assert Assert.IsTrue(result); } } diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs index 491b4fe21e..cedc68bb4d 100644 --- a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs +++ b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs @@ -70,7 +70,7 @@ public void TestValidTransaction() // Make transaction - var tx = CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); + var tx = TestUtils.CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); senderProbe.Send(system.Blockchain, tx); senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed); @@ -91,30 +91,6 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) }; } - private static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce) - { - var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = account, - Value = new BigDecimal(BigInteger.One,8) - } - }, - account); - - tx.Nonce = nonce; - - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); - Assert.IsNull(data.GetSignatures(tx.Sender)); - Assert.IsTrue(wallet.Sign(data)); - Assert.IsTrue(data.Completed); - Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count()); - - tx.Witnesses = data.GetWitnesses(); - return tx; - } [TestMethod] public void TestMaliciousOnChainConflict() @@ -141,9 +117,9 @@ public void TestMaliciousOnChainConflict() // Create transactions: // tx1 conflicts with tx2 and has the same sender (thus, it's a valid conflict and must prevent tx2 from entering the chain); // tx2 conflicts with tx3 and has different sender (thus, this conflict is invalid and must not prevent tx3 from entering the chain). - var tx1 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); - var tx2 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); - var tx3 = CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); + var tx1 = TestUtils.CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); + var tx2 = TestUtils.CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); + var tx3 = TestUtils.CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx2.Hash }, new Conflicts() { Hash = tx3.Hash } }; diff --git a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs index 7942847f94..ea797f8be3 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs @@ -69,11 +69,11 @@ public void TestGetBlock() Transaction = tx2, BlockIndex = 1 }; - UT_SmartContractHelper.TransactionAdd(snapshot, state1, state2); + TestUtils.TransactionAdd(snapshot, state1, state2); TrimmedBlock tblock = GetTrimmedBlockWithNoTransaction(); tblock.Hashes = new UInt256[] { tx1.Hash, tx2.Hash }; - UT_SmartContractHelper.BlocksAdd(snapshot, tblock.Hash, tblock); + TestUtils.BlocksAdd(snapshot, tblock.Hash, tblock); Block block = NativeContract.Ledger.GetBlock(snapshot, tblock.Hash); diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs index df33505965..a597c88cb6 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs @@ -56,7 +56,7 @@ public void TrimTest() TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _, out _); uut.Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }; - UT_SmartContractHelper.BlocksAdd(snapshot, uut.Hash, new TrimmedBlock() + TestUtils.BlocksAdd(snapshot, uut.Hash, new TrimmedBlock() { Header = new Header { diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 51f26d22eb..06290922da 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -476,7 +476,7 @@ public void TestBlockchain_GetTransactionHeight() BlockIndex = 0, Transaction = TestUtils.CreateRandomHashTransaction() }; - UT_SmartContractHelper.TransactionAdd(engine.Snapshot, state); + TestUtils.TransactionAdd(engine.Snapshot, state); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.Ledger.Hash, "getTransactionHeight", state.Transaction.Hash); diff --git a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs index c14b15c241..c1161213ce 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs @@ -27,9 +27,6 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_SmartContractHelper { - const byte Prefix_Block = 5; - const byte Prefix_BlockHash = 9; - const byte Prefix_Transaction = 11; [TestMethod] public void TestIsMultiSigContract() @@ -129,7 +126,7 @@ public void TestVerifyWitnesses() { var snapshot1 = TestBlockchain.GetTestSnapshot().CreateSnapshot(); UInt256 index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); - BlocksAdd(snapshot1, index1, new TrimmedBlock() + TestUtils.BlocksAdd(snapshot1, index1, new TrimmedBlock() { Header = new Header { @@ -141,7 +138,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }); - BlocksDelete(snapshot1, index1); + TestUtils.BlocksDelete(snapshot1, index1); Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, TestProtocolSettings.Default, snapshot1, 100)); var snapshot2 = TestBlockchain.GetTestSnapshot(); @@ -158,7 +155,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - BlocksAdd(snapshot2, index2, block2); + TestUtils.BlocksAdd(snapshot2, index2, block2); Header header2 = new() { PrevHash = index2, Witness = new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } }; snapshot2.AddContract(UInt160.Zero, new ContractState()); @@ -179,7 +176,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - BlocksAdd(snapshot3, index3, block3); + TestUtils.BlocksAdd(snapshot3, index3, block3); Header header3 = new() { PrevHash = index3, @@ -213,25 +210,5 @@ public void TestVerifyWitnesses() Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, TestProtocolSettings.Default, snapshot3, 1000)); } - - private static void BlocksDelete(DataCache snapshot, UInt256 hash) - { - snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); - snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); - } - - public static void TransactionAdd(DataCache snapshot, params TransactionState[] txs) - { - foreach (TransactionState tx in txs) - { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); - } - } - - public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) - { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); - } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs index 7791b8acef..76448855d7 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -84,7 +84,7 @@ public void System_Blockchain_GetBlock() var height = snapshot[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); height.Index = block.Index + TestProtocolSettings.Default.MaxTraceableBlocks; - UT_SmartContractHelper.BlocksAdd(snapshot, block.Hash, block); + TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Hash), new StorageItem(new TransactionState { BlockIndex = block.Index, diff --git a/tests/Neo.UnitTests/TestUtils.Block.cs b/tests/Neo.UnitTests/TestUtils.Block.cs new file mode 100644 index 0000000000..4f663fec2d --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Block.cs @@ -0,0 +1,144 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.Block.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.Util.Internal; +using Neo.Cryptography; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.UnitTests; + +public partial class TestUtils +{ + const byte Prefix_Block = 5; + const byte Prefix_BlockHash = 9; + const byte Prefix_Transaction = 11; + + /// + /// Test Util function SetupHeaderWithValues + /// + /// The header to be assigned + /// PrevHash + /// MerkleRoot + /// NextConsensus + /// Timestamp + /// Index + /// Nonce + /// Witness + public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal) + { + header.PrevHash = val256; + header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); + header.Timestamp = timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM + header.Index = indexVal = 0; + header.Nonce = nonceVal = 0; + header.NextConsensus = val160 = UInt160.Zero; + header.Witness = scriptVal = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = new[] { (byte)OpCode.PUSH1 } + }; + } + + public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) + { + Header header = new Header(); + SetupHeaderWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); + + transactionsVal = new Transaction[numberOfTransactions]; + if (numberOfTransactions > 0) + { + for (int i = 0; i < numberOfTransactions; i++) + { + transactionsVal[i] = GetTransaction(UInt160.Zero); + } + } + + block.Header = header; + block.Transactions = transactionsVal; + + header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + } + + public static Block CreateBlockWithValidTransactions(DataCache snapshot, NEP6Wallet wallet, WalletAccount account, int numberOfTransactions) + { + var block = new Block(); + + var transactions = new List(); + for (var i = 0; i < numberOfTransactions; i++) + { + transactions.Add(CreateValidTx(snapshot, wallet, account)); + } + + var header = new Header(); + SetupHeaderWithValues(header, RandomUInt256(), out _, out _, out _, out _, out _, out _); + + block.Header = header; + block.Transactions = transactions.ToArray(); + + header.MerkleRoot = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + return block; + } + + public static void BlocksDelete(DataCache snapshot, UInt256 hash) + { + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); + } + + public static void TransactionAdd(DataCache snapshot, params TransactionState[] txs) + { + foreach (var tx in txs) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); + } + } + + public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); + } + + public static void BlocksAdd(DataCache snapshot, UInt256 hash, Block block) + { + + block.Transactions.ForEach(tx => + { + var state = new TransactionState + { + BlockIndex = block.Index, + Transaction = tx + }; + TransactionAdd(snapshot, state); + }); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToTrimmedBlock().ToArray())); + } + + public static TrimmedBlock ToTrimmedBlock(this Block block) + { + return new TrimmedBlock + { + Header = block.Header, + Hashes = block.Transactions.Select(p => p.Hash).ToArray() + }; + } +} diff --git a/tests/Neo.UnitTests/TestUtils.cs b/tests/Neo.UnitTests/TestUtils.cs index f8f63de53a..55492d882c 100644 --- a/tests/Neo.UnitTests/TestUtils.cs +++ b/tests/Neo.UnitTests/TestUtils.cs @@ -10,25 +10,43 @@ // modifications are permitted. using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; +using Neo.Wallets; using Neo.Wallets.NEP6; using System; using System.Linq; +using System.Numerics; namespace Neo.UnitTests { - public static class TestUtils + public static partial class TestUtils { public static readonly Random TestRandom = new Random(1337); // use fixed seed for guaranteed determinism + public static UInt256 RandomUInt256() + { + byte[] data = new byte[32]; + TestRandom.NextBytes(data); + return new UInt256(data); + } + + public static UInt160 RandomUInt160() + { + byte[] data = new byte[20]; + TestRandom.NextBytes(data); + return new UInt160(data); + } + public static ContractManifest CreateDefaultManifest() { return new ContractManifest() @@ -111,6 +129,37 @@ public static NEP6Wallet GenerateTestWallet(string password) return new NEP6Wallet(null, password, TestProtocolSettings.Default, wallet); } + public static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, WalletAccount account) + { + return CreateValidTx(snapshot, wallet, account.ScriptHash, (uint)new Random().Next()); + } + + public static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce) + { + var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account, + Value = new BigDecimal(BigInteger.One,8) + } + }, + account); + + tx.Nonce = nonce; + + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + Assert.IsNull(data.GetSignatures(tx.Sender)); + Assert.IsTrue(wallet.Sign(data)); + Assert.IsTrue(data.Completed); + Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count()); + + tx.Witnesses = data.GetWitnesses(); + return tx; + } + + public static Transaction GetTransaction(UInt160 sender) { return new Transaction @@ -133,7 +182,7 @@ public static Transaction GetTransaction(UInt160 sender) }; } - internal static ContractState GetContract(string method = "test", int parametersCount = 0) + public static ContractState GetContract(string method = "test", int parametersCount = 0) { NefFile nef = new() { @@ -188,50 +237,13 @@ internal static StorageKey GetStorageKey(int id, byte[] keyValue) }; } - /// - /// Test Util function SetupHeaderWithValues - /// - /// The header to be assigned - /// PrevHash - /// MerkleRoot - /// NextConsensus - /// Timestamp - /// Index - /// Nonce - /// Witness - public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal) - { - header.PrevHash = val256; - header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); - header.Timestamp = timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM - header.Index = indexVal = 0; - header.Nonce = nonceVal = 0; - header.NextConsensus = val160 = UInt160.Zero; - header.Witness = scriptVal = new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSH1 } - }; - } - - public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) + public static void StorageItemAdd(DataCache snapshot, int id, byte[] keyValue, byte[] value) { - Header header = new Header(); - SetupHeaderWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); - - transactionsVal = new Transaction[numberOfTransactions]; - if (numberOfTransactions > 0) + snapshot.Add(new StorageKey { - for (int i = 0; i < numberOfTransactions; i++) - { - transactionsVal[i] = GetTransaction(UInt160.Zero); - } - } - - block.Header = header; - block.Transactions = transactionsVal; - - header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + Id = id, + Key = keyValue + }, new StorageItem(value)); } public static Transaction CreateRandomHashTransaction()