diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..5acd074d26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +############################### +# Core EditorConfig Options # +############################### + +# dotnet-format requires version 3.1.37601 +# dotnet tool update -g dotnet-format +# remember to have: git config --global core.autocrlf false #(which is usually default) + +root = true + +# Every file + +[*] +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +end_of_line = lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..f69eccca73 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to detail an error or unexpected behavior +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open the project, run '...' +2. Type '...' or do '...' +3. ... + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Platform:** + - OS: [e.g. Windows 10 x64] + - Version [e.g. neo-cli 2.10.2] + +**(Optional) Additional context** +Add any other context about the problem here. + +However, if your issue does not fit these aforementioned criteria, or it can be understood in another manner, feel free to open it in a different format. diff --git a/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md new file mode 100644 index 0000000000..a2ddf9047c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md @@ -0,0 +1,27 @@ +--- +name: Feature or enhancement request +about: Suggest an idea for Neo +title: '' +labels: discussion +assignees: '' +--- + +**Summary** +A summary of the problem you want to solve or metric you want to improve + +**Do you have any solution you want to propose?** +A clear and concise description of what you expect with this change. + +**Where in the software does this update applies to?** +- Compiler +- Consensus +- CLI +- Plugins +- Ledger +- Network Policy +- P2P (TCP) +- RPC (HTTP) +- SDK +- VM +- Other: + diff --git a/.github/ISSUE_TEMPLATE/questions.md b/.github/ISSUE_TEMPLATE/questions.md new file mode 100644 index 0000000000..4d97925f89 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/questions.md @@ -0,0 +1,9 @@ +--- +name: Questions +about: Questions about Neo Platform +title: '' +labels: question +assignees: '' +--- + +**Delete this: We would like to use GitHub for bug reports and feature requests only however if you are unable to get support from our team in: our [Discord](https://discord.io/neo) server or in our [offical documentation](https://docs.neo.org/docs/en-us/index.html), feel encouraged to create an issue here on GitHub.** diff --git a/.github/images/compiler.png b/.github/images/compiler.png new file mode 100644 index 0000000000..affa7ca8ac Binary files /dev/null and b/.github/images/compiler.png differ diff --git a/.github/images/consensus.png b/.github/images/consensus.png new file mode 100644 index 0000000000..b1c6a009c5 Binary files /dev/null and b/.github/images/consensus.png differ diff --git a/.github/images/cosmetic.png b/.github/images/cosmetic.png new file mode 100644 index 0000000000..8793610920 Binary files /dev/null and b/.github/images/cosmetic.png differ diff --git a/.github/images/discussion.png b/.github/images/discussion.png new file mode 100644 index 0000000000..c9b09dcfe5 Binary files /dev/null and b/.github/images/discussion.png differ diff --git a/.github/images/enhancement.png b/.github/images/enhancement.png new file mode 100644 index 0000000000..34fc8c9775 Binary files /dev/null and b/.github/images/enhancement.png differ diff --git a/.github/images/house-keeping.png b/.github/images/house-keeping.png new file mode 100644 index 0000000000..84db938661 Binary files /dev/null and b/.github/images/house-keeping.png differ diff --git a/.github/images/ledger.png b/.github/images/ledger.png new file mode 100644 index 0000000000..babf6a3ca7 Binary files /dev/null and b/.github/images/ledger.png differ diff --git a/.github/images/migration.png b/.github/images/migration.png new file mode 100644 index 0000000000..69ca781055 Binary files /dev/null and b/.github/images/migration.png differ diff --git a/.github/images/network-policy.png b/.github/images/network-policy.png new file mode 100644 index 0000000000..84452a613e Binary files /dev/null and b/.github/images/network-policy.png differ diff --git a/.github/images/new-feature.png b/.github/images/new-feature.png new file mode 100644 index 0000000000..fba5012ff4 Binary files /dev/null and b/.github/images/new-feature.png differ diff --git a/.github/images/p2p.png b/.github/images/p2p.png new file mode 100644 index 0000000000..d638bd2342 Binary files /dev/null and b/.github/images/p2p.png differ diff --git a/.github/images/ready-to-implement.png b/.github/images/ready-to-implement.png new file mode 100644 index 0000000000..b5366b2c85 Binary files /dev/null and b/.github/images/ready-to-implement.png differ diff --git a/.github/images/rpc.png b/.github/images/rpc.png new file mode 100644 index 0000000000..24b6c35375 Binary files /dev/null and b/.github/images/rpc.png differ diff --git a/.github/images/sdk.png b/.github/images/sdk.png new file mode 100644 index 0000000000..7e46ae0f74 Binary files /dev/null and b/.github/images/sdk.png differ diff --git a/.github/images/solution-design.png b/.github/images/solution-design.png new file mode 100644 index 0000000000..552c083d0b Binary files /dev/null and b/.github/images/solution-design.png differ diff --git a/.github/images/to-review.png b/.github/images/to-review.png new file mode 100644 index 0000000000..25054ecc88 Binary files /dev/null and b/.github/images/to-review.png differ diff --git a/.github/images/vm.png b/.github/images/vm.png new file mode 100644 index 0000000000..103970371d Binary files /dev/null and b/.github/images/vm.png differ diff --git a/.github/images/wallet.png b/.github/images/wallet.png new file mode 100644 index 0000000000..42705c14ff Binary files /dev/null and b/.github/images/wallet.png differ diff --git a/.travis.yml b/.travis.yml index 516dd5855c..f08f8110bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,16 +10,32 @@ osx_image: xcode9.1 mono: none dotnet: 2.2.300 +env: + - TEST_SUITE="without-cultures" + - TEST_SUITE="cultures" + before_install: - - cd neo.UnitTests - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 2048; fi +install: + - dotnet tool install -g dotnet-format --version 4.0.40103 --add-source https://dotnet.myget.org/F/format/api/v3/index.json + - export PATH="$PATH:$HOME/.dotnet/tools" + - dotnet-format --version +before_script: + - echo "Checking format..." + - dotnet format --check --dry-run -w . -v diagnostic # check C# formatting for neo.sln + - cd neo.UnitTests +script: | + dotnet restore + if [[ "$TEST_SUITE" == cultures ]]; then + dotnet test + else + find * -name *.csproj | xargs -I % dotnet add % package coverlet.msbuild + dotnet test -v n --filter FullyQualifiedName!=Neo.UnitTests.UT_Culture.All_Tests_Cultures /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + fi +after_success: | + if [[ "$TEST_SUITE" == "without-cultures" ]]; then + echo "Test Success - Branch($TRAVIS_BRANCH) Pull Request($TRAVIS_PULL_REQUEST) Tag($TRAVIS_TAG)" + bash <(curl -s https://codecov.io/bash) -v + fi -script: - - dotnet restore - - find * -name *.csproj | xargs -I % dotnet add % package coverlet.msbuild - - dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover -after_success: - # After all tests OK, Send CodeDov report - - echo "Test Success - Branch($TRAVIS_BRANCH) Pull Request($TRAVIS_PULL_REQUEST) Tag($TRAVIS_TAG)" - - bash <(curl -s https://codecov.io/bash) -v diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..5fd0592924 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,82 @@ + +# Contributing to NEO +Neo is an open-source project and it depends on its contributors and constant community feedback to implement the features required for a smart economy. You are more than welcome to join us in the development of Neo. + +Read this document to understand how issues are organized and how you can start contributing. + +*This document covers this repository only and does not include community repositories or repositories managed by NGD Shanghai and NGD Seattle.* + +### Questions and Support +The issue list is reserved exclusively for bug reports and features discussions. If you have questions or need support, please visit us in our [Discord](https://discord.io/neo) server. + +### dApp Development +This document does not relate to dApp development. If you are looking to build a dApp using Neo, please [start here](https://neo.org/dev). + +### Contributing to open source projects +If you are new to open-source development, please [read here](https://opensource.guide/how-to-contribute/#opening-a-pull-request) how to submit your code. + +## Developer Guidance +We try to have as few rules as possible, just enough to keep the project organized: + + +1. **Discuss before coding**. Proposals must be discussed before being implemented. +Avoid implementing issues with the discussion tag. +2. **Tests during code review**. We expect reviewers to test the issue before approving or requesting changes. + +3. **Wait for at least 2 reviews before merging**. Changes can be merged after 2 approvals, for Neo 3.x branch, and 3 approvals for Neo 2.x branch. + +3. **Give time to other developers review an issue**. Even if the code has been approved, you should leave at least 24 hours for others to review it before merging the code. + +4. **Create unit tests**. It is important that the developer includes basic unit tests so reviewers can test it. + +5. **Task assignment**. If a developer wants to work in a specific issue, he may ask the team to assign it to himself. The proposer of an issue has priority in task assignment. + + +### Issues for beginners +If you are looking to start contributing to NEO, we suggest you start working on issues with ![](./.github/images/cosmetic.png) or ![](./.github/images/house-keeping.png) tags since they usually do not depend on extensive NEO platform knowledge. + +### Tags for Issues States + +![Discussion](./.github/images/discussion.png) Whenever someone posts a new feature request, the tag discussion is added. This means that there is no consensus if the feature should be implemented or not. Avoid creating PR to solve issues in this state since it may be completely discarded. + +![Design](./.github/images/solution-design.png) When a feature request is accepted by the team, but there is no consensus about the implementation, the issue is tagged with design. We recommend the team to agree in the solution design before anyone attempts to implement it, using text or UML. It is not recommended, but developers can also present their solution using code. +Note that PRs for issues in this state may also be discarded if the team disagree with the proposed solution. + +![Ready-to-implement](./.github/images/ready-to-implement.png) Once the team has agreed on feature and the proposed solution, the issue is tagged with ready-to-implement. When implementing it, please follow the solution accepted by the team. + +### Tags for Issue Types + +![Cosmetic](./.github/images/cosmetic.png) Issues with the cosmetic tag are usually changes in code or documentation that improve user experience without affecting current functionality. These issues are recommended for beginners because they require little to no knowledge about Neo platform. + +![Enhancement](./.github/images/enhancement.png) Enhancements are platform changes that may affect performance, usability or add new features to existing modules. It is recommended that developers have previous knowledge in the platform to work in these improvements, specially in more complicated modules like the compiler, ledger and consensus. + +![Feature](./.github/images/new-feature.png) New features may include large changes in the code base. Some are complex, but some are not. So, a few issues with new-feature may be recommended for starters, specially those related to the rpc and the sdk module. + +![Migration](./.github/images/migration.png) Issues related to the migration from Neo 2 to Neo 3 are tagged with migration. These issues are usually the most complicated ones since they require a deep knowledge in both versions. + +### Tags for Project Modules +These tags do not necessarily represent each module at code level. Modules consensus and compiler are not recommended for beginners. + +![Compiler](./.github/images/compiler.png) Issues that are related or influence the behavior of our C# compiler. Note that the compiler itself is hosted in the [neo-devpack-dotnet](https://github.com/neo-project/neo-devpack-dotnet) repository. + +![Consensus](./.github/images/consensus.png) Changes to consensus are usually harder to make and test. Avoid implementing issues in this module that are not yet decided. + +![Ledger](./.github/images/ledger.png) The ledger is our 'database', any changes in the way we store information or the data-structures have this tag. + +![House-keeping](./.github/images/house-keeping.png) 'Small' enhancements that need to be done in order to keep the project organised and ensure overall quality. These changes may be applied in any place in code, as long as they are small or do not alter current behavior. + +![Network-policy](./.github/images/network-policy.png) Identify issues that affect the network-policy like fees, access list or other related issues. Voting may also be related to the network policy module. + +![P2P](./.github/images/p2p.png) This module includes peer-to-peer message exchange and network optimisations, at TCP or UDP level (not HTTP). + +![RPC](./.github/images/rpc.png) All HTTP communication is handled by the RPC module. This module usually provides support methods since the main communication protocol takes place at the p2p module. + +![VM](./.github/images/vm.png) New features that affect the Neo Virtual Machine or the Interop layer. + +![SDK](./.github/images/sdk.png) Neo provides an SDK to help developers to interact with the blockchain. Changes in this module must not impact other parts of the software. + +![Wallet](./.github/images/wallet.png) Wallets are used to track funds and interact with the blockchain. Note that this module depends on a full node implementation (data stored on local disk). + + + + diff --git a/NuGet.Config b/NuGet.Config index 640fd0fe31..c06788942f 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,4 +1,4 @@ - + diff --git a/README.md b/README.md index 77e2a2e566..ac251a7a77 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,6 @@ Current Coverage Status. - - Current total lines. - License. diff --git a/neo.UnitTests/UT_Consensus.cs b/neo.UnitTests/Consensus/UT_Consensus.cs similarity index 94% rename from neo.UnitTests/UT_Consensus.cs rename to neo.UnitTests/Consensus/UT_Consensus.cs index d410bb5142..14c287bcff 100644 --- a/neo.UnitTests/UT_Consensus.cs +++ b/neo.UnitTests/Consensus/UT_Consensus.cs @@ -18,7 +18,7 @@ using System.Security.Cryptography; using ECPoint = Neo.Cryptography.ECC.ECPoint; -namespace Neo.UnitTests +namespace Neo.UnitTests.Consensus { [TestClass] @@ -47,10 +47,10 @@ public void ConsensusService_Primary_Sends_PrepareRequest_After_OnStart() int timeIndex = 0; var timeValues = new[] { //new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc), // For tests here - new DateTime(1968, 06, 01, 0, 0, 1, DateTimeKind.Utc), // For receiving block - new DateTime(1968, 06, 01, 0, 0, (int) Blockchain.SecondsPerBlock, DateTimeKind.Utc), // For Initialize - new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc), // unused - new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc) // unused + new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc), // For receiving block + new DateTime(1980, 06, 01, 0, 0, (int) Blockchain.MillisecondsPerBlock / 1000, 100, DateTimeKind.Utc), // For Initialize + new DateTime(1980, 06, 01, 0, 0, 15, 001, DateTimeKind.Utc), // unused + new DateTime(1980, 06, 01, 0, 0, 15, 001, DateTimeKind.Utc) // unused }; //TimeProvider.Current.UtcNow.ToTimestamp().Should().Be(4244941711); //1968-06-01 00:00:15 @@ -74,14 +74,14 @@ public void ConsensusService_Primary_Sends_PrepareRequest_After_OnStart() // Creating proposed block Header header = new Header(); - TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal); - header.Size.Should().Be(101); + TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal); + header.Size.Should().Be(105); - Console.WriteLine($"header {header} hash {header.Hash} timstamp {timestampVal}"); + Console.WriteLine($"header {header} hash {header.Hash} timestamp {timestampVal}"); - timestampVal.Should().Be(4244941696); //1968-06-01 00:00:00 - // check basic ConsensusContext - //mockConsensusContext.Object.block_received_time.ToTimestamp().Should().Be(4244941697); //1968-06-01 00:00:01 + timestampVal.Should().Be(328665601001); // GMT: Sunday, June 1, 1980 12:00:01.001 AM + // check basic ConsensusContext + //mockConsensusContext.Object.block_received_time.ToTimestamp().Should().Be(4244941697); //1968-06-01 00:00:01 // ============================================================================ // creating ConsensusService actor @@ -151,13 +151,13 @@ public void TestSerializeAndDeserializeConsensusContext() ViewNumber = 2, Validators = new ECPoint[7] { - ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", Cryptography.ECC.ECCurve.Secp256r1) + ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", Neo.Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", Neo.Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", Neo.Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", Neo.Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", Neo.Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", Neo.Cryptography.ECC.ECCurve.Secp256r1), + ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", Neo.Cryptography.ECC.ECCurve.Secp256r1) }, MyIndex = -1 }; @@ -197,7 +197,7 @@ public void TestSerializeAndDeserializeConsensusContext() consensusContext.CommitPayloads[6] = MakeSignedPayload(consensusContext, new Commit { Signature = sha256.ComputeHash(testTx2.Hash.ToArray()) }, 3, new[] { (byte)'6', (byte)'7' }); } - consensusContext.Block.Timestamp = TimeProvider.Current.UtcNow.ToTimestamp(); + consensusContext.Block.Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS(); consensusContext.ChangeViewPayloads = new ConsensusPayload[consensusContext.Validators.Length]; consensusContext.ChangeViewPayloads[0] = MakeSignedPayload(consensusContext, new ChangeView { ViewNumber = 1, Timestamp = 6 }, 0, new[] { (byte)'A' }); diff --git a/neo.UnitTests/Consensus/UT_ConsensusContext.cs b/neo.UnitTests/Consensus/UT_ConsensusContext.cs new file mode 100644 index 0000000000..9a0dd39ba7 --- /dev/null +++ b/neo.UnitTests/Consensus/UT_ConsensusContext.cs @@ -0,0 +1,163 @@ +using Akka.TestKit.Xunit2; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Consensus; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Linq; + +namespace Neo.UnitTests.Consensus +{ + + [TestClass] + public class UT_ConsensusContext : TestKit + { + ConsensusContext _context; + KeyPair[] _validatorKeys; + + [TestInitialize] + public void TestSetup() + { + TestBlockchain.InitializeMockNeoSystem(); + + var rand = new Random(); + var mockWallet = new Mock(); + mockWallet.Setup(p => p.GetAccount(It.IsAny())).Returns(p => new TestWalletAccount(p)); + + // Create dummy validators + + _validatorKeys = new KeyPair[7]; + for (int x = 0; x < _validatorKeys.Length; x++) + { + var pk = new byte[32]; + rand.NextBytes(pk); + + _validatorKeys[x] = new KeyPair(pk); + } + + _context = new ConsensusContext(mockWallet.Object, TestBlockchain.GetStore()) + { + Validators = _validatorKeys.Select(u => u.PublicKey).ToArray() + }; + _context.Reset(0); + } + + [TestCleanup] + public void Cleanup() + { + Shutdown(); + } + + [TestMethod] + public void TestMaxBlockSize_Good() + { + // Only one tx, is included + + var tx1 = CreateTransactionWithSize(200); + _context.EnsureMaxBlockSize(new Transaction[] { tx1 }); + EnsureContext(_context, tx1); + + // All txs included + + var max = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(_context.Snapshot); + var txs = new Transaction[max]; + + for (int x = 0; x < max; x++) txs[x] = CreateTransactionWithSize(100); + + _context.EnsureMaxBlockSize(txs); + EnsureContext(_context, txs); + } + + [TestMethod] + public void TestMaxBlockSize_Exceed() + { + // Two tx, the last one exceed the size rule, only the first will be included + + var tx1 = CreateTransactionWithSize(200); + var tx2 = CreateTransactionWithSize(256 * 1024); + _context.EnsureMaxBlockSize(new Transaction[] { tx1, tx2 }); + EnsureContext(_context, tx1); + + // Exceed txs number, just MaxTransactionsPerBlock included + + var max = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(_context.Snapshot); + var txs = new Transaction[max + 1]; + + for (int x = 0; x < max; x++) txs[x] = CreateTransactionWithSize(100); + + _context.EnsureMaxBlockSize(txs); + EnsureContext(_context, txs.Take(max).ToArray()); + } + + private Transaction CreateTransactionWithSize(int v) + { + var r = new Random(); + var tx = new Transaction() + { + Cosigners = new Cosigner[0], + Attributes = new TransactionAttribute[0], + NetworkFee = 0, + Nonce = (uint)Environment.TickCount, + Script = new byte[0], + Sender = UInt160.Zero, + SystemFee = 0, + ValidUntilBlock = (uint)r.Next(), + Version = 0, + Witnesses = new Witness[0], + }; + + // Could be higher (few bytes) if varSize grows + tx.Script = new byte[v - tx.Size]; + return tx; + } + + private Block SignBlock(ConsensusContext context) + { + context.Block.MerkleRoot = null; + + for (int x = 0; x < _validatorKeys.Length; x++) + { + _context.MyIndex = x; + + var com = _context.MakeCommit(); + _context.CommitPayloads[_context.MyIndex] = com; + } + + // Manual block sign + + Contract contract = Contract.CreateMultiSigContract(context.M, context.Validators); + ContractParametersContext sc = new ContractParametersContext(context.Block); + for (int i = 0, j = 0; i < context.Validators.Length && j < context.M; i++) + { + if (context.CommitPayloads[i]?.ConsensusMessage.ViewNumber != context.ViewNumber) continue; + sc.AddSignature(contract, context.Validators[i], context.CommitPayloads[i].GetDeserializedMessage().Signature); + j++; + } + context.Block.Witness = sc.GetWitnesses()[0]; + context.Block.Transactions = context.TransactionHashes.Select(p => context.Transactions[p]).ToArray(); + return context.Block; + } + + private void EnsureContext(ConsensusContext context, params Transaction[] expected) + { + // Check all tx + + Assert.AreEqual(expected.Length, context.Transactions.Count); + Assert.IsTrue(expected.All(tx => context.Transactions.ContainsKey(tx.Hash))); + + Assert.AreEqual(expected.Length, context.TransactionHashes.Length); + Assert.IsTrue(expected.All(tx => context.TransactionHashes.Count(t => t == tx.Hash) == 1)); + + // Ensure length + + var block = SignBlock(context); + + Assert.AreEqual(context.GetExpectedBlockSize(), block.Size); + Assert.IsTrue(block.Size < NativeContract.Policy.GetMaxBlockSize(context.Snapshot)); + } + } +} diff --git a/neo.UnitTests/UT_ConsensusServiceMailbox.cs b/neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs similarity index 96% rename from neo.UnitTests/UT_ConsensusServiceMailbox.cs rename to neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs index fded2ab54e..3c58c53473 100644 --- a/neo.UnitTests/UT_ConsensusServiceMailbox.cs +++ b/neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs @@ -1,4 +1,4 @@ -using Akka.TestKit; +using Akka.TestKit; using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -10,7 +10,7 @@ using Akka.Configuration; using Neo.Consensus; -namespace Neo.UnitTests +namespace Neo.UnitTests.Consensus { [TestClass] public class UT_ConsensusServiceMailbox : TestKit diff --git a/neo.UnitTests/Cryptography/ECC/UT_ECDsa.cs b/neo.UnitTests/Cryptography/ECC/UT_ECDsa.cs new file mode 100644 index 0000000000..6c24fcb922 --- /dev/null +++ b/neo.UnitTests/Cryptography/ECC/UT_ECDsa.cs @@ -0,0 +1,55 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Wallets; +using System; +using System.Numerics; +using ECDsa = Neo.Cryptography.ECC.ECDsa; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_ECDsa + { + private KeyPair key = null; + + [TestInitialize] + public void TestSetup() + { + key = UT_Crypto.generateCertainKey(32); + } + + [TestMethod] + public void TestECDsaConstructor() + { + Action action = () => new ECDsa(key.PublicKey); + action.ShouldNotThrow(); + action = () => new ECDsa(key.PrivateKey, key.PublicKey.Curve); + action.ShouldNotThrow(); + } + + [TestMethod] + public void TestGenerateSignature() + { + ECDsa sa = new ECDsa(key.PrivateKey, key.PublicKey.Curve); + byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld"); + for (int i = 0; i < 30; i++) + { + BigInteger[] result = sa.GenerateSignature(message); + result.Length.Should().Be(2); + } + sa = new ECDsa(key.PublicKey); + Action action = () => sa.GenerateSignature(message); + action.ShouldThrow(); + } + + [TestMethod] + public void TestVerifySignature() + { + ECDsa sa = new ECDsa(key.PrivateKey, key.PublicKey.Curve); + byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld"); + BigInteger[] result = sa.GenerateSignature(message); + sa.VerifySignature(message, result[0], result[1]).Should().BeTrue(); + sa.VerifySignature(message, new BigInteger(-100), result[1]).Should().BeFalse(); + } + } +} diff --git a/neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs b/neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs new file mode 100644 index 0000000000..90a31b495d --- /dev/null +++ b/neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs @@ -0,0 +1,69 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using System; +using System.Globalization; +using System.Numerics; +using System.Reflection; + +namespace Neo.UnitTests.Cryptography.ECC +{ + [TestClass] + public class UT_ECFieldElement + { + [TestMethod] + public void TestECFieldElementConstructor() + { + BigInteger input = new BigInteger(100); + Action action = () => new ECFieldElement(input, ECCurve.Secp256k1); + action.ShouldNotThrow(); + + input = ECCurve.Secp256k1.Q; + action = () => new ECFieldElement(input, ECCurve.Secp256k1); + action.ShouldThrow(); + } + + [TestMethod] + public void TestEquals() + { + BigInteger input = new BigInteger(100); + object element = new ECFieldElement(input, ECCurve.Secp256k1); + element.Equals(element).Should().BeTrue(); + element.Equals(1).Should().BeFalse(); + + input = new BigInteger(200); + element.Equals(new ECFieldElement(input, ECCurve.Secp256k1)).Should().BeFalse(); + } + + [TestMethod] + public void TestSqrt() + { + ECFieldElement element = new ECFieldElement(new BigInteger(100), ECCurve.Secp256k1); + element.Sqrt().Should().Be(new ECFieldElement(BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007908834671653"), ECCurve.Secp256k1)); + + ConstructorInfo constructor = typeof(ECCurve).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(BigInteger), typeof(BigInteger), typeof(BigInteger), typeof(BigInteger), typeof(byte[]) }, null); + ECCurve testCruve = constructor.Invoke(new object[] { + BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFF0", NumberStyles.AllowHexSpecifier), + BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFF00", NumberStyles.AllowHexSpecifier), + BigInteger.Parse("005AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", NumberStyles.AllowHexSpecifier), + BigInteger.Parse("00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", NumberStyles.AllowHexSpecifier), + ("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes() }) as ECCurve; + element = new ECFieldElement(new BigInteger(200), testCruve); + element.Sqrt().Should().Be(null); + } + + [TestMethod] + public void TestToByteArray() + { + byte[] result = new byte[32]; + result[31] = 100; + new ECFieldElement(new BigInteger(100), ECCurve.Secp256k1).ToByteArray().Should().BeEquivalentTo(result); + + byte[] result2 = { 2, 53, 250, 221, 129, 194, 130, 43, 179, 240, 120, 119, 151, 61, 80, 242, 139, 242, 42, 49, 190, 142, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + new ECFieldElement(BigInteger.Pow(new BigInteger(10), 75), ECCurve.Secp256k1).ToByteArray().Should().BeEquivalentTo(result2); + + byte[] result3 = { 221, 21, 254, 134, 175, 250, 217, 18, 73, 239, 14, 183, 19, 243, 158, 190, 170, 152, 123, 110, 111, 210, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + new ECFieldElement(BigInteger.Pow(new BigInteger(10), 77), ECCurve.Secp256k1).ToByteArray().Should().BeEquivalentTo(result3); + } + } +} diff --git a/neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs b/neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs new file mode 100644 index 0000000000..26d71cc3e6 --- /dev/null +++ b/neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs @@ -0,0 +1,352 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.IO; +using System; +using System.IO; +using System.Linq; +using System.Numerics; +using ECCurve = Neo.Cryptography.ECC.ECCurve; +using ECPoint = Neo.Cryptography.ECC.ECPoint; + +namespace Neo.UnitTests.Cryptography.ECC +{ + [TestClass] + public class UT_ECPoint + { + public static byte[] generatePrivateKey(int privateKeyLength) + { + byte[] privateKey = new byte[privateKeyLength]; + for (int i = 0; i < privateKeyLength; i++) + { + privateKey[i] = (byte)((byte)i % byte.MaxValue); + } + return privateKey; + } + + [TestMethod] + public void TestCompareTo() + { + ECFieldElement X1 = new ECFieldElement(new BigInteger(100), ECCurve.Secp256k1); + ECFieldElement Y1 = new ECFieldElement(new BigInteger(200), ECCurve.Secp256k1); + ECFieldElement X2 = new ECFieldElement(new BigInteger(300), ECCurve.Secp256k1); + ECFieldElement Y2 = new ECFieldElement(new BigInteger(400), ECCurve.Secp256k1); + ECPoint point1 = new ECPoint(X1, Y1, ECCurve.Secp256k1); + ECPoint point2 = new ECPoint(X2, Y1, ECCurve.Secp256k1); + ECPoint point3 = new ECPoint(X1, Y2, ECCurve.Secp256k1); + + point1.CompareTo(point1).Should().Be(0); + point1.CompareTo(point2).Should().Be(-1); + point2.CompareTo(point1).Should().Be(1); + point1.CompareTo(point3).Should().Be(-1); + point3.CompareTo(point1).Should().Be(1); + } + + [TestMethod] + public void TestECPointConstructor() + { + ECPoint point = new ECPoint(); + point.X.Should().BeNull(); + point.Y.Should().BeNull(); + point.Curve.Should().Be(ECCurve.Secp256r1); + + ECFieldElement X = new ECFieldElement(new BigInteger(100), ECCurve.Secp256k1); + ECFieldElement Y = new ECFieldElement(new BigInteger(200), ECCurve.Secp256k1); + point = new ECPoint(X, Y, ECCurve.Secp256k1); + point.X.Should().Be(X); + point.Y.Should().Be(Y); + point.Curve.Should().Be(ECCurve.Secp256k1); + Action action = () => new ECPoint(X, null, ECCurve.Secp256k1); + action.ShouldThrow(); + action = () => new ECPoint(null, Y, ECCurve.Secp256k1); + action.ShouldThrow(); + } + + [TestMethod] + public void TestDecodePoint() + { + byte[] input1 = { 0 }; + Action action = () => ECPoint.DecodePoint(input1, ECCurve.Secp256k1); + action.ShouldThrow(); + + byte[] input2 = { 4, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 72, + 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, 17, 8, 168, 253, 23, 180, 72, 166, 133, 84, 25, 156, 71, 208, 143, 251, 16, 212, 184 }; + ECPoint.DecodePoint(input2, ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + action = () => ECPoint.DecodePoint(input2.Take(32).ToArray(), ECCurve.Secp256k1); + action.ShouldThrow(); + + byte[] input3 = { 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152 }; + byte[] input4 = { 3, 107, 23, 209, 242, 225, 44, 66, 71, 248, 188, 230, 229, 99, 164, 64, 242, 119, 3, 125, 129, 45, 235, 51, 160, 244, 161, 57, 69, 216, 152, 194, 150 }; + ECPoint.DecodePoint(input3, ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + ECPoint.DecodePoint(input4, ECCurve.Secp256r1).Should().Be(ECCurve.Secp256r1.G); + + action = () => ECPoint.DecodePoint(input3.Take(input3.Length - 1).ToArray(), ECCurve.Secp256k1); + action.ShouldThrow(); + } + + [TestMethod] + public void TestDeserializeFrom() + { + byte[] input1 = { 0 }; + Action action = () => ECPoint.DeserializeFrom(new BinaryReader(new MemoryStream(input1)), ECCurve.Secp256k1); + action.ShouldThrow(); + + byte[] input2 = { 4, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 72, + 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, 17, 8, 168, 253, 23, 180, 72, 166, 133, 84, 25, 156, 71, 208, 143, 251, 16, 212, 184 }; + ECPoint.DeserializeFrom(new BinaryReader(new MemoryStream(input2)), ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + action = () => ECPoint.DeserializeFrom(new BinaryReader(new MemoryStream(input2.Take(32).ToArray())), ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + action.ShouldThrow(); + + byte[] input3 = { 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152 }; + ECPoint.DeserializeFrom(new BinaryReader(new MemoryStream(input3)), ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + byte[] input4 = { 3, 107, 23, 209, 242, 225, 44, 66, 71, 248, 188, 230, 229, 99, 164, 64, 242, 119, 3, 125, 129, 45, 235, 51, 160, 244, 161, 57, 69, 216, 152, 194, 150 }; + ECPoint.DeserializeFrom(new BinaryReader(new MemoryStream(input4)), ECCurve.Secp256r1).Should().Be(ECCurve.Secp256r1.G); + + action = () => ECPoint.DeserializeFrom(new BinaryReader(new MemoryStream(input3.Take(input3.Length - 1).ToArray())), ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + action.ShouldThrow(); + } + + [TestMethod] + public void TestEncodePoint() + { + ECPoint point = new ECPoint(null, null, ECCurve.Secp256k1); + byte[] result1 = { 0 }; + point.EncodePoint(true).Should().BeEquivalentTo(result1); + + point = ECCurve.Secp256k1.G; + byte[] result2 = { 4, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 72, + 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, 17, 8, 168, 253, 23, 180, 72, 166, 133, 84, 25, 156, 71, 208, 143, 251, 16, 212, 184 }; + point.EncodePoint(false).Should().BeEquivalentTo(result2); + + byte[] result3 = { 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152 }; + point.EncodePoint(true).Should().BeEquivalentTo(result3); + + point = ECCurve.Secp256r1.G; + byte[] result4 = { 3, 107, 23, 209, 242, 225, 44, 66, 71, 248, 188, 230, 229, 99, 164, 64, 242, 119, 3, 125, 129, 45, 235, 51, 160, 244, 161, 57, 69, 216, 152, 194, 150 }; + point.EncodePoint(true).Should().BeEquivalentTo(result4); + } + + [TestMethod] + public void TestEquals() + { + ECPoint point = ECCurve.Secp256k1.G; + point.Equals(point).Should().BeTrue(); + point.Equals(null).Should().BeFalse(); + + point = new ECPoint(null, null, ECCurve.Secp256k1); + point.Equals(new ECPoint(null, null, ECCurve.Secp256r1)).Should().BeTrue(); + point.Equals(ECCurve.Secp256r1.G).Should().BeFalse(); + ECCurve.Secp256r1.G.Equals(point).Should().BeFalse(); + + ECFieldElement X1 = new ECFieldElement(new BigInteger(100), ECCurve.Secp256k1); + ECFieldElement Y1 = new ECFieldElement(new BigInteger(200), ECCurve.Secp256k1); + ECFieldElement X2 = new ECFieldElement(new BigInteger(300), ECCurve.Secp256k1); + ECFieldElement Y2 = new ECFieldElement(new BigInteger(400), ECCurve.Secp256k1); + ECPoint point1 = new ECPoint(X1, Y1, ECCurve.Secp256k1); + ECPoint point2 = new ECPoint(X2, Y1, ECCurve.Secp256k1); + ECPoint point3 = new ECPoint(X1, Y2, ECCurve.Secp256k1); + point1.Equals(point2).Should().BeFalse(); + point1.Equals(point3).Should().BeFalse(); + } + + [TestMethod] + public void TestEqualsObject() + { + object point = ECCurve.Secp256k1.G; + point.Equals(point).Should().BeTrue(); + point.Equals(null).Should().BeFalse(); + point.Equals(1u).Should().BeFalse(); + + point = new ECPoint(null, null, ECCurve.Secp256k1); + point.Equals(new ECPoint(null, null, ECCurve.Secp256r1)).Should().BeTrue(); + point.Equals(ECCurve.Secp256r1.G).Should().BeFalse(); + ECCurve.Secp256r1.G.Equals(point).Should().BeFalse(); + + ECFieldElement X1 = new ECFieldElement(new BigInteger(100), ECCurve.Secp256k1); + ECFieldElement Y1 = new ECFieldElement(new BigInteger(200), ECCurve.Secp256k1); + ECFieldElement X2 = new ECFieldElement(new BigInteger(300), ECCurve.Secp256k1); + ECFieldElement Y2 = new ECFieldElement(new BigInteger(400), ECCurve.Secp256k1); + object point1 = new ECPoint(X1, Y1, ECCurve.Secp256k1); + object point2 = new ECPoint(X2, Y1, ECCurve.Secp256k1); + object point3 = new ECPoint(X1, Y2, ECCurve.Secp256k1); + point1.Equals(point2).Should().BeFalse(); + point1.Equals(point3).Should().BeFalse(); + } + + [TestMethod] + public void TestFromBytes() + { + byte[] input1 = { 0 }; + Action action = () => ECPoint.FromBytes(input1, ECCurve.Secp256k1); + action.ShouldThrow(); + + byte[] input2 = { 4, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 72, + 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, 17, 8, 168, 253, 23, 180, 72, 166, 133, 84, 25, 156, 71, 208, 143, 251, 16, 212, 184 }; + ECPoint.FromBytes(input2, ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + + byte[] input3 = { 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152 }; + ECPoint.FromBytes(input3, ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + ECPoint.FromBytes(input2.Skip(1).ToArray(), ECCurve.Secp256k1).Should().Be(ECCurve.Secp256k1.G); + + byte[] input4 = generatePrivateKey(72); + ECPoint.FromBytes(input4, ECCurve.Secp256k1).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("3634473727541135791764834762056624681715094789735830699031648" + + "273128038409767"), ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("18165245710263168158644330920009617039772504630129940696140050972160274286151"), + ECCurve.Secp256k1), ECCurve.Secp256k1)); + + byte[] input5 = generatePrivateKey(96); + ECPoint.FromBytes(input5, ECCurve.Secp256k1).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("1780731860627700044960722568376592200742329637303199754547598" + + "369979440671"), ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("14532552714582660066924456880521368950258152170031413196862950297402215317055"), + ECCurve.Secp256k1), ECCurve.Secp256k1)); + + byte[] input6 = generatePrivateKey(104); + ECPoint.FromBytes(input6, ECCurve.Secp256k1).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("3634473727541135791764834762056624681715094789735830699031648" + + "273128038409767"), ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("18165245710263168158644330920009617039772504630129940696140050972160274286151"), + ECCurve.Secp256k1), ECCurve.Secp256k1)); + } + + [TestMethod] + public void TestGetSize() + { + ECCurve.Secp256k1.G.Size.Should().Be(33); + ECCurve.Secp256k1.Infinity.Size.Should().Be(1); + } + + [TestMethod] + public void TestMultiply() + { + ECPoint p = ECCurve.Secp256k1.G; + BigInteger k = BigInteger.Parse("100"); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("107303582290733097924842193972465022053148211775194373671539518313500194639752"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("103795966108782717446806684023742168462365449272639790795591544606836007446638"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + + k = BigInteger.Parse("10000"); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("55279067612272658004429375184716238028207484982037227804583126224321918234542"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("93139664895507357192565643142424306097487832058389223752321585898830257071353"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + + k = BigInteger.Parse("10000000000000"); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("115045167963494515061513744671884131783397561769819471159495798754884242293003"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("93759167105263077270762304290738437383691912799231615884447658154878797241853"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + + k = BigInteger.Parse("1000000000000000000000000000000000000000"); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("114831276968810911840931876895388845736099852671055832194631099067239418074350"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("16721517996619732311261078486295444964227498319433363271180755596201863690708"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + + k = new BigInteger(generatePrivateKey(100)); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("19222995016448259376216431079553428738726180595337971417371897285865264889977"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("6637081904924493791520919212064582313497884724460823966446023080706723904419"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + + k = new BigInteger(generatePrivateKey(120)); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("79652345192111851576650978679091010173409410384772942769927955775006682639778"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("6460429961979335115790346961011058418773289452368186110818621539624566803831"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + + k = new BigInteger(generatePrivateKey(300)); + ECPoint.Multiply(p, k).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("105331914562708556186724786757483927866790351460145374033180496740107603569412"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("60523670886755698512704385951571322569877668383890769288780681319304421873758"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + } + + [TestMethod] + public void TestDeserialize() + { + ECPoint point = new ECPoint(null, null, ECCurve.Secp256k1); + ISerializable serializable = point; + byte[] input = { 4, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, + 72, 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, 17, 8, 168, 253, 23, 180, 72, 166, 133, 84, 25, 156, 71, 208, 143, 251, 16, 212, 184 }; + serializable.Deserialize(new BinaryReader(new MemoryStream(input))); + point.X.Should().Be(ECCurve.Secp256k1.G.X); + point.Y.Should().Be(ECCurve.Secp256k1.G.Y); + } + + [TestMethod] + public void TestSerialize() + { + MemoryStream stream = new MemoryStream(); + ECPoint point = new ECPoint(null, null, ECCurve.Secp256k1); + ISerializable serializable = point; + serializable.Serialize(new BinaryWriter(stream)); + stream.ToArray().Should().BeEquivalentTo(new byte[] { 0 }); + } + + [TestMethod] + public void TestOpAddition() + { + (ECCurve.Secp256k1.Infinity + ECCurve.Secp256k1.G).Should().Be(ECCurve.Secp256k1.G); + (ECCurve.Secp256k1.G + ECCurve.Secp256k1.Infinity).Should().Be(ECCurve.Secp256k1.G); + (ECCurve.Secp256k1.G + ECCurve.Secp256k1.G).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("8956589192654700423125292042593569236064414582962220983368432" + + "9913297188986597"), ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("12158399299693830322967808612713398636155367887041628176798871954788371653930"), + ECCurve.Secp256k1), ECCurve.Secp256k1)); + (ECCurve.Secp256k1.G + new ECPoint(ECCurve.Secp256k1.G.X, new ECFieldElement(BigInteger.One, ECCurve.Secp256k1), ECCurve.Secp256k1)).Should().Be(ECCurve.Secp256k1.Infinity); + (ECCurve.Secp256k1.G + ECCurve.Secp256k1.G + ECCurve.Secp256k1.G).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("112711660439710606056748659173929673102" + + "114977341539408544630613555209775888121"), ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("2558302798057088369165690587740197640644886825481629506991988" + + "8960541586679410"), ECCurve.Secp256k1), ECCurve.Secp256k1)); + } + + [TestMethod] + public void TestOpMultiply() + { + ECPoint p = null; + byte[] n = new byte[] { 1 }; + Action action = () => p = p * n; + action.ShouldThrow(); + + p = ECCurve.Secp256k1.G; + n = null; + action.ShouldThrow(); + + n = new byte[] { 1 }; + action.ShouldThrow(); + + p = ECCurve.Secp256k1.Infinity; + n = new byte[32]; + (p * n).Should().Be(p); + + p = ECCurve.Secp256k1.G; + (p * n).Should().Be(ECCurve.Secp256k1.Infinity); + + n[0] = 1; + (p * n).Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("63395642421589016740518975608504846303065672135176650115036476193363423546538"), ECCurve.Secp256k1), + new ECFieldElement(BigInteger.Parse("29236048674093813394523910922582374630829081423043497254162533033164154049666"), ECCurve.Secp256k1), ECCurve.Secp256k1)); + } + + [TestMethod] + public void TestOpSubtraction() + { + (ECCurve.Secp256k1.G - ECCurve.Secp256k1.Infinity).Should().Be(ECCurve.Secp256k1.G); + (ECCurve.Secp256k1.G - ECCurve.Secp256k1.G).Should().Be(ECCurve.Secp256k1.Infinity); + } + + [TestMethod] + public void TestOpUnaryNegation() + { + (-ECCurve.Secp256k1.G).Should().Be(new ECPoint(ECCurve.Secp256k1.G.X, -ECCurve.Secp256k1.G.Y, ECCurve.Secp256k1)); + } + + [TestMethod] + public void TestTryParse() + { + ECPoint.TryParse("00", ECCurve.Secp256k1, out ECPoint result).Should().BeFalse(); + result.Should().BeNull(); + + ECPoint.TryParse("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", ECCurve.Secp256k1, + out result).Should().BeTrue(); + result.Should().Be(ECCurve.Secp256k1.G); + } + + [TestMethod] + public void TestTwice() + { + ECCurve.Secp256k1.Infinity.Twice().Should().Be(ECCurve.Secp256k1.Infinity); + new ECPoint(new ECFieldElement(BigInteger.Zero, ECCurve.Secp256k1), new ECFieldElement(BigInteger.Zero, ECCurve.Secp256k1), + ECCurve.Secp256k1).Twice().Should().Be(ECCurve.Secp256k1.Infinity); + ECCurve.Secp256k1.G.Twice().Should().Be(new ECPoint(new ECFieldElement(BigInteger.Parse("89565891926547004231252920425935692360644145829622209833684329913297188986597"), + ECCurve.Secp256k1), new ECFieldElement(BigInteger.Parse("12158399299693830322967808612713398636155367887041628176798871954788371653930"), ECCurve.Secp256k1), + ECCurve.Secp256k1)); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_Base58.cs b/neo.UnitTests/Cryptography/UT_Base58.cs new file mode 100644 index 0000000000..1a539cc42c --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_Base58.cs @@ -0,0 +1,31 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using System; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_Base58 + { + byte[] decoded1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + string encoded1 = "1kA3B2yGe2z4"; + byte[] decoded2 = { 0, 0, 0, 0, 0 }; + string encoded2 = "1111"; + + [TestMethod] + public void TestEncode() + { + Base58.Encode(decoded1).Should().Be(encoded1); + } + + [TestMethod] + public void TestDecode() + { + Base58.Decode(encoded1).Should().BeEquivalentTo(decoded1); + Base58.Decode(encoded2).Should().BeEquivalentTo(decoded2); + Action action = () => Base58.Decode(encoded1 + "l").Should().BeEquivalentTo(decoded1); + action.ShouldThrow(); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_BloomFilter.cs b/neo.UnitTests/Cryptography/UT_BloomFilter.cs new file mode 100644 index 0000000000..5deabe1b8e --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_BloomFilter.cs @@ -0,0 +1,69 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using System; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_BloomFilter + { + [TestMethod] + public void TestAddCheck() + { + int m = 7, n = 10; + uint nTweak = 123456; + byte[] elements = { 0, 1, 2, 3, 4 }; + BloomFilter filter = new BloomFilter(m, n, nTweak); + filter.Add(elements); + filter.Check(elements).Should().BeTrue(); + byte[] anotherElements = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + filter.Check(anotherElements).Should().BeFalse(); + } + + [TestMethod] + public void TestBloomFIlterConstructorGetKMTweak() + { + int m = -7, n = 10; + uint nTweak = 123456; + Action action = () => new BloomFilter(m, n, nTweak); + action.ShouldThrow(); + + m = 7; + n = -10; + action = () => new BloomFilter(m, n, nTweak); + action.ShouldThrow(); + + n = 10; + BloomFilter filter = new BloomFilter(m, n, nTweak); + filter.M.Should().Be(m); + filter.K.Should().Be(n); + filter.Tweak.Should().Be(nTweak); + + byte[] shorterElements = { 0, 1, 2, 3, 4 }; + filter = new BloomFilter(m, n, nTweak, shorterElements); + filter.M.Should().Be(m); + filter.K.Should().Be(n); + filter.Tweak.Should().Be(nTweak); + + byte[] longerElements = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + filter = new BloomFilter(m, n, nTweak, longerElements); + filter.M.Should().Be(m); + filter.K.Should().Be(n); + filter.Tweak.Should().Be(nTweak); + } + + [TestMethod] + public void TestGetBits() + { + int m = 7, n = 10; + uint nTweak = 123456; + byte[] elements = { 0, 1, 2, 3, 4 }; + BloomFilter filter = new BloomFilter(m, n, nTweak); + byte[] result = new byte[m]; + filter.GetBits(result); + foreach (byte value in result) + value.Should().Be(0); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_Crypto.cs b/neo.UnitTests/Cryptography/UT_Crypto.cs new file mode 100644 index 0000000000..c46f25aa69 --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_Crypto.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.Wallets; +using System; +using System.Linq; +using System.Security.Cryptography; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_Crypto + { + private KeyPair key = null; + + public static KeyPair generateKey(int privateKeyLength) + { + byte[] privateKey = new byte[privateKeyLength]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + return new KeyPair(privateKey); + } + + public static KeyPair generateCertainKey(int privateKeyLength) + { + byte[] privateKey = new byte[privateKeyLength]; + for (int i = 0; i < privateKeyLength; i++) + { + privateKey[i] = (byte)((byte)i % byte.MaxValue); + } + return new KeyPair(privateKey); + } + + [TestInitialize] + public void TestSetup() + { + key = generateKey(32); + } + + [TestMethod] + public void TestVerifySignature() + { + byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld"); + byte[] signature = Crypto.Default.Sign(message, key.PrivateKey, key.PublicKey.EncodePoint(false).Skip(1).ToArray()); + Crypto.Default.VerifySignature(message, signature, key.PublicKey.EncodePoint(false)).Should().BeTrue(); + Crypto.Default.VerifySignature(message, signature, key.PublicKey.EncodePoint(false).Skip(1).ToArray()).Should().BeTrue(); + Crypto.Default.VerifySignature(message, signature, key.PublicKey.EncodePoint(false).Skip(1).ToArray()).Should().BeTrue(); + + byte[] wrongKey = new byte[33]; + wrongKey[0] = 0x02; + Crypto.Default.VerifySignature(message, signature, wrongKey).Should().BeFalse(); + + wrongKey[0] = 0x03; + for (int i = 1; i < 33; i++) wrongKey[i] = byte.MaxValue; + Crypto.Default.VerifySignature(message, signature, wrongKey).Should().BeFalse(); + + wrongKey = new byte[36]; + Action action = () => Crypto.Default.VerifySignature(message, signature, wrongKey).Should().BeFalse(); + action.ShouldThrow(); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs new file mode 100644 index 0000000000..67ed2a920d --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs @@ -0,0 +1,226 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.Network.P2P.Payloads; +using System; +using System.Security; +using System.Text; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_Cryptography_Helper + { + [TestMethod] + public void TestAES256Encrypt() + { + byte[] block = Encoding.ASCII.GetBytes("00000000000000000000000000000000"); + byte[] key = Encoding.ASCII.GetBytes("1234567812345678"); + byte[] result = block.AES256Encrypt(key); + string encryptString = result.ToHexString(); + encryptString.Should().Be("f69e0923d8247eef417d6a78944a4b39f69e0923d8247eef417d6a78944a4b39"); + } + + [TestMethod] + public void TestAES256Decrypt() + { + byte[] block = new byte[32]; + byte[] key = Encoding.ASCII.GetBytes("1234567812345678"); + string decryptString = "f69e0923d8247eef417d6a78944a4b39f69e0923d8247eef417d6a78944a4b399ae8fd02b340288a0e7bbff0f0ba54d6"; + for (int i = 0; i < 32; i++) + block[i] = Convert.ToByte(decryptString.Substring(i * 2, 2), 16); + string str = System.Text.Encoding.Default.GetString(block.AES256Decrypt(key)); + str.Should().Be("00000000000000000000000000000000"); + } + + [TestMethod] + public void TestAesEncrypt() + { + byte[] data = Encoding.ASCII.GetBytes("00000000000000000000000000000000"); + byte[] key = Encoding.ASCII.GetBytes("12345678123456781234567812345678"); + byte[] iv = Encoding.ASCII.GetBytes("1234567812345678"); + byte[] result = data.AesEncrypt(key, iv); + + string encryptString = result.ToHexString(); + encryptString.Should().Be("07c748cf7d326782f82e60ebe60e2fac289e84e9ce91c1bc41565d14ecb53640"); + + byte[] nullData = null; + Action action = () => nullData.AesEncrypt(key, iv); + action.ShouldThrow(); + + byte[] nullKey = null; + action = () => data.AesEncrypt(nullKey, iv); + action.ShouldThrow(); + + byte[] nullIv = null; + action = () => data.AesEncrypt(key, nullIv); + action.ShouldThrow(); + + byte[] wrongData = Encoding.ASCII.GetBytes("000000000000000000000000000000001"); ; + action = () => wrongData.AesEncrypt(key, iv); + action.ShouldThrow(); + + byte[] wrongKey = Encoding.ASCII.GetBytes("123456781234567812345678123456780"); ; + action = () => data.AesEncrypt(wrongKey, iv); + action.ShouldThrow(); + + byte[] wrongIv = Encoding.ASCII.GetBytes("12345678123456780"); ; + action = () => data.AesEncrypt(key, wrongIv); + action.ShouldThrow(); + } + + [TestMethod] + public void TestAesDecrypt() + { + byte[] data = new byte[32]; + byte[] key = Encoding.ASCII.GetBytes("12345678123456781234567812345678"); + byte[] iv = Encoding.ASCII.GetBytes("1234567812345678"); + string decryptString = "07c748cf7d326782f82e60ebe60e2fac289e84e9ce91c1bc41565d14ecb5364073f28c9aa7bd6b069e44d8a97beb2b58"; + for (int i = 0; i < 32; i++) + data[i] = Convert.ToByte(decryptString.Substring(i * 2, 2), 16); + string str = System.Text.Encoding.Default.GetString(data.AesDecrypt(key, iv)); + str.Should().Be("00000000000000000000000000000000"); + + byte[] nullData = null; + Action action = () => nullData.AesDecrypt(key, iv); + action.ShouldThrow(); + + byte[] nullKey = null; + action = () => data.AesDecrypt(nullKey, iv); + action.ShouldThrow(); + + byte[] nullIv = null; + action = () => data.AesDecrypt(key, nullIv); + action.ShouldThrow(); + + byte[] wrongData = Encoding.ASCII.GetBytes("00000000000000001"); ; + action = () => wrongData.AesDecrypt(key, iv); + action.ShouldThrow(); + + byte[] wrongKey = Encoding.ASCII.GetBytes("123456781234567812345678123456780"); ; + action = () => data.AesDecrypt(wrongKey, iv); + action.ShouldThrow(); + + byte[] wrongIv = Encoding.ASCII.GetBytes("12345678123456780"); ; + action = () => data.AesDecrypt(key, wrongIv); + action.ShouldThrow(); + } + + [TestMethod] + public void TestBase58CheckDecode() + { + string input = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] result = input.Base58CheckDecode(); + byte[] helloWorld = { 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 }; + result.Should().Equal(helloWorld); + + input = "3v"; + Action action = () => input.Base58CheckDecode(); + action.ShouldThrow(); + + input = "3vQB7B6MrGQZaxCuFg4og"; + action = () => input.Base58CheckDecode(); + action.ShouldThrow(); + } + + [TestMethod] + public void TestSha256() + { + byte[] value = Encoding.ASCII.GetBytes("hello world"); + byte[] result = value.Sha256(0, value.Length); + string resultStr = result.ToHexString(); + resultStr.Should().Be("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"); + } + + [TestMethod] + public void TestTest() + { + int m = 7, n = 10; + uint nTweak = 123456; + BloomFilter filter = new BloomFilter(m, n, nTweak); + + Transaction tx = new Transaction + { + Script = TestUtils.GetByteArray(32, 0x42), + Sender = UInt160.Zero, + SystemFee = 4200000000, + Attributes = new TransactionAttribute[0], + Cosigners = new Cosigner[0], + Witnesses = new[] + { + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } + } + }; + filter.Test(tx).Should().BeFalse(); + + filter.Add(tx.Witnesses[0].ScriptHash.ToArray()); + filter.Test(tx).Should().BeTrue(); + + filter.Add(tx.Hash.ToArray()); + filter.Test(tx).Should().BeTrue(); + } + + [TestMethod] + public void TestStringToAesKey() + { + string password = "hello world"; + string string1 = "bc62d4b80d9e36da29c16c5d4d9f11731f36052c72401a76c23c0fb5a9b74423"; + byte[] byteArray = new byte[string1.Length / 2]; + byteArray = string1.HexToBytes(); + password.ToAesKey().Should().Equal(byteArray); + } + + [TestMethod] + public void TestSecureStringToAesKey() + { + var password = new SecureString(); + password.AppendChar('h'); + password.AppendChar('e'); + password.AppendChar('l'); + password.AppendChar('l'); + password.AppendChar('o'); + password.AppendChar(' '); + password.AppendChar('w'); + password.AppendChar('o'); + password.AppendChar('r'); + password.AppendChar('l'); + password.AppendChar('d'); + string string1 = "bc62d4b80d9e36da29c16c5d4d9f11731f36052c72401a76c23c0fb5a9b74423"; + byte[] byteArray = new byte[string1.Length / 2]; + byteArray = string1.HexToBytes(); + password.ToAesKey().Should().Equal(byteArray); + } + + [TestMethod] + public void TestToArray() + { + var password = new SecureString(); + password.AppendChar('h'); + password.AppendChar('e'); + password.AppendChar('l'); + password.AppendChar('l'); + password.AppendChar('o'); + password.AppendChar(' '); + password.AppendChar('w'); + password.AppendChar('o'); + password.AppendChar('r'); + password.AppendChar('l'); + password.AppendChar('d'); + byte[] byteArray = { 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64 }; + password.ToArray().Should().Equal(byteArray); + + SecureString nullString = null; + Action action = () => nullString.ToArray(); + action.ShouldThrow(); + + var zeroString = new SecureString(); + var result = zeroString.ToArray(); + byteArray = new byte[0]; + result.Should().Equal(byteArray); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_MerkleTree.cs b/neo.UnitTests/Cryptography/UT_MerkleTree.cs new file mode 100644 index 0000000000..4b56380957 --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_MerkleTree.cs @@ -0,0 +1,80 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_MerkleTree + { + public UInt256 GetByteArrayHash(byte[] byteArray) + { + if (byteArray == null || byteArray.Length == 0) throw new ArgumentNullException(); + var hash = new UInt256(Crypto.Default.Hash256(byteArray)); + return hash; + } + + [TestMethod] + public void TestBuildAndDepthFirstSearch() + { + IReadOnlyList hashNull = new UInt256[] { }; + Action action = () => new MerkleTree(hashNull); + action.ShouldThrow(); + + byte[] array1 = { 0x01 }; + var hash1 = GetByteArrayHash(array1); + + byte[] array2 = { 0x02 }; + var hash2 = GetByteArrayHash(array2); + + byte[] array3 = { 0x03 }; + var hash3 = GetByteArrayHash(array3); + + IReadOnlyList hashes = new UInt256[] { hash1, hash2, hash3 }; + MerkleTree tree = new MerkleTree(hashes); + var hashArray = tree.ToHashArray(); + hashArray[0].Should().Be(hash1); + hashArray[1].Should().Be(hash2); + hashArray[2].Should().Be(hash3); + hashArray[3].Should().Be(hash3); + + var rootHash = MerkleTree.ComputeRoot(hashes); + var hash4 = Crypto.Default.Hash256(hash1.ToArray().Concat(hash2.ToArray()).ToArray()); + var hash5 = Crypto.Default.Hash256(hash3.ToArray().Concat(hash3.ToArray()).ToArray()); + var result = new UInt256(Crypto.Default.Hash256(hash4.ToArray().Concat(hash5.ToArray()).ToArray())); + rootHash.Should().Be(result); + } + + [TestMethod] + public void TestTrim() + { + byte[] array1 = { 0x01 }; + var hash1 = GetByteArrayHash(array1); + + byte[] array2 = { 0x02 }; + var hash2 = GetByteArrayHash(array2); + + byte[] array3 = { 0x03 }; + var hash3 = GetByteArrayHash(array3); + + IReadOnlyList hashes = new UInt256[] { hash1, hash2, hash3 }; + MerkleTree tree = new MerkleTree(hashes); + + bool[] boolArray = { false, false, false }; + BitArray bitArray = new BitArray(boolArray); + tree.Trim(bitArray); + var hashArray = tree.ToHashArray(); + + hashArray.Length.Should().Be(1); + var rootHash = MerkleTree.ComputeRoot(hashes); + var hash4 = Crypto.Default.Hash256(hash1.ToArray().Concat(hash2.ToArray()).ToArray()); + var hash5 = Crypto.Default.Hash256(hash3.ToArray().Concat(hash3.ToArray()).ToArray()); + var result = new UInt256(Crypto.Default.Hash256(hash4.ToArray().Concat(hash5.ToArray()).ToArray())); + hashArray[0].Should().Be(result); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs b/neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs new file mode 100644 index 0000000000..78274c2d18 --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs @@ -0,0 +1,51 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using System.Text; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_MerkleTreeNode + { + private MerkleTreeNode node = new MerkleTreeNode(); + + [TestInitialize] + public void TestSetup() + { + node.Hash = null; + node.Parent = null; + node.LeftChild = null; + node.RightChild = null; + } + + [TestMethod] + public void TestConstructor() + { + byte[] byteArray = Encoding.ASCII.GetBytes("hello world"); + var hash = new UInt256(Crypto.Default.Hash256(byteArray)); + node.Hash = hash; + + node.Hash.Should().Be(hash); + node.Parent.Should().BeNull(); + node.LeftChild.Should().BeNull(); + node.RightChild.Should().BeNull(); + } + + [TestMethod] + public void TestGetIsLeaf() + { + node.IsLeaf.Should().BeTrue(); + + MerkleTreeNode child = new MerkleTreeNode(); + node.LeftChild = child; + node.IsLeaf.Should().BeFalse(); + } + + [TestMethod] + public void TestGetIsRoot() + { + node.IsRoot.Should().BeTrue(); + } + } +} diff --git a/neo.UnitTests/Cryptography/UT_Murmur3.cs b/neo.UnitTests/Cryptography/UT_Murmur3.cs new file mode 100644 index 0000000000..3ed3ab22fa --- /dev/null +++ b/neo.UnitTests/Cryptography/UT_Murmur3.cs @@ -0,0 +1,24 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; + +namespace Neo.UnitTests.Cryptography +{ + [TestClass] + public class UT_Murmur3 + { + [TestMethod] + public void TestGetHashSize() + { + Murmur3 murmur3 = new Murmur3(1); + murmur3.HashSize.Should().Be(32); + } + + [TestMethod] + public void TestHashCore() + { + byte[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1 }; + array.Murmur32(10u).Should().Be(378574820u); + } + } +} diff --git a/neo.UnitTests/UT_Scrypt.cs b/neo.UnitTests/Cryptography/UT_SCrypt.cs similarity index 80% rename from neo.UnitTests/UT_Scrypt.cs rename to neo.UnitTests/Cryptography/UT_SCrypt.cs index 8e2bf1e30a..612a8307c6 100644 --- a/neo.UnitTests/UT_Scrypt.cs +++ b/neo.UnitTests/Cryptography/UT_SCrypt.cs @@ -1,11 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; -using System; -namespace Neo.UnitTests +namespace Neo.UnitTests.Cryptography { [TestClass] - public class UT_Scrypt + public class UT_SCrypt { [TestMethod] public void DeriveKeyTest() diff --git a/neo.UnitTests/Extensions/NativeContractExtensions.cs b/neo.UnitTests/Extensions/NativeContractExtensions.cs index 7d33841ef8..4d3f7bcb95 100644 --- a/neo.UnitTests/Extensions/NativeContractExtensions.cs +++ b/neo.UnitTests/Extensions/NativeContractExtensions.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -8,12 +8,12 @@ namespace Neo.UnitTests.Extensions { public static class NativeContractExtensions { - public static StackItem Call(this NativeContract contract, Persistence.Snapshot snapshot, string method, params ContractParameter[] args) + public static StackItem Call(this NativeContract contract, Neo.Persistence.Snapshot snapshot, string method, params ContractParameter[] args) { return Call(contract, snapshot, null, method, args); } - public static StackItem Call(this NativeContract contract, Persistence.Snapshot snapshot, IVerifiable container, string method, params ContractParameter[] args) + public static StackItem Call(this NativeContract contract, Neo.Persistence.Snapshot snapshot, IVerifiable container, string method, params ContractParameter[] args) { var engine = new ApplicationEngine(TriggerType.Application, container, snapshot, 0, true); @@ -37,4 +37,4 @@ public static StackItem Call(this NativeContract contract, Persistence.Snapshot return engine.ResultStack.Pop(); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs index a87fd7c3e1..a531591d34 100644 --- a/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs +++ b/neo.UnitTests/Extensions/Nep5NativeContractExtensions.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -191,4 +191,4 @@ public static string Name(this NativeContract contract) return Encoding.UTF8.GetString((result as VM.Types.ByteArray).GetByteArray()); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/IO/Caching/UT_Cache.cs b/neo.UnitTests/IO/Caching/UT_Cache.cs new file mode 100644 index 0000000000..930547f314 --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_Cache.cs @@ -0,0 +1,256 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Neo.UnitTests.IO.Caching +{ + class MyCache : Cache + { + public MyCache(int max_capacity) : base(max_capacity) { } + + protected override int GetKeyForItem(string item) + { + return item.GetHashCode(); + } + + protected override void OnAccess(CacheItem item) { } + + public IEnumerator MyGetEnumerator() + { + IEnumerable enumerable = this; + return enumerable.GetEnumerator(); + } + } + + class CacheDisposableEntry : IDisposable + { + public int Key { get; set; } + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + } + + class MyDisposableCache : Cache + { + public MyDisposableCache(int max_capacity) : base(max_capacity) { } + + protected override int GetKeyForItem(CacheDisposableEntry item) + { + return item.Key; + } + + protected override void OnAccess(CacheItem item) { } + + public IEnumerator MyGetEnumerator() + { + IEnumerable enumerable = this; + return enumerable.GetEnumerator(); + } + } + + [TestClass] + public class UT_Cache + { + MyCache cache; + readonly int max_capacity = 4; + + [TestInitialize] + public void init() + { + cache = new MyCache(max_capacity); + } + + [TestMethod] + public void TestCount() + { + cache.Count.Should().Be(0); + + cache.Add("hello"); + cache.Add("world"); + cache.Count.Should().Be(2); + + cache.Remove("hello"); + cache.Count.Should().Be(1); + } + + [TestMethod] + public void TestIsReadOnly() + { + cache.IsReadOnly.Should().BeFalse(); + } + + [TestMethod] + public void TestAddAndAddInternal() + { + cache.Add("hello"); + cache.Contains("hello").Should().BeTrue(); + cache.Contains("world").Should().BeFalse(); + cache.Add("hello"); + cache.Count.Should().Be(1); + } + + [TestMethod] + public void TestAddRange() + { + string[] range = { "hello", "world" }; + cache.AddRange(range); + cache.Count.Should().Be(2); + cache.Contains("hello").Should().BeTrue(); + cache.Contains("world").Should().BeTrue(); + cache.Contains("non exist string").Should().BeFalse(); + } + + [TestMethod] + public void TestClear() + { + cache.Add("hello"); + cache.Add("world"); + cache.Count.Should().Be(2); + cache.Clear(); + cache.Count.Should().Be(0); + } + + [TestMethod] + public void TestContainsKey() + { + cache.Add("hello"); + cache.Contains("hello").Should().BeTrue(); + cache.Contains("world").Should().BeFalse(); + } + + [TestMethod] + public void TestContainsValue() + { + cache.Add("hello"); + cache.Contains("hello".GetHashCode()).Should().BeTrue(); + cache.Contains("world".GetHashCode()).Should().BeFalse(); + } + + [TestMethod] + public void TestCopyTo() + { + cache.Add("hello"); + cache.Add("world"); + string[] temp = new string[2]; + + Action action = () => cache.CopyTo(null, 1); + action.ShouldThrow(); + + action = () => cache.CopyTo(temp, -1); + action.ShouldThrow(); + + action = () => cache.CopyTo(temp, 1); + action.ShouldThrow(); + + cache.CopyTo(temp, 0); + temp[0].Should().Be("hello"); + temp[1].Should().Be("world"); + } + + [TestMethod] + public void TestRemoveKey() + { + cache.Add("hello"); + cache.Remove("hello".GetHashCode()).Should().BeTrue(); + cache.Remove("world".GetHashCode()).Should().BeFalse(); + cache.Contains("hello").Should().BeFalse(); + } + + [TestMethod] + public void TestRemoveDisposableKey() + { + var entry = new CacheDisposableEntry() { Key = 1 }; + var dcache = new MyDisposableCache(100); + dcache.Add(entry); + + entry.IsDisposed.Should().BeFalse(); + dcache.Remove(entry.Key).Should().BeTrue(); + dcache.Remove(entry.Key).Should().BeFalse(); + entry.IsDisposed.Should().BeTrue(); + } + + [TestMethod] + public void TestRemoveValue() + { + cache.Add("hello"); + cache.Remove("hello").Should().BeTrue(); + cache.Remove("world").Should().BeFalse(); + cache.Contains("hello").Should().BeFalse(); + } + + [TestMethod] + public void TestTryGet() + { + cache.Add("hello"); + cache.TryGet("hello".GetHashCode(), out string output).Should().BeTrue(); + output.Should().Be("hello"); + cache.TryGet("world".GetHashCode(), out string output2).Should().BeFalse(); + output2.Should().NotBe("world"); + output2.Should().BeNull(); + } + + [TestMethod] + public void TestArrayIndexAccess() + { + cache.Add("hello"); + cache.Add("world"); + cache["hello".GetHashCode()].Should().Be("hello"); + cache["world".GetHashCode()].Should().Be("world"); + + Action action = () => + { + string temp = cache["non exist string".GetHashCode()]; + }; + action.ShouldThrow(); + } + + [TestMethod] + public void TestGetEnumerator() + { + cache.Add("hello"); + cache.Add("world"); + int i = 0; + foreach (string item in cache) + { + if (i == 0) item.Should().Be("hello"); + if (i == 1) item.Should().Be("world"); + i++; + } + i.Should().Be(2); + cache.MyGetEnumerator().Should().NotBeNull(); + } + + [TestMethod] + public void TestOverMaxCapacity() + { + int i = 1; + for (; i <= max_capacity; i++) + { + cache.Add(i.ToString()); + } + cache.Add(i.ToString()); // The first one will be deleted + cache.Count.Should().Be(max_capacity); + cache.Contains((max_capacity + 1).ToString()).Should().BeTrue(); + } + + [TestMethod] + public void TestDispose() + { + cache.Add("hello"); + cache.Add("world"); + cache.Dispose(); + + Action action = () => + { + int count = cache.Count; + }; + action.ShouldThrow(); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_CloneCache.cs b/neo.UnitTests/IO/Caching/UT_CloneCache.cs new file mode 100644 index 0000000000..afa550e45c --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_CloneCache.cs @@ -0,0 +1,127 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.IO.Caching; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.UnitTests.IO.Caching +{ + [TestClass] + public class UT_CloneCache + { + CloneCache cloneCache; + MyDataCache myDataCache; + + [TestInitialize] + public void Init() + { + myDataCache = new MyDataCache(); + cloneCache = new CloneCache(myDataCache); + } + + [TestMethod] + public void TestCloneCache() + { + cloneCache.Should().NotBeNull(); + } + + [TestMethod] + public void TestAddInternal() + { + cloneCache.Add(new MyKey("key1"), new MyValue("value1")); + cloneCache[new MyKey("key1")].Should().Be(new MyValue("value1")); + + cloneCache.Commit(); + myDataCache[new MyKey("key1")].Should().Be(new MyValue("value1")); + } + + [TestMethod] + public void TestDeleteInternal() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + cloneCache.Delete(new MyKey("key1")); // trackable.State = TrackState.Deleted + cloneCache.Commit(); + + cloneCache.TryGet(new MyKey("key1")).Should().BeNull(); + myDataCache.TryGet(new MyKey("key1")).Should().BeNull(); + } + + [TestMethod] + public void TestFindInternal() + { + cloneCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + + var items = cloneCache.Find(new MyKey("key1").ToArray()); + items.ElementAt(0).Key.Should().Be(new MyKey("key1")); + items.ElementAt(0).Value.Should().Be(new MyValue("value1")); + items.Count().Should().Be(1); + + items = cloneCache.Find(new MyKey("key2").ToArray()); + items.ElementAt(0).Key.Should().Be(new MyKey("key2")); + items.ElementAt(0).Value.Should().Be(new MyValue("value2")); + items.Count().Should().Be(1); + + items = cloneCache.Find(new MyKey("key3").ToArray()); + items.ElementAt(0).Key.Should().Be(new MyKey("key3")); + items.ElementAt(0).Value.Should().Be(new MyValue("value3")); + items.Count().Should().Be(1); + + items = cloneCache.Find(new MyKey("key4").ToArray()); + items.Count().Should().Be(0); + } + + [TestMethod] + public void TestGetInternal() + { + cloneCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + + cloneCache[new MyKey("key1")].Should().Be(new MyValue("value1")); + cloneCache[new MyKey("key2")].Should().Be(new MyValue("value2")); + cloneCache[new MyKey("key3")].Should().Be(new MyValue("value3")); + + Action action = () => + { + var item = cloneCache[new MyKey("key4")]; + }; + action.ShouldThrow(); + } + + [TestMethod] + public void TestTryGetInternal() + { + cloneCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + + cloneCache.TryGet(new MyKey("key1")).Should().Be(new MyValue("value1")); + cloneCache.TryGet(new MyKey("key2")).Should().Be(new MyValue("value2")); + cloneCache.TryGet(new MyKey("key3")).Should().Be(new MyValue("value3")); + cloneCache.TryGet(new MyKey("key4")).Should().BeNull(); + } + + [TestMethod] + public void TestUpdateInternal() + { + cloneCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + + cloneCache.GetAndChange(new MyKey("key1")).Value = "value_new_1"; + cloneCache.GetAndChange(new MyKey("key2")).Value = "value_new_2"; + cloneCache.GetAndChange(new MyKey("key3")).Value = "value_new_3"; + + cloneCache.Commit(); + + cloneCache[new MyKey("key1")].Should().Be(new MyValue("value_new_1")); + cloneCache[new MyKey("key2")].Should().Be(new MyValue("value_new_2")); + cloneCache[new MyKey("key3")].Should().Be(new MyValue("value_new_3")); + myDataCache[new MyKey("key2")].Should().Be(new MyValue("value_new_2")); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_CloneMetaCache.cs b/neo.UnitTests/IO/Caching/UT_CloneMetaCache.cs new file mode 100644 index 0000000000..591ea8ea05 --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_CloneMetaCache.cs @@ -0,0 +1,48 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; + +namespace Neo.UnitTests.IO.Caching +{ + [TestClass] + public class UT_CloneMetaCache + { + MyMetaCache myMetaCache; + CloneMetaCache cloneMetaCache; + + [TestInitialize] + public void Init() + { + myMetaCache = new MyMetaCache(() => new MyValue()); + cloneMetaCache = new CloneMetaCache(myMetaCache); + } + + [TestMethod] + public void TestConstructor() + { + cloneMetaCache.Should().NotBeNull(); + } + + [TestMethod] + public void TestTryGetInternal() + { + MyValue value = myMetaCache.GetAndChange(); + value.Value = "value1"; + + cloneMetaCache.Get().Should().Be(value); + } + + [TestMethod] + public void TestUpdateInternal() + { + MyValue value = myMetaCache.GetAndChange(); + value.Value = "value1"; + + MyValue value2 = cloneMetaCache.GetAndChange(); + value2.Value = "value2"; + + cloneMetaCache.Commit(); + value.Value.Should().Be("value2"); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_DataCache.cs b/neo.UnitTests/IO/Caching/UT_DataCache.cs new file mode 100644 index 0000000000..f21624814d --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_DataCache.cs @@ -0,0 +1,347 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.IO.Caching; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Neo.UnitTests.IO.Caching +{ + class MyKey : ISerializable, IEquatable + { + public string Key; + + public int Size => Key.Length; + + public MyKey() { } + + public MyKey(string val) + { + Key = val; + } + + public void Deserialize(BinaryReader reader) + { + Key = reader.ReadString(); + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Key); + } + + public bool Equals(MyKey other) + { + return Key.Equals(other.Key); + } + + public override bool Equals(object obj) + { + if (!(obj is MyKey key)) return false; + return Equals(key); + } + + public override int GetHashCode() + { + return Key.GetHashCode(); + } + } + + public class MyValue : ISerializable, ICloneable + { + public string Value; + + public int Size => Value.Length; + + public MyValue() { } + + public MyValue(string val) + { + Value = val; + } + + public void Deserialize(BinaryReader reader) + { + Value = reader.ReadString(); + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Value); + } + + MyValue ICloneable.Clone() + { + return new MyValue(Value); + } + + void ICloneable.FromReplica(MyValue replica) + { + Value = replica.Value; + } + + public bool Equals(MyValue other) + { + return (Value == null && other.Value == null) || Value.Equals(other.Value); + } + + public override bool Equals(object obj) + { + if (!(obj is MyValue key)) return false; + return Equals(key); + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + } + + class MyDataCache : DataCache + where TKey : IEquatable, ISerializable, new() + where TValue : class, ICloneable, ISerializable, new() + { + public Dictionary InnerDict = new Dictionary(); + + public override void DeleteInternal(TKey key) + { + InnerDict.Remove(key); + } + + protected override void AddInternal(TKey key, TValue value) + { + InnerDict.Add(key, value); + } + + protected override IEnumerable> FindInternal(byte[] key_prefix) + { + return InnerDict.Where(kvp => kvp.Key.ToArray().Take(key_prefix.Length).SequenceEqual(key_prefix)); + } + + protected override TValue GetInternal(TKey key) + { + if (InnerDict.TryGetValue(key, out TValue value)) + { + return value.Clone(); + } + throw new KeyNotFoundException(); + } + + protected override TValue TryGetInternal(TKey key) + { + if (InnerDict.TryGetValue(key, out TValue value)) + { + return value.Clone(); + } + return null; + } + + protected override void UpdateInternal(TKey key, TValue value) + { + InnerDict[key] = value; + } + } + + [TestClass] + public class UT_DataCache + { + MyDataCache myDataCache; + + [TestInitialize] + public void Initialize() + { + myDataCache = new MyDataCache(); + } + + [TestMethod] + public void TestAccessByKey() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + + myDataCache[new MyKey("key1")].Should().Be(new MyValue("value1")); + + // case 2 read from inner + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache[new MyKey("key3")].Should().Be(new MyValue("value3")); + } + + [TestMethod] + public void TestAccessByNotFoundKey() + { + Action action = () => + { + var item = myDataCache[new MyKey("key1")]; + }; + action.ShouldThrow(); + } + + [TestMethod] + public void TestAccessByDeletedKey() + { + myDataCache.InnerDict.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Delete(new MyKey("key1")); + + Action action = () => + { + var item = myDataCache[new MyKey("key1")]; + }; + action.ShouldThrow(); + } + + [TestMethod] + public void TestAdd() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache[new MyKey("key1")].Should().Be(new MyValue("value1")); + + Action action = () => myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + action.ShouldThrow(); + + myDataCache.InnerDict.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.Delete(new MyKey("key2")); // trackable.State = TrackState.Deleted + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); // trackable.State = TrackState.Changed + + action = () => myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + action.ShouldThrow(); + } + + [TestMethod] + public void TestCommit() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); // trackable.State = TrackState.Added + + myDataCache.InnerDict.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.Delete(new MyKey("key2")); // trackable.State = TrackState.Deleted + + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.Delete(new MyKey("key3")); // trackable.State = TrackState.Deleted + myDataCache.Add(new MyKey("key3"), new MyValue("value4")); // trackable.State = TrackState.Changed + + myDataCache.Commit(); + + myDataCache.InnerDict[new MyKey("key1")].Should().Be(new MyValue("value1")); + myDataCache.InnerDict.ContainsKey(new MyKey("key2")).Should().BeFalse(); + myDataCache.InnerDict[new MyKey("key3")].Should().Be(new MyValue("value4")); + } + + [TestMethod] + public void TestCreateSnapshot() + { + myDataCache.CreateSnapshot().Should().NotBeNull(); + } + + [TestMethod] + public void TestDelete() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Delete(new MyKey("key1")); + myDataCache.InnerDict.ContainsKey(new MyKey("key1")).Should().BeFalse(); + + myDataCache.InnerDict.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.Delete(new MyKey("key2")); + myDataCache.Commit(); + myDataCache.InnerDict.ContainsKey(new MyKey("key2")).Should().BeFalse(); + } + + [TestMethod] + public void TestDeleteWhere() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.InnerDict.Add(new MyKey("key4"), new MyValue("value4")); + + myDataCache.DeleteWhere((k, v) => k.Key.StartsWith("key")); + myDataCache.Commit(); + myDataCache.TryGet(new MyKey("key1")).Should().BeNull(); + myDataCache.TryGet(new MyKey("key2")).Should().BeNull(); + myDataCache.InnerDict.ContainsKey(new MyKey("key1")).Should().BeFalse(); + myDataCache.InnerDict.ContainsKey(new MyKey("key2")).Should().BeFalse(); + } + + [TestMethod] + public void TestFind() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); + + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.InnerDict.Add(new MyKey("key4"), new MyValue("value4")); + + var items = myDataCache.Find(new MyKey("key1").ToArray()); + items.ElementAt(0).Key.Should().Be(new MyKey("key1")); + items.ElementAt(0).Value.Should().Be(new MyValue("value1")); + items.Count().Should().Be(1); + + items = myDataCache.Find(new MyKey("key5").ToArray()); + items.Count().Should().Be(0); + } + + [TestMethod] + public void TestGetChangeSet() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); // trackable.State = TrackState.Added + myDataCache.Add(new MyKey("key2"), new MyValue("value2")); // trackable.State = TrackState.Added + + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.InnerDict.Add(new MyKey("key4"), new MyValue("value4")); + myDataCache.Delete(new MyKey("key3")); // trackable.State = TrackState.Deleted + myDataCache.Delete(new MyKey("key4")); // trackable.State = TrackState.Deleted + + var items = myDataCache.GetChangeSet(); + int i = 0; + foreach (var item in items) + { + i++; + item.Key.Should().Be(new MyKey("key" + i)); + item.Item.Should().Be(new MyValue("value" + i)); + } + i.Should().Be(4); + } + + [TestMethod] + public void TestGetAndChange() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); // trackable.State = TrackState.Added + myDataCache.InnerDict.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.Delete(new MyKey("key3")); // trackable.State = TrackState.Deleted + + myDataCache.GetAndChange(new MyKey("key1"), () => new MyValue("value_bk_1")).Should().Be(new MyValue("value1")); + myDataCache.GetAndChange(new MyKey("key2"), () => new MyValue("value_bk_2")).Should().Be(new MyValue("value2")); + myDataCache.GetAndChange(new MyKey("key3"), () => new MyValue("value_bk_3")).Should().Be(new MyValue("value_bk_3")); + myDataCache.GetAndChange(new MyKey("key4"), () => new MyValue("value_bk_4")).Should().Be(new MyValue("value_bk_4")); + } + + [TestMethod] + public void TestGetOrAdd() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); // trackable.State = TrackState.Added + myDataCache.InnerDict.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.Delete(new MyKey("key3")); // trackable.State = TrackState.Deleted + + myDataCache.GetOrAdd(new MyKey("key1"), () => new MyValue("value_bk_1")).Should().Be(new MyValue("value1")); + myDataCache.GetOrAdd(new MyKey("key2"), () => new MyValue("value_bk_2")).Should().Be(new MyValue("value2")); + myDataCache.GetOrAdd(new MyKey("key3"), () => new MyValue("value_bk_3")).Should().Be(new MyValue("value_bk_3")); + myDataCache.GetOrAdd(new MyKey("key4"), () => new MyValue("value_bk_4")).Should().Be(new MyValue("value_bk_4")); + } + + [TestMethod] + public void TestTryGet() + { + myDataCache.Add(new MyKey("key1"), new MyValue("value1")); // trackable.State = TrackState.Added + myDataCache.InnerDict.Add(new MyKey("key2"), new MyValue("value2")); + myDataCache.InnerDict.Add(new MyKey("key3"), new MyValue("value3")); + myDataCache.Delete(new MyKey("key3")); // trackable.State = TrackState.Deleted + + myDataCache.TryGet(new MyKey("key1")).Should().Be(new MyValue("value1")); + myDataCache.TryGet(new MyKey("key2")).Should().Be(new MyValue("value2")); + myDataCache.TryGet(new MyKey("key3")).Should().BeNull(); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_FIFOSet.cs b/neo.UnitTests/IO/Caching/UT_FIFOSet.cs new file mode 100644 index 0000000000..9b781858ee --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_FIFOSet.cs @@ -0,0 +1,149 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using System; +using System.Collections; +using System.Linq; + +namespace Neo.UnitTests.IO.Caching +{ + [TestClass] + public class UT_FIFOSet + { + [TestMethod] + public void FIFOSetTest() + { + var a = UInt256.Zero; + var b = new UInt256(); + var c = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01 + }); + + var set = new FIFOSet(3); + + Assert.IsTrue(set.Add(a)); + Assert.IsFalse(set.Add(a)); + Assert.IsFalse(set.Add(b)); + Assert.IsTrue(set.Add(c)); + + CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { a, c }); + + var d = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02 + }); + + // Testing Fifo max size + Assert.IsTrue(set.Add(d)); + CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { a, c, d }); + + var e = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x03 + }); + + Assert.IsTrue(set.Add(e)); + Assert.IsFalse(set.Add(e)); + CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { c, d, e }); + } + + [TestMethod] + public void TestConstructor() + { + Action action1 = () => new FIFOSet(-1); + action1.ShouldThrow(); + + Action action2 = () => new FIFOSet(1, -1); + action2.ShouldThrow(); + + Action action3 = () => new FIFOSet(1, 2); + action3.ShouldThrow(); + } + + [TestMethod] + public void TestAdd() + { + var a = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01 + }); + var b = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02 + }); + var set = new FIFOSet(1, 1) + { + a, + b + }; + CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { b }); + } + + [TestMethod] + public void TestGetEnumerator() + { + var a = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01 + }); + var b = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02 + }); + var set = new FIFOSet(1, 1) + { + a, + b + }; + IEnumerable ie = set; + ie.GetEnumerator().Should().NotBeNull(); + } + + [TestMethod] + public void TestExceptWith() + { + var a = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01 + }); + var b = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02 + }); + var c = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x03 + }); + + var set = new FIFOSet(10) + { + a, + b, + c + }; + set.ExceptWith(new UInt256[] { b, c }); + CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { a }); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_MetaDataCache.cs b/neo.UnitTests/IO/Caching/UT_MetaDataCache.cs new file mode 100644 index 0000000000..1ed84a5a0e --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_MetaDataCache.cs @@ -0,0 +1,76 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.IO.Caching; +using System; + +namespace Neo.UnitTests.IO.Caching +{ + public class MyMetaCache : MetaDataCache + where T : class, ICloneable, ISerializable, new() + { + public T Value; + + public MyMetaCache(Func factory) : base(factory) { } + + protected override void AddInternal(T item) + { + Value = item; + } + + protected override T TryGetInternal() + { + return Value; + } + + protected override void UpdateInternal(T item) + { + Value = item; + } + } + + [TestClass] + public class UT_MetaDataCache + { + MyMetaCache myMetaCache; + + [TestInitialize] + public void SetUp() + { + myMetaCache = new MyMetaCache(() => new MyValue()); + } + + [TestMethod] + public void TestContructor() + { + myMetaCache.Should().NotBeNull(); + } + + [TestMethod] + public void TestCommitAndAddInternal() + { + MyValue value = myMetaCache.Get(); + value.Should().NotBeNull(); + value.Value.Should().BeNull(); + + myMetaCache.Commit(); + myMetaCache.Value.Should().Be(value); + } + + public void TestCommitAndUpdateInternal() + { + MyValue value = myMetaCache.GetAndChange(); + value.Value = "value1"; + + myMetaCache.Commit(); + myMetaCache.Value.Should().Be(value); + myMetaCache.Value.Value.Should().Be("value1"); + } + + [TestMethod] + public void TestCreateSnapshot() + { + myMetaCache.CreateSnapshot().Should().NotBeNull(); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_OrderedDictionary.cs b/neo.UnitTests/IO/Caching/UT_OrderedDictionary.cs new file mode 100644 index 0000000000..66b1dd0e7c --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_OrderedDictionary.cs @@ -0,0 +1,129 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using System.Collections; +using System.Collections.Generic; + +namespace Neo.UnitTests.IO.Caching +{ + [TestClass] + public class UT_OrderedDictionary + { + private OrderedDictionary od; + + [TestInitialize] + public void SetUp() + { + od = new OrderedDictionary(); + od.Add("a", 1); + od.Add("b", 2); + od.Add("c", 3); + } + + [TestMethod] + public void TestClear() + { + od.Clear(); + od.Count.Should().Be(0); + od.TryGetValue("a", out uint i).Should().BeFalse(); + } + + [TestMethod] + public void TestCount() + { + od.Count.Should().Be(3); + od.Add("d", 4); + od.Count.Should().Be(4); + } + + [TestMethod] + public void TestIsReadOnly() + { + od.IsReadOnly.Should().BeFalse(); + } + + [TestMethod] + public void TestSetAndGetItem() + { + var val = od["a"]; + val.Should().Be(1); + od["d"] = 10; + od["d"].Should().Be(10); + od["d"] = 15; + od["d"].Should().Be(15); + } + + [TestMethod] + public void TestGetKeys() + { + var keys = od.Keys; + keys.Contains("a").Should().BeTrue(); + keys.Count.Should().Be(3); + } + + [TestMethod] + public void TestGetValues() + { + var values = od.Values; + values.Contains(1).Should().BeTrue(); + values.Count.Should().Be(3); + } + + [TestMethod] + public void TestRemove() + { + od.Remove("a"); + od.Count.Should().Be(2); + od.ContainsKey("a").Should().BeFalse(); + } + + [TestMethod] + public void TestTryGetValue() + { + od.TryGetValue("a", out uint i).Should().BeTrue(); + i.Should().Be(1); + od.TryGetValue("d", out uint j).Should().BeFalse(); + j.Should().Be(0); + } + + [TestMethod] + public void TestCollectionAddAndContains() + { + var pair = new KeyValuePair("d", 4); + ICollection> collection = od; + collection.Add(pair); + collection.Contains(pair).Should().BeTrue(); + } + + [TestMethod] + public void TestCollectionCopyTo() + { + var arr = new KeyValuePair[3]; + ICollection> collection = od; + collection.CopyTo(arr, 0); + arr[0].Key.Should().Be("a"); + arr[0].Value.Should().Be(1); + arr[1].Key.Should().Be("b"); + arr[1].Value.Should().Be(2); + arr[2].Key.Should().Be("c"); + arr[2].Value.Should().Be(3); + } + + [TestMethod] + public void TestCollectionRemove() + { + ICollection> collection = od; + var pair = new KeyValuePair("a", 1); + collection.Remove(pair); + collection.Contains(pair).Should().BeFalse(); + collection.Count.Should().Be(2); + } + + [TestMethod] + public void TestGetEnumerator() + { + IEnumerable collection = od; + collection.GetEnumerator().MoveNext().Should().BeTrue(); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_ReflectionCache.cs b/neo.UnitTests/IO/Caching/UT_ReflectionCache.cs new file mode 100644 index 0000000000..e51f6f81d6 --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_ReflectionCache.cs @@ -0,0 +1,80 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using System; + +namespace Neo.UnitTests.IO.Caching +{ + public class TestItem { } + + public class TestItem1 : TestItem { } + + public class TestItem2 : TestItem { } + + public enum MyTestEnum : byte + { + [ReflectionCache(typeof(TestItem1))] + Item1 = 0x00, + + [ReflectionCache(typeof(TestItem2))] + Item2 = 0x01, + } + + public enum MyEmptyEnum : byte { } + + [TestClass] + public class UT_ReflectionCache + { + ReflectionCache reflectionCache; + + [TestInitialize] + public void SetUp() + { + reflectionCache = ReflectionCache.CreateFromEnum(); + } + + [TestMethod] + public void TestCreateFromEnum() + { + reflectionCache.Should().NotBeNull(); + } + + [TestMethod] + public void TestCreateFromObjectNotEnum() + { + Action action = () => ReflectionCache.CreateFromEnum(); + action.ShouldThrow(); + } + + [TestMethod] + public void TestCreateFromEmptyEnum() + { + reflectionCache = ReflectionCache.CreateFromEnum(); + reflectionCache.Count.Should().Be(0); + } + + [TestMethod] + public void TestCreateInstance() + { + object item1 = reflectionCache.CreateInstance((byte)MyTestEnum.Item1, null); + (item1 is TestItem1).Should().BeTrue(); + + object item2 = reflectionCache.CreateInstance((byte)MyTestEnum.Item2, null); + (item2 is TestItem2).Should().BeTrue(); + + object item3 = reflectionCache.CreateInstance(0x02, null); + item3.Should().BeNull(); + } + + [TestMethod] + public void TestCreateInstance2() + { + TestItem defaultItem = new TestItem1(); + object item2 = reflectionCache.CreateInstance((byte)MyTestEnum.Item2, defaultItem); + (item2 is TestItem2).Should().BeTrue(); + + object item1 = reflectionCache.CreateInstance(0x02, new TestItem1()); + (item1 is TestItem1).Should().BeTrue(); + } + } +} diff --git a/neo.UnitTests/IO/Caching/UT_RelayCache.cs b/neo.UnitTests/IO/Caching/UT_RelayCache.cs new file mode 100644 index 0000000000..4608dd0e14 --- /dev/null +++ b/neo.UnitTests/IO/Caching/UT_RelayCache.cs @@ -0,0 +1,42 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using Neo.Network.P2P.Payloads; + +namespace Neo.UnitTests.IO.Caching +{ + [TestClass] + public class UT_RelayCache + { + RelayCache relayCache; + + [TestInitialize] + public void SetUp() + { + relayCache = new RelayCache(10); + } + + [TestMethod] + public void TestGetKeyForItem() + { + Transaction tx = new Transaction() + { + Version = 0, + Nonce = 1, + Sender = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"), + SystemFee = 0, + NetworkFee = 0, + ValidUntilBlock = 100, + Cosigners = new Cosigner[0], + Attributes = new TransactionAttribute[0], + Script = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }, + Witnesses = new Witness[0] + }; + relayCache.Add(tx); + + relayCache.Contains(tx).Should().BeTrue(); + relayCache.TryGet(tx.Hash, out IInventory tmp).Should().BeTrue(); + (tmp is Transaction).Should().BeTrue(); + } + } +} diff --git a/neo.UnitTests/IO/Data/LevelDb/UT_Slice.cs b/neo.UnitTests/IO/Data/LevelDb/UT_Slice.cs new file mode 100644 index 0000000000..d9ad5af951 --- /dev/null +++ b/neo.UnitTests/IO/Data/LevelDb/UT_Slice.cs @@ -0,0 +1,408 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.IO.Data.LevelDB; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Neo.UnitTests.IO.Data.LevelDb +{ + public class Test { } + + [TestClass] + public class UT_Slice + { + private Slice sliceTest; + + [TestMethod] + public void TestConstructor() + { + IntPtr parr = Marshal.AllocHGlobal(1); + Marshal.WriteByte(parr, 0x01); + UIntPtr plength = new UIntPtr(1); + sliceTest = new Slice(parr, plength); + Assert.IsNotNull(sliceTest); + Assert.IsInstanceOfType(sliceTest, typeof(Slice)); + Slice slice = (byte)0x01; + Assert.AreEqual(slice, sliceTest); + Marshal.FreeHGlobal(parr); + } + + [TestMethod] + public void TestCompareTo() + { + Slice slice = new byte[] { 0x01, 0x02 }; + sliceTest = new byte[] { 0x01, 0x02 }; + int result = sliceTest.CompareTo(slice); + Assert.AreEqual(0, result); + sliceTest = new byte[] { 0x01 }; + result = sliceTest.CompareTo(slice); + Assert.AreEqual(-1, result); + sliceTest = new byte[] { 0x01, 0x02, 0x03 }; + result = sliceTest.CompareTo(slice); + Assert.AreEqual(1, result); + sliceTest = new byte[] { 0x01, 0x03 }; + result = sliceTest.CompareTo(slice); + Assert.AreEqual(1, result); + sliceTest = new byte[] { 0x01, 0x01 }; + result = sliceTest.CompareTo(slice); + Assert.AreEqual(-1, result); + } + + [TestMethod] + public void TestEqualsSlice() + { + byte[] arr1 = { 0x01, 0x02 }; + byte[] arr2 = { 0x01, 0x02 }; + Slice slice = arr1; + sliceTest = arr1; + Assert.IsTrue(sliceTest.Equals(slice)); + sliceTest = arr2; + Assert.IsTrue(sliceTest.Equals(slice)); + sliceTest = new byte[] { 0x01, 0x03 }; + Assert.IsFalse(sliceTest.Equals(slice)); + } + + [TestMethod] + public void TestEqualsObj() + { + sliceTest = new byte[] { 0x01 }; + object slice = null; + bool result = sliceTest.Equals(slice); + Assert.AreEqual(false, result); + slice = new Test(); + result = sliceTest.Equals(slice); + Assert.AreEqual(false, result); + slice = sliceTest; + result = sliceTest.Equals(slice); + Assert.AreEqual(true, result); + Slice s = new byte[] { 0x01 }; + result = sliceTest.Equals(s); + Assert.AreEqual(true, result); + s = new byte[] { 0x01, 0x02 }; + result = sliceTest.Equals(s); + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestGetHashCode() + { + byte[] arr = new byte[] { 0x01, 0x02 }; + sliceTest = arr; + int hash1 = (int)arr.Murmur32(0); + int hash2 = sliceTest.GetHashCode(); + Assert.AreEqual(hash2, hash1); + } + + [TestMethod] + public void TestFromArray() + { + byte[] arr = new byte[]{ + 0x01,0x01,0x01,0x01, + }; + IntPtr parr = Marshal.AllocHGlobal(arr.Length); + for (int i = 0; i < arr.Length; i++) + { + Marshal.WriteByte(parr + i, 0x01); + } + UIntPtr plength = new UIntPtr((uint)arr.Length); + Slice slice = new Slice(parr, plength); + sliceTest = arr; + Assert.AreEqual(slice, sliceTest); + Marshal.FreeHGlobal(parr); + } + + [TestMethod] + public void TestToArray() + { + sliceTest = new Slice(); + byte[] arr = sliceTest.ToArray(); + Assert.AreEqual(0, arr.Length); + arr = new byte[] { 0x01, 0x02 }; + sliceTest = arr; + byte[] parr = sliceTest.ToArray(); + Assert.AreSame(parr, arr); + } + + [TestMethod] + public void TestToBoolean() + { + sliceTest = new byte[] { 0x01, 0x02 }; + Assert.ThrowsException(() => sliceTest.ToBoolean()); + sliceTest = (byte)0x01; + bool result = sliceTest.ToBoolean(); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestToByte() + { + sliceTest = new byte[] { 0x01, 0x02 }; + Assert.ThrowsException(() => sliceTest.ToByte()); + sliceTest = (byte)0x01; + byte result = sliceTest.ToByte(); + Assert.AreEqual((byte)0x01, result); + } + + [TestMethod] + public void TestToDouble() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToDouble()); + byte[] arr = new byte[sizeof(double)]; + sliceTest = arr; + double result = sliceTest.ToDouble(); + Assert.AreEqual(0D, result); + sliceTest = 0.5D; + Assert.AreEqual(0.5D, sliceTest.ToDouble()); + } + + [TestMethod] + public void TestToInt16() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToInt16()); + sliceTest = (Int16)(-15); + Assert.AreEqual((Int16)(-15), sliceTest.ToInt16()); + } + + [TestMethod] + public void TestToInt32() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToInt32()); + sliceTest = (Int32)(-15); + Assert.AreEqual((Int32)(-15), sliceTest.ToInt32()); + } + + [TestMethod] + public void TestToInt64() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToInt64()); + sliceTest = Int64.MaxValue; + Assert.AreEqual(Int64.MaxValue, sliceTest.ToInt64()); + } + + [TestMethod] + public void TestToSingle() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToSingle()); + sliceTest = (float)(-15.5); + Assert.AreEqual((float)(-15.5), sliceTest.ToSingle()); + } + + [TestMethod] + public void TestToString() + { + sliceTest = "abc你好"; + Assert.AreEqual("abc你好", sliceTest.ToString()); + } + + [TestMethod] + public void TestToUint16() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToUInt16()); + sliceTest = (UInt16)(25); + Assert.AreEqual((UInt16)25, sliceTest.ToUInt16()); + } + + [TestMethod] + public void TestToUint32() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToUInt32()); + sliceTest = (UInt32)(2525252525); + Assert.AreEqual((UInt32)2525252525, sliceTest.ToUInt32()); + } + + [TestMethod] + public void TestToUint64() + { + sliceTest = new byte[] { 0x01 }; + Assert.ThrowsException(() => sliceTest.ToUInt64()); + sliceTest = (UInt64)(0x2525252525252525); + Assert.AreEqual((UInt64)(0x2525252525252525), sliceTest.ToUInt64()); + } + + [TestMethod] + public void TestFromBool() + { + byte[] arr = { 0x01 }; + Slice slice = arr; + sliceTest = true; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromByte() + { + sliceTest = (byte)0x01; + byte[] arr = { 0x01 }; + Slice slice = arr; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromDouble() + { + Slice slice = BitConverter.GetBytes(1.23D); + sliceTest = 1.23D; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromShort() + { + Slice slice = BitConverter.GetBytes((short)1234); + sliceTest = (short)1234; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromInt() + { + Slice slice = BitConverter.GetBytes(-1234); + sliceTest = -1234; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromLong() + { + Slice slice = BitConverter.GetBytes(-1234L); + sliceTest = -1234L; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromFloat() + { + Slice slice = BitConverter.GetBytes(1.234F); + sliceTest = 1.234F; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromString() + { + string str = "abcdefghijklmnopqrstuvwxwz!@#$%^&*&()_+?><你好"; + Slice slice = Encoding.UTF8.GetBytes(str); + sliceTest = str; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromUnshort() + { + Slice slice = BitConverter.GetBytes((ushort)12345); + sliceTest = (ushort)12345; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromUint() + { + Slice slice = BitConverter.GetBytes((uint)12345); + sliceTest = (uint)12345; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestFromUlong() + { + Slice slice = BitConverter.GetBytes(12345678UL); + sliceTest = 12345678UL; + Assert.AreEqual(slice, sliceTest); + } + + [TestMethod] + public void TestLessThan() + { + sliceTest = new byte[] { 0x01 }; + Slice slice = new byte[] { 0x02 }; + bool result = sliceTest < slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x01 }; + result = sliceTest < slice; + Assert.AreEqual(false, result); + slice = new byte[] { 0x00 }; + result = sliceTest < slice; + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestLessThanAndEqual() + { + sliceTest = new byte[] { 0x01 }; + Slice slice = new byte[] { 0x02 }; + bool result = sliceTest <= slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x01 }; + result = sliceTest <= slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x00 }; + result = sliceTest <= slice; + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestGreatThan() + { + sliceTest = new byte[] { 0x01 }; + Slice slice = new byte[] { 0x00 }; + bool result = sliceTest > slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x01 }; + result = sliceTest > slice; + Assert.AreEqual(false, result); + slice = new byte[] { 0x02 }; + result = sliceTest > slice; + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestGreatThanAndEqual() + { + sliceTest = new byte[] { 0x01 }; + Slice slice = new byte[] { 0x00 }; + bool result = sliceTest >= slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x01 }; + result = sliceTest >= slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x02 }; + result = sliceTest >= slice; + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestEqual() + { + sliceTest = new byte[] { 0x01 }; + Slice slice = new byte[] { 0x00 }; + bool result = sliceTest == slice; + Assert.AreEqual(false, result); + slice = new byte[] { 0x01 }; + result = sliceTest == slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x02 }; + result = sliceTest == slice; + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestUnequal() + { + sliceTest = new byte[] { 0x01 }; + Slice slice = new byte[] { 0x00 }; + bool result = sliceTest != slice; + Assert.AreEqual(true, result); + slice = new byte[] { 0x01 }; + result = sliceTest != slice; + Assert.AreEqual(false, result); + } + } +} diff --git a/neo.UnitTests/IO/Json/UT_JArray.cs b/neo.UnitTests/IO/Json/UT_JArray.cs new file mode 100644 index 0000000000..5b7a30c9ae --- /dev/null +++ b/neo.UnitTests/IO/Json/UT_JArray.cs @@ -0,0 +1,252 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using System; +using System.Collections; +using System.Linq; + +namespace Neo.UnitTests.IO.Json +{ + enum Foo + { + male, + female + } + + [TestClass] + public class UT_JArray + { + private JObject alice; + private JObject bob; + + [TestInitialize] + public void SetUp() + { + alice = new JObject(); + alice["name"] = "alice"; + alice["age"] = 30; + alice["score"] = 100.001; + alice["gender"] = Foo.female; + alice["isMarried"] = true; + var pet1 = new JObject(); + pet1["name"] = "Tom"; + pet1["type"] = "cat"; + alice["pet"] = pet1; + + bob = new JObject(); + bob["name"] = "bob"; + bob["age"] = 100000; + bob["score"] = 0.001; + bob["gender"] = Foo.male; + bob["isMarried"] = false; + var pet2 = new JObject(); + pet2["name"] = "Paul"; + pet2["type"] = "dog"; + bob["pet"] = pet2; + } + + [TestMethod] + public void TestAdd() + { + var jArray = new JArray + { + alice, + bob + }; + var jAlice = jArray[0]; + var jBob = jArray[1]; + jAlice["name"].ToString().Should().Be(alice["name"].ToString()); + jAlice["age"].ToString().Should().Be(alice["age"].ToString()); + jAlice["score"].ToString().Should().Be(alice["score"].ToString()); + jAlice["gender"].ToString().Should().Be(alice["gender"].ToString()); + jAlice["isMarried"].ToString().Should().Be(alice["isMarried"].ToString()); + jAlice["pet"].ToString().Should().Be(alice["pet"].ToString()); + jBob["name"].ToString().Should().Be(bob["name"].ToString()); + jBob["age"].ToString().Should().Be(bob["age"].ToString()); + jBob["score"].ToString().Should().Be(bob["score"].ToString()); + jBob["gender"].ToString().Should().Be(bob["gender"].ToString()); + jBob["isMarried"].ToString().Should().Be(bob["isMarried"].ToString()); + jBob["pet"].ToString().Should().Be(bob["pet"].ToString()); + } + + [TestMethod] + public void TestSetItem() + { + var jArray = new JArray + { + alice + }; + jArray[0] = bob; + Assert.AreEqual(jArray[0], bob); + + Action action = () => jArray[1] = alice; + action.ShouldThrow(); + } + + [TestMethod] + public void TestClear() + { + var jArray = new JArray + { + alice + }; + var jAlice = jArray[0]; + jAlice["name"].ToString().Should().Be(alice["name"].ToString()); + jAlice["age"].ToString().Should().Be(alice["age"].ToString()); + jAlice["score"].ToString().Should().Be(alice["score"].ToString()); + jAlice["gender"].ToString().Should().Be(alice["gender"].ToString()); + jAlice["isMarried"].ToString().Should().Be(alice["isMarried"].ToString()); + jAlice["pet"].ToString().Should().Be(alice["pet"].ToString()); + + jArray.Clear(); + Action action = () => jArray[0].ToString(); + action.ShouldThrow(); + } + + [TestMethod] + public void TestContains() + { + var jArray = new JArray + { + alice + }; + jArray.Contains(alice).Should().BeTrue(); + jArray.Contains(bob).Should().BeFalse(); + } + + [TestMethod] + public void TestCopyTo() + { + var jArray = new JArray + { + alice, + bob + }; + + JObject[] jObjects1 = new JObject[2]; + jArray.CopyTo(jObjects1, 0); + var jAlice1 = jObjects1[0]; + var jBob1 = jObjects1[1]; + Assert.AreEqual(alice, jAlice1); + Assert.AreEqual(bob, jBob1); + + JObject[] jObjects2 = new JObject[4]; + jArray.CopyTo(jObjects2, 2); + var jAlice2 = jObjects2[2]; + var jBob2 = jObjects2[3]; + jObjects2[0].Should().BeNull(); + jObjects2[1].Should().BeNull(); + Assert.AreEqual(alice, jAlice2); + Assert.AreEqual(bob, jBob2); + } + + [TestMethod] + public void TestInsert() + { + var jArray = new JArray + { + alice, + alice, + alice, + alice + }; + + jArray.Insert(1, bob); + jArray.Count().Should().Be(5); + jArray[0].Should().Be(alice); + jArray[1].Should().Be(bob); + jArray[2].Should().Be(alice); + + jArray.Insert(5, bob); + jArray.Count().Should().Be(6); + jArray[5].Should().Be(bob); + } + + [TestMethod] + public void TestIndexOf() + { + var jArray = new JArray(); + jArray.IndexOf(alice).Should().Be(-1); + + jArray.Add(alice); + jArray.Add(alice); + jArray.Add(alice); + jArray.Add(alice); + jArray.IndexOf(alice).Should().Be(0); + + jArray.Insert(1, bob); + jArray.IndexOf(bob).Should().Be(1); + } + + [TestMethod] + public void TestIsReadOnly() + { + var jArray = new JArray(); + jArray.IsReadOnly.Should().BeFalse(); + } + + [TestMethod] + public void TestRemove() + { + var jArray = new JArray + { + alice + }; + jArray.Count().Should().Be(1); + jArray.Remove(alice); + jArray.Count().Should().Be(0); + + jArray.Add(alice); + jArray.Add(alice); + jArray.Count().Should().Be(2); + jArray.Remove(alice); + jArray.Count().Should().Be(1); + } + + [TestMethod] + public void TestRemoveAt() + { + var jArray = new JArray + { + alice, + bob, + alice + }; + jArray.RemoveAt(1); + jArray.Count().Should().Be(2); + jArray.Contains(bob).Should().BeFalse(); + } + + [TestMethod] + public void TestGetEnumerator() + { + var jArray = new JArray + { + alice, + bob, + alice, + bob + }; + int i = 0; + foreach (var item in jArray) + { + if (i % 2 == 0) item.Should().Be(alice); + if (i % 2 != 0) item.Should().Be(bob); + i++; + } + Assert.IsNotNull(((IEnumerable)jArray).GetEnumerator()); + } + + [TestMethod] + public void TestAsString() + { + var jArray = new JArray + { + alice, + bob, + }; + var s = jArray.AsString(); + Assert.AreEqual(s, "{\"name\":\"alice\",\"age\":30,\"score\":100.001,\"gender\":\"female\",\"isMarried\":true,\"pet\":{\"name\":\"Tom\",\"type\":\"cat\"}},{\"name\":\"bob\",\"age\":100000,\"score\":0.001,\"gender\":\"male\",\"isMarried\":false,\"pet\":{\"name\":\"Paul\",\"type\":\"dog\"}}"); + } + } +} diff --git a/neo.UnitTests/IO/Json/UT_JBoolean.cs b/neo.UnitTests/IO/Json/UT_JBoolean.cs new file mode 100644 index 0000000000..8e5f4acdd6 --- /dev/null +++ b/neo.UnitTests/IO/Json/UT_JBoolean.cs @@ -0,0 +1,77 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using System; +using System.IO; + +namespace Neo.UnitTests.IO.Json +{ + [TestClass] + public class UT_JBoolean + { + private JBoolean jFalse; + private JBoolean jTrue; + + [TestInitialize] + public void SetUp() + { + jFalse = new JBoolean(); + jTrue = new JBoolean(true); + } + + [TestMethod] + public void TestAsNumber() + { + jFalse.AsNumber().Should().Be(0); + jTrue.AsNumber().Should().Be(1); + } + + [TestMethod] + public void TestParse() + { + TextReader tr1 = new StringReader("true"); + JBoolean ret1 = JBoolean.Parse(tr1); + ret1.AsBoolean().Should().BeTrue(); + + TextReader tr2 = new StringReader("false"); + JBoolean ret2 = JBoolean.Parse(tr2); + ret2.AsBoolean().Should().BeFalse(); + + TextReader tr3 = new StringReader("aaa"); + Action action = () => JBoolean.Parse(tr3); + action.ShouldThrow(); + } + + [TestMethod] + public void TestParseFalse() + { + TextReader tr1 = new StringReader("false"); + JBoolean ret1 = JBoolean.ParseFalse(tr1); + ret1.AsBoolean().Should().BeFalse(); + + TextReader tr2 = new StringReader("aaa"); + Action action = () => JBoolean.ParseFalse(tr2); + action.ShouldThrow(); + + TextReader tr3 = new StringReader("\t\rfalse"); + JBoolean ret3 = JBoolean.ParseFalse(tr3); + ret3.AsBoolean().Should().BeFalse(); + } + + [TestMethod] + public void TestParseTrue() + { + TextReader tr1 = new StringReader("true"); + JBoolean ret1 = JBoolean.ParseTrue(tr1); + ret1.AsBoolean().Should().BeTrue(); + + TextReader tr2 = new StringReader("aaa"); + Action action = () => JBoolean.ParseTrue(tr2); + action.ShouldThrow(); + + TextReader tr3 = new StringReader(" true"); + JBoolean ret3 = JBoolean.ParseTrue(tr3); + ret3.AsBoolean().Should().BeTrue(); + } + } +} diff --git a/neo.UnitTests/IO/Json/UT_JNumber.cs b/neo.UnitTests/IO/Json/UT_JNumber.cs new file mode 100644 index 0000000000..16adbe9d80 --- /dev/null +++ b/neo.UnitTests/IO/Json/UT_JNumber.cs @@ -0,0 +1,67 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using System; +using System.IO; + +namespace Neo.UnitTests.IO.Json +{ + enum Woo + { + Tom, + Jerry, + James + } + + [TestClass] + public class UT_JNumber + { + private JNumber maxInt; + private JNumber minInt; + private JNumber zero; + + [TestInitialize] + public void SetUp() + { + maxInt = new JNumber(JNumber.MAX_SAFE_INTEGER); + minInt = new JNumber(JNumber.MIN_SAFE_INTEGER); + zero = new JNumber(); + } + + [TestMethod] + public void TestAsBoolean() + { + maxInt.AsBoolean().Should().BeTrue(); + zero.AsBoolean().Should().BeFalse(); + } + + [TestMethod] + public void TestAsString() + { + Action action1 = () => new JNumber(double.PositiveInfinity).AsString(); + action1.ShouldThrow(); + + Action action2 = () => new JNumber(double.NegativeInfinity).AsString(); + action2.ShouldThrow(); + } + + [TestMethod] + public void TestTryGetEnum() + { + zero.TryGetEnum().Should().Be(Woo.Tom); + new JNumber(1).TryGetEnum().Should().Be(Woo.Jerry); + new JNumber(2).TryGetEnum().Should().Be(Woo.James); + new JNumber(3).TryGetEnum().Should().Be(Woo.Tom); + } + + [TestMethod] + public void TestParse() + { + Action action1 = () => JNumber.Parse(new StringReader("100.a")); + action1.ShouldThrow(); + + Action action2 = () => JNumber.Parse(new StringReader("100.+")); + action2.ShouldThrow(); + } + } +} diff --git a/neo.UnitTests/IO/Json/UT_JObject.cs b/neo.UnitTests/IO/Json/UT_JObject.cs new file mode 100644 index 0000000000..990363bcb1 --- /dev/null +++ b/neo.UnitTests/IO/Json/UT_JObject.cs @@ -0,0 +1,118 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using System; +using System.IO; + +namespace Neo.UnitTests.IO.Json +{ + [TestClass] + public class UT_JObject + { + private JObject alice; + private JObject bob; + + [TestInitialize] + public void SetUp() + { + alice = new JObject(); + alice["name"] = "alice"; + alice["age"] = 30; + alice["score"] = 100.001; + alice["gender"] = Foo.female; + alice["isMarried"] = true; + var pet1 = new JObject(); + pet1["name"] = "Tom"; + pet1["type"] = "cat"; + alice["pet"] = pet1; + + bob = new JObject(); + bob["name"] = "bob"; + bob["age"] = 100000; + bob["score"] = 0.001; + bob["gender"] = Foo.male; + bob["isMarried"] = false; + var pet2 = new JObject(); + pet2["name"] = "Paul"; + pet2["type"] = "dog"; + bob["pet"] = pet2; + } + + [TestMethod] + public void TestAsBoolean() + { + alice.AsBoolean().Should().BeTrue(); + } + + [TestMethod] + public void TestAsNumber() + { + alice.AsNumber().Should().Be(double.NaN); + } + + [TestMethod] + public void TestParse() + { + Action action = () => JObject.Parse(new StringReader(""), -1); + action.ShouldThrow(); + } + + [TestMethod] + public void TestParseNull() + { + Action action = () => JObject.Parse(new StringReader("naaa")); + action.ShouldThrow(); + + JObject.Parse(new StringReader("null")).Should().BeNull(); + } + + [TestMethod] + public void TestParseObject() + { + Action action1 = () => JObject.Parse(new StringReader("{\"k1\":\"v1\",\"k1\":\"v2\"}"), 100); + action1.ShouldThrow(); + + Action action2 = () => JObject.Parse(new StringReader("{\"k1\",\"k1\"}"), 100); + action2.ShouldThrow(); + + Action action3 = () => JObject.Parse(new StringReader("{\"k1\":\"v1\""), 100); + action3.ShouldThrow(); + + Action action4 = () => JObject.Parse(new StringReader("aaa"), 100); + action4.ShouldThrow(); + + JObject.Parse(new StringReader("{\"k1\":\"v1\"}"), 100).ToString().Should().Be("{\"k1\":\"v1\"}"); + } + + [TestMethod] + public void TestTryGetEnum() + { + alice.TryGetEnum().Should().Be(Woo.Tom); + } + + [TestMethod] + public void TestOpImplicitEnum() + { + var obj = new JObject(); + obj = Woo.Tom; + obj.AsString().Should().Be("Tom"); + } + + [TestMethod] + public void TestOpImplicitString() + { + var obj = new JObject(); + obj = null; + obj.Should().BeNull(); + + obj = "{\"aaa\":\"111\"}"; + obj.AsString().Should().Be("{\"aaa\":\"111\"}"); + } + + [TestMethod] + public void TestGetNull() + { + JObject.Null.Should().BeNull(); + } + } +} diff --git a/neo.UnitTests/IO/Json/UT_JString.cs b/neo.UnitTests/IO/Json/UT_JString.cs new file mode 100644 index 0000000000..5d20b30fa0 --- /dev/null +++ b/neo.UnitTests/IO/Json/UT_JString.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using Neo.SmartContract; +using System; +using System.IO; + +namespace Neo.UnitTests.IO +{ + [TestClass] + public class UT_JString + { + [TestMethod] + public void TestConstructor() + { + String s = "hello world"; + JString jstring = new JString(s); + Assert.AreEqual(s, jstring.Value); + Assert.ThrowsException(() => new JString(null)); + } + + [TestMethod] + public void TestAsBoolean() + { + String s1 = "hello world"; + String s2 = ""; + JString jstring1 = new JString(s1); + JString jstring2 = new JString(s2); + Assert.AreEqual(true, jstring1.AsBoolean()); + Assert.AreEqual(false, jstring2.AsBoolean()); + } + + [TestMethod] + public void TestParse() + { + TextReader tr = new StringReader("\"hello world\""); + String s = JString.Parse(tr).Value; + Assert.AreEqual("hello world", s); + + tr = new StringReader("hello world"); + Assert.ThrowsException(() => JString.Parse(tr)); + + tr = new StringReader("\"\\s\""); + Assert.ThrowsException(() => JString.Parse(tr)); + + tr = new StringReader("\"\\\"\\\\\\/\\b\\f\\n\\r\\t\""); + s = JString.Parse(tr).Value; + Assert.AreEqual("\"\\/\b\f\n\r\t", s); + + tr = new StringReader("\"\\u0030\""); + s = JString.Parse(tr).Value; + Assert.AreEqual("0", s); + + tr = new StringReader("\"a"); + Assert.ThrowsException(() => JString.Parse(tr)); + + byte[] byteArray = new byte[] { 0x22, 0x01, 0x22 }; + tr = new StringReader(System.Text.Encoding.ASCII.GetString(byteArray)); + Assert.ThrowsException(() => JString.Parse(tr)); + } + + [TestMethod] + public void TestTryGetEnum() + { + JString s = new JString("Signature"); + ContractParameterType cp = s.TryGetEnum(ContractParameterType.Void, false); + Assert.AreEqual(ContractParameterType.Signature, cp); + + s = new JString(""); + cp = s.TryGetEnum(ContractParameterType.Void, false); + Assert.AreEqual(ContractParameterType.Void, cp); + } + } +} diff --git a/neo.UnitTests/IO/UT_IOHelper.cs b/neo.UnitTests/IO/UT_IOHelper.cs new file mode 100644 index 0000000000..350cc31425 --- /dev/null +++ b/neo.UnitTests/IO/UT_IOHelper.cs @@ -0,0 +1,549 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Neo.UnitTests.IO +{ + [TestClass] + public class UT_IOHelper + { + [TestMethod] + public void TestAsSerializableGeneric() + { + byte[] caseArray = new byte[] { 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00 }; + UInt160 result = Neo.IO.Helper.AsSerializable(caseArray); + Assert.AreEqual(UInt160.Zero, result); + } + + [TestMethod] + public void TestAsSerializable() + { + for (int i = 0; i < 2; i++) + { + if (i == 0) + { + byte[] caseArray = new byte[] { 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00 }; + ISerializable result = Neo.IO.Helper.AsSerializable(caseArray, typeof(UInt160)); + Assert.AreEqual(UInt160.Zero, result); + } + else + { + Action action = () => Neo.IO.Helper.AsSerializable(new byte[0], typeof(Double)); + action.ShouldThrow(); + } + } + } + + [TestMethod] + public void TestAsSerializableArray() + { + byte[] byteArray = Neo.IO.Helper.ToByteArray(new UInt160[] { UInt160.Zero }); + UInt160[] result = Neo.IO.Helper.AsSerializableArray(byteArray); + Assert.AreEqual(1, result.Length); + Assert.AreEqual(UInt160.Zero, result[0]); + } + + [TestMethod] + public void TestGetVarSizeInt() + { + for (int i = 0; i < 3; i++) + { + if (i == 0) + { + int result = Neo.IO.Helper.GetVarSize(1); + Assert.AreEqual(1, result); + } + else if (i == 1) + { + int result = Neo.IO.Helper.GetVarSize(0xFFFF); + Assert.AreEqual(3, result); + } + else + { + int result = Neo.IO.Helper.GetVarSize(0xFFFFFF); + Assert.AreEqual(5, result); + } + } + } + enum TestEnum0 : sbyte + { + case1 = 1, case2 = 2 + } + + enum TestEnum1 : byte + { + case1 = 1, case2 = 2 + } + + enum TestEnum2 : short + { + case1 = 1, case2 = 2 + } + + enum TestEnum3 : ushort + { + case1 = 1, case2 = 2 + } + + enum TestEnum4 : int + { + case1 = 1, case2 = 2 + } + + enum TestEnum5 : uint + { + case1 = 1, case2 = 2 + } + + enum TestEnum6 : long + { + case1 = 1, case2 = 2 + } + + [TestMethod] + public void TestGetVarSizeGeneric() + { + for (int i = 0; i < 9; i++) + { + if (i == 0) + { + int result = Neo.IO.Helper.GetVarSize(new UInt160[] { UInt160.Zero }); + Assert.AreEqual(21, result); + } + else if (i == 1)//sbyte + { + List initList = new List + { + TestEnum0.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(2, result); + } + else if (i == 2)//byte + { + List initList = new List + { + TestEnum1.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(2, result); + } + else if (i == 3)//short + { + List initList = new List + { + TestEnum2.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(3, result); + } + else if (i == 4)//ushort + { + List initList = new List + { + TestEnum3.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(3, result); + } + else if (i == 5)//int + { + List initList = new List + { + TestEnum4.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(5, result); + } + else if (i == 6)//uint + { + List initList = new List + { + TestEnum5.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(5, result); + } + else if (i == 7)//long + { + List initList = new List + { + TestEnum6.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(9, result); + } + else if (i == 8) + { + List initList = new List + { + 1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = Neo.IO.Helper.GetVarSize(testList); + Assert.AreEqual(5, result); + } + } + } + + [TestMethod] + public void TestGetVarSizeString() + { + int result = Neo.IO.Helper.GetVarSize("AA"); + Assert.AreEqual(3, result); + } + + [TestMethod] + public void TestReadBytesWithGrouping() + { + for (int i = 0; i < 2; i++) + { + if (i == 0) + { + byte[] caseArray = new byte[] { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA}; + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteBytesWithGrouping(writer, caseArray); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + byte[] result = Neo.IO.Helper.ReadBytesWithGrouping(reader); + Assert.AreEqual(Encoding.Default.GetString(caseArray), Encoding.Default.GetString(result)); + } + else + { + byte[] caseArray = new byte[] { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0x10, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0x10, + 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x11}; + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(caseArray); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + Action action = () => Neo.IO.Helper.ReadBytesWithGrouping(reader); + action.ShouldThrow(); + } + } + } + + [TestMethod] + public void TestReadFixedString() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteFixedString(writer, "AA", Encoding.UTF8.GetBytes("AA").Length + 1); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + string result = Neo.IO.Helper.ReadFixedString(reader, Encoding.UTF8.GetBytes("AA").Length + 1); + Assert.AreEqual("AA", result); + } + + [TestMethod] + public void TestReadSerializable() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.Write(writer, UInt160.Zero); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + UInt160 result = Neo.IO.Helper.ReadSerializable(reader); + Assert.AreEqual(UInt160.Zero, result); + } + + [TestMethod] + public void TestReadSerializableArray() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.Write(writer, new UInt160[] { UInt160.Zero }); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + UInt160[] resultArray = Neo.IO.Helper.ReadSerializableArray(reader); + Assert.AreEqual(1, resultArray.Length); + Assert.AreEqual(UInt160.Zero, resultArray[0]); + } + + [TestMethod] + public void TestReadVarBytes() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarBytes(writer, new byte[] { 0xAA, 0xAA }); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + byte[] byteArray = Neo.IO.Helper.ReadVarBytes(reader, 10); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0xAA, 0xAA }), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestReadVarInt() + { + for (int i = 0; i < 4; i++) + { + if (i == 0) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xFFFF); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + ulong result = Neo.IO.Helper.ReadVarInt(reader, 0xFFFF); + Assert.AreEqual((ulong)0xFFFF, result); + } + else if (i == 1) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xFFFFFFFF); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + ulong result = Neo.IO.Helper.ReadVarInt(reader, 0xFFFFFFFF); + Assert.AreEqual(0xFFFFFFFF, result); + } + else + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xFFFFFFFFFF); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + Action action = () => Neo.IO.Helper.ReadVarInt(reader, 0xFFFFFFFF); + action.ShouldThrow(); + } + } + } + + [TestMethod] + public void TestReadVarString() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarString(writer, "AAAAAAA"); + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + string result = Neo.IO.Helper.ReadVarString(reader, 10); + stream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual("AAAAAAA", result); + } + + [TestMethod] + public void TestToArray() + { + byte[] byteArray = Neo.IO.Helper.ToArray(UInt160.Zero); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00}), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestToByteArrayGeneric() + { + byte[] byteArray = Neo.IO.Helper.ToByteArray(new UInt160[] { UInt160.Zero }); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00}), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestWrite() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.Write(writer, UInt160.Zero); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00}), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestWriteGeneric() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.Write(writer, new UInt160[] { UInt160.Zero }); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00}), Encoding.Default.GetString(byteArray)); + } + + + [TestMethod] + public void TestWriteBytesWithGrouping() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteBytesWithGrouping(writer, new byte[] { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA}); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0x10, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0x10, + 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x04}), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestWriteFixedString() + { + for (int i = 0; i < 5; i++) + { + if (i == 0) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Action action = () => Neo.IO.Helper.WriteFixedString(writer, null, 0); + action.ShouldThrow(); + } + else if (i == 1) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Action action = () => Neo.IO.Helper.WriteFixedString(writer, "AA", Encoding.UTF8.GetBytes("AA").Length - 1); + action.ShouldThrow(); + } + else if (i == 2) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Action action = () => Neo.IO.Helper.WriteFixedString(writer, "拉拉", Encoding.UTF8.GetBytes("拉拉").Length - 1); + action.ShouldThrow(); + } + else if (i == 3) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteFixedString(writer, "AA", Encoding.UTF8.GetBytes("AA").Length + 1); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + byte[] newArray = new byte[Encoding.UTF8.GetBytes("AA").Length + 1]; + Encoding.UTF8.GetBytes("AA").CopyTo(newArray, 0); + Assert.AreEqual(Encoding.Default.GetString(newArray), Encoding.Default.GetString(byteArray)); + } + } + } + + [TestMethod] + public void TestWriteVarBytes() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarBytes(writer, new byte[] { 0xAA }); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01, 0xAA }), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestWriteVarInt() + { + for (int i = 0; i < 5; i++) + { + if (i == 0) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Action action = () => Neo.IO.Helper.WriteVarInt(writer, -1); + action.ShouldThrow(); + } + else if (i == 1) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xFC); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(0xFC, byteArray[0]); + } + else if (i == 2) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xFFFF); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(0xFD, byteArray[0]); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0xFF, 0xFF }), Encoding.Default.GetString(byteArray.Skip(1).Take(byteArray.Length - 1).ToArray())); + } + else if (i == 3) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xFFFFFFFF); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(0xFE, byteArray[0]); + Assert.AreEqual(0xFFFFFFFF, BitConverter.ToUInt32(byteArray, 1)); + } + else + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarInt(writer, 0xAEFFFFFFFF); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(0xFF, byteArray[0]); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00 }), Encoding.Default.GetString(byteArray.Skip(1).Take(byteArray.Length - 1).ToArray())); + } + } + } + + [TestMethod] + public void TestWriteVarString() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + Neo.IO.Helper.WriteVarString(writer, "a"); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + Assert.AreEqual(0x01, byteArray[0]); + Assert.AreEqual(0x61, byteArray[1]); + } + } +} diff --git a/neo.UnitTests/IO/Wrappers/UT_SerializableWrapper.cs b/neo.UnitTests/IO/Wrappers/UT_SerializableWrapper.cs new file mode 100644 index 0000000000..aa6c0cfbc8 --- /dev/null +++ b/neo.UnitTests/IO/Wrappers/UT_SerializableWrapper.cs @@ -0,0 +1,50 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Wrappers; +using System.IO; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_SerializableWrapper + { + [TestMethod] + public void TestGetSize() + { + SerializableWrapper temp = new UInt32Wrapper(); + Assert.AreEqual(4, temp.Size); + } + + [TestMethod] + public void TestEqualsOtherObject() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + writer.Write((uint)1); + stream.Seek(0, SeekOrigin.Begin); + SerializableWrapper temp = new UInt32Wrapper(); + temp.Deserialize(reader); + Assert.AreEqual(true, temp.Equals(1)); + } + + [TestMethod] + public void TestEqualsOtherSerializableWrapper() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + writer.Write((uint)1); + stream.Seek(0, SeekOrigin.Begin); + SerializableWrapper temp = new UInt32Wrapper(); + temp.Deserialize(reader); + MemoryStream stream2 = new MemoryStream(); + BinaryWriter writer2 = new BinaryWriter(stream2); + BinaryReader reader2 = new BinaryReader(stream2); + writer2.Write((uint)1); + stream2.Seek(0, SeekOrigin.Begin); + SerializableWrapper temp2 = new UInt32Wrapper(); + temp2.Deserialize(reader2); + Assert.AreEqual(true, temp.Equals(temp2)); + } + } +} diff --git a/neo.UnitTests/IO/Wrappers/UT_UInt32Wrapper.cs b/neo.UnitTests/IO/Wrappers/UT_UInt32Wrapper.cs new file mode 100644 index 0000000000..766ed8e694 --- /dev/null +++ b/neo.UnitTests/IO/Wrappers/UT_UInt32Wrapper.cs @@ -0,0 +1,97 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Wrappers; +using System.IO; +using System.Text; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_UInt32Wrapper + { + [TestMethod] + public void TestGetSize() + { + UInt32Wrapper temp = new UInt32Wrapper(); + Assert.AreEqual(4, temp.Size); + } + + [TestMethod] + public void TestDeserialize() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + writer.Write(new byte[] { 0x00, 0x00, 0x00, 0x01 }); + stream.Seek(0, SeekOrigin.Begin); + UInt32Wrapper temp = new UInt32Wrapper(); + temp.Deserialize(reader); + MemoryStream stream2 = new MemoryStream(); + BinaryWriter writer2 = new BinaryWriter(stream2); + temp.Serialize(writer2); + stream2.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream2.Length]; + stream2.Read(byteArray, 0, (int)stream2.Length); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x00, 0x00, 0x00, 0x01 }), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestSerialize() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + writer.Write(new byte[] { 0x00, 0x00, 0x00, 0x01 }); + stream.Seek(0, SeekOrigin.Begin); + UInt32Wrapper temp = new UInt32Wrapper(); + temp.Deserialize(reader); + MemoryStream stream2 = new MemoryStream(); + BinaryWriter writer2 = new BinaryWriter(stream2); + temp.Serialize(writer2); + stream2.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream2.Length]; + stream2.Read(byteArray, 0, (int)stream2.Length); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x00, 0x00, 0x00, 0x01 }), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestEqualsUInt32Wrapper() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + writer.Write((uint)1); + stream.Seek(0, SeekOrigin.Begin); + UInt32Wrapper temp = new UInt32Wrapper(); + temp.Deserialize(reader); + MemoryStream stream2 = new MemoryStream(); + BinaryWriter writer2 = new BinaryWriter(stream2); + BinaryReader reader2 = new BinaryReader(stream2); + writer2.Write((uint)1); + stream2.Seek(0, SeekOrigin.Begin); + UInt32Wrapper temp2 = new UInt32Wrapper(); + temp2.Deserialize(reader2); + Assert.AreEqual(true, temp.Equals(temp2)); + } + + [TestMethod] + public void TestOperatorUint() + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + writer.Write((uint)1); + stream.Seek(0, SeekOrigin.Begin); + UInt32Wrapper temp = new UInt32Wrapper(); + temp.Deserialize(reader); + uint result = temp; + Assert.AreEqual((uint)1, result); + } + + [TestMethod] + public void TestOperatorUInt32Wrapper() + { + UInt32Wrapper temp = 1; + Assert.AreEqual(true, temp.Equals(1)); + } + } +} diff --git a/neo.UnitTests/UT_MemoryPool.cs b/neo.UnitTests/Ledger/UT_MemoryPool.cs similarity index 99% rename from neo.UnitTests/UT_MemoryPool.cs rename to neo.UnitTests/Ledger/UT_MemoryPool.cs index ee79d96842..ea797d7c03 100644 --- a/neo.UnitTests/UT_MemoryPool.cs +++ b/neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Neo.Ledger; @@ -8,7 +8,7 @@ using System.Collections.Generic; using System.Linq; -namespace Neo.UnitTests +namespace Neo.UnitTests.Ledger { [TestClass] public class UT_MemoryPool @@ -54,6 +54,7 @@ private Transaction CreateTransactionWithFee(long fee) mock.Object.Sender = UInt160.Zero; mock.Object.NetworkFee = fee; mock.Object.Attributes = new TransactionAttribute[0]; + mock.Object.Cosigners = new Cosigner[0]; mock.Object.Witnesses = new[] { new Witness diff --git a/neo.UnitTests/UT_PoolItem.cs b/neo.UnitTests/Ledger/UT_PoolItem.cs similarity index 93% rename from neo.UnitTests/UT_PoolItem.cs rename to neo.UnitTests/Ledger/UT_PoolItem.cs index cedf7f3fa9..10a57ecc90 100644 --- a/neo.UnitTests/UT_PoolItem.cs +++ b/neo.UnitTests/Ledger/UT_PoolItem.cs @@ -1,11 +1,12 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Neo.Ledger; +using Neo.IO; using Neo.Network.P2P.Payloads; using System; -namespace Neo.UnitTests +namespace Neo.UnitTests.Ledger { [TestClass] public class UT_PoolItem @@ -36,10 +37,10 @@ public void TestCleanup() [TestMethod] public void PoolItem_CompareTo_Fee() { - int size1 = 50; + int size1 = 51; int netFeeSatoshi1 = 1; var tx1 = GenerateTx(netFeeSatoshi1, size1); - int size2 = 50; + int size2 = 51; int netFeeSatoshi2 = 2; var tx2 = GenerateTx(netFeeSatoshi2, size2); @@ -56,7 +57,7 @@ public void PoolItem_CompareTo_Fee() [TestMethod] public void PoolItem_CompareTo_Hash() { - int sizeFixed = 50; + int sizeFixed = 51; int netFeeSatoshiFixed = 1; for (int testRuns = 0; testRuns < 30; testRuns++) @@ -122,6 +123,7 @@ public static Transaction GenerateTx(long networkFee, int size, byte[] overrideS Sender = UInt160.Zero, NetworkFee = networkFee, Attributes = new TransactionAttribute[0], + Cosigners = new Cosigner[0], Witnesses = new[] { new Witness @@ -132,6 +134,9 @@ public static Transaction GenerateTx(long networkFee, int size, byte[] overrideS } }; + tx.Attributes.Length.Should().Be(0); + tx.Cosigners.Length.Should().Be(0); + int diff = size - tx.Size; if (diff < 0) throw new ArgumentException(); if (diff > 0) diff --git a/neo.UnitTests/UT_StorageItem.cs b/neo.UnitTests/Ledger/UT_StorageItem.cs similarity index 98% rename from neo.UnitTests/UT_StorageItem.cs rename to neo.UnitTests/Ledger/UT_StorageItem.cs index c7372eb455..9afab26372 100644 --- a/neo.UnitTests/UT_StorageItem.cs +++ b/neo.UnitTests/Ledger/UT_StorageItem.cs @@ -5,7 +5,7 @@ using System.IO; using System.Text; -namespace Neo.UnitTests +namespace Neo.UnitTests.Ledger { [TestClass] public class UT_StorageItem diff --git a/neo.UnitTests/UT_StorageKey.cs b/neo.UnitTests/Ledger/UT_StorageKey.cs similarity index 99% rename from neo.UnitTests/UT_StorageKey.cs rename to neo.UnitTests/Ledger/UT_StorageKey.cs index ab718f0f89..6e2223cdcb 100644 --- a/neo.UnitTests/UT_StorageKey.cs +++ b/neo.UnitTests/Ledger/UT_StorageKey.cs @@ -3,7 +3,7 @@ using Neo.IO; using Neo.Ledger; -namespace Neo.UnitTests +namespace Neo.UnitTests.Ledger { [TestClass] public class UT_StorageKey @@ -128,4 +128,4 @@ public void GetHashCode_Get() uut.GetHashCode().Should().Be(806209853); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/UT_Block.cs b/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs similarity index 79% rename from neo.UnitTests/UT_Block.cs rename to neo.UnitTests/Network/P2P/Payloads/UT_Block.cs index f6b9218070..f27421dd74 100644 --- a/neo.UnitTests/UT_Block.cs +++ b/neo.UnitTests/Network/P2P/Payloads/UT_Block.cs @@ -1,12 +1,11 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.IO.Json; using Neo.Network.P2P.Payloads; using System.IO; -using System.Text; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P.Payloads { [TestClass] public class UT_Block @@ -44,9 +43,9 @@ public void Size_Get() { UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 0); - // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 4 + // blockbase 4 + 64 + 1 + 32 + 4 + 4 + 20 + 4 // block 9 + 1 - uut.Size.Should().Be(110); + uut.Size.Should().Be(114); } [TestMethod] @@ -60,7 +59,7 @@ public void Size_Get_1_Transaction() TestUtils.GetTransaction() }; - uut.Size.Should().Be(161); + uut.Size.Should().Be(166); } [TestMethod] @@ -76,7 +75,7 @@ public void Size_Get_3_Transaction() TestUtils.GetTransaction() }; - uut.Size.Should().Be(263); + uut.Size.Should().Be(270); } [TestMethod] @@ -85,15 +84,8 @@ public void Serialize() UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, 1); - byte[] data; - using (MemoryStream stream = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII, true)) - { - uut.Serialize(writer); - data = stream.ToArray(); - } - - Assert.AreEqual(data.ToHexString(), "0000000000000000000000000000000000000000000000000000000000000000000000000f29b0d748a9ccf8c5af3cde10db3e36ec9a5f720643a2bcb4add76b3daf41d880ab04fd0000000000000000000000000000000000000000000000000100015101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100010000"); + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000000f29b0d748a9ccf8c5af3cde10db3e36ec9a5f720643a2bcb4add76b3daf41d8e913ff854c000000000000000000000000000000000000000000000000000000010001510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100010000"; + uut.ToArray().ToHexString().Should().Be(hex); } [TestMethod] @@ -102,18 +94,19 @@ public void Deserialize() UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(new Block(), val256, out var merkRoot, out var val160, out var timestampVal, out var indexVal, out var scriptVal, out var transactionsVal, 1); - uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c000000000000000000000000000000000000000000000000000000010001510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100010000"; - using (MemoryStream ms = new MemoryStream("0000000000000000000000000000000000000000000000000000000000000000000000000f29b0d748a9ccf8c5af3cde10db3e36ec9a5f720643a2bcb4add76b3daf41d880ab04fd0000000000000000000000000000000000000000000000000100015101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100010000".HexToBytes(), false)) + using (MemoryStream ms = new MemoryStream(hex.HexToBytes(), false)) using (BinaryReader reader = new BinaryReader(ms)) { uut.Deserialize(reader); + merkRoot = uut.MerkleRoot; } assertStandardBlockTestVals(val256, merkRoot, val160, timestampVal, indexVal, scriptVal, transactionsVal); } - private void assertStandardBlockTestVals(UInt256 val256, UInt256 merkRoot, UInt160 val160, uint timestampVal, uint indexVal, Witness scriptVal, Transaction[] transactionsVal, bool testTransactions = true) + private void assertStandardBlockTestVals(UInt256 val256, UInt256 merkRoot, UInt160 val160, ulong timestampVal, uint indexVal, Witness scriptVal, Transaction[] transactionsVal, bool testTransactions = true) { uut.PrevHash.Should().Be(val256); uut.MerkleRoot.Should().Be(merkRoot); @@ -144,7 +137,8 @@ public void Equals_DiffObj() UInt256 prevHash = new UInt256(TestUtils.GetByteArray(32, 0x42)); UInt256 merkRoot; UInt160 val160; - uint timestampVal, indexVal; + ulong timestampVal; + uint indexVal; Witness scriptVal; Transaction[] transactionsVal; TestUtils.SetupBlockWithValues(newBlock, val256, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 1); @@ -167,7 +161,8 @@ public void Equals_SameHash() UInt256 prevHash = new UInt256(TestUtils.GetByteArray(32, 0x42)); UInt256 merkRoot; UInt160 val160; - uint timestampVal, indexVal; + ulong timestampVal; + uint indexVal; Witness scriptVal; Transaction[] transactionsVal; TestUtils.SetupBlockWithValues(newBlock, prevHash, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 1); @@ -182,7 +177,8 @@ public void RebuildMerkleRoot_Updates() UInt256 val256 = UInt256.Zero; UInt256 merkRoot; UInt160 val160; - uint timestampVal, indexVal; + ulong timestampVal; + uint indexVal; Witness scriptVal; Transaction[] transactionsVal; TestUtils.SetupBlockWithValues(uut, val256, out merkRoot, out val160, out timestampVal, out indexVal, out scriptVal, out transactionsVal, 1); @@ -203,12 +199,12 @@ public void ToJson() JObject jObj = uut.ToJson(); jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0x1d8642796276c8ce3c5c03b8984a1b593d99b49a63d830bb06f800b8c953be77"); - jObj["size"].AsNumber().Should().Be(161); + jObj["hash"].AsString().Should().Be("0x4e1d8392e7c44830e7e45c18e5e0e3ef3c36af883868846d3691a436a62494b2"); + jObj["size"].AsNumber().Should().Be(166); jObj["version"].AsNumber().Should().Be(0); jObj["previousblockhash"].AsString().Should().Be("0x0000000000000000000000000000000000000000000000000000000000000000"); jObj["merkleroot"].AsString().Should().Be("0xd841af3d6bd7adb4bca24306725f9aec363edb10de3cafc5f8cca948d7b0290f"); - jObj["time"].AsNumber().Should().Be(4244941696); + jObj["time"].AsNumber().Should().Be(328665601001); jObj["index"].AsNumber().Should().Be(0); jObj["nextconsensus"].AsString().Should().Be("AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM"); @@ -218,10 +214,11 @@ public void ToJson() jObj["tx"].Should().NotBeNull(); JArray txObj = (JArray)jObj["tx"]; - txObj[0]["hash"].AsString().Should().Be("0x64ed4e0d79407c60bde534feb44fbbd19bd065282d27ecd3a1a7a647f66affa6"); - txObj[0]["size"].AsNumber().Should().Be(51); + txObj[0]["hash"].AsString().Should().Be("0xf374032ea013e3d9272f7a25dc1fe497a489ae36e94aa06270b26e60ab693435"); + txObj[0]["size"].AsNumber().Should().Be(52); txObj[0]["version"].AsNumber().Should().Be(0); ((JArray)txObj[0]["attributes"]).Count.Should().Be(0); + ((JArray)txObj[0]["cosigners"]).Count.Should().Be(0); txObj[0]["net_fee"].AsString().Should().Be("0"); } } diff --git a/neo.UnitTests/Network/P2P/Payloads/UT_Cosigner.cs b/neo.UnitTests/Network/P2P/Payloads/UT_Cosigner.cs new file mode 100644 index 0000000000..e21f468575 --- /dev/null +++ b/neo.UnitTests/Network/P2P/Payloads/UT_Cosigner.cs @@ -0,0 +1,165 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; + +namespace Neo.UnitTests.Network.P2P.Payloads +{ + [TestClass] + public class UT_Cosigner + { + [TestMethod] + public void Serialize_Deserialize_Global() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.Global, + Account = UInt160.Zero + }; + + var hex = "000000000000000000000000000000000000000000"; + attr.ToArray().ToHexString().Should().Be(hex); + + var copy = hex.HexToBytes().AsSerializable(); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Serialize_Deserialize_CalledByEntry() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.CalledByEntry, + Account = UInt160.Zero + }; + + var hex = "000000000000000000000000000000000000000001"; + attr.ToArray().ToHexString().Should().Be(hex); + + var copy = hex.HexToBytes().AsSerializable(); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Serialize_Deserialize_CustomContracts() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.CustomContracts, + AllowedContracts = new[] { UInt160.Zero }, + Account = UInt160.Zero + }; + + var hex = "000000000000000000000000000000000000000010010000000000000000000000000000000000000000"; + attr.ToArray().ToHexString().Should().Be(hex); + + var copy = hex.HexToBytes().AsSerializable(); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + CollectionAssert.AreEqual(attr.AllowedContracts, copy.AllowedContracts); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Serialize_Deserialize_CustomGroups() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.CustomGroups, + AllowedGroups = new[] { ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1) }, + Account = UInt160.Zero + }; + + var hex = "0000000000000000000000000000000000000000200103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"; + attr.ToArray().ToHexString().Should().Be(hex); + + var copy = hex.HexToBytes().AsSerializable(); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + CollectionAssert.AreEqual(attr.AllowedGroups, copy.AllowedGroups); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Json_Global() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.Global, + Account = UInt160.Zero + }; + + var json = "{\"account\":\"0x0000000000000000000000000000000000000000\",\"scopes\":\"Global\"}"; + attr.ToJson().ToString().Should().Be(json); + + var copy = Cosigner.FromJson(JObject.Parse(json)); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Json_CalledByEntry() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.CalledByEntry, + Account = UInt160.Zero + }; + + var json = "{\"account\":\"0x0000000000000000000000000000000000000000\",\"scopes\":\"CalledByEntry\"}"; + attr.ToJson().ToString().Should().Be(json); + + var copy = Cosigner.FromJson(JObject.Parse(json)); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Json_CustomContracts() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.CustomContracts, + AllowedContracts = new[] { UInt160.Zero }, + Account = UInt160.Zero + }; + + var json = "{\"account\":\"0x0000000000000000000000000000000000000000\",\"scopes\":\"CustomContracts\",\"allowedContracts\":[\"0x0000000000000000000000000000000000000000\"]}"; + attr.ToJson().ToString().Should().Be(json); + + var copy = Cosigner.FromJson(JObject.Parse(json)); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + CollectionAssert.AreEqual(attr.AllowedContracts, copy.AllowedContracts); + Assert.AreEqual(attr.Account, copy.Account); + } + + [TestMethod] + public void Json_CustomGroups() + { + var attr = new Cosigner() + { + Scopes = WitnessScope.CustomGroups, + AllowedGroups = new[] { ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1) }, + Account = UInt160.Zero + }; + + var json = "{\"account\":\"0x0000000000000000000000000000000000000000\",\"scopes\":\"CustomGroups\",\"allowedGroups\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"]}"; + attr.ToJson().ToString().Should().Be(json); + + var copy = Cosigner.FromJson(JObject.Parse(json)); + + Assert.AreEqual(attr.Scopes, copy.Scopes); + CollectionAssert.AreEqual(attr.AllowedGroups, copy.AllowedGroups); + Assert.AreEqual(attr.Account, copy.Account); + } + } +} diff --git a/neo.UnitTests/UT_Header.cs b/neo.UnitTests/Network/P2P/Payloads/UT_Header.cs similarity index 59% rename from neo.UnitTests/UT_Header.cs rename to neo.UnitTests/Network/P2P/Payloads/UT_Header.cs index 82744a7f8b..3eabd054b6 100644 --- a/neo.UnitTests/UT_Header.cs +++ b/neo.UnitTests/Network/P2P/Payloads/UT_Header.cs @@ -1,10 +1,10 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; using Neo.Network.P2P.Payloads; using System.IO; -using System.Text; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P.Payloads { [TestClass] public class UT_Header @@ -22,22 +22,22 @@ public void Size_Get() { UInt256 val256 = UInt256.Zero; TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); - // blockbase 4 + 32 + 32 + 4 + 4 + 20 + 4 + // blockbase 4 + 64 + 1 + 32 + 4 + 4 + 20 + 4 // header 1 - uut.Size.Should().Be(101); + uut.Size.Should().Be(105); } [TestMethod] public void Deserialize() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupHeaderWithValues(new Header(), val256, out UInt256 merkRoot, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal); + TestUtils.SetupHeaderWithValues(new Header(), val256, out UInt256 merkRoot, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal); uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 41, 176, 215, 72, 169, 204, 248, 197, 175, 60, 222, 16, 219, 62, 54, 236, 154, 95, 114, 6, 67, 162, 188, 180, 173, 215, 107, 61, 175, 65, 216, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0 }; - int index = 0; - using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000000f29b0d748a9ccf8c5af3cde10db3e36ec9a5f720643a2bcb4add76b3daf41d8e913ff854c0000000000000000000000000000000000000000000000000000000100015100"; + + using (MemoryStream ms = new MemoryStream(hex.HexToBytes(), false)) { using (BinaryReader reader = new BinaryReader(ms)) { @@ -48,7 +48,7 @@ public void Deserialize() assertStandardHeaderTestVals(val256, merkRoot, val160, timestampVal, indexVal, scriptVal); } - private void assertStandardHeaderTestVals(UInt256 val256, UInt256 merkRoot, UInt160 val160, uint timestampVal, uint indexVal, Witness scriptVal) + private void assertStandardHeaderTestVals(UInt256 val256, UInt256 merkRoot, UInt160 val160, ulong timestampVal, uint indexVal, Witness scriptVal) { uut.PrevHash.Should().Be(val256); uut.MerkleRoot.Should().Be(merkRoot); @@ -96,23 +96,8 @@ public void Serialize() UInt256 val256 = UInt256.Zero; TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _); - byte[] data; - using (MemoryStream stream = new MemoryStream()) - { - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII, true)) - { - uut.Serialize(writer); - data = stream.ToArray(); - } - } - - byte[] requiredData = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 41, 176, 215, 72, 169, 204, 248, 197, 175, 60, 222, 16, 219, 62, 54, 236, 154, 95, 114, 6, 67, 162, 188, 180, 173, 215, 107, 61, 175, 65, 216, 128, 171, 4, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 81, 0 }; - - data.Length.Should().Be(requiredData.Length); - for (int i = 0; i < data.Length; i++) - { - data[i].Should().Be(requiredData[i]); - } + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000000f29b0d748a9ccf8c5af3cde10db3e36ec9a5f720643a2bcb4add76b3daf41d8e913ff854c0000000000000000000000000000000000000000000000000000000100015100"; + uut.ToArray().ToHexString().Should().Be(hex); } } } diff --git a/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs new file mode 100644 index 0000000000..fc32744b96 --- /dev/null +++ b/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs @@ -0,0 +1,1073 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.SmartContract.Native.Tokens; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Numerics; + +namespace Neo.UnitTests.Network.P2P.Payloads +{ + [TestClass] + public class UT_Transaction + { + Transaction uut; + Store store; + + [TestInitialize] + public void TestSetup() + { + uut = new Transaction(); + store = TestBlockchain.GetStore(); + } + + [TestMethod] + public void Script_Get() + { + uut.Script.Should().BeNull(); + } + + [TestMethod] + public void Script_Set() + { + byte[] val = TestUtils.GetByteArray(32, 0x42); + uut.Script = val; + uut.Script.Length.Should().Be(32); + for (int i = 0; i < val.Length; i++) + { + uut.Script[i].Should().Be(val[i]); + } + } + + [TestMethod] + public void Gas_Get() + { + uut.SystemFee.Should().Be(0); + } + + [TestMethod] + public void Gas_Set() + { + long val = 4200000000; + uut.SystemFee = val; + uut.SystemFee.Should().Be(val); + } + + [TestMethod] + public void Size_Get() + { + uut.Script = TestUtils.GetByteArray(32, 0x42); + uut.Sender = UInt160.Zero; + uut.Attributes = new TransactionAttribute[0]; + uut.Cosigners = new Cosigner[0]; + uut.Witnesses = new[] + { + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } + }; + + uut.Version.Should().Be(0); + uut.Script.Length.Should().Be(32); + uut.Script.GetVarSize().Should().Be(33); + uut.Size.Should().Be(83); + } + + [TestMethod] + public void FeeIsMultiSigContract() + { + var store = TestBlockchain.GetStore(); + var walletA = TestUtils.GenerateTestWallet(); + var walletB = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + using (var unlockA = walletA.Unlock("123")) + using (var unlockB = walletB.Unlock("123")) + { + var a = walletA.CreateAccount(); + var b = walletB.CreateAccount(); + + var multiSignContract = Contract.CreateMultiSigContract(2, + new ECPoint[] + { + a.GetKey().PublicKey, + b.GetKey().PublicKey + }); + + walletA.CreateAccount(multiSignContract, a.GetKey()); + var acc = walletB.CreateAccount(multiSignContract, b.GetKey()); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + + var tx = walletA.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(1,8) + } + }, acc.ScriptHash); + + Assert.IsNotNull(tx); + + // Sign + + var data = new ContractParametersContext(tx); + Assert.IsTrue(walletA.Sign(data)); + Assert.IsTrue(walletB.Sign(data)); + Assert.IsTrue(data.Completed); + + tx.Witnesses = data.GetWitnesses(); + + // Fast check + + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + Assert.AreEqual(verificationGas, 2000540); + Assert.AreEqual(sizeGas, 358000); + Assert.AreEqual(verificationGas + sizeGas, 2358540); + Assert.AreEqual(tx.NetworkFee, 2358540); + } + } + + [TestMethod] + public void FeeIsSignatureContractDetailed() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + using (var unlock = wallet.Unlock("123")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + + // self-transfer of 1e-8 GAS + var tx = wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(1,8) + } + }, acc.ScriptHash); + + Assert.IsNotNull(tx); + Assert.IsNull(tx.Witnesses); + + // check pre-computed network fee (already guessing signature sizes) + tx.NetworkFee.Should().Be(1257240); + + // ---- + // Sign + // ---- + + var data = new ContractParametersContext(tx); + // 'from' is always required as witness + // if not included on cosigner with a scope, its scope should be considered 'CalledByEntry' + data.ScriptHashes.Count.Should().Be(1); + data.ScriptHashes[0].ShouldBeEquivalentTo(acc.ScriptHash); + // will sign tx + bool signed = wallet.Sign(data); + Assert.IsTrue(signed); + // get witnesses from signed 'data' + tx.Witnesses = data.GetWitnesses(); + tx.Witnesses.Length.Should().Be(1); + + // Fast check + + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + Assert.AreEqual(verificationGas, 1000240); + + // ------------------ + // check tx_size cost + // ------------------ + Assert.AreEqual(tx.Size, 257); + + // will verify tx size, step by step + + // Part I + Assert.AreEqual(Transaction.HeaderSize, 45); + // Part II + Assert.AreEqual(tx.Attributes.GetVarSize(), 1); + Assert.AreEqual(tx.Attributes.Length, 0); + Assert.AreEqual(tx.Cosigners.Length, 1); + Assert.AreEqual(tx.Cosigners.GetVarSize(), 22); + // Note that Data size and Usage size are different (because of first byte on GetVarSize()) + Assert.AreEqual(tx.Cosigners[0].Size, 21); + // Part III + Assert.AreEqual(tx.Script.GetVarSize(), 82); + // Part IV + Assert.AreEqual(tx.Witnesses.GetVarSize(), 107); + // I + II + III + IV + Assert.AreEqual(tx.Size, 45 + 23 + 82 + 107); + + Assert.AreEqual(NativeContract.Policy.GetFeePerByte(snapshot), 1000); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + Assert.AreEqual(sizeGas, 257000); + + // final check on sum: verification_cost + tx_size + Assert.AreEqual(verificationGas + sizeGas, 1257240); + // final assert + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_Global() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // trying global scope + var cosigners = new Cosigner[]{ new Cosigner + { + Account = acc.ScriptHash, + Scopes = WitnessScope.Global + } }; + + // using this... + + var tx = wallet.MakeTransaction(script, acc.ScriptHash, new TransactionAttribute[0], cosigners); + + Assert.IsNotNull(tx); + Assert.IsNull(tx.Witnesses); + + // ---- + // Sign + // ---- + + var data = new ContractParametersContext(tx); + bool signed = wallet.Sign(data); + Assert.IsTrue(signed); + + // get witnesses from signed 'data' + tx.Witnesses = data.GetWitnesses(); + tx.Witnesses.Length.Should().Be(1); + + // Fast check + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + // get sizeGas + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + // final check on sum: verification_cost + tx_size + Assert.AreEqual(verificationGas + sizeGas, 1257240); + // final assert + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // trying global scope + var cosigners = new Cosigner[]{ new Cosigner + { + Account = acc.ScriptHash, + Scopes = WitnessScope.CustomContracts, + AllowedContracts = new[] { NativeContract.GAS.Hash } + } }; + + // using this... + + var tx = wallet.MakeTransaction(script, acc.ScriptHash, new TransactionAttribute[0], cosigners); + + Assert.IsNotNull(tx); + Assert.IsNull(tx.Witnesses); + + // ---- + // Sign + // ---- + + var data = new ContractParametersContext(tx); + bool signed = wallet.Sign(data); + Assert.IsTrue(signed); + + // get witnesses from signed 'data' + tx.Witnesses = data.GetWitnesses(); + tx.Witnesses.Length.Should().Be(1); + + // Fast check + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + // get sizeGas + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + // final check on sum: verification_cost + tx_size + Assert.AreEqual(verificationGas + sizeGas, 1278240); + // final assert + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // trying CalledByEntry together with GAS + var cosigners = new Cosigner[]{ new Cosigner + { + Account = acc.ScriptHash, + // This combination is supposed to actually be an OR, + // where it's valid in both Entry and also for Custom hash provided (in any execution level) + // it would be better to test this in the future including situations where a deeper call level uses this custom witness successfully + Scopes = WitnessScope.CustomContracts | WitnessScope.CalledByEntry, + AllowedContracts = new[] { NativeContract.GAS.Hash } + } }; + + // using this... + + var tx = wallet.MakeTransaction(script, acc.ScriptHash, new TransactionAttribute[0], cosigners); + + Assert.IsNotNull(tx); + Assert.IsNull(tx.Witnesses); + + // ---- + // Sign + // ---- + + var data = new ContractParametersContext(tx); + bool signed = wallet.Sign(data); + Assert.IsTrue(signed); + + // get witnesses from signed 'data' + tx.Witnesses = data.GetWitnesses(); + tx.Witnesses.Length.Should().Be(1); + + // Fast check + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + // get sizeGas + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + // final check on sum: verification_cost + tx_size + Assert.AreEqual(verificationGas + sizeGas, 1278240); + // final assert + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // trying global scope + var cosigners = new Cosigner[]{ new Cosigner + { + Account = acc.ScriptHash, + Scopes = WitnessScope.CustomContracts, + AllowedContracts = new[] { NativeContract.NEO.Hash } + } }; + + // using this... + + // expects FAULT on execution of 'transfer' Application script + // due to lack of a valid witness validation + Transaction tx = null; + Assert.ThrowsException(() => + tx = wallet.MakeTransaction(script, acc.ScriptHash, new TransactionAttribute[0], cosigners)); + Assert.IsNull(tx); + } + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // trying two custom hashes, for same target account + var cosigners = new Cosigner[]{ new Cosigner + { + Account = acc.ScriptHash, + Scopes = WitnessScope.CustomContracts, + AllowedContracts = new[] { NativeContract.NEO.Hash, NativeContract.GAS.Hash } + } }; + + // using this... + + var tx = wallet.MakeTransaction(script, acc.ScriptHash, new TransactionAttribute[0], cosigners); + + Assert.IsNotNull(tx); + Assert.IsNull(tx.Witnesses); + + // ---- + // Sign + // ---- + + var data = new ContractParametersContext(tx); + bool signed = wallet.Sign(data); + Assert.IsTrue(signed); + + // get witnesses from signed 'data' + tx.Witnesses = data.GetWitnesses(); + // only a single witness should exist + tx.Witnesses.Length.Should().Be(1); + // no attributes must exist + tx.Attributes.Length.Should().Be(0); + // one cosigner must exist + tx.Cosigners.Length.Should().Be(1); + + // Fast check + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + // get sizeGas + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + // final check on sum: verification_cost + tx_size + Assert.AreEqual(verificationGas + sizeGas, 1298240); + // final assert + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_NoScopeFAULT() + { + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // trying with no scope + var attributes = new TransactionAttribute[] { }; + var cosigners = new Cosigner[] { }; + + // using this... + + // expects FAULT on execution of 'transfer' Application script + // due to lack of a valid witness validation + Transaction tx = null; + Assert.ThrowsException(() => tx = wallet.MakeTransaction(script, acc.ScriptHash, attributes, cosigners)); + Assert.IsNull(tx); + } + } + + [TestMethod] + public void Transaction_Serialize_Deserialize_Simple() + { + // good and simple transaction + Transaction txSimple = new Transaction + { + Version = 0x00, + Nonce = 0x01020304, + Sender = UInt160.Zero, + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + NetworkFee = 0x0000000000000001, + ValidUntilBlock = 0x01020304, + Attributes = new TransactionAttribute[0] { }, + Cosigners = new Cosigner[0] { }, + Script = new byte[] { (byte)OpCode.PUSH1 }, + Witnesses = new Witness[0] { } + }; + + byte[] sTx = txSimple.ToArray(); + + // detailed hexstring info (basic checking) + sTx.ToHexString().Should().Be("00" + // version + "04030201" + // nonce + "0000000000000000000000000000000000000000" + // sender + "00e1f50500000000" + // system fee (1 GAS) + "0100000000000000" + // network fee (1 satoshi) + "04030201" + // timelimit + "00" + // no attributes + "00" + // no cosigners + "0151" + // push1 script + "00"); // no witnesses + + // try to deserialize + Transaction tx2 = Neo.IO.Helper.AsSerializable(sTx); + + tx2.Version.Should().Be(0x00); + tx2.Nonce.Should().Be(0x01020304); + tx2.Sender.Should().Be(UInt160.Zero); + tx2.SystemFee.Should().Be(0x0000000005f5e100); // 1 GAS (long)BigInteger.Pow(10, 8) + tx2.NetworkFee.Should().Be(0x0000000000000001); + tx2.ValidUntilBlock.Should().Be(0x01020304); + tx2.Attributes.Should().BeEquivalentTo(new TransactionAttribute[0] { }); + tx2.Cosigners.Should().BeEquivalentTo(new Cosigner[0] { }); + tx2.Script.Should().BeEquivalentTo(new byte[] { (byte)OpCode.PUSH1 }); + tx2.Witnesses.Should().BeEquivalentTo(new Witness[0] { }); + } + + [TestMethod] + public void Transaction_Serialize_Deserialize_DistinctCosigners() + { + // cosigners must be distinct (regarding account) + + Transaction txDoubleCosigners = new Transaction + { + Version = 0x00, + Nonce = 0x01020304, + Sender = UInt160.Zero, + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + NetworkFee = 0x0000000000000001, + ValidUntilBlock = 0x01020304, + Attributes = new TransactionAttribute[0] { }, + Cosigners = new Cosigner[] { + new Cosigner + { + Account = UInt160.Parse("0x0001020304050607080900010203040506070809"), + Scopes = WitnessScope.Global + }, + new Cosigner + { + Account = UInt160.Parse("0x0001020304050607080900010203040506070809"), // same account as above + Scopes = WitnessScope.CalledByEntry // different scope, but still, same account (cannot do that) + } + }, + Script = new byte[] { (byte)OpCode.PUSH1 }, + Witnesses = new Witness[0] { } + }; + + byte[] sTx = txDoubleCosigners.ToArray(); + + // no need for detailed hexstring here (see basic tests for it) + sTx.ToHexString().Should().Be("0004030201000000000000000000000000000000000000000000e1f505000000000100000000000000040302010002090807060504030201000908070605040302010000090807060504030201000908070605040302010001015100"); + + // back to transaction (should fail, due to non-distinct cosigners) + Transaction tx2 = null; + Assert.ThrowsException(() => + tx2 = Neo.IO.Helper.AsSerializable(sTx) + ); + Assert.IsNull(tx2); + } + + + [TestMethod] + public void Transaction_Serialize_Deserialize_MaxSizeCosigners() + { + // cosigners must respect count + + int maxCosigners = 16; + + // -------------------------------------- + // this should pass (respecting max size) + + var cosigners1 = new Cosigner[maxCosigners]; + for (int i = 0; i < cosigners1.Length; i++) + { + string hex = i.ToString("X4"); + while (hex.Length < 40) + hex = hex.Insert(0, "0"); + cosigners1[i] = new Cosigner + { + Account = UInt160.Parse(hex) + }; + } + + Transaction txCosigners1 = new Transaction + { + Version = 0x00, + Nonce = 0x01020304, + Sender = UInt160.Zero, + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + NetworkFee = 0x0000000000000001, + ValidUntilBlock = 0x01020304, + Attributes = new TransactionAttribute[0] { }, + Cosigners = cosigners1, // max + 1 (should fail) + Script = new byte[] { (byte)OpCode.PUSH1 }, + Witnesses = new Witness[0] { } + }; + + byte[] sTx1 = txCosigners1.ToArray(); + + // back to transaction (should fail, due to non-distinct cosigners) + Transaction tx1 = Neo.IO.Helper.AsSerializable(sTx1); + Assert.IsNotNull(tx1); + + // ---------------------------- + // this should fail (max + 1) + + var cosigners = new Cosigner[maxCosigners + 1]; + for (var i = 0; i < maxCosigners + 1; i++) + { + string hex = i.ToString("X4"); + while (hex.Length < 40) + hex = hex.Insert(0, "0"); + cosigners[i] = new Cosigner + { + Account = UInt160.Parse(hex) + }; + } + + Transaction txCosigners = new Transaction + { + Version = 0x00, + Nonce = 0x01020304, + Sender = UInt160.Zero, + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + NetworkFee = 0x0000000000000001, + ValidUntilBlock = 0x01020304, + Attributes = new TransactionAttribute[0] { }, + Cosigners = cosigners, // max + 1 (should fail) + Script = new byte[] { (byte)OpCode.PUSH1 }, + Witnesses = new Witness[0] { } + }; + + byte[] sTx2 = txCosigners.ToArray(); + + // back to transaction (should fail, due to non-distinct cosigners) + Transaction tx2 = null; + Assert.ThrowsException(() => + tx2 = Neo.IO.Helper.AsSerializable(sTx2) + ); + Assert.IsNull(tx2); + } + + [TestMethod] + public void FeeIsSignatureContract_TestScope_Global_Default() + { + // Global is supposed to be default + + Cosigner cosigner = new Cosigner(); + cosigner.Scopes.Should().Be(WitnessScope.Global); + + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // no password on this wallet + using (var unlock = wallet.Unlock("")) + { + var acc = wallet.CreateAccount(); + + // Fake balance + + var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); + + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + // Make transaction + // Manually creating script + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + // self-transfer of 1e-8 GAS + System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value; + sb.EmitAppCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value); + sb.Emit(OpCode.THROWIFNOT); + script = sb.ToArray(); + } + + // default to global scope + var cosigners = new Cosigner[]{ new Cosigner + { + Account = acc.ScriptHash + } }; + + // using this... + + var tx = wallet.MakeTransaction(script, acc.ScriptHash, new TransactionAttribute[0], cosigners); + + Assert.IsNotNull(tx); + Assert.IsNull(tx.Witnesses); + + // ---- + // Sign + // ---- + + var data = new ContractParametersContext(tx); + bool signed = wallet.Sign(data); + Assert.IsTrue(signed); + + // get witnesses from signed 'data' + tx.Witnesses = data.GetWitnesses(); + tx.Witnesses.Length.Should().Be(1); + + // Fast check + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + + // Check + long verificationGas = 0; + foreach (var witness in tx.Witnesses) + { + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) + { + engine.LoadScript(witness.VerificationScript); + engine.LoadScript(witness.InvocationScript); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); + verificationGas += engine.GasConsumed; + } + } + // get sizeGas + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + // final check on sum: verification_cost + tx_size + Assert.AreEqual(verificationGas + sizeGas, 1257240); + // final assert + Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); + } + } + + [TestMethod] + public void ToJson() + { + uut.Script = TestUtils.GetByteArray(32, 0x42); + uut.Sender = UInt160.Zero; + uut.SystemFee = 4200000000; + uut.Attributes = new TransactionAttribute[] { }; + uut.Cosigners = new Cosigner[] { }; + uut.Witnesses = new[] + { + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } + }; + + JObject jObj = uut.ToJson(); + jObj.Should().NotBeNull(); + jObj["hash"].AsString().Should().Be("0x11e3ee692015f0cd3cb8b6db7a4fc37568540f020cb9ca497a9917c81f20b62f"); + jObj["size"].AsNumber().Should().Be(83); + jObj["version"].AsNumber().Should().Be(0); + ((JArray)jObj["attributes"]).Count.Should().Be(0); + ((JArray)jObj["cosigners"]).Count.Should().Be(0); + jObj["net_fee"].AsString().Should().Be("0"); + jObj["script"].AsString().Should().Be("4220202020202020202020202020202020202020202020202020202020202020"); + jObj["sys_fee"].AsString().Should().Be("4200000000"); + } + } +} diff --git a/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs b/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs new file mode 100644 index 0000000000..5114d524b2 --- /dev/null +++ b/neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs @@ -0,0 +1,196 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.IO; +using System.Linq; + +namespace Neo.UnitTests.Network.P2P.Payloads +{ + [TestClass] + public class UT_Witness + { + class DummyVerificable : IVerifiable + { + private UInt160 _hash; + + public Witness[] Witnesses { get; set; } + + public int Size => 1; + + public DummyVerificable(UInt160 hash) + { + _hash = hash; + } + + public void Deserialize(BinaryReader reader) + { + DeserializeUnsigned(reader); + Witnesses = reader.ReadSerializableArray(16); + } + + public void DeserializeUnsigned(BinaryReader reader) + { + reader.ReadByte(); + } + + public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) + { + return new UInt160[] { _hash }; + } + + public void Serialize(BinaryWriter writer) + { + SerializeUnsigned(writer); + writer.Write(Witnesses); + } + + public void SerializeUnsigned(BinaryWriter writer) + { + writer.Write((byte)1); + } + } + + Witness uut; + + [TestInitialize] + public void TestSetup() + { + uut = new Witness(); + } + + [TestMethod] + public void InvocationScript_Get() + { + uut.InvocationScript.Should().BeNull(); + } + + private Witness PrepareDummyWitness(int maxAccounts) + { + var store = TestBlockchain.GetStore(); + var wallet = TestUtils.GenerateTestWallet(); + var snapshot = store.GetSnapshot(); + + // Prepare + + var address = new WalletAccount[maxAccounts]; + var wallets = new NEP6Wallet[maxAccounts]; + var walletsUnlocks = new IDisposable[maxAccounts]; + + for (int x = 0; x < maxAccounts; x++) + { + wallets[x] = TestUtils.GenerateTestWallet(); + walletsUnlocks[x] = wallets[x].Unlock("123"); + address[x] = wallets[x].CreateAccount(); + } + + // Generate multisignature + + var multiSignContract = Contract.CreateMultiSigContract(maxAccounts, address.Select(a => a.GetKey().PublicKey).ToArray()); + + for (int x = 0; x < maxAccounts; x++) + { + wallets[x].CreateAccount(multiSignContract, address[x].GetKey()); + } + + // Sign + + var data = new ContractParametersContext(new DummyVerificable(multiSignContract.ScriptHash)); + + for (int x = 0; x < maxAccounts; x++) + { + Assert.IsTrue(wallets[x].Sign(data)); + } + + Assert.IsTrue(data.Completed); + return data.GetWitnesses()[0]; + } + + [TestMethod] + public void MaxSize_OK() + { + var witness = PrepareDummyWitness(10); + + // Check max size + + witness.Size.Should().Be(1003); + witness.InvocationScript.GetVarSize().Should().Be(653); + witness.VerificationScript.GetVarSize().Should().Be(350); + + Assert.IsTrue(witness.Size <= 1024); + + var copy = witness.ToArray().AsSerializable(); + + CollectionAssert.AreEqual(witness.InvocationScript, copy.InvocationScript); + CollectionAssert.AreEqual(witness.VerificationScript, copy.VerificationScript); + } + + [TestMethod] + public void MaxSize_Error() + { + var witness = PrepareDummyWitness(11); + + // Check max size + + Assert.IsTrue(witness.Size > 1024); + Assert.ThrowsException(() => witness.ToArray().AsSerializable()); + } + + [TestMethod] + public void InvocationScript_Set() + { + byte[] dataArray = new byte[] { 0, 32, 32, 20, 32, 32 }; + uut.InvocationScript = dataArray; + uut.InvocationScript.Length.Should().Be(6); + Assert.AreEqual(uut.InvocationScript.ToHexString(), "002020142020"); + } + + private void setupWitnessWithValues(Witness uut, int lenghtInvocation, int lengthVerification, out byte[] invocationScript, out byte[] verificationScript) + { + invocationScript = TestUtils.GetByteArray(lenghtInvocation, 0x20); + verificationScript = TestUtils.GetByteArray(lengthVerification, 0x20); + uut.InvocationScript = invocationScript; + uut.VerificationScript = verificationScript; + } + + [TestMethod] + public void SizeWitness_Small_Arrary() + { + byte[] invocationScript; + byte[] verificationScript; + setupWitnessWithValues(uut, 252, 253, out invocationScript, out verificationScript); + + uut.Size.Should().Be(509); // (1 + 252*1) + (1 + 2 + 253*1) + } + + [TestMethod] + public void SizeWitness_Large_Arrary() + { + byte[] invocationScript; + byte[] verificationScript; + setupWitnessWithValues(uut, 65535, 65536, out invocationScript, out verificationScript); + + uut.Size.Should().Be(131079); // (1 + 2 + 65535*1) + (1 + 4 + 65536*1) + } + + [TestMethod] + public void ToJson() + { + byte[] invocationScript; + byte[] verificationScript; + setupWitnessWithValues(uut, 2, 3, out invocationScript, out verificationScript); + + JObject json = uut.ToJson(); + Assert.IsTrue(json.ContainsProperty("invocation")); + Assert.IsTrue(json.ContainsProperty("verification")); + Assert.AreEqual(json["invocation"].AsString(), "2020"); + Assert.AreEqual(json["verification"].AsString(), "202020"); + } + } +} diff --git a/neo.UnitTests/UT_P2PMessage.cs b/neo.UnitTests/Network/P2P/UT_Message.cs similarity index 98% rename from neo.UnitTests/UT_P2PMessage.cs rename to neo.UnitTests/Network/P2P/UT_Message.cs index 99af976992..6f194ea862 100644 --- a/neo.UnitTests/UT_P2PMessage.cs +++ b/neo.UnitTests/Network/P2P/UT_Message.cs @@ -6,10 +6,10 @@ using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P { [TestClass] - public class UT_P2PMessage + public class UT_Message { [TestMethod] public void Serialize_Deserialize() @@ -112,4 +112,4 @@ public void Compression() ((ServerCapability)payloadCopy.Capabilities[0]).Port.Should().Be(25); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/UT_ProtocolHandler.cs b/neo.UnitTests/Network/P2P/UT_ProtocolHandler.cs similarity index 95% rename from neo.UnitTests/UT_ProtocolHandler.cs rename to neo.UnitTests/Network/P2P/UT_ProtocolHandler.cs index 63bfe90e44..f8b932c4f2 100644 --- a/neo.UnitTests/UT_ProtocolHandler.cs +++ b/neo.UnitTests/Network/P2P/UT_ProtocolHandler.cs @@ -1,10 +1,10 @@ -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P { [TestClass] public class UT_ProtocolHandler : TestKit diff --git a/neo.UnitTests/UT_ProtocolHandlerMailbox.cs b/neo.UnitTests/Network/P2P/UT_ProtocolHandlerMailbox.cs similarity index 98% rename from neo.UnitTests/UT_ProtocolHandlerMailbox.cs rename to neo.UnitTests/Network/P2P/UT_ProtocolHandlerMailbox.cs index edd5a4191f..cb9c4586a8 100644 --- a/neo.UnitTests/UT_ProtocolHandlerMailbox.cs +++ b/neo.UnitTests/Network/P2P/UT_ProtocolHandlerMailbox.cs @@ -1,18 +1,13 @@ -using Akka.TestKit; using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using Moq; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.Network.P2P; -using Akka.Configuration; using Neo.IO; -using System.Linq; +using Neo.Network.P2P; +using System; using System.Collections.Generic; +using System.Linq; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P { [TestClass] public class UT_ProtocolHandlerMailbox : TestKit diff --git a/neo.UnitTests/UT_RemoteNode.cs b/neo.UnitTests/Network/P2P/UT_RemoteNode.cs similarity index 98% rename from neo.UnitTests/UT_RemoteNode.cs rename to neo.UnitTests/Network/P2P/UT_RemoteNode.cs index 60d587ed13..570cc904da 100644 --- a/neo.UnitTests/UT_RemoteNode.cs +++ b/neo.UnitTests/Network/P2P/UT_RemoteNode.cs @@ -6,9 +6,9 @@ using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P { - [TestClass] + [TestClass] [NotReRunnable] public class UT_RemoteNode : TestKit { diff --git a/neo.UnitTests/UT_RemoteNodeMailbox.cs b/neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs similarity index 96% rename from neo.UnitTests/UT_RemoteNodeMailbox.cs rename to neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs index ff3e6e17d9..b57e9e93a7 100644 --- a/neo.UnitTests/UT_RemoteNodeMailbox.cs +++ b/neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs @@ -1,11 +1,11 @@ -using System; using Akka.IO; using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; +using System; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P { [TestClass] [NotReRunnable] diff --git a/neo.UnitTests/UT_TaskManagerMailbox.cs b/neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs similarity index 94% rename from neo.UnitTests/UT_TaskManagerMailbox.cs rename to neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs index 2ba4ff2d85..fca280d1eb 100644 --- a/neo.UnitTests/UT_TaskManagerMailbox.cs +++ b/neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs @@ -1,15 +1,11 @@ -using Akka.TestKit; using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using Moq; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; using Neo.Network.P2P; -using Akka.Configuration; +using Neo.Network.P2P.Payloads; +using System; -namespace Neo.UnitTests +namespace Neo.UnitTests.Network.P2P { [TestClass] public class UT_TaskManagerMailbox : TestKit diff --git a/neo.UnitTests/Network/RPC/UT_ContractClient.cs b/neo.UnitTests/Network/RPC/UT_ContractClient.cs new file mode 100644 index 0000000000..fde8a7747d --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_ContractClient.cs @@ -0,0 +1,68 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Network.RPC; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_ContractClient + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + } + + [TestMethod] + public void TestMakeScript() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + + Assert.AreEqual("14000000000000000000000000000000000000000051c10962616c616e63654f66142582d1b275e86c8f0e93a9b2facd5fdb760976a168627d5b52", + testScript.ToHexString()); + } + + [TestMethod] + public void TestInvoke() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.ByteArray, Value = "00e057eb481b".HexToBytes() }); + + ContractClient contractClient = new ContractClient(rpcClientMock.Object); + var result = contractClient.TestInvoke(NativeContract.GAS.Hash, "balanceOf", UInt160.Zero); + + Assert.AreEqual(30000000000000L, (long)result.Stack[0].ToStackItem().GetBigInteger()); + } + + [TestMethod] + public void TestDeployContract() + { + byte[] script; + var manifest = ContractManifest.CreateDefault(new byte[1].ToScriptHash()); + manifest.Features = ContractFeatures.HasStorage | ContractFeatures.Payable; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitSysCall(InteropService.Neo_Contract_Create, new byte[1], manifest.ToString()); + script = sb.ToArray(); + } + + UT_TransactionManager.MockInvokeScript(rpcClientMock, script, new ContractParameter()); + + ContractClient contractClient = new ContractClient(rpcClientMock.Object); + var result = contractClient.DeployContract(new byte[1], manifest, keyPair1); + + Assert.IsNotNull(result); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_Nep5API.cs b/neo.UnitTests/Network/RPC/UT_Nep5API.cs new file mode 100644 index 0000000000..72e0c29487 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_Nep5API.cs @@ -0,0 +1,89 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Network.RPC; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_Nep5API + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + Nep5API nep5API; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + nep5API = new Nep5API(rpcClientMock.Object); + } + + [TestMethod] + public void TestBalanceOf() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(10000) }); + + var balance = nep5API.BalanceOf(NativeContract.GAS.Hash, UInt160.Zero); + Assert.AreEqual(10000, (int)balance); + } + + [TestMethod] + public void TestGetName() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("name"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Name }); + + var result = nep5API.Name(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Name, result); + } + + [TestMethod] + public void TestGetSymbol() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("symbol"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }); + + var result = nep5API.Symbol(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Symbol, result); + } + + [TestMethod] + public void TestGetDecimals() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("decimals"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }); + + var result = nep5API.Decimals(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Decimals, (byte)result); + } + + [TestMethod] + public void TestGetTotalSupply() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("totalSupply"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var result = nep5API.TotalSupply(NativeContract.GAS.Hash); + Assert.AreEqual(1_00000000, (int)result); + } + + [TestMethod] + public void TestTransfer() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000)); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter()); + + var result = nep5API.Transfer(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000)); + Assert.IsNotNull(result); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_PolicyAPI.cs b/neo.UnitTests/Network/RPC/UT_PolicyAPI.cs new file mode 100644 index 0000000000..6b6c449111 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_PolicyAPI.cs @@ -0,0 +1,69 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Network.RPC; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_PolicyAPI + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + PolicyAPI policyAPI; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + policyAPI = new PolicyAPI(rpcClientMock.Object); + } + + [TestMethod] + public void TestGetMaxTransactionsPerBlock() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getMaxTransactionsPerBlock"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(512) }); + + var result = policyAPI.GetMaxTransactionsPerBlock(); + Assert.AreEqual(512u, result); + } + + [TestMethod] + public void TestGetMaxBlockSize() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getMaxBlockSize"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1024u * 256u) }); + + var result = policyAPI.GetMaxBlockSize(); + Assert.AreEqual(1024u * 256u, result); + } + + [TestMethod] + public void TestGetFeePerByte() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1000) }); + + var result = policyAPI.GetFeePerByte(); + Assert.AreEqual(1000L, result); + } + + [TestMethod] + public void TestGetBlockedAccounts() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getBlockedAccounts"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Array, Value = new[] { new ContractParameter { Type = ContractParameterType.Hash160, Value = UInt160.Zero } } }); + + var result = policyAPI.GetBlockedAccounts(); + Assert.AreEqual(UInt160.Zero, result[0]); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_RpcClient.cs b/neo.UnitTests/Network/RPC/UT_RpcClient.cs new file mode 100644 index 0000000000..0875a5a793 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_RpcClient.cs @@ -0,0 +1,544 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Moq.Protected; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.VM; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_RpcClient + { + RpcClient rpc; + Mock handlerMock; + + [TestInitialize] + public void TestSetup() + { + handlerMock = new Mock(MockBehavior.Strict); + + // use real http client with mocked handler here + var httpClient = new HttpClient(handlerMock.Object) + { + BaseAddress = new Uri("http://seed1.neo.org:10331"), + }; + + rpc = new RpcClient(httpClient); + } + + private void MockResponse(string content) + { + handlerMock.Protected() + // Setup the PROTECTED method to mock + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + // prepare the expected response of the mocked http call + .ReturnsAsync(new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(content), + }) + .Verifiable(); + } + + private JObject CreateErrorResponse(JObject id, int code, string message, JObject data = null) + { + JObject response = CreateResponse(id); + response["error"] = new JObject(); + response["error"]["code"] = code; + response["error"]["message"] = message; + if (data != null) + response["error"]["data"] = data; + return response; + } + + private JObject CreateResponse(JObject id) + { + JObject response = new JObject(); + response["jsonrpc"] = "2.0"; + response["id"] = id; + return response; + } + + [TestMethod] + public void TestErrorResponse() + { + JObject response = CreateErrorResponse(null, -32700, "Parse error"); + MockResponse(response.ToString()); + try + { + var result = rpc.GetBlockHex("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); + } + catch (RpcException ex) + { + Assert.AreEqual(-32700, ex.HResult); + Assert.AreEqual("Parse error", ex.Message); + } + } + + [TestMethod] + public void TestGetBestBlockHash() + { + JObject response = CreateResponse(1); + response["result"] = "000000002deadfa82cbc4682f5800"; + MockResponse(response.ToString()); + + var result = rpc.GetBestBlockHash(); + Assert.AreEqual("000000002deadfa82cbc4682f5800", result); + } + + [TestMethod] + public void TestGetBlockHex() + { + JObject response = CreateResponse(1); + response["result"] = "000000002deadfa82cbc4682f5800"; + MockResponse(response.ToString()); + + var result = rpc.GetBlockHex("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); + Assert.AreEqual("000000002deadfa82cbc4682f5800", result); + } + + [TestMethod] + public void TestGetBlock() + { + // create block + var block = new Block(); + TestUtils.SetupBlockWithValues(block, UInt256.Zero, out _, out UInt160 _, out ulong _, out uint _, out Witness _, out Transaction[] _, 0); + + block.Transactions = new[] + { + TestUtils.GetTransaction(), + TestUtils.GetTransaction(), + TestUtils.GetTransaction() + }; + + JObject json = block.ToJson(); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetBlock("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); + Assert.AreEqual(block.Hash.ToString(), result.Block.Hash.ToString()); + Assert.IsNull(result.Confirmations); + Assert.AreEqual(block.Transactions.Length, result.Block.Transactions.Length); + Assert.AreEqual(block.Transactions[0].Hash.ToString(), result.Block.Transactions[0].Hash.ToString()); + + // verbose with confirmations + json["confirmations"] = 20; + json["nextblockhash"] = "773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"; + MockResponse(response.ToString()); + result = rpc.GetBlock("773dd2dae4a9c9275290f89b56e67d7363ea4826dfd4fc13cc01cf73a44b0d0e"); + Assert.AreEqual(block.Hash.ToString(), result.Block.Hash.ToString()); + Assert.AreEqual(20, result.Confirmations); + Assert.AreEqual(block.Transactions.Length, result.Block.Transactions.Length); + Assert.AreEqual(block.Transactions[0].Hash.ToString(), result.Block.Transactions[0].Hash.ToString()); + } + + [TestMethod] + public void TestGetBlockCount() + { + JObject response = CreateResponse(1); + response["result"] = 100; + MockResponse(response.ToString()); + + var result = rpc.GetBlockCount(); + Assert.AreEqual(100u, result); + } + + [TestMethod] + public void TestGetBlockHash() + { + JObject response = CreateResponse(1); + response["result"] = "0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2"; + MockResponse(response.ToString()); + + var result = rpc.GetBlockHash(100); + Assert.AreEqual("0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2", result); + } + + [TestMethod] + public void TestGetBlockHeaderHex() + { + JObject response = CreateResponse(1); + response["result"] = "0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2"; + MockResponse(response.ToString()); + + var result = rpc.GetBlockHeaderHex("100"); + Assert.AreEqual("0x4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2", result); + } + + [TestMethod] + public void TestGetBlockHeader() + { + Header header = new Header(); + TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 _, out UInt160 _, out ulong _, out uint _, out Witness _); + + JObject json = header.ToJson(); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetBlockHeader("100"); + Assert.AreEqual(header.Hash.ToString(), result.Header.Hash.ToString()); + Assert.IsNull(result.Confirmations); + + json["confirmations"] = 20; + json["nextblockhash"] = "4c1e879872344349067c3b1a30781eeb4f9040d3795db7922f513f6f9660b9b2"; + MockResponse(response.ToString()); + result = rpc.GetBlockHeader("100"); + Assert.AreEqual(header.Hash.ToString(), result.Header.Hash.ToString()); + Assert.AreEqual(20, result.Confirmations); + } + + [TestMethod] + public void TestGetBlockSysFee() + { + JObject response = CreateResponse(1); + response["result"] = "195500"; + MockResponse(response.ToString()); + + var result = rpc.GetBlockSysFee(100); + Assert.AreEqual("195500", result); + } + + [TestMethod] + public void TestGetConnectionCount() + { + JObject response = CreateResponse(1); + response["result"] = 9; + MockResponse(response.ToString()); + + var result = rpc.GetConnectionCount(); + Assert.AreEqual(9, result); + } + + [TestMethod] + public void TestGetContractState() + { + byte[] script; + using (var sb = new ScriptBuilder()) + { + sb.EmitSysCall(InteropService.System_Runtime_GetInvocationCounter); + script = sb.ToArray(); + } + + ContractState state = new ContractState + { + Script = new byte[] { (byte)OpCode.DROP, (byte)OpCode.DROP }.Concat(script).ToArray(), + Manifest = ContractManifest.CreateDefault(UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")) + }; + + JObject response = CreateResponse(1); + response["result"] = state.ToJson(); + MockResponse(response.ToString()); + + var result = rpc.GetContractState("17694b31cc7ee215cea2ded146e0b2b28768fc46"); + + Assert.AreEqual(state.Script.ToHexString(), result.Script.ToHexString()); + Assert.AreEqual(state.Manifest.Abi.EntryPoint.Name, result.Manifest.Abi.EntryPoint.Name); + } + + [TestMethod] + public void TestGetPeers() + { + JObject response = CreateResponse(1); + response["result"] = JObject.Parse(@"{ + ""unconnected"": [ + { + ""address"": ""::ffff:70.73.16.236"", + ""port"": 10333 + }, + { + ""address"": ""::ffff:82.95.77.148"", + ""port"": 10333 + }, + { + ""address"": ""::ffff:49.50.215.166"", + ""port"": 10333 + } + ], + ""bad"": [], + ""connected"": [ + { + ""address"": ""::ffff:139.219.106.33"", + ""port"": 10333 + }, + { + ""address"": ""::ffff:47.88.53.224"", + ""port"": 10333 + } + ] + }"); + MockResponse(response.ToString()); + + var result = rpc.GetPeers(); + Assert.AreEqual("::ffff:139.219.106.33", result.Connected[0].Address); + Assert.AreEqual("::ffff:82.95.77.148", result.Unconnected[1].Address); + } + + [TestMethod] + public void TestGetRawMempool() + { + JObject response = CreateResponse(1); + response["result"] = JObject.Parse(@"[ + ""0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e"", + ""0xb488ad25eb474f89d5ca3f985cc047ca96bc7373a6d3da8c0f192722896c1cd7"", + ""0xf86f6f2c08fbf766ebe59dc84bc3b8829f1053f0a01deb26bf7960d99fa86cd6"" + ]"); + MockResponse(response.ToString()); + + var result = rpc.GetRawMempool(); + Assert.AreEqual("0xb488ad25eb474f89d5ca3f985cc047ca96bc7373a6d3da8c0f192722896c1cd7", result[1]); + } + + [TestMethod] + public void TestGetRawMempoolBoth() + { + JObject json = new JObject(); + json["height"] = 65535; + json["verified"] = new JArray(new[] { "0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e" }.Select(p => (JObject)p)); + json["unverified"] = new JArray(new[] { "0xb488ad25eb474f89d5ca3f985cc047ca96bc7373a6d3da8c0f192722896c1cd7", "0xf86f6f2c08fbf766ebe59dc84bc3b8829f1053f0a01deb26bf7960d99fa86cd6" }.Select(p => (JObject)p)); + + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetRawMempoolBoth(); + Assert.AreEqual(65535u, result.Height); + Assert.AreEqual("0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e", result.Verified[0]); + Assert.AreEqual("0xf86f6f2c08fbf766ebe59dc84bc3b8829f1053f0a01deb26bf7960d99fa86cd6", result.UnVerified[1]); + } + + [TestMethod] + public void TestGetRawTransactionHex() + { + var json = TestUtils.GetTransaction().ToArray().ToHexString(); + + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + //var result = rpc.GetRawTransactionHex("0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e"); + var result = rpc.GetRawTransactionHex(TestUtils.GetTransaction().Hash.ToString()); + Assert.AreEqual(json, result); + } + + [TestMethod] + public void TestGetRawTransaction() + { + var transaction = TestUtils.GetTransaction(); + JObject json = transaction.ToJson(); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetRawTransaction("0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e"); + Assert.AreEqual(transaction.Hash, result.Transaction.Hash); + Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + + json["blockhash"] = UInt256.Zero.ToString(); + json["confirmations"] = 100; + json["blocktime"] = 10; + MockResponse(response.ToString()); + + result = rpc.GetRawTransaction("0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e"); + Assert.AreEqual(transaction.Hash, result.Transaction.Hash); + Assert.AreEqual(100, result.Confirmations); + Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public void TestGetStorage() + { + JObject json = "4c696e"; + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetStorage("03febccf81ac85e3d795bc5cbd4e84e907812aa3", "5065746572"); + Assert.AreEqual("4c696e", result); + } + + [TestMethod] + public void TestGetTransactionHeight() + { + JObject json = 10000; + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetTransactionHeight("9c909e1e3ba03290553a68d862e002c7a21ba302e043fc492fe069bf6a134d29"); + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetValidators() + { + JObject json = JObject.Parse(@"[ + { + ""publickey"": ""02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"", + ""votes"": ""46632420"", + ""active"": true + }, + { + ""publickey"": ""024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"", + ""votes"": ""46632420"", + ""active"": true + } + ]"); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetValidators(); + Assert.AreEqual(((JArray)json)[0].ToString(), (result[0]).ToJson().ToString()); + } + + [TestMethod] + public void TestGetVersion() + { + JObject json = new JObject(); + json["tcpPort"] = 30001; + json["wsPort"] = 30002; + json["nonce"] = 1546258664; + json["useragent"] = "/NEO:2.7.5/"; + + var json1 = JObject.Parse(@"{ + ""tcpPort"": 30001, + ""wsPort"": 30002, + ""nonce"": 1546258664, + ""useragent"": ""/NEO:2.7.5/"" + }"); + Assert.AreEqual(json.ToString(), json1.ToString()); + + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.GetVersion(); + Assert.AreEqual(30001, result.TcpPort); + Assert.AreEqual("/NEO:2.7.5/", result.UserAgent); + } + + [TestMethod] + public void TestInvokeFunction() + { + JObject json = JObject.Parse(@" + { + ""script"": ""1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf"", + ""state"": ""HALT"", + ""gas_consumed"": ""0.311"", + ""stack"": [ + { + ""type"": ""ByteArray"", + ""value"": ""262bec084432"" + } + ], + ""tx"":""d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"" + }"); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.InvokeFunction("af7c7328eee5a275a3bcaee2bf0cf662b5e739be", "balanceOf", new[] { new RpcStack { Type = "Hash160", Value = "91b83e96f2a7c4fdf0c1688441ec61986c7cae26" } }); + Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public void TestInvokeScript() + { + JObject json = JObject.Parse(@" + { + ""script"": ""1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf"", + ""state"": ""HALT"", + ""gas_consumed"": ""0.311"", + ""stack"": [ + { + ""type"": ""ByteArray"", + ""value"": ""262bec084432"" + } + ], + ""tx"":""d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"" + }"); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191".HexToBytes()); + Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public void TestListPlugins() + { + JObject json = JObject.Parse(@"[{ + ""name"": ""SimplePolicyPlugin"", + ""version"": ""2.10.1.0"", + ""interfaces"": [ + ""ILogPlugin"", + ""IPolicyPlugin"" + ] + }]"); + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.ListPlugins(); + Assert.AreEqual(((JArray)json)[0].ToString(), result[0].ToJson().ToString()); + } + + [TestMethod] + public void TestSendRawTransaction() + { + JObject json = true; + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.SendRawTransaction("80000001195876cb34364dc38b730077156c6bc3a7fc570044a66fbfeeea56f71327e8ab0000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c65eaf440000000f9a23e06f74cf86b8827a9108ec2e0f89ad956c9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50092e14b5e00000030aab52ad93f6ce17ca07fa88fc191828c58cb71014140915467ecd359684b2dc358024ca750609591aa731a0b309c7fb3cab5cd0836ad3992aa0a24da431f43b68883ea5651d548feb6bd3c8e16376e6e426f91f84c58232103322f35c7819267e721335948d385fae5be66e7ba8c748ac15467dcca0693692dac".HexToBytes()); + Assert.AreEqual(json.ToString(), ((JObject)result).ToString()); + } + + [TestMethod] + public void TestSubmitBlock() + { + JObject json = true; + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.SubmitBlock("03febccf81ac85e3d795bc5cbd4e84e907812aa3".HexToBytes()); + Assert.AreEqual(json.ToString(), ((JObject)result).ToString()); + } + + [TestMethod] + public void TestValidateAddress() + { + JObject json = new JObject(); + json["address"] = "AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"; + json["isvalid"] = false; + JObject response = CreateResponse(1); + response["result"] = json; + MockResponse(response.ToString()); + + var result = rpc.ValidateAddress("AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"); + Assert.AreEqual(json.ToString(), result.ToJson().ToString()); + } + } +} diff --git a/neo.UnitTests/Network/RPC/UT_TransactionManager.cs b/neo.UnitTests/Network/RPC/UT_TransactionManager.cs new file mode 100644 index 0000000000..ee55f81bb9 --- /dev/null +++ b/neo.UnitTests/Network/RPC/UT_TransactionManager.cs @@ -0,0 +1,194 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Json; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; + +namespace Neo.UnitTests.Network.RPC +{ + [TestClass] + public class UT_TransactionManager + { + TransactionManager txManager; + Mock rpcClientMock; + KeyPair keyPair1; + KeyPair keyPair2; + UInt160 sender; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + keyPair2 = new KeyPair(Wallet.GetPrivateKeyFromWIF("L2LGkrwiNmUAnWYb1XGd5mv7v2eDf6P4F3gHyXSrNJJR4ArmBp7Q")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = MockRpcClient(sender, new byte[1]); + } + + public static Mock MockRpcClient(UInt160 sender, byte[] script) + { + var mockRpc = new Mock(MockBehavior.Strict, "http://seed1.neo.org:10331"); + + // MockHeight + mockRpc.Setup(p => p.RpcSend("getblockcount")).Returns(100).Verifiable(); + + // MockGasBalance + byte[] balanceScript = NativeContract.GAS.Hash.MakeScript("balanceOf", sender); + var balanceResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("10000000000000000") }; + + MockInvokeScript(mockRpc, balanceScript, balanceResult); + + // MockFeePerByte + byte[] policyScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + var policyResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("1000") }; + + MockInvokeScript(mockRpc, policyScript, policyResult); + + // MockGasConsumed + var result = new ContractParameter(); + MockInvokeScript(mockRpc, script, result); + + return mockRpc; + } + + public static void MockInvokeScript(Mock mockClient, byte[] script, params ContractParameter[] parameters) + { + var result = new RpcInvokeResult() + { + Stack = parameters, + GasConsumed = "100", + Script = script.ToHexString(), + State = "", + Tx = "" + }; + + mockClient.Setup(p => p.RpcSend("invokescript", It.Is(j => j.AsString() == script.ToHexString()))) + .Returns(result.ToJson()) + .Verifiable(); + } + + [TestMethod] + public void TestMakeTransaction() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + TransactionAttribute[] attributes = new TransactionAttribute[1] + { + new TransactionAttribute + { + Usage = TransactionAttributeUsage.Url, + Data = "53616d706c6555726c".HexToBytes() // "SampleUrl" + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, attributes, null, 60000); + + var tx = txManager.Tx; + Assert.AreEqual("53616d706c6555726c", tx.Attributes[0].Data.ToHexString()); + Assert.AreEqual(0, tx.SystemFee % (long)NativeContract.GAS.Factor); + Assert.AreEqual(60000, tx.NetworkFee); + } + + [TestMethod] + public void TestSign() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + TransactionAttribute[] attributes = new TransactionAttribute[1] + { + new TransactionAttribute + { + Usage = TransactionAttributeUsage.Url, + Data = "53616d706c6555726c".HexToBytes() // "SampleUrl" + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, attributes) + .AddSignature(keyPair1) + .Sign(); + + // get signature from Witnesses + var tx = txManager.Tx; + byte[] signature = tx.Witnesses[0].InvocationScript.Skip(1).ToArray(); + + Assert.IsTrue(Crypto.Default.VerifySignature(tx.GetHashData(), signature, keyPair1.PublicKey.EncodePoint(false).Skip(1).ToArray())); + + // duplicate sign should not add new witness + txManager.AddSignature(keyPair1).Sign(); + Assert.AreEqual(1, txManager.Tx.Witnesses.Length); + + // throw exception when the KeyPair is wrong + Assert.ThrowsException(() => txManager.AddSignature(keyPair2)); + } + + [TestMethod] + public void TestSignMulti() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + var multiContract = Contract.CreateMultiSigContract(2, keyPair1.PublicKey, keyPair2.PublicKey); + + // Cosigner needs multi signature + Cosigner[] cosigners = new Cosigner[1] + { + new Cosigner + { + Account = multiContract.ScriptHash, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, null, cosigners, 0_10000000) + .AddMultiSig(keyPair1, 2, keyPair1.PublicKey, keyPair2.PublicKey) + .AddMultiSig(keyPair2, 2, keyPair1.PublicKey, keyPair2.PublicKey) + .AddSignature(keyPair1) + .Sign(); + + var store = TestBlockchain.GetStore(); + var snapshot = store.GetSnapshot(); + + var tx = txManager.Tx; + Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); + } + + [TestMethod] + public void TestAddWitness() + { + txManager = new TransactionManager(rpcClientMock.Object, sender); + + // Cosigner as contract scripthash + Cosigner[] cosigners = new Cosigner[1] + { + new Cosigner + { + Account = UInt160.Zero, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager.MakeTransaction(script, null, cosigners, 0_10000000); + txManager.AddWitness(UInt160.Zero); + txManager.AddSignature(keyPair1); + txManager.Sign(); + + var tx = txManager.Tx; + Assert.AreEqual(2, tx.Witnesses.Length); + Assert.AreEqual(0, tx.Witnesses[0].VerificationScript.Length); + Assert.AreEqual(0, tx.Witnesses[0].InvocationScript.Length); + } + } +} diff --git a/neo.UnitTests/UT_ConcatenatedIterator.cs b/neo.UnitTests/SmartContract/Iterators/UT_ConcatenatedIterator.cs similarity index 97% rename from neo.UnitTests/UT_ConcatenatedIterator.cs rename to neo.UnitTests/SmartContract/Iterators/UT_ConcatenatedIterator.cs index 7051f5f4f8..5fad01c94b 100644 --- a/neo.UnitTests/UT_ConcatenatedIterator.cs +++ b/neo.UnitTests/SmartContract/Iterators/UT_ConcatenatedIterator.cs @@ -4,7 +4,7 @@ using Neo.VM.Types; using System.Numerics; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract.Iterators { [TestClass] @@ -66,4 +66,4 @@ private Integer MakeIntegerStackItem(int val) return new Integer(new BigInteger(val)); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/UT_ContractManifest.cs b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs similarity index 98% rename from neo.UnitTests/UT_ContractManifest.cs rename to neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index 451791e9bd..a96c3ff4c5 100644 --- a/neo.UnitTests/UT_ContractManifest.cs +++ b/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -1,8 +1,8 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.SmartContract.Manifest; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract.Manifest { [TestClass] public class UT_ContractManifest diff --git a/neo.UnitTests/UT_GasToken.cs b/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs similarity index 98% rename from neo.UnitTests/UT_GasToken.cs rename to neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index 72a0c4d7ae..0202e9d6d0 100644 --- a/neo.UnitTests/UT_GasToken.cs +++ b/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -11,7 +11,7 @@ using System.Linq; using System.Numerics; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract.Native.Tokens { [TestClass] public class UT_GasToken @@ -141,4 +141,4 @@ public void Check_BadScript() NativeContract.GAS.Invoke(engine).Should().BeFalse(); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/UT_NeoToken.cs b/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs similarity index 94% rename from neo.UnitTests/UT_NeoToken.cs rename to neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs index 3997573c0f..38befbe790 100644 --- a/neo.UnitTests/UT_NeoToken.cs +++ b/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Cryptography.ECC; @@ -15,7 +15,7 @@ using System.Linq; using System.Numerics; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract.Native.Tokens { [TestClass] public class UT_NeoToken @@ -128,6 +128,20 @@ public void Check_RegisterValidator() ret.Result.Should().BeTrue(); snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 1); // New validator + + // Check GetRegisteredValidators + + var validators = NativeContract.NEO.GetRegisteredValidators(snapshot).OrderBy(u => u.PublicKey).ToArray(); + var check = Blockchain.StandbyValidators.Select(u => u.EncodePoint(true)).ToList(); + check.Add(point); // Add the new member + + for (int x = 0; x < validators.Length; x++) + { + Assert.AreEqual(1, check.RemoveAll(u => u.SequenceEqual(validators[x].PublicKey.EncodePoint(true)))); + Assert.AreEqual(0, validators[x].Votes); + } + + Assert.AreEqual(0, check.Count); } [TestMethod] @@ -357,4 +371,4 @@ internal static void CheckBalance(byte[] account, DataCache(); ret.GetBigInteger().Should().Be(512); + ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); + ret.Should().BeOfType(); + ret.GetBigInteger().Should().Be(1024 * 256); + ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); ret.GetBigInteger().Should().Be(1000); @@ -48,20 +52,66 @@ public void Check_Initialize() } [TestMethod] - public void Check_SetMaxTransactionsPerBlock() + public void Check_SetMaxBlockSize() { var snapshot = Store.GetSnapshot().Clone(); // Fake blockchain snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - snapshot.Blocks.Add(UInt256.Zero, new Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); + snapshot.Blocks.Add(UInt256.Zero, new Neo.Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); // Without signature var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), + "setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = 1024 }); + ret.Should().BeOfType(); + ret.GetBoolean().Should().BeFalse(); + + ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); + ret.Should().BeOfType(); + ret.GetBigInteger().Should().Be(1024 * 256); + + // More than expected + + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), + "setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = Neo.Network.P2P.Message.PayloadMaxSize }); + ret.Should().BeOfType(); + ret.GetBoolean().Should().BeFalse(); + + ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); + ret.Should().BeOfType(); + ret.GetBigInteger().Should().Be(1024 * 256); + + // With signature + + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero), + "setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = 1024 }); + ret.Should().BeOfType(); + ret.GetBoolean().Should().BeTrue(); + + ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize"); + ret.Should().BeOfType(); + ret.GetBigInteger().Should().Be(1024); + } + + [TestMethod] + public void Check_SetMaxTransactionsPerBlock() + { + var snapshot = Store.GetSnapshot().Clone(); + + // Fake blockchain + + snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero }; + snapshot.Blocks.Add(UInt256.Zero, new Neo.Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); + + NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); + + // Without signature + + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(), "setMaxTransactionsPerBlock", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -90,13 +140,13 @@ public void Check_SetFeePerByte() // Fake blockchain snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - snapshot.Blocks.Add(UInt256.Zero, new Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); + snapshot.Blocks.Add(UInt256.Zero, new Neo.Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); // Without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(), "setFeePerByte", new ContractParameter(ContractParameterType.Integer) { Value = 1 }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -125,13 +175,13 @@ public void Check_Block_UnblockAccount() // Fake blockchain snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero }; - snapshot.Blocks.Add(UInt256.Zero, new Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); + snapshot.Blocks.Add(UInt256.Zero, new Neo.Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero }); NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0)).Should().BeTrue(); // Block without signature - var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), + var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(), "blockAccount", new ContractParameter(ContractParameterType.Hash160) { Value = UInt160.Zero }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); @@ -154,7 +204,7 @@ public void Check_Block_UnblockAccount() // Unblock without signature - ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null), + ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(), "unblockAccount", new ContractParameter(ContractParameterType.Hash160) { Value = UInt160.Zero }); ret.Should().BeOfType(); ret.GetBoolean().Should().BeFalse(); diff --git a/neo.UnitTests/UT_InteropPrices.cs b/neo.UnitTests/SmartContract/UT_InteropPrices.cs similarity index 99% rename from neo.UnitTests/UT_InteropPrices.cs rename to neo.UnitTests/SmartContract/UT_InteropPrices.cs index 27c0a4cdc0..d6b9b8254b 100644 --- a/neo.UnitTests/UT_InteropPrices.cs +++ b/neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -3,7 +3,7 @@ using Neo.SmartContract; using Neo.VM; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract { [TestClass] public class UT_InteropPrices diff --git a/neo.UnitTests/UT_InteropService.cs b/neo.UnitTests/SmartContract/UT_InteropService.cs similarity index 97% rename from neo.UnitTests/UT_InteropService.cs rename to neo.UnitTests/SmartContract/UT_InteropService.cs index b5e3626109..6a33a32abc 100644 --- a/neo.UnitTests/UT_InteropService.cs +++ b/neo.UnitTests/SmartContract/UT_InteropService.cs @@ -1,10 +1,9 @@ -using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract { [TestClass] public class UT_InteropService @@ -42,7 +41,7 @@ public void Runtime_GetNotifications_Test() scriptHash2 = script.ToArray().ToScriptHash(); snapshot.Contracts.Delete(scriptHash2); - snapshot.Contracts.Add(scriptHash2, new Ledger.ContractState() + snapshot.Contracts.Add(scriptHash2, new Neo.Ledger.ContractState() { Script = script.ToArray(), Manifest = ContractManifest.CreateDefault(scriptHash2), @@ -86,7 +85,7 @@ public void Runtime_GetNotifications_Test() // Receive all notifications - script.EmitPush(UInt160.Zero.ToArray()); + script.EmitPush(new byte[0]); script.EmitSysCall(InteropService.System_Runtime_GetNotifications); // Execute @@ -190,4 +189,4 @@ private void AssertNotification(StackItem stackItem, UInt160 scriptHash, int not Assert.AreEqual(notification, array[1].GetBigInteger()); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/UT_JsonSerializer.cs b/neo.UnitTests/SmartContract/UT_JsonSerializer.cs similarity index 99% rename from neo.UnitTests/UT_JsonSerializer.cs rename to neo.UnitTests/SmartContract/UT_JsonSerializer.cs index ed20b15392..e7fdf30667 100644 --- a/neo.UnitTests/UT_JsonSerializer.cs +++ b/neo.UnitTests/SmartContract/UT_JsonSerializer.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Json; using Neo.SmartContract; using Neo.VM; @@ -7,7 +7,7 @@ using System.Linq; using System.Numerics; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract { [TestClass] public class UT_JsonSerializer diff --git a/neo.UnitTests/UT_OpCodePrices.cs b/neo.UnitTests/SmartContract/UT_OpCodePrices.cs similarity index 79% rename from neo.UnitTests/UT_OpCodePrices.cs rename to neo.UnitTests/SmartContract/UT_OpCodePrices.cs index b53d745d6a..cb04990e31 100644 --- a/neo.UnitTests/UT_OpCodePrices.cs +++ b/neo.UnitTests/SmartContract/UT_OpCodePrices.cs @@ -1,9 +1,9 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; using Neo.VM; using System; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract { [TestClass] public class UT_OpCodePrices diff --git a/neo.UnitTests/UT_Syscalls.cs b/neo.UnitTests/SmartContract/UT_Syscalls.cs similarity index 96% rename from neo.UnitTests/UT_Syscalls.cs rename to neo.UnitTests/SmartContract/UT_Syscalls.cs index c6642c4e88..65066b9079 100644 --- a/neo.UnitTests/UT_Syscalls.cs +++ b/neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -1,10 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Ledger; using Neo.SmartContract; using Neo.VM; using System.Linq; -namespace Neo.UnitTests +namespace Neo.UnitTests.SmartContract { [TestClass] public class UT_Syscalls @@ -63,4 +63,4 @@ public void System_Runtime_GetInvocationCounter() ); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/TestBlockchain.cs b/neo.UnitTests/TestBlockchain.cs index 3037e9e3dc..5276dc2e4c 100644 --- a/neo.UnitTests/TestBlockchain.cs +++ b/neo.UnitTests/TestBlockchain.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using Neo.IO.Wrappers; using Neo.Ledger; using Neo.Persistence; @@ -59,4 +59,4 @@ public static NeoSystem InitializeMockNeoSystem() return TheNeoSystem; } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/TestDataCache.cs b/neo.UnitTests/TestDataCache.cs index 68f3b07da7..44d86141b5 100644 --- a/neo.UnitTests/TestDataCache.cs +++ b/neo.UnitTests/TestDataCache.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Caching; using System; using System.Collections.Generic; diff --git a/neo.UnitTests/TestMetaDataCache.cs b/neo.UnitTests/TestMetaDataCache.cs index 55dca734f4..0cb5646928 100644 --- a/neo.UnitTests/TestMetaDataCache.cs +++ b/neo.UnitTests/TestMetaDataCache.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Caching; namespace Neo.UnitTests diff --git a/neo.UnitTests/TestUtils.cs b/neo.UnitTests/TestUtils.cs index 9bf06acbf7..554b492d4b 100644 --- a/neo.UnitTests/TestUtils.cs +++ b/neo.UnitTests/TestUtils.cs @@ -1,6 +1,11 @@ -using Neo.IO; +using FluentAssertions; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Manifest; using Neo.VM; +using Neo.Wallets.NEP6; using System; using System.IO; @@ -21,6 +26,18 @@ public static byte[] GetByteArray(int length, byte firstByte) return array; } + public static NEP6Wallet GenerateTestWallet() + { + JObject wallet = new JObject(); + wallet["name"] = "noname"; + wallet["version"] = new System.Version().ToString(); + wallet["scrypt"] = new ScryptParameters(0, 0, 0).ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = null; + wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"0.0\",\"scrypt\":{\"n\":0,\"r\":0,\"p\":0},\"accounts\":[],\"extra\":null}"); + return new NEP6Wallet(wallet); + } + public static Transaction GetTransaction() { return new Transaction @@ -28,6 +45,7 @@ public static Transaction GetTransaction() Script = new byte[1], Sender = UInt160.Zero, Attributes = new TransactionAttribute[0], + Cosigners = new Cosigner[0], Witnesses = new Witness[]{ new Witness { InvocationScript = new byte[0], @@ -36,12 +54,21 @@ public static Transaction GetTransaction() }; } - public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal) + internal static ContractState GetContract() + { + return new ContractState + { + Script = new byte[] { 0x01, 0x01, 0x01, 0x01 }, + Manifest = ContractManifest.CreateDefault(UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01")) + }; + } + + public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal) { setupBlockBaseWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); } - public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) + public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) { setupBlockBaseWithValues(block, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out scriptVal); @@ -58,13 +85,12 @@ public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 block.Transactions = transactionsVal; } - private static void setupBlockBaseWithValues(BlockBase bb, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal) + private static void setupBlockBaseWithValues(BlockBase bb, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal) { bb.PrevHash = val256; merkRootVal = UInt256.Parse("0xd841af3d6bd7adb4bca24306725f9aec363edb10de3cafc5f8cca948d7b0290f"); - bb.MerkleRoot = merkRootVal; - timestampVal = new DateTime(1968, 06, 01, 0, 0, 0, DateTimeKind.Utc).ToTimestamp(); + timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM bb.Timestamp = timestampVal; indexVal = 0; bb.Index = indexVal; @@ -87,6 +113,7 @@ public static Transaction CreateRandomHashTransaction() Script = randomBytes, Sender = UInt160.Zero, Attributes = new TransactionAttribute[0], + Cosigners = new Cosigner[0], Witnesses = new[] { new Witness @@ -108,5 +135,13 @@ public static Transaction CreateRandomHashTransaction() return newObj; } + + public static void DeleteFile(string file) + { + if (File.Exists(file)) + { + File.Delete(file); + } + } } } diff --git a/neo.UnitTests/TestVerifiable.cs b/neo.UnitTests/TestVerifiable.cs index dfe21c2d0f..c0920df480 100644 --- a/neo.UnitTests/TestVerifiable.cs +++ b/neo.UnitTests/TestVerifiable.cs @@ -42,4 +42,4 @@ public void SerializeUnsigned(BinaryWriter writer) writer.Write((string)testStr); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/TestWalletAccount.cs b/neo.UnitTests/TestWalletAccount.cs index 5b07045d1e..b8a242c578 100644 --- a/neo.UnitTests/TestWalletAccount.cs +++ b/neo.UnitTests/TestWalletAccount.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using Neo.SmartContract; using Neo.Wallets; using System; diff --git a/neo.UnitTests/UT_BigDecimal.cs b/neo.UnitTests/UT_BigDecimal.cs new file mode 100644 index 0000000000..2d359da891 --- /dev/null +++ b/neo.UnitTests/UT_BigDecimal.cs @@ -0,0 +1,191 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Numerics; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_BigDecimal + { + [TestMethod] + public void TestChangeDecimals() + { + BigDecimal originalValue = new BigDecimal(new BigInteger(12300), 5); + BigDecimal result1 = originalValue.ChangeDecimals(7); + result1.Value.Should().Be(new BigInteger(1230000)); + result1.Decimals.Should().Be(7); + BigDecimal result2 = originalValue.ChangeDecimals(3); + result2.Value.Should().Be(new BigInteger(123)); + result2.Decimals.Should().Be(3); + BigDecimal result3 = originalValue.ChangeDecimals(5); + result3.Value.Should().Be(originalValue.Value); + Action action = () => originalValue.ChangeDecimals(2); + action.ShouldThrow(); + } + + [TestMethod] + public void TestBigDecimalConstructor() + { + BigDecimal value = new BigDecimal(new BigInteger(45600), 7); + value.Value.Should().Be(new BigInteger(45600)); + value.Decimals.Should().Be(7); + value = new BigDecimal(new BigInteger(0), 5); + value.Value.Should().Be(new BigInteger(0)); + value.Decimals.Should().Be(5); + value = new BigDecimal(new BigInteger(-10), 0); + value.Value.Should().Be(new BigInteger(-10)); + value.Decimals.Should().Be(0); + } + + [TestMethod] + public void TestGetDecimals() + { + BigDecimal value = new BigDecimal(new BigInteger(45600), 7); + value.Sign.Should().Be(1); + value = new BigDecimal(new BigInteger(0), 5); + value.Sign.Should().Be(0); + value = new BigDecimal(new BigInteger(-10), 0); + value.Sign.Should().Be(-1); + } + + [TestMethod] + public void TestGetSign() + { + BigDecimal value = new BigDecimal(new BigInteger(45600), 7); + value.Sign.Should().Be(1); + value = new BigDecimal(new BigInteger(0), 5); + value.Sign.Should().Be(0); + value = new BigDecimal(new BigInteger(-10), 0); + value.Sign.Should().Be(-1); + } + + [TestMethod] + public void TestParse() + { + string s = "12345"; + byte decimals = 0; + BigDecimal.Parse(s, decimals).Should().Be(new BigDecimal(new BigInteger(12345), 0)); + + s = "abcdEfg"; + Action action = () => BigDecimal.Parse(s, decimals); + action.ShouldThrow(); + } + + [TestMethod] + public void TestToString() + { + BigDecimal value = new BigDecimal(new BigInteger(100000), 5); + value.ToString().Should().Be("1"); + value = new BigDecimal(new BigInteger(123456), 5); + value.ToString().Should().Be("1.23456"); + } + + [TestMethod] + public void TestTryParse() + { + string s = ""; + byte decimals = 0; + BigDecimal result; + + s = "12345"; + decimals = 0; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(12345), 0)); + + s = "12345E-5"; + decimals = 5; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(12345), 5)); + + s = "abcdEfg"; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "123.45"; + decimals = 2; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(12345), 2)); + + s = "123.45E-5"; + decimals = 7; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(12345), 7)); + + s = "12345E-5"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "1.2345"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "1.2345E-5"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "12345"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(12345000), 3)); + + s = "12345E-2"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(123450), 3)); + + s = "123.45"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(123450), 3)); + + s = "123.45E3"; + decimals = 3; + BigDecimal.TryParse(s, decimals, out result).Should().BeTrue(); + result.Should().Be(new BigDecimal(new BigInteger(123450000), 3)); + + s = "a456bcdfg"; + decimals = 0; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a456bce-5"; + decimals = 5; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a4.56bcd"; + decimals = 5; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a4.56bce3"; + decimals = 2; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a456bcd"; + decimals = 2; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a456bcdE3"; + decimals = 2; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a456b.cd"; + decimals = 5; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + + s = "a456b.cdE3"; + decimals = 5; + BigDecimal.TryParse(s, decimals, out result).Should().BeFalse(); + result.Should().Be(default(BigDecimal)); + } + } +} diff --git a/neo.UnitTests/UT_Culture.cs b/neo.UnitTests/UT_Culture.cs index 807e67990c..37a44b24ef 100644 --- a/neo.UnitTests/UT_Culture.cs +++ b/neo.UnitTests/UT_Culture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; diff --git a/neo.UnitTests/UT_DataCache.cs b/neo.UnitTests/UT_DataCache.cs new file mode 100644 index 0000000000..a428e72d8d --- /dev/null +++ b/neo.UnitTests/UT_DataCache.cs @@ -0,0 +1,126 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using Neo.Ledger; +using System.Linq; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_DataCache + { + [TestInitialize] + public void TestSetup() + { + TestBlockchain.InitializeMockNeoSystem(); + } + + [TestMethod] + public void TestCachedFind_Between() + { + var snapshot = TestBlockchain.GetStore().GetSnapshot(); + var storages = snapshot.Storages; + var cache = new CloneCache(storages); + + storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); + + storages.Add + ( + new StorageKey() { Key = new byte[] { 0x01, 0x01 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + storages.Add + ( + new StorageKey() { Key = new byte[] { 0x00, 0x01 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + storages.Add + ( + new StorageKey() { Key = new byte[] { 0x00, 0x03 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + cache.Add + ( + new StorageKey() { Key = new byte[] { 0x01, 0x02 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + cache.Add + ( + new StorageKey() { Key = new byte[] { 0x00, 0x02 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + + CollectionAssert.AreEqual( + cache.Find(new byte[21]).Select(u => u.Key.Key[1]).ToArray(), + new byte[] { 0x01, 0x02, 0x03 } + ); + + storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); + } + + [TestMethod] + public void TestCachedFind_Last() + { + var snapshot = TestBlockchain.GetStore().GetSnapshot(); + var storages = snapshot.Storages; + var cache = new CloneCache(storages); + + storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); + + storages.Add + ( + new StorageKey() { Key = new byte[] { 0x00, 0x01 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + storages.Add + ( + new StorageKey() { Key = new byte[] { 0x01, 0x01 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + cache.Add + ( + new StorageKey() { Key = new byte[] { 0x00, 0x02 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + cache.Add + ( + new StorageKey() { Key = new byte[] { 0x01, 0x02 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + + CollectionAssert.AreEqual( + cache.Find(new byte[21]).Select(u => u.Key.Key[1]).ToArray(), + new byte[] { 0x01, 0x02 } + ); + + storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); + } + + [TestMethod] + public void TestCachedFind_Empty() + { + var snapshot = TestBlockchain.GetStore().GetSnapshot(); + var storages = snapshot.Storages; + var cache = new CloneCache(storages); + + storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); + + cache.Add + ( + new StorageKey() { Key = new byte[] { 0x00, 0x02 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + cache.Add + ( + new StorageKey() { Key = new byte[] { 0x01, 0x02 }, ScriptHash = UInt160.Zero }, + new StorageItem() { IsConstant = false, Value = new byte[] { } } + ); + + CollectionAssert.AreEqual( + cache.Find(new byte[21]).Select(u => u.Key.Key[1]).ToArray(), + new byte[] { 0x02 } + ); + + storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); + } + } +} diff --git a/neo.UnitTests/UT_FifoSet.cs b/neo.UnitTests/UT_FifoSet.cs deleted file mode 100644 index ff5ccf9c27..0000000000 --- a/neo.UnitTests/UT_FifoSet.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO.Caching; -using System.Linq; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_FifoSet - { - [TestMethod] - public void FifoSetTest() - { - var a = UInt256.Zero; - var b = new UInt256(); - var c = new UInt256(new byte[32] { - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01 - }); - - var set = new FIFOSet(3); - - Assert.IsTrue(set.Add(a)); - Assert.IsFalse(set.Add(a)); - Assert.IsFalse(set.Add(b)); - Assert.IsTrue(set.Add(c)); - - CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { a, c }); - - var d = new UInt256(new byte[32] { - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x02 - }); - - // Testing Fifo max size - Assert.IsTrue(set.Add(d)); - CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { a, c, d }); - - var e = new UInt256(new byte[32] { - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x03 - }); - - Assert.IsTrue(set.Add(e)); - Assert.IsFalse(set.Add(e)); - CollectionAssert.AreEqual(set.ToArray(), new UInt256[] { c, d, e }); - } - } -} diff --git a/neo.UnitTests/UT_Helper.cs b/neo.UnitTests/UT_Helper.cs index d64d9c9173..aaad993bb1 100644 --- a/neo.UnitTests/UT_Helper.cs +++ b/neo.UnitTests/UT_Helper.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; using Neo.SmartContract; diff --git a/neo.UnitTests/UT_NEP6Wallet.cs b/neo.UnitTests/UT_NEP6Wallet.cs deleted file mode 100644 index 187e487030..0000000000 --- a/neo.UnitTests/UT_NEP6Wallet.cs +++ /dev/null @@ -1,41 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO.Json; -using Neo.Wallets.NEP6; -using System; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_NEP6Wallet - { - NEP6Wallet uut; - - [TestInitialize] - public void TestSetup() - { - JObject wallet = new JObject(); - wallet["name"] = "name"; - wallet["version"] = new System.Version().ToString(); - wallet["scrypt"] = ScryptParameters.Default.ToJson(); - // test minimally scryptparameters parsing here - ScryptParameters.FromJson(wallet["scrypt"]).Should().NotBeNull(); - ScryptParameters.FromJson(wallet["scrypt"]).N.Should().Be(ScryptParameters.Default.N); - wallet["accounts"] = new JArray(); - //accounts = ((JArray)wallet["accounts"]).Select(p => NEP6Account.FromJson(p, this)).ToDictionary(p => p.ScriptHash); - wallet["extra"] = new JObject(); - // check string json - wallet.ToString().Should().Be("{\"name\":\"name\",\"version\":\"0.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[],\"extra\":{}}"); - uut = new NEP6Wallet(wallet); - } - - [TestMethod] - public void Test_NEP6Wallet_Json() - { - uut.Name.Should().Be("name"); - uut.Version.Should().Be(new Version()); - uut.Scrypt.Should().NotBeNull(); - uut.Scrypt.N.Should().Be(ScryptParameters.Default.N); - } - } -} diff --git a/neo.UnitTests/UT_NefFile.cs b/neo.UnitTests/UT_NefFile.cs new file mode 100644 index 0000000000..056333163d --- /dev/null +++ b/neo.UnitTests/UT_NefFile.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.IO; +using Neo.SmartContract; +using System; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_NefFile + { + [TestMethod] + public void ParseTest() + { + var file = new NefFile() + { + Compiler = "".PadLeft(32, ' '), + Version = new Version(1, 2, 3, 4), + Script = new byte[] { 0x01, 0x02, 0x03 } + }; + + file.ScriptHash = file.Script.ToScriptHash(); + file.CheckSum = NefFile.ComputeChecksum(file); + + var data = file.ToArray(); + file = data.AsSerializable(); + + Assert.AreEqual("".PadLeft(32, ' '), file.Compiler); + Assert.AreEqual(new Version(1, 2, 3, 4), file.Version); + Assert.AreEqual(file.Script.ToScriptHash(), file.ScriptHash); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, file.Script); + } + + [TestMethod] + public void LimitTest() + { + var file = new NefFile() + { + Compiler = "".PadLeft(byte.MaxValue, ' '), + Version = new Version(1, 2, 3, 4), + Script = new byte[1024 * 1024], + ScriptHash = new byte[1024 * 1024].ToScriptHash(), + CheckSum = 0 + }; + + // Wrong compiler + + Assert.ThrowsException(() => file.ToArray()); + + // Wrong script + + file.Compiler = ""; + file.Script = new byte[(1024 * 1024) + 1]; + file.ScriptHash = file.Script.ToScriptHash(); + var data = file.ToArray(); + + Assert.ThrowsException(() => data.AsSerializable()); + + // Wrong script hash + + file.Script = new byte[1024 * 1024]; + data = file.ToArray(); + + Assert.ThrowsException(() => data.AsSerializable()); + + // Wrong checksum + + file.Script = new byte[1024]; + data = file.ToArray(); + file.CheckSum = NefFile.ComputeChecksum(file) + 1; + + Assert.ThrowsException(() => data.AsSerializable()); + } + } +} diff --git a/neo.UnitTests/UT_ProtocolSettings.cs b/neo.UnitTests/UT_ProtocolSettings.cs index 92cabae19b..a5fa189231 100644 --- a/neo.UnitTests/UT_ProtocolSettings.cs +++ b/neo.UnitTests/UT_ProtocolSettings.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; diff --git a/neo.UnitTests/UT_Transaction.cs b/neo.UnitTests/UT_Transaction.cs deleted file mode 100644 index dc41d3a0d8..0000000000 --- a/neo.UnitTests/UT_Transaction.cs +++ /dev/null @@ -1,279 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Cryptography.ECC; -using Neo.IO; -using Neo.IO.Json; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.SmartContract.Native.Tokens; -using Neo.VM; -using Neo.Wallets; -using Neo.Wallets.NEP6; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_Transaction - { - Transaction uut; - Store store; - - [TestInitialize] - public void TestSetup() - { - uut = new Transaction(); - store = TestBlockchain.GetStore(); - } - - [TestMethod] - public void Script_Get() - { - uut.Script.Should().BeNull(); - } - - [TestMethod] - public void Script_Set() - { - byte[] val = TestUtils.GetByteArray(32, 0x42); - uut.Script = val; - uut.Script.Length.Should().Be(32); - for (int i = 0; i < val.Length; i++) - { - uut.Script[i].Should().Be(val[i]); - } - } - - [TestMethod] - public void Gas_Get() - { - uut.SystemFee.Should().Be(0); - } - - [TestMethod] - public void Gas_Set() - { - long val = 4200000000; - uut.SystemFee = val; - uut.SystemFee.Should().Be(val); - } - - [TestMethod] - public void Size_Get() - { - uut.Script = TestUtils.GetByteArray(32, 0x42); - uut.Sender = UInt160.Zero; - uut.Attributes = new TransactionAttribute[0]; - uut.Witnesses = new[] - { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new byte[0] - } - }; - - uut.Version.Should().Be(0); - uut.Script.Length.Should().Be(32); - uut.Script.GetVarSize().Should().Be(33); - uut.Size.Should().Be(82); - } - - private NEP6Wallet GenerateTestWallet() - { - JObject wallet = new JObject(); - wallet["name"] = "noname"; - wallet["version"] = new System.Version().ToString(); - wallet["scrypt"] = new ScryptParameters(0, 0, 0).ToJson(); - wallet["accounts"] = new JArray(); - wallet["extra"] = null; - wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"0.0\",\"scrypt\":{\"n\":0,\"r\":0,\"p\":0},\"accounts\":[],\"extra\":null}"); - return new NEP6Wallet(wallet); - } - - [TestMethod] - public void FeeIsMultiSigContract() - { - var store = TestBlockchain.GetStore(); - var walletA = GenerateTestWallet(); - var walletB = GenerateTestWallet(); - var snapshot = store.GetSnapshot(); - - using (var unlockA = walletA.Unlock("123")) - using (var unlockB = walletB.Unlock("123")) - { - var a = walletA.CreateAccount(); - var b = walletB.CreateAccount(); - - var multiSignContract = Contract.CreateMultiSigContract(2, - new ECPoint[] - { - a.GetKey().PublicKey, - b.GetKey().PublicKey - }); - - walletA.CreateAccount(multiSignContract, a.GetKey()); - var acc = walletB.CreateAccount(multiSignContract, b.GetKey()); - - // Fake balance - - var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem - { - Value = new Nep5AccountState().ToByteArray() - }); - - entry.Value = new Nep5AccountState() - { - Balance = 10000 * NativeContract.GAS.Factor - } - .ToByteArray(); - - // Make transaction - - var tx = walletA.MakeTransaction(new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = acc.ScriptHash, - Value = new BigDecimal(1,8) - } - }, acc.ScriptHash); - - Assert.IsNotNull(tx); - - // Sign - - var data = new ContractParametersContext(tx); - Assert.IsTrue(walletA.Sign(data)); - Assert.IsTrue(walletB.Sign(data)); - Assert.IsTrue(data.Completed); - - tx.Witnesses = data.GetWitnesses(); - - // Fast check - - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); - - // Check - - long verificationGas = 0; - foreach (var witness in tx.Witnesses) - { - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) - { - engine.LoadScript(witness.VerificationScript); - engine.LoadScript(witness.InvocationScript); - Assert.AreEqual(VMState.HALT, engine.Execute()); - Assert.AreEqual(1, engine.ResultStack.Count); - Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; - } - } - - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); - Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); - } - } - - [TestMethod] - public void FeeIsSignatureContract() - { - var wallet = GenerateTestWallet(); - var snapshot = store.GetSnapshot(); - - using (var unlock = wallet.Unlock("123")) - { - var acc = wallet.CreateAccount(); - - // Fake balance - - var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - - var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem - { - Value = new Nep5AccountState().ToByteArray() - }); - - entry.Value = new Nep5AccountState() - { - Balance = 10000 * NativeContract.GAS.Factor - } - .ToByteArray(); - - // Make transaction - - var tx = wallet.MakeTransaction(new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = acc.ScriptHash, - Value = new BigDecimal(1,8) - } - }, acc.ScriptHash); - - Assert.IsNotNull(tx); - - // Sign - - var data = new ContractParametersContext(tx); - Assert.IsTrue(wallet.Sign(data)); - tx.Witnesses = data.GetWitnesses(); - - // Fast check - - Assert.IsTrue(tx.VerifyWitnesses(snapshot, tx.NetworkFee)); - - // Check - - long verificationGas = 0; - foreach (var witness in tx.Witnesses) - { - using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, tx, snapshot, tx.NetworkFee, false)) - { - engine.LoadScript(witness.VerificationScript); - engine.LoadScript(witness.InvocationScript); - Assert.AreEqual(VMState.HALT, engine.Execute()); - Assert.AreEqual(1, engine.ResultStack.Count); - Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; - } - } - - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); - Assert.AreEqual(tx.NetworkFee, verificationGas + sizeGas); - } - } - - [TestMethod] - public void ToJson() - { - uut.Script = TestUtils.GetByteArray(32, 0x42); - uut.Sender = UInt160.Zero; - uut.SystemFee = 4200000000; - uut.Attributes = new TransactionAttribute[0]; - uut.Witnesses = new[] - { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new byte[0] - } - }; - - JObject jObj = uut.ToJson(); - jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0xee00d595ccd48a650f62adaccbb9c979e2dc7ef66fb5b1413f0f74d563a2d9c6"); - jObj["size"].AsNumber().Should().Be(82); - jObj["version"].AsNumber().Should().Be(0); - ((JArray)jObj["attributes"]).Count.Should().Be(0); - jObj["net_fee"].AsString().Should().Be("0"); - jObj["script"].AsString().Should().Be("4220202020202020202020202020202020202020202020202020202020202020"); - jObj["sys_fee"].AsNumber().Should().Be(42); - } - } -} diff --git a/neo.UnitTests/UT_Witness.cs b/neo.UnitTests/UT_Witness.cs deleted file mode 100644 index 4ca00b6643..0000000000 --- a/neo.UnitTests/UT_Witness.cs +++ /dev/null @@ -1,76 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.IO.Json; -using Neo.Network.P2P.Payloads; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_Witness - { - Witness uut; - - [TestInitialize] - public void TestSetup() - { - uut = new Witness(); - } - - [TestMethod] - public void InvocationScript_Get() - { - uut.InvocationScript.Should().BeNull(); - } - - [TestMethod] - public void InvocationScript_Set() - { - byte[] dataArray = new byte[] { 0, 32, 32, 20, 32, 32 }; - uut.InvocationScript = dataArray; - uut.InvocationScript.Length.Should().Be(6); - Assert.AreEqual(uut.InvocationScript.ToHexString(), "002020142020"); - } - - private void setupWitnessWithValues(Witness uut, int lenghtInvocation, int lengthVerification, out byte[] invocationScript, out byte[] verificationScript) - { - invocationScript = TestUtils.GetByteArray(lenghtInvocation, 0x20); - verificationScript = TestUtils.GetByteArray(lengthVerification, 0x20); - uut.InvocationScript = invocationScript; - uut.VerificationScript = verificationScript; - } - - [TestMethod] - public void SizeWitness_Small_Arrary() - { - byte[] invocationScript; - byte[] verificationScript; - setupWitnessWithValues(uut, 252, 253, out invocationScript, out verificationScript); - - uut.Size.Should().Be(509); // (1 + 252*1) + (1 + 2 + 253*1) - } - - [TestMethod] - public void SizeWitness_Large_Arrary() - { - byte[] invocationScript; - byte[] verificationScript; - setupWitnessWithValues(uut, 65535, 65536, out invocationScript, out verificationScript); - - uut.Size.Should().Be(131079); // (1 + 2 + 65535*1) + (1 + 4 + 65536*1) - } - - [TestMethod] - public void ToJson() - { - byte[] invocationScript; - byte[] verificationScript; - setupWitnessWithValues(uut, 2, 3, out invocationScript, out verificationScript); - - JObject json = uut.ToJson(); - Assert.IsTrue(json.ContainsProperty("invocation")); - Assert.IsTrue(json.ContainsProperty("verification")); - Assert.AreEqual(json["invocation"].AsString(), "2020"); - Assert.AreEqual(json["verification"].AsString(), "202020"); - } - } -} \ No newline at end of file diff --git a/neo.UnitTests/VM/UT_Helper.cs b/neo.UnitTests/VM/UT_Helper.cs new file mode 100644 index 0000000000..cf5ad0bff5 --- /dev/null +++ b/neo.UnitTests/VM/UT_Helper.cs @@ -0,0 +1,142 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.SmartContract; +using Neo.VM; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace Neo.UnitTests.IO +{ + [TestClass] + public class UT_Helper + { + [TestMethod] + public void TestEmit() + { + ScriptBuilder sb = new ScriptBuilder(); + sb.Emit(new OpCode[] { OpCode.PUSH0 }); + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x00 }), Encoding.Default.GetString(sb.ToArray())); + } + + [TestMethod] + public void TestEmitAppCall1() + { + //format:(byte)0x00+(byte)OpCode.NEWARRAY+(string)operation+(Uint160)scriptHash+(uint)InteropService.System_Contract_Call + ScriptBuilder sb = new ScriptBuilder(); + sb.EmitAppCall(UInt160.Zero, "AAAAA"); + byte[] tempArray = new byte[34]; + tempArray[0] = 0x00;//0 + tempArray[1] = 0xC5;//OpCode.NEWARRAY + tempArray[2] = 5;//operation.Length + Array.Copy(Encoding.UTF8.GetBytes("AAAAA"), 0, tempArray, 3, 5);//operation.data + tempArray[8] = 0x14;//scriptHash.Length + Array.Copy(UInt160.Zero.ToArray(), 0, tempArray, 9, 20);//operation.data + uint api = InteropService.System_Contract_Call; + tempArray[29] = 0x68;//OpCode.SYSCALL + Array.Copy(BitConverter.GetBytes(api), 0, tempArray, 30, 4);//api.data + byte[] resultArray = sb.ToArray(); + Assert.AreEqual(Encoding.Default.GetString(tempArray), Encoding.Default.GetString(resultArray)); + } + + [TestMethod] + public void TestEmitAppCall2() + { + //format:(ContractParameter[])ContractParameter+(byte)OpCode.PACK+(string)operation+(Uint160)scriptHash+(uint)InteropService.System_Contract_Call + ScriptBuilder sb = new ScriptBuilder(); + sb.EmitAppCall(UInt160.Zero, "AAAAA", new ContractParameter[] { new ContractParameter(ContractParameterType.Integer) }); + byte[] tempArray = new byte[35]; + tempArray[0] = 0x00;//0 + tempArray[1] = 0x51;//ContractParameter.Length + tempArray[2] = 0xC1;//OpCode.PACK + tempArray[3] = 0x05;//operation.Length + Array.Copy(Encoding.UTF8.GetBytes("AAAAA"), 0, tempArray, 4, 5);//operation.data + tempArray[9] = 0x14;//scriptHash.Length + Array.Copy(UInt160.Zero.ToArray(), 0, tempArray, 10, 20);//operation.data + uint api = InteropService.System_Contract_Call; + tempArray[30] = 0x68;//OpCode.SYSCALL + Array.Copy(BitConverter.GetBytes(api), 0, tempArray, 31, 4);//api.data + byte[] resultArray = sb.ToArray(); + Assert.AreEqual(Encoding.Default.GetString(tempArray), Encoding.Default.GetString(resultArray)); + } + + [TestMethod] + public void TestEmitAppCall3() + { + //format:(object[])args+(byte)OpCode.PACK+(string)operation+(Uint160)scriptHash+(uint)InteropService.System_Contract_Call + ScriptBuilder sb = new ScriptBuilder(); + sb.EmitAppCall(UInt160.Zero, "AAAAA", true); + byte[] tempArray = new byte[35]; + tempArray[0] = 0x51;//arg + tempArray[1] = 0x51;//args.Length + tempArray[2] = 0xC1;//OpCode.PACK + tempArray[3] = 0x05;//operation.Length + Array.Copy(Encoding.UTF8.GetBytes("AAAAA"), 0, tempArray, 4, 5);//operation.data + tempArray[9] = 0x14;//scriptHash.Length + Array.Copy(UInt160.Zero.ToArray(), 0, tempArray, 10, 20);//operation.data + uint api = InteropService.System_Contract_Call; + tempArray[30] = 0x68;//OpCode.SYSCALL + Array.Copy(BitConverter.GetBytes(api), 0, tempArray, 31, 4);//api.data + byte[] resultArray = sb.ToArray(); + Assert.AreEqual(Encoding.Default.GetString(tempArray), Encoding.Default.GetString(resultArray)); + } + + [TestMethod] + public void TestToParameter() + { + StackItem byteItem = "00e057eb481b".HexToBytes(); + Assert.AreEqual(30000000000000L, (long)new BigInteger(byteItem.ToParameter().Value as byte[])); + + StackItem boolItem = false; + Assert.AreEqual(false, (bool)boolItem.ToParameter().Value); + + StackItem intItem = new BigInteger(1000); + Assert.AreEqual(1000, (BigInteger)intItem.ToParameter().Value); + + StackItem interopItem = new VM.Types.InteropInterface("test"); + Assert.AreEqual(null, interopItem.ToParameter().Value); + + StackItem arrayItem = new VM.Types.Array(new[] { byteItem, boolItem, intItem, interopItem }); + Assert.AreEqual(1000, (BigInteger)(arrayItem.ToParameter().Value as List)[2].Value); + + StackItem mapItem = new VM.Types.Map(new Dictionary(new[] { new KeyValuePair(byteItem, intItem) })); + Assert.AreEqual(1000, (BigInteger)(mapItem.ToParameter().Value as List>)[0].Value.Value); + } + + [TestMethod] + public void TestToStackItem() + { + ContractParameter byteParameter = new ContractParameter { Type = ContractParameterType.ByteArray, Value = "00e057eb481b".HexToBytes() }; + Assert.AreEqual(30000000000000L, (long)byteParameter.ToStackItem().GetBigInteger()); + + ContractParameter boolParameter = new ContractParameter { Type = ContractParameterType.Boolean, Value = false }; + Assert.AreEqual(false, boolParameter.ToStackItem().GetBoolean()); + + ContractParameter intParameter = new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1000) }; + Assert.AreEqual(1000, intParameter.ToStackItem().GetBigInteger()); + + ContractParameter h160Parameter = new ContractParameter { Type = ContractParameterType.Hash160, Value = UInt160.Zero }; + Assert.AreEqual(0, h160Parameter.ToStackItem().GetBigInteger()); + + ContractParameter h256Parameter = new ContractParameter { Type = ContractParameterType.Hash256, Value = UInt256.Zero }; + Assert.AreEqual(0, h256Parameter.ToStackItem().GetBigInteger()); + + ContractParameter pkParameter = new ContractParameter { Type = ContractParameterType.PublicKey, Value = ECPoint.Parse("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575", ECCurve.Secp256r1) }; + Assert.AreEqual("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575", pkParameter.ToStackItem().GetByteArray().ToHexString()); + + ContractParameter strParameter = new ContractParameter { Type = ContractParameterType.String, Value = "test😂👍" }; + Assert.AreEqual("test😂👍", Encoding.UTF8.GetString(strParameter.ToStackItem().GetByteArray())); + + ContractParameter interopParameter = new ContractParameter { Type = ContractParameterType.InteropInterface }; + Assert.AreEqual(null, interopParameter.ToStackItem()); + + ContractParameter arrayParameter = new ContractParameter { Type = ContractParameterType.Array, Value = new[] { byteParameter, boolParameter, intParameter, h160Parameter, h256Parameter, pkParameter, strParameter, interopParameter }.ToList() }; + Assert.AreEqual(1000, ((VM.Types.Array)arrayParameter.ToStackItem())[2].GetBigInteger()); + + ContractParameter mapParameter = new ContractParameter { Type = ContractParameterType.Map, Value = new[] { new KeyValuePair(byteParameter, pkParameter) } }; + Assert.AreEqual(30000000000000L, (long)((VM.Types.Map)mapParameter.ToStackItem()).Keys.First().GetBigInteger()); + } + } +} diff --git a/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs b/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs new file mode 100644 index 0000000000..e2f4ca13f4 --- /dev/null +++ b/neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs @@ -0,0 +1,140 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.IO.Json; +using Neo.SmartContract; +using Neo.Wallets; +using Neo.Wallets.NEP6; + +namespace Neo.UnitTests.Wallets.NEP6 +{ + [TestClass] + public class UT_NEP6Account + { + NEP6Account account; + UInt160 hash; + NEP6Wallet wallet; + private static string nep2; + private static KeyPair keyPair; + + [ClassInitialize] + public static void ClassSetup(TestContext context) + { + byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + keyPair = new KeyPair(privateKey); + nep2 = keyPair.Export("Satoshi", 0, 0, 0); + } + + [TestInitialize] + public void TestSetup() + { + wallet = TestUtils.GenerateTestWallet(); + byte[] array1 = { 0x01 }; + hash = new UInt160(Crypto.Default.Hash160(array1)); + account = new NEP6Account(wallet, hash); + } + + [TestMethod] + public void TestConstructorWithNep2Key() + { + account.ScriptHash.Should().Be(hash); + account.Decrypted.Should().BeTrue(); + account.HasKey.Should().BeFalse(); + } + + [TestMethod] + public void TestConstructorWithKeyPair() + { + NEP6Wallet wallet = new NEP6Wallet("a"); + byte[] array1 = { 0x01 }; + var hash = new UInt160(Crypto.Default.Hash160(array1)); + string password = "hello world"; + NEP6Account account = new NEP6Account(wallet, hash, keyPair, password); + account.ScriptHash.Should().Be(hash); + account.Decrypted.Should().BeTrue(); + account.HasKey.Should().BeTrue(); + } + + [TestMethod] + public void TestFromJson() + { + JObject json = new JObject(); + json["address"] = "ARxgjcH2K1yeW5f5ryuRQNaBzSa9TZzmVS"; + json["key"] = null; + json["label"] = null; + json["isDefault"] = true; + json["lock"] = false; + json["contract"] = null; + json["extra"] = null; + NEP6Account account = NEP6Account.FromJson(json, wallet); + account.ScriptHash.Should().Be("ARxgjcH2K1yeW5f5ryuRQNaBzSa9TZzmVS".ToScriptHash()); + account.Label.Should().BeNull(); + account.IsDefault.Should().BeTrue(); + account.Lock.Should().BeFalse(); + account.Contract.Should().BeNull(); + account.Extra.Should().BeNull(); + account.GetKey().Should().BeNull(); + + json["key"] = "6PYRjVE1gAbCRyv81FTiFz62cxuPGw91vMjN4yPa68bnoqJtioreTznezn"; + json["label"] = "label"; + account = NEP6Account.FromJson(json, wallet); + account.Label.Should().Be("label"); + account.HasKey.Should().BeTrue(); + } + + [TestMethod] + public void TestGetKey() + { + account.GetKey().Should().BeNull(); + wallet.Unlock("Satoshi"); + account = new NEP6Account(wallet, hash, nep2); + account.GetKey().Should().Be(keyPair); + } + + [TestMethod] + public void TestGetKeyWithString() + { + account.GetKey("Satoshi").Should().BeNull(); + account = new NEP6Account(wallet, hash, nep2); + account.GetKey("Satoshi").Should().Be(keyPair); + } + + [TestMethod] + public void TestToJson() + { + JObject nep6contract = new JObject(); + nep6contract["script"] = "2103603f3880eb7aea0ad4500893925e4a42fea48a44ee6f898a10b3c7ce05d2a267ac"; + JObject parameters = new JObject(); + parameters["type"] = 0x00; + parameters["name"] = "Sig"; + JArray array = new JArray + { + parameters + }; + nep6contract["parameters"] = array; + nep6contract["deployed"] = false; + account.Contract = NEP6Contract.FromJson(nep6contract); + JObject json = account.ToJson(); + json["address"].Should().Equals("AZk5bAanTtD6AvpeesmYgL8CLRYUt5JQsX"); + json["label"].Should().BeNull(); + json["isDefault"].ToString().Should().Be("false"); + json["lock"].ToString().Should().Be("false"); + json["key"].Should().BeNull(); + json["contract"]["script"].ToString().Should().Be("\"2103603f3880eb7aea0ad4500893925e4a42fea48a44ee6f898a10b3c7ce05d2a267ac\""); + json["extra"].Should().BeNull(); + + account.Contract = null; + json = account.ToJson(); + json["contract"].Should().BeNull(); + } + + [TestMethod] + public void TestVerifyPassword() + { + account = new NEP6Account(wallet, hash, nep2); + account.VerifyPassword("Satoshi").Should().BeTrue(); + account.VerifyPassword("b").Should().BeFalse(); + } + } +} diff --git a/neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs b/neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs new file mode 100644 index 0000000000..3f4e7b3a63 --- /dev/null +++ b/neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs @@ -0,0 +1,67 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using Neo.SmartContract; +using Neo.Wallets.NEP6; + +namespace Neo.UnitTests.Wallets.NEP6 +{ + [TestClass] + public class UT_NEP6Contract + { + [TestMethod] + public void TestFromNullJson() + { + NEP6Contract nep6Contract = NEP6Contract.FromJson(null); + nep6Contract.Should().BeNull(); + } + + [TestMethod] + public void TestFromJson() + { + string json = "{\"script\":\"2103ef891df4c0b7eefb937d21ea0fb88cde8e0d82a7ff11872b5e7047969dafb4eb68747476aa\"," + + "\"parameters\":[{\"name\":\"signature\",\"type\":\"Signature\"}],\"deployed\":false}"; + JObject @object = JObject.Parse(json); + + NEP6Contract nep6Contract = NEP6Contract.FromJson(@object); + nep6Contract.Script.Should().BeEquivalentTo("2103ef891df4c0b7eefb937d21ea0fb88cde8e0d82a7ff11872b5e7047969dafb4eb68747476aa".HexToBytes()); + nep6Contract.ParameterList.Length.Should().Be(1); + nep6Contract.ParameterList[0].Should().Be(ContractParameterType.Signature); + nep6Contract.ParameterNames.Length.Should().Be(1); + nep6Contract.ParameterNames[0].Should().Be("signature"); + nep6Contract.Deployed.Should().BeFalse(); + } + + [TestMethod] + public void TestToJson() + { + NEP6Contract nep6Contract = new NEP6Contract() + { + Script = new byte[] { 0x00, 0x01 }, + ParameterList = new ContractParameterType[] { ContractParameterType.Boolean, ContractParameterType.Integer }, + ParameterNames = new string[] { "param1", "param2" }, + Deployed = false + }; + + JObject @object = nep6Contract.ToJson(); + JString jString = (JString)@object["script"]; + jString.Value.Should().Be(nep6Contract.Script.ToHexString()); + + JBoolean jBoolean = (JBoolean)@object["deployed"]; + jBoolean.Value.Should().BeFalse(); + + JArray parameters = (JArray)@object["parameters"]; + parameters.Count.Should().Be(2); + + jString = (JString)(parameters[0]["name"]); + jString.Value.Should().Be("param1"); + jString = (JString)(parameters[0]["type"]); + jString.Value.Should().Be(ContractParameterType.Boolean.ToString()); + + jString = (JString)(parameters[1]["name"]); + jString.Value.Should().Be("param2"); + jString = (JString)(parameters[1]["type"]); + jString.Value.Should().Be(ContractParameterType.Integer.ToString()); + } + } +} diff --git a/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs b/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs new file mode 100644 index 0000000000..294afd91cd --- /dev/null +++ b/neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs @@ -0,0 +1,400 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using Neo.Wallets.SQLite; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Threading; + +namespace Neo.UnitTests.Wallets.NEP6 +{ + [TestClass] + public class UT_NEP6Wallet + { + private NEP6Wallet uut; + private static string wPath; + private static KeyPair keyPair; + private static string nep2key; + private static UInt160 testScriptHash; + + public static string GetRandomPath() + { + string threadName = Thread.CurrentThread.ManagedThreadId.ToString(); + return Path.GetFullPath(string.Format("Wallet_{0}", new Random().Next(1, 1000000).ToString("X8")) + threadName); + } + + [ClassInitialize] + public static void ClassInit(TestContext context) + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + keyPair = new KeyPair(privateKey); + testScriptHash = Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey).ScriptHash; + nep2key = keyPair.Export("123", 0, 0, 0); + } + + private NEP6Wallet CreateWallet() + { + return TestUtils.GenerateTestWallet(); + } + + private string CreateWalletFile() + { + string path = GetRandomPath(); + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + path = Path.Combine(path, "wallet.json"); + File.WriteAllText(path, "{\"name\":\"name\",\"version\":\"0.0\",\"scrypt\":{\"n\":0,\"r\":0,\"p\":0},\"accounts\":[],\"extra\":{}}"); + return path; + } + + [TestInitialize] + public void TestSetup() + { + uut = CreateWallet(); + wPath = CreateWalletFile(); + } + + [TestCleanup] + public void TestCleanUp() + { + if (File.Exists(wPath)) File.Delete(wPath); + } + + [TestMethod] + public void TestConstructorWithPathAndName() + { + NEP6Wallet wallet = new NEP6Wallet(wPath); + Assert.AreEqual("name", wallet.Name); + Assert.AreEqual(new ScryptParameters(0, 0, 0).ToJson().ToString(), wallet.Scrypt.ToJson().ToString()); + Assert.AreEqual(new Version().ToString(), wallet.Version.ToString()); + wallet = new NEP6Wallet("", "test"); + Assert.AreEqual("test", wallet.Name); + Assert.AreEqual(ScryptParameters.Default.ToJson().ToString(), wallet.Scrypt.ToJson().ToString()); + Assert.AreEqual(Version.Parse("1.0"), wallet.Version); + } + + [TestMethod] + public void TestConstructorWithJObject() + { + JObject wallet = new JObject(); + wallet["name"] = "test"; + wallet["version"] = Version.Parse("1.0").ToString(); + wallet["scrypt"] = ScryptParameters.Default.ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = new JObject(); + wallet.ToString().Should().Be("{\"name\":\"test\",\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[],\"extra\":{}}"); + NEP6Wallet w = new NEP6Wallet(wallet); + Assert.AreEqual("test", w.Name); + Assert.AreEqual(Version.Parse("1.0").ToString(), w.Version.ToString()); + } + + [TestMethod] + public void TestGetName() + { + Assert.AreEqual("noname", uut.Name); + } + + [TestMethod] + public void TestGetVersion() + { + Assert.AreEqual(new System.Version().ToString(), uut.Version.ToString()); + } + + [TestMethod] + public void TestContains() + { + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.CreateAccount(testScriptHash); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestAddCount() + { + uut.CreateAccount(testScriptHash); + Assert.IsTrue(uut.Contains(testScriptHash)); + WalletAccount account = uut.GetAccount(testScriptHash); + Assert.IsTrue(account.WatchOnly); + Assert.IsFalse(account.HasKey); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + account = uut.GetAccount(testScriptHash); + Assert.IsFalse(account.WatchOnly); + Assert.IsTrue(account.HasKey); + uut.CreateAccount(testScriptHash); + account = uut.GetAccount(testScriptHash); + Assert.IsFalse(account.WatchOnly); + Assert.IsFalse(account.HasKey); + uut.CreateAccount(keyPair.PrivateKey); + account = uut.GetAccount(testScriptHash); + Assert.IsFalse(account.WatchOnly); + Assert.IsTrue(account.HasKey); + } + + [TestMethod] + public void TestCreateAccountWithPrivateKey() + { + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestCreateAccountWithKeyPair() + { + Neo.SmartContract.Contract contract = Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey); + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.CreateAccount(contract); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + uut.DeleteAccount(testScriptHash); + result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.Unlock("123"); + uut.CreateAccount(contract, keyPair); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestCreateAccountWithScriptHash() + { + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.CreateAccount(testScriptHash); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestDecryptKey() + { + string nep2key = keyPair.Export("123", 0, 0, 0); + uut.Unlock("123"); + KeyPair key1 = uut.DecryptKey(nep2key); + bool result = key1.Equals(keyPair); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestDeleteAccount() + { + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.CreateAccount(testScriptHash); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + uut.DeleteAccount(testScriptHash); + result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + } + + [TestMethod] + public void TestGetAccount() + { + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + WalletAccount account = uut.GetAccount(testScriptHash); + Assert.AreEqual(Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey).Address, account.Address); + } + + [TestMethod] + public void TestGetAccounts() + { + Dictionary keys = new Dictionary(); + uut.Unlock("123"); + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + Neo.SmartContract.Contract contract = Neo.SmartContract.Contract.CreateSignatureContract(key.PublicKey); + keys.Add(contract.Address, key); + keys.Add(Neo.SmartContract.Contract.CreateSignatureContract(keyPair.PublicKey).Address, keyPair); + uut.CreateAccount(key.PrivateKey); + uut.CreateAccount(keyPair.PrivateKey); + foreach (var account in uut.GetAccounts()) + { + if (!keys.TryGetValue(account.Address, out KeyPair k)) + { + Assert.Fail(); + } + } + } + + public X509Certificate2 NewCertificate() + { + ECDsa key = ECDsa.Create(ECCurve.NamedCurves.nistP256); + CertificateRequest request = new CertificateRequest( + "CN=Self-Signed ECDSA", + key, + HashAlgorithmName.SHA256); + request.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: false)); + request.CertificateExtensions.Add( + new X509BasicConstraintsExtension(false, false, 0, false)); + DateTimeOffset start = DateTimeOffset.UtcNow; + X509Certificate2 cert = request.CreateSelfSigned(notBefore: start, notAfter: start.AddMonths(3)); + return cert; + } + + [TestMethod] + public void TestImportCert() + { + X509Certificate2 cert = NewCertificate(); + Assert.IsNotNull(cert); + Assert.AreEqual(true, cert.HasPrivateKey); + uut.Unlock("123"); + WalletAccount account = uut.Import(cert); + Assert.IsNotNull(account); + } + + [TestMethod] + public void TestImportWif() + { + string wif = keyPair.Export(); + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.Unlock("123"); + uut.Import(wif); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestImportNep2() + { + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.Import(nep2key, "123", 0, 0, 0); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + uut.DeleteAccount(testScriptHash); + result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + JObject wallet = new JObject(); + wallet["name"] = "name"; + wallet["version"] = new Version().ToString(); + wallet["scrypt"] = new ScryptParameters(0, 0, 0).ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = new JObject(); + uut = new NEP6Wallet(wallet); + result = uut.Contains(testScriptHash); + Assert.AreEqual(false, result); + uut.Import(nep2key, "123", 0, 0, 0); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestLock() + { + Assert.ThrowsException(() => uut.CreateAccount(keyPair.PrivateKey)); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + uut.DeleteAccount(testScriptHash); + uut.Lock(); + Assert.ThrowsException(() => uut.CreateAccount(keyPair.PrivateKey)); + } + + [TestMethod] + public void TestMigrate() + { + string path = GetRandomPath(); + UserWallet uw = UserWallet.Create(path, "123"); + uw.CreateAccount(keyPair.PrivateKey); + string npath = Path.Combine(path, "w.json"); + NEP6Wallet nw = NEP6Wallet.Migrate(npath, path, "123"); + bool result = nw.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestSave() + { + JObject wallet = new JObject(); + wallet["name"] = "name"; + wallet["version"] = new System.Version().ToString(); + wallet["scrypt"] = new ScryptParameters(0, 0, 0).ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = new JObject(); + File.WriteAllText(wPath, wallet.ToString()); + uut = new NEP6Wallet(wPath); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + uut.Save(); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + } + + [TestMethod] + public void TestUnlock() + { + Assert.ThrowsException(() => uut.CreateAccount(keyPair.PrivateKey)); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + bool result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + Assert.ThrowsException(() => uut.Unlock("1")); + } + + [TestMethod] + public void TestVerifyPassword() + { + bool result = uut.VerifyPassword("123"); + Assert.AreEqual(true, result); + Assert.ThrowsException(() => uut.CreateAccount(keyPair.PrivateKey)); + uut.Unlock("123"); + uut.CreateAccount(keyPair.PrivateKey); + result = uut.Contains(testScriptHash); + Assert.AreEqual(true, result); + result = uut.VerifyPassword("123"); + Assert.AreEqual(true, result); + uut.DeleteAccount(testScriptHash); + Assert.AreEqual(false, uut.Contains(testScriptHash)); + JObject wallet = new JObject(); + wallet["name"] = "name"; + wallet["version"] = new Version().ToString(); + wallet["scrypt"] = new ScryptParameters(0, 0, 0).ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = new JObject(); + uut = new NEP6Wallet(wallet); + nep2key = keyPair.Export("123", 0, 0, 0); + uut.Import(nep2key, "123", 0, 0, 0); + Assert.IsFalse(uut.VerifyPassword("1")); + Assert.IsTrue(uut.VerifyPassword("123")); + } + + [TestMethod] + public void Test_NEP6Wallet_Json() + { + uut.Name.Should().Be("noname"); + uut.Version.Should().Be(new Version()); + uut.Scrypt.Should().NotBeNull(); + uut.Scrypt.N.Should().Be(new ScryptParameters(0, 0, 0).N); + } + } +} diff --git a/neo.UnitTests/UT_ScryptParameters.cs b/neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs similarity index 78% rename from neo.UnitTests/UT_ScryptParameters.cs rename to neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs index e0021f0eb0..65d29e9fce 100644 --- a/neo.UnitTests/UT_ScryptParameters.cs +++ b/neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs @@ -1,9 +1,9 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Json; using Neo.Wallets.NEP6; -namespace Neo.UnitTests +namespace Neo.UnitTests.Wallets.NEP6 { [TestClass] public class UT_ScryptParameters @@ -46,5 +46,15 @@ public void Test_Default_ScryptParameters_FromJson() uut2.R.Should().Be(ScryptParameters.Default.R); uut2.P.Should().Be(ScryptParameters.Default.P); } + + [TestMethod] + public void TestScryptParametersConstructor() + { + int n = 1, r = 2, p = 3; + ScryptParameters parameter = new ScryptParameters(n, r, p); + parameter.N.Should().Be(n); + parameter.R.Should().Be(r); + parameter.P.Should().Be(p); + } } } diff --git a/neo.UnitTests/Wallets/SQLite/UT_Account.cs b/neo.UnitTests/Wallets/SQLite/UT_Account.cs new file mode 100644 index 0000000000..0fabb80b1e --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_Account.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Wallets.SQLite; +using System.Text; + +namespace Neo.UnitTests.Wallets.SQLite +{ + [TestClass] + public class UT_Account + { + [TestMethod] + public void TestGenerator() + { + Account account = new Account(); + Assert.IsNotNull(account); + } + + [TestMethod] + public void TestSetAndGetPrivateKeyEncrypted() + { + Account account = new Account + { + PrivateKeyEncrypted = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(account.PrivateKeyEncrypted)); + } + + [TestMethod] + public void TestSetAndGetPublicKeyHash() + { + Account account = new Account + { + PublicKeyHash = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(account.PublicKeyHash)); + } + } +} diff --git a/neo.UnitTests/Wallets/SQLite/UT_Address.cs b/neo.UnitTests/Wallets/SQLite/UT_Address.cs new file mode 100644 index 0000000000..6157d26fe7 --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_Address.cs @@ -0,0 +1,27 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Wallets.SQLite; +using System.Text; + +namespace Neo.UnitTests.Wallets.SQLite +{ + [TestClass] + public class UT_Address + { + [TestMethod] + public void TestGenerator() + { + Address address = new Address(); + Assert.IsNotNull(address); + } + + [TestMethod] + public void TestSetAndGetScriptHash() + { + Address address = new Address + { + ScriptHash = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(address.ScriptHash)); + } + } +} diff --git a/neo.UnitTests/Wallets/SQLite/UT_Contract.cs b/neo.UnitTests/Wallets/SQLite/UT_Contract.cs new file mode 100644 index 0000000000..262c74ce08 --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_Contract.cs @@ -0,0 +1,65 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Wallets.SQLite; +using System.Text; + +namespace Neo.UnitTests.Wallets.SQLite +{ + [TestClass] + public class UT_Contract + { + [TestMethod] + public void TestGenerator() + { + Contract contract = new Contract(); + Assert.IsNotNull(contract); + } + + [TestMethod] + public void TestSetAndGetRawData() + { + Contract contract = new Contract + { + RawData = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(contract.RawData)); + } + + [TestMethod] + public void TestSetAndGetScriptHash() + { + Contract contract = new Contract + { + ScriptHash = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(contract.ScriptHash)); + } + + [TestMethod] + public void TestSetAndGetPublicKeyHash() + { + Contract contract = new Contract + { + PublicKeyHash = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(contract.PublicKeyHash)); + } + + [TestMethod] + public void TestSetAndGetAccount() + { + Contract contract = new Contract(); + Account account = new Account(); + contract.Account = account; + Assert.AreEqual(account, contract.Account); + } + + [TestMethod] + public void TestSetAndGetAddress() + { + Contract contract = new Contract(); + Address address = new Address(); + contract.Address = address; + Assert.AreEqual(address, contract.Address); + } + } +} diff --git a/neo.UnitTests/Wallets/SQLite/UT_Key.cs b/neo.UnitTests/Wallets/SQLite/UT_Key.cs new file mode 100644 index 0000000000..f4876cee11 --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_Key.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Wallets.SQLite; +using System.Text; + +namespace Neo.UnitTests.Wallets.SQLite +{ + [TestClass] + public class UT_Key + { + [TestMethod] + public void TestGenerator() + { + Key key = new Key(); + Assert.IsNotNull(key); + } + + [TestMethod] + public void TestSetAndGetName() + { + Key key = new Key + { + Name = "AAA" + }; + Assert.AreEqual("AAA", key.Name); + } + + [TestMethod] + public void TestSetAndGetValue() + { + Key key = new Key + { + Value = new byte[] { 0x01 } + }; + Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(key.Value)); + } + } +} diff --git a/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs b/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs new file mode 100644 index 0000000000..dcba52bc11 --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_UserWallet.cs @@ -0,0 +1,210 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.Wallets; +using Neo.Wallets.SQLite; +using System; +using System.IO; +using System.Security; +using System.Security.Cryptography; +using System.Threading; +using Contract = Neo.SmartContract.Contract; + +namespace Neo.UnitTests.Wallets.SQLite +{ + [TestClass] + public class UT_UserWallet + { + private string path; + private UserWallet wallet; + public static string GetRandomPath() + { + string threadName = Thread.CurrentThread.ManagedThreadId.ToString(); + return Path.GetFullPath(string.Format("Wallet_{0}", new Random().Next(1, 1000000).ToString("X8")) + threadName); + } + + [TestInitialize] + public void Setup() + { + path = GetRandomPath(); + wallet = UserWallet.Create(path, "123456"); + } + + [TestCleanup] + public void Cleanup() + { + TestUtils.DeleteFile(path); + } + + [TestMethod] + public void TestGetName() + { + wallet.Name.Should().Be(Path.GetFileNameWithoutExtension(path)); + } + + [TestMethod] + public void TestGetVersion() + { + Action action = () => wallet.Version.ToString(); + action.ShouldNotThrow(); + } + + [TestMethod] + public void TestCreateAndOpenSecureString() + { + string myPath = GetRandomPath(); + var ss = new SecureString(); + ss.AppendChar('a'); + ss.AppendChar('b'); + ss.AppendChar('c'); + + var w1 = UserWallet.Create(myPath, ss); + w1.Should().NotBeNull(); + + var w2 = UserWallet.Open(myPath, ss); + w2.Should().NotBeNull(); + + ss.AppendChar('d'); + Action action = () => UserWallet.Open(myPath, ss); + action.ShouldThrow(); + + TestUtils.DeleteFile(myPath); + } + + [TestMethod] + public void TestOpen() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + var account = wallet.CreateAccount(privateKey); + var w1 = UserWallet.Open(path, "123456"); + w1.Should().NotBeNull(); + + Action action = () => UserWallet.Open(path, "123"); + action.ShouldThrow(); + } + + [TestMethod] + public void TestCreateAccountAndGetByPrivateKey() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + var account = wallet.CreateAccount(privateKey); + var dbAccount = wallet.GetAccount(account.ScriptHash); + account.Should().Be(dbAccount); + + var account1 = wallet.CreateAccount(privateKey); + var dbAccount1 = wallet.GetAccount(account1.ScriptHash); + account1.Should().Be(dbAccount1); + } + + [TestMethod] + public void TestCreateAccountByScriptHash() + { + var account = wallet.CreateAccount(UInt160.Parse("0xa6ee944042f3c7ea900481a95d65e4a887320cf0")); + var dbAccount = wallet.GetAccount(account.ScriptHash); + account.Should().Be(dbAccount); + } + + [TestMethod] + public void TestCreateAccountBySmartContract() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract = new VerificationContract + { + Script = Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + var account = wallet.CreateAccount(contract, key); + var dbAccount = wallet.GetAccount(account.ScriptHash); + account.Should().Be(dbAccount); + + byte[] privateKey2 = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey2); + } + KeyPair key2 = new KeyPair(privateKey2); + Contract contract2 = new Contract + { + Script = Contract.CreateSignatureRedeemScript(key2.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + var account2 = wallet.CreateAccount(contract2, key2); + var dbAccount2 = wallet.GetAccount(account2.ScriptHash); + account2.Should().Be(dbAccount2); + } + + [TestMethod] + public void TestDeleteAccount() + { + bool ret = wallet.DeleteAccount(UInt160.Parse("0xa6ee944042f3c7ea900481a95d65e4a887320cf0")); + ret.Should().BeFalse(); + + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + var account = wallet.CreateAccount(privateKey); + bool ret2 = wallet.DeleteAccount(account.ScriptHash); + ret2.Should().BeTrue(); + } + + [TestMethod] + public void TestChangePassword() + { + wallet.ChangePassword("123455", "654321").Should().BeFalse(); + wallet.ChangePassword("123456", "654321").Should().BeTrue(); + } + + [TestMethod] + public void TestContains() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + var account = wallet.CreateAccount(privateKey); + wallet.Contains(account.ScriptHash).Should().BeTrue(); + } + + [TestMethod] + public void TestGetAccounts() + { + var ret = wallet.GetAccounts(); + ret.Should().BeNullOrEmpty(); + + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + var account = wallet.CreateAccount(privateKey); + ret = wallet.GetAccounts(); + foreach (var dbAccount in ret) + { + dbAccount.Should().Be(account); + } + } + + [TestMethod] + public void TestVerifyPassword() + { + wallet.VerifyPassword("123456").Should().BeTrue(); + wallet.VerifyPassword("123").Should().BeFalse(); + } + } +} diff --git a/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs b/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs new file mode 100644 index 0000000000..0a1fde1321 --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_UserWalletAccount.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Wallets.SQLite; +using System.Text; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_UserWalletAccount + { + [TestMethod] + public void TestGenerator() + { + UserWalletAccount account = new UserWalletAccount(UInt160.Zero); + Assert.IsNotNull(account); + } + + [TestMethod] + public void TestGetHasKey() + { + UserWalletAccount account = new UserWalletAccount(UInt160.Zero); + Assert.AreEqual(false, account.HasKey); + } + + [TestMethod] + public void TestGetKey() + { + UserWalletAccount account = new UserWalletAccount(UInt160.Zero); + Assert.AreEqual(null, account.GetKey()); + } + } +} diff --git a/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs b/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs new file mode 100644 index 0000000000..8f111f2112 --- /dev/null +++ b/neo.UnitTests/Wallets/SQLite/UT_VerificationContract.cs @@ -0,0 +1,148 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.Wallets; +using Neo.Wallets.SQLite; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_VerificationContract + { + [TestMethod] + public void TestGenerator() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + Assert.IsNotNull(contract); + } + + [TestMethod] + public void TestDeserialize() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract1 = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + BinaryReader reader = new BinaryReader(stream); + contract1.Serialize(writer); + stream.Seek(0, SeekOrigin.Begin); + VerificationContract contract2 = new VerificationContract(); + contract2.Deserialize(reader); + Assert.AreEqual(Encoding.Default.GetString(contract2.Script), Encoding.Default.GetString(contract1.Script)); + Assert.AreEqual(1, contract2.ParameterList.Length); + Assert.AreEqual(ContractParameterType.Signature, contract2.ParameterList[0]); + } + + [TestMethod] + public void TestEquals() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract1 = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + object tempObject = contract1; + VerificationContract contract2 = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + Assert.AreEqual(true, contract1.Equals(tempObject)); + Assert.AreEqual(true, contract1.Equals(contract1)); + Assert.AreEqual(false, contract1.Equals(null)); + Assert.AreEqual(true, contract1.Equals(contract2)); + } + + [TestMethod] + public void TestGetHashCode() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract1 = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + byte[] script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey); + Assert.AreEqual(script.ToScriptHash().GetHashCode(), contract1.GetHashCode()); + } + + [TestMethod] + public void TestSerialize() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract1 = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + contract1.Serialize(writer); + stream.Seek(0, SeekOrigin.Begin); + byte[] byteArray = new byte[stream.Length]; + stream.Read(byteArray, 0, (int)stream.Length); + byte[] script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey); + byte[] result = new byte[62]; + result[20] = 0x01; + result[21] = 0x00; + result[22] = 0x27; + Array.Copy(script, 0, result, 23, 39); + Assert.AreEqual(Encoding.Default.GetString(result), Encoding.Default.GetString(byteArray)); + } + + [TestMethod] + public void TestGetSize() + { + byte[] privateKey = new byte[32]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(privateKey); + } + KeyPair key = new KeyPair(privateKey); + VerificationContract contract1 = new VerificationContract + { + Script = Neo.SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + Assert.AreEqual(62, contract1.Size); + } + } +} diff --git a/neo.UnitTests/UT_AssetDescription.cs b/neo.UnitTests/Wallets/UT_AssetDescriptor.cs similarity index 53% rename from neo.UnitTests/UT_AssetDescription.cs rename to neo.UnitTests/Wallets/UT_AssetDescriptor.cs index 175814b91f..14eb6766b1 100644 --- a/neo.UnitTests/UT_AssetDescription.cs +++ b/neo.UnitTests/Wallets/UT_AssetDescriptor.cs @@ -1,12 +1,13 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Persistence; using Neo.SmartContract.Native; +using System; -namespace Neo.UnitTests +namespace Neo.UnitTests.Wallets { [TestClass] - public class UT_AssetDescription + public class UT_AssetDescriptor { private Store Store; @@ -17,22 +18,34 @@ public void TestSetup() Store = TestBlockchain.GetStore(); } + [TestMethod] + public void TestConstructorWithNonexistAssetId() + { + Action action = () => + { + var descriptor = new Neo.Wallets.AssetDescriptor(UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); + }; + action.ShouldThrow(); + } + [TestMethod] public void Check_GAS() { - var descriptor = new Wallets.AssetDescriptor(NativeContract.GAS.Hash); + var descriptor = new Neo.Wallets.AssetDescriptor(NativeContract.GAS.Hash); descriptor.AssetId.Should().Be(NativeContract.GAS.Hash); descriptor.AssetName.Should().Be("GAS"); + descriptor.ToString().Should().Be("GAS"); descriptor.Decimals.Should().Be(8); } [TestMethod] public void Check_NEO() { - var descriptor = new Wallets.AssetDescriptor(NativeContract.NEO.Hash); + var descriptor = new Neo.Wallets.AssetDescriptor(NativeContract.NEO.Hash); descriptor.AssetId.Should().Be(NativeContract.NEO.Hash); descriptor.AssetName.Should().Be("NEO"); + descriptor.ToString().Should().Be("NEO"); descriptor.Decimals.Should().Be(0); } } -} \ No newline at end of file +} diff --git a/neo.UnitTests/Wallets/UT_KeyPair.cs b/neo.UnitTests/Wallets/UT_KeyPair.cs new file mode 100644 index 0000000000..57f06bb5a9 --- /dev/null +++ b/neo.UnitTests/Wallets/UT_KeyPair.cs @@ -0,0 +1,114 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.Wallets; +using System; +using System.Linq; + +namespace Neo.UnitTests.Wallets +{ + [TestClass] + public class UT_KeyPair + { + [TestMethod] + public void TestConstructor() + { + Random random = new Random(); + byte[] privateKey = new byte[32]; + for (int i = 0; i < privateKey.Length; i++) + privateKey[i] = (byte)random.Next(256); + KeyPair keyPair = new KeyPair(privateKey); + ECPoint publicKey = ECCurve.Secp256r1.G * privateKey; + keyPair.PrivateKey.Should().BeEquivalentTo(privateKey); + keyPair.PublicKey.Should().Be(publicKey); + + byte[] privateKey96 = new byte[96]; + for (int i = 0; i < privateKey96.Length; i++) + privateKey96[i] = (byte)random.Next(256); + keyPair = new KeyPair(privateKey96); + publicKey = ECPoint.DecodePoint(new byte[] { 0x04 }.Concat(privateKey96.Skip(privateKey96.Length - 96).Take(64)).ToArray(), ECCurve.Secp256r1); + keyPair.PrivateKey.Should().BeEquivalentTo(privateKey96.Skip(64).Take(32)); + keyPair.PublicKey.Should().Be(publicKey); + + byte[] privateKey31 = new byte[31]; + for (int i = 0; i < privateKey31.Length; i++) + privateKey31[i] = (byte)random.Next(256); + Action action = () => new KeyPair(privateKey31); + action.ShouldThrow(); + } + + [TestMethod] + public void TestEquals() + { + Random random = new Random(); + byte[] privateKey = new byte[32]; + for (int i = 0; i < privateKey.Length; i++) + privateKey[i] = (byte)random.Next(256); + KeyPair keyPair = new KeyPair(privateKey); + KeyPair keyPair2 = keyPair; + keyPair.Equals(keyPair2).Should().BeTrue(); + + KeyPair keyPair3 = null; + keyPair.Equals(keyPair3).Should().BeFalse(); + + byte[] privateKey1 = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + byte[] privateKey2 = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02}; + KeyPair keyPair4 = new KeyPair(privateKey1); + KeyPair keyPair5 = new KeyPair(privateKey2); + keyPair4.Equals(keyPair5).Should().BeFalse(); + } + + [TestMethod] + public void TestEqualsWithObj() + { + Random random = new Random(); + byte[] privateKey = new byte[32]; + for (int i = 0; i < privateKey.Length; i++) + privateKey[i] = (byte)random.Next(256); + KeyPair keyPair = new KeyPair(privateKey); + Object keyPair2 = keyPair as Object; + keyPair.Equals(keyPair2).Should().BeTrue(); + } + + [TestMethod] + public void TestExport() + { + byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + byte[] data = { 0x80, 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + KeyPair keyPair = new KeyPair(privateKey); + keyPair.Export().Should().Be(data.Base58CheckEncode()); + } + + [TestMethod] + public void TestGetPublicKeyHash() + { + byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + KeyPair keyPair = new KeyPair(privateKey); + keyPair.PublicKeyHash.ToString().Should().Be("0x4ab3d6ac3a0609e87af84599c93d57c2d0890406"); + } + + [TestMethod] + public void TestGetHashCode() + { + byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + KeyPair keyPair = new KeyPair(privateKey); + keyPair.GetHashCode().Should().Be(1544360595); + } + + [TestMethod] + public void TestToString() + { + byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + KeyPair keyPair = new KeyPair(privateKey); + keyPair.ToString().Should().Be("026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca16"); + } + } +} diff --git a/neo.UnitTests/Wallets/UT_Wallet.cs b/neo.UnitTests/Wallets/UT_Wallet.cs new file mode 100644 index 0000000000..c09f8e26c2 --- /dev/null +++ b/neo.UnitTests/Wallets/UT_Wallet.cs @@ -0,0 +1,436 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.SmartContract.Native.Tokens; +using Neo.UnitTests.Cryptography; +using Neo.Wallets; +using System; +using System.Collections.Generic; + +namespace Neo.UnitTests.Wallets +{ + internal class MyWallet : Wallet + { + public override string Name => "MyWallet"; + + public override Version Version => Version.Parse("0.0.1"); + + Dictionary accounts = new Dictionary(); + + public override bool Contains(UInt160 scriptHash) + { + return accounts.ContainsKey(scriptHash); + } + + public void AddAccount(WalletAccount account) + { + accounts.Add(account.ScriptHash, account); + } + + public override WalletAccount CreateAccount(byte[] privateKey) + { + KeyPair key = new KeyPair(privateKey); + Neo.Wallets.SQLite.VerificationContract contract = new Neo.Wallets.SQLite.VerificationContract + { + Script = Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + MyWalletAccount account = new MyWalletAccount(contract.ScriptHash); + account.SetKey(key); + account.Contract = contract; + AddAccount(account); + return account; + } + + public override WalletAccount CreateAccount(Contract contract, KeyPair key = null) + { + MyWalletAccount account = new MyWalletAccount(contract.ScriptHash) + { + Contract = contract + }; + account.SetKey(key); + AddAccount(account); + return account; + } + + public override WalletAccount CreateAccount(UInt160 scriptHash) + { + MyWalletAccount account = new MyWalletAccount(scriptHash); + AddAccount(account); + return account; + } + + public override bool DeleteAccount(UInt160 scriptHash) + { + return accounts.Remove(scriptHash); + } + + public override WalletAccount GetAccount(UInt160 scriptHash) + { + accounts.TryGetValue(scriptHash, out WalletAccount account); + return account; + } + + public override IEnumerable GetAccounts() + { + return accounts.Values; + } + + public override bool VerifyPassword(string password) + { + return true; + } + } + + [TestClass] + public class UT_Wallet + { + Store store; + private static KeyPair glkey; + private static string nep2Key; + + [ClassInitialize] + public static void ClassInit(TestContext context) + { + glkey = UT_Crypto.generateCertainKey(32); + nep2Key = glkey.Export("pwd", 0, 0, 0); + } + + [TestInitialize] + public void TestSetup() + { + store = TestBlockchain.GetStore(); + } + + [TestMethod] + public void TestContains() + { + MyWallet wallet = new MyWallet(); + Action action = () => wallet.Contains(UInt160.Zero); + action.ShouldNotThrow(); + } + + [TestMethod] + public void TestCreateAccount1() + { + MyWallet wallet = new MyWallet(); + wallet.CreateAccount(new byte[32]).Should().NotBeNull(); + } + + [TestMethod] + public void TestCreateAccount2() + { + MyWallet wallet = new MyWallet(); + Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + WalletAccount account = wallet.CreateAccount(contract, UT_Crypto.generateCertainKey(32).PrivateKey); + account.Should().NotBeNull(); + + wallet = new MyWallet(); + account = wallet.CreateAccount(contract, (byte[])(null)); + account.Should().NotBeNull(); + } + + [TestMethod] + public void TestCreateAccount3() + { + MyWallet wallet = new MyWallet(); + Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + wallet.CreateAccount(contract, glkey).Should().NotBeNull(); + } + + [TestMethod] + public void TestCreateAccount4() + { + MyWallet wallet = new MyWallet(); + wallet.CreateAccount(UInt160.Zero).Should().NotBeNull(); + } + + [TestMethod] + public void TestGetName() + { + MyWallet wallet = new MyWallet(); + wallet.Name.Should().Be("MyWallet"); + } + + [TestMethod] + public void TestGetVersion() + { + MyWallet wallet = new MyWallet(); + wallet.Version.Should().Be(Version.Parse("0.0.1")); + } + + [TestMethod] + public void TestGetAccount1() + { + MyWallet wallet = new MyWallet(); + wallet.CreateAccount(UInt160.Parse("522a2b818c308c7a2c77cfdda11763fe043bfb40")); + WalletAccount account = wallet.GetAccount(ECCurve.Secp256r1.G); + account.ScriptHash.Should().Be(UInt160.Parse("0x522a2b818c308c7a2c77cfdda11763fe043bfb40")); + } + + [TestMethod] + public void TestGetAccount2() + { + MyWallet wallet = new MyWallet(); + Action action = () => wallet.GetAccount(UInt160.Zero); + action.ShouldNotThrow(); + } + + [TestMethod] + public void TestGetAccounts() + { + MyWallet wallet = new MyWallet(); + Action action = () => wallet.GetAccounts(); + action.ShouldNotThrow(); + } + + [TestMethod] + public void TestGetAvailable() + { + MyWallet wallet = new MyWallet(); + Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); + account.Lock = false; + + // Fake balance + var snapshot = store.GetSnapshot(); + var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + wallet.GetAvailable(NativeContract.GAS.Hash).Should().Be(new BigDecimal(1000000000000, 8)); + + entry.Value = new Nep5AccountState() + { + Balance = 0 + } + .ToByteArray(); + } + + [TestMethod] + public void TestGetBalance() + { + MyWallet wallet = new MyWallet(); + Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); + account.Lock = false; + + // Fake balance + var snapshot = store.GetSnapshot(); + var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + wallet.GetBalance(UInt160.Zero, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(0, 0)); + wallet.GetBalance(NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(1000000000000, 8)); + + entry.Value = new Nep5AccountState() + { + Balance = 0 + } + .ToByteArray(); + } + + [TestMethod] + public void TestGetPrivateKeyFromNEP2() + { + Action action = () => Wallet.GetPrivateKeyFromNEP2(null, null, 0, 0, 0); + action.ShouldThrow(); + + action = () => Wallet.GetPrivateKeyFromNEP2("TestGetPrivateKeyFromNEP2", null, 0, 0, 0); + action.ShouldThrow(); + + action = () => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", 0, 0, 0); + action.ShouldThrow(); + + action = () => Wallet.GetPrivateKeyFromNEP2(nep2Key, "Test", 0, 0, 0); + action.ShouldThrow(); + + Wallet.GetPrivateKeyFromNEP2(nep2Key, "pwd", 0, 0, 0).Should().BeEquivalentTo(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }); + } + + [TestMethod] + public void TestGetPrivateKeyFromWIF() + { + Action action = () => Wallet.GetPrivateKeyFromWIF(null); + action.ShouldThrow(); + + action = () => Wallet.GetPrivateKeyFromWIF("3vQB7B6MrGQZaxCuFg4oh"); + action.ShouldThrow(); + + Wallet.GetPrivateKeyFromWIF("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU").Should().BeEquivalentTo(new byte[] { 199, 19, 77, 111, 216, 231, 61, 129, 158, 130, 117, 92, 100, 201, 55, 136, 216, 219, 9, 97, 146, 158, 2, 90, 83, 54, 60, 76, 192, 42, 105, 98 }); + } + + [TestMethod] + public void TestImport1() + { + MyWallet wallet = new MyWallet(); + wallet.Import("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU").Should().NotBeNull(); + } + + [TestMethod] + public void TestImport2() + { + MyWallet wallet = new MyWallet(); + wallet.Import(nep2Key, "pwd", 0, 0, 0).Should().NotBeNull(); + } + + [TestMethod] + public void TestMakeTransaction1() + { + MyWallet wallet = new MyWallet(); + Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); + account.Lock = false; + + Action action = () => wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(1,8) + } + }, UInt160.Zero); + action.ShouldThrow(); + + action = () => wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(1,8) + } + }, account.ScriptHash); + action.ShouldThrow(); + + action = () => wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = UInt160.Zero, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(1,8) + } + }, account.ScriptHash); + action.ShouldThrow(); + + // Fake balance + var snapshot = store.GetSnapshot(); + var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + entry.Value = new Nep5AccountState() + { + Balance = 10000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + key = NativeContract.NEO.CreateStorageKey(20, account.ScriptHash); + entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + entry.Value = new NeoToken.AccountState() + { + Balance = 10000 * NativeContract.NEO.Factor + } + .ToByteArray(); + + var tx = wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(1,8) + } + }); + tx.Should().NotBeNull(); + + tx = wallet.MakeTransaction(new TransferOutput[] + { + new TransferOutput() + { + AssetId = NativeContract.NEO.Hash, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(1,8) + } + }); + tx.Should().NotBeNull(); + + entry.Value = new NeoToken.AccountState() + { + Balance = 0 + } + .ToByteArray(); + } + + [TestMethod] + public void TestMakeTransaction2() + { + MyWallet wallet = new MyWallet(); + Action action = () => wallet.MakeTransaction(new byte[] { }, UInt160.Zero, new TransactionAttribute[] { }); + action.ShouldThrow(); + + Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); + account.Lock = false; + + // Fake balance + var snapshot = store.GetSnapshot(); + var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); + var entry = snapshot.Storages.GetAndChange(key, () => new StorageItem + { + Value = new Nep5AccountState().ToByteArray() + }); + entry.Value = new Nep5AccountState() + { + Balance = 1000000 * NativeContract.GAS.Factor + } + .ToByteArray(); + + var tx = wallet.MakeTransaction(new byte[] { }, account.ScriptHash, new TransactionAttribute[] { }); + tx.Should().NotBeNull(); + + tx = wallet.MakeTransaction(new byte[] { }, null, new TransactionAttribute[] { }); + tx.Should().NotBeNull(); + + entry.Value = new NeoToken.AccountState() + { + Balance = 0 + } + .ToByteArray(); + } + + [TestMethod] + public void TestVerifyPassword() + { + MyWallet wallet = new MyWallet(); + Action action = () => wallet.VerifyPassword("Test"); + action.ShouldNotThrow(); + } + } +} diff --git a/neo.UnitTests/Wallets/UT_WalletAccount.cs b/neo.UnitTests/Wallets/UT_WalletAccount.cs new file mode 100644 index 0000000000..413caf6880 --- /dev/null +++ b/neo.UnitTests/Wallets/UT_WalletAccount.cs @@ -0,0 +1,48 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.Wallets; + +namespace Neo.UnitTests.Wallets +{ + public class MyWalletAccount : WalletAccount + { + private KeyPair key = null; + public override bool HasKey => key != null; + + public MyWalletAccount(UInt160 scriptHash) + : base(scriptHash) + { + } + + public override KeyPair GetKey() + { + return key; + } + + public void SetKey(KeyPair inputKey) + { + key = inputKey; + } + } + + [TestClass] + public class UT_WalletAccount + { + [TestMethod] + public void TestGetAddress() + { + MyWalletAccount walletAccount = new MyWalletAccount(UInt160.Zero); + walletAccount.Address.Should().Be("AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM"); + } + + [TestMethod] + public void TestGetWatchOnly() + { + MyWalletAccount walletAccount = new MyWalletAccount(UInt160.Zero); + walletAccount.WatchOnly.Should().BeTrue(); + walletAccount.Contract = new Contract(); + walletAccount.WatchOnly.Should().BeFalse(); + } + } +} diff --git a/neo.UnitTests/Wallets/UT_Wallets_Helper.cs b/neo.UnitTests/Wallets/UT_Wallets_Helper.cs new file mode 100644 index 0000000000..4abf10d9ed --- /dev/null +++ b/neo.UnitTests/Wallets/UT_Wallets_Helper.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.Wallets; +using System; + +namespace Neo.UnitTests.Wallets +{ + [TestClass] + public class UT_Wallets_Helper + { + [TestMethod] + public void TestToScriptHash() + { + byte[] array = { 0x01 }; + UInt160 scriptHash = new UInt160(Crypto.Default.Hash160(array)); + "AZk5bAanTtD6AvpeesmYgL8CLRYUt5JQsX".ToScriptHash().Should().Be(scriptHash); + + Action action = () => "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(); + action.ShouldThrow(); + + var address = scriptHash.ToAddress(); + byte[] data = new byte[21]; + // NEO version is 0x17 + data[0] = 0x01; + Buffer.BlockCopy(scriptHash.ToArray(), 0, data, 1, 20); + address = data.Base58CheckEncode(); + action = () => address.ToScriptHash(); + action.ShouldThrow(); + } + } +} diff --git a/neo.UnitTests/neo.UnitTests.csproj b/neo.UnitTests/neo.UnitTests.csproj index 9f57166c69..4cbc7f4164 100644 --- a/neo.UnitTests/neo.UnitTests.csproj +++ b/neo.UnitTests/neo.UnitTests.csproj @@ -1,4 +1,4 @@ - + Exe @@ -31,5 +31,4 @@ - diff --git a/neo.UnitTests/protocol.json b/neo.UnitTests/protocol.json index 476a25d4aa..06aec5ccce 100644 --- a/neo.UnitTests/protocol.json +++ b/neo.UnitTests/protocol.json @@ -1,5 +1,5 @@ { "ProtocolConfiguration": { - "SecondsPerBlock": 2 + "MillisecondsPerBlock": 2000 } } diff --git a/neo.sln b/neo.sln index 9d4c436c0e..fd72762fc2 100644 --- a/neo.sln +++ b/neo.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26430.15 diff --git a/neo/BigDecimal.cs b/neo/BigDecimal.cs index 1d65859b0f..aaf541caa9 100644 --- a/neo/BigDecimal.cs +++ b/neo/BigDecimal.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Numerics; namespace Neo diff --git a/neo/Consensus/ChangeView.cs b/neo/Consensus/ChangeView.cs index 7d5b3e96eb..607e44cfd5 100644 --- a/neo/Consensus/ChangeView.cs +++ b/neo/Consensus/ChangeView.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Neo.Consensus { @@ -14,7 +14,7 @@ public class ChangeView : ConsensusMessage /// they only respond once to a specific ChangeView request (it thus prevents replay of the ChangeView /// message from repeatedly broadcasting RecoveryMessages). /// - public uint Timestamp; + public ulong Timestamp; /// /// Reason @@ -22,7 +22,7 @@ public class ChangeView : ConsensusMessage public ChangeViewReason Reason; public override int Size => base.Size + - sizeof(uint) + // Timestamp + sizeof(ulong) + // Timestamp sizeof(ChangeViewReason); // Reason public ChangeView() : base(ConsensusMessageType.ChangeView) { } @@ -30,7 +30,7 @@ public class ChangeView : ConsensusMessage public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); - Timestamp = reader.ReadUInt32(); + Timestamp = reader.ReadUInt64(); Reason = (ChangeViewReason)reader.ReadByte(); } diff --git a/neo/Consensus/ChangeViewReason.cs b/neo/Consensus/ChangeViewReason.cs index 64a2a6053e..87c88c74c2 100644 --- a/neo/Consensus/ChangeViewReason.cs +++ b/neo/Consensus/ChangeViewReason.cs @@ -1,4 +1,4 @@ -namespace Neo.Consensus +namespace Neo.Consensus { public enum ChangeViewReason : byte { @@ -7,5 +7,6 @@ public enum ChangeViewReason : byte TxNotFound = 0x2, TxRejectedByPolicy = 0x3, TxInvalid = 0x4, + BlockRejectedByPolicy = 0x5 } -} \ No newline at end of file +} diff --git a/neo/Consensus/Commit.cs b/neo/Consensus/Commit.cs index f7c17e559a..84585e1ee4 100644 --- a/neo/Consensus/Commit.cs +++ b/neo/Consensus/Commit.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Neo.Consensus { diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index 0d6855a623..0d9edbb3dc 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -4,9 +4,9 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.Plugins; using Neo.SmartContract; using Neo.SmartContract.Native; +using Neo.VM; using Neo.Wallets; using System; using System.Collections.Generic; @@ -19,9 +19,9 @@ namespace Neo.Consensus internal class ConsensusContext : IDisposable, ISerializable { /// - /// Prefix for saving consensus state. + /// Key for saving consensus state. /// - public const byte CN_Context = 0xf4; + private static readonly byte[] ConsensusStateKey = { 0xf4 }; public Block Block; public byte ViewNumber; @@ -39,6 +39,7 @@ internal class ConsensusContext : IDisposable, ISerializable public Snapshot Snapshot { get; private set; } private KeyPair keyPair; + private int _witnessSize; private readonly Wallet wallet; private readonly Store store; private readonly Random random = new Random(); @@ -95,17 +96,14 @@ public void Deserialize(BinaryReader reader) Reset(0); if (reader.ReadUInt32() != Block.Version) throw new FormatException(); if (reader.ReadUInt32() != Block.Index) throw new InvalidOperationException(); - Block.Timestamp = reader.ReadUInt32(); + Block.Timestamp = reader.ReadUInt64(); Block.NextConsensus = reader.ReadSerializable(); if (Block.NextConsensus.Equals(UInt160.Zero)) Block.NextConsensus = null; Block.ConsensusData = reader.ReadSerializable(); ViewNumber = reader.ReadByte(); TransactionHashes = reader.ReadSerializableArray(); - if (TransactionHashes.Length == 0) - TransactionHashes = null; Transaction[] transactions = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); - Transactions = transactions.Length == 0 ? null : transactions.ToDictionary(p => p.Hash); PreparationPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; for (int i = 0; i < PreparationPayloads.Length; i++) PreparationPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; @@ -118,6 +116,9 @@ public void Deserialize(BinaryReader reader) LastChangeViewPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; for (int i = 0; i < LastChangeViewPayloads.Length; i++) LastChangeViewPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; + if (TransactionHashes.Length == 0 && !RequestSentOrReceived) + TransactionHashes = null; + Transactions = transactions.Length == 0 && !RequestSentOrReceived ? null : transactions.ToDictionary(p => p.Hash); } public void Dispose() @@ -142,7 +143,7 @@ public uint GetPrimaryIndex(byte viewNumber) public bool Load() { - byte[] data = store.Get(CN_Context, new byte[0]); + byte[] data = store.Get(ConsensusStateKey); if (data is null || data.Length == 0) return false; using (MemoryStream ms = new MemoryStream(data, false)) using (BinaryReader reader = new BinaryReader(ms)) @@ -164,7 +165,7 @@ public ConsensusPayload MakeChangeView(ChangeViewReason reason) return ChangeViewPayloads[MyIndex] = MakeSignedPayload(new ChangeView { Reason = reason, - Timestamp = TimeProvider.Current.UtcNow.ToTimestamp() + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() }); } @@ -206,18 +207,79 @@ private void SignPayload(ConsensusPayload payload) payload.Witness = sc.GetWitnesses()[0]; } + /// + /// Return the expected block size + /// + internal int GetExpectedBlockSize() + { + return GetExpectedBlockSizeWithoutTransactions(Transactions.Count) + // Base size + Transactions.Values.Sum(u => u.Size); // Sum Txs + } + + /// + /// Return the expected block size without txs + /// + /// Expected transactions + internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions) + { + var blockSize = + // BlockBase + sizeof(uint) + //Version + UInt256.Length + //PrevHash + UInt256.Length + //MerkleRoot + sizeof(ulong) + //Timestamp + sizeof(uint) + //Index + UInt160.Length + //NextConsensus + 1 + // + _witnessSize; //Witness + + blockSize += + // Block + Block.ConsensusData.Size + //ConsensusData + IO.Helper.GetVarSize(expectedTransactions + 1); //Transactions count + + return blockSize; + } + + /// + /// Prevent that block exceed the max size + /// + /// Ordered transactions + internal void EnsureMaxBlockSize(IEnumerable txs) + { + uint maxBlockSize = NativeContract.Policy.GetMaxBlockSize(Snapshot); + uint maxTransactionsPerBlock = NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot); + + // Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool + txs = txs.Take((int)maxTransactionsPerBlock); + List hashes = new List(); + Transactions = new Dictionary(); + + // Expected block size + var blockSize = GetExpectedBlockSizeWithoutTransactions(txs.Count()); + + // Iterate transaction until reach the size + foreach (Transaction tx in txs) + { + // Check if maximum block size has been already exceeded with the current selected set + blockSize += tx.Size; + if (blockSize > maxBlockSize) break; + + hashes.Add(tx.Hash); + Transactions.Add(tx.Hash, tx); + } + + TransactionHashes = hashes.ToArray(); + } + public ConsensusPayload MakePrepareRequest() { byte[] buffer = new byte[sizeof(ulong)]; random.NextBytes(buffer); - IEnumerable memoryPoolTransactions = Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions(); - foreach (IPolicyPlugin plugin in Plugin.Policies) - memoryPoolTransactions = plugin.FilterForBlock(memoryPoolTransactions); - List transactions = memoryPoolTransactions.ToList(); - TransactionHashes = transactions.Select(p => p.Hash).ToArray(); - Transactions = transactions.ToDictionary(p => p.Hash); - Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestamp(), PrevHeader.Timestamp + 1); Block.ConsensusData.Nonce = BitConverter.ToUInt64(buffer, 0); + EnsureMaxBlockSize(Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions()); + Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); + return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest { Timestamp = Block.Timestamp, @@ -230,7 +292,7 @@ public ConsensusPayload MakeRecoveryRequest() { return MakeSignedPayload(new RecoveryRequest { - Timestamp = TimeProvider.Current.UtcNow.ToTimestamp() + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() }); } @@ -278,10 +340,26 @@ public void Reset(byte viewNumber) { PrevHash = Snapshot.CurrentBlockHash, Index = Snapshot.Height + 1, - NextConsensus = Blockchain.GetConsensusAddress(NativeContract.NEO.GetValidators(Snapshot).ToArray()), - ConsensusData = new ConsensusData() + NextConsensus = Blockchain.GetConsensusAddress(NativeContract.NEO.GetValidators(Snapshot).ToArray()) }; + var pv = Validators; Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot); + if (_witnessSize == 0 || (pv != null && pv.Length != Validators.Length)) + { + // Compute the expected size of the witness + using (ScriptBuilder sb = new ScriptBuilder()) + { + for (int x = 0; x < M; x++) + { + sb.EmitPush(new byte[64]); + } + _witnessSize = new Witness + { + InvocationScript = sb.ToArray(), + VerificationScript = Contract.CreateMultiSigRedeemScript(M, Validators) + }.Size; + } + } MyIndex = -1; ChangeViewPayloads = new ConsensusPayload[Validators.Length]; LastChangeViewPayloads = new ConsensusPayload[Validators.Length]; @@ -311,7 +389,10 @@ public void Reset(byte viewNumber) LastChangeViewPayloads[i] = null; } ViewNumber = viewNumber; - Block.ConsensusData.PrimaryIndex = GetPrimaryIndex(viewNumber); + Block.ConsensusData = new ConsensusData + { + PrimaryIndex = GetPrimaryIndex(viewNumber) + }; Block.MerkleRoot = null; Block.Timestamp = 0; Block.Transactions = null; @@ -322,7 +403,7 @@ public void Reset(byte viewNumber) public void Save() { - store.PutSync(CN_Context, new byte[0], this.ToArray()); + store.PutSync(ConsensusStateKey, this.ToArray()); } public void Serialize(BinaryWriter writer) diff --git a/neo/Consensus/ConsensusMessage.cs b/neo/Consensus/ConsensusMessage.cs index 85bf3174e5..af16db03e3 100644 --- a/neo/Consensus/ConsensusMessage.cs +++ b/neo/Consensus/ConsensusMessage.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Caching; using System; using System.IO; diff --git a/neo/Consensus/ConsensusMessageType.cs b/neo/Consensus/ConsensusMessageType.cs index 0aff263595..fe13207db1 100644 --- a/neo/Consensus/ConsensusMessageType.cs +++ b/neo/Consensus/ConsensusMessageType.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; namespace Neo.Consensus { diff --git a/neo/Consensus/ConsensusService.cs b/neo/Consensus/ConsensusService.cs index b8e594d642..a65916b5aa 100644 --- a/neo/Consensus/ConsensusService.cs +++ b/neo/Consensus/ConsensusService.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Configuration; using Neo.Cryptography; using Neo.IO; @@ -8,6 +8,7 @@ using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.Plugins; +using Neo.SmartContract.Native; using Neo.Wallets; using System; using System.Collections.Generic; @@ -66,19 +67,32 @@ private bool AddTransaction(Transaction tx, bool verify) RequestChangeView(ChangeViewReason.TxInvalid); return false; } - if (!Plugin.CheckPolicy(tx)) + if (!NativeContract.Policy.CheckPolicy(tx, context.Snapshot)) { Log($"reject tx: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); RequestChangeView(ChangeViewReason.TxRejectedByPolicy); return false; } context.Transactions[tx.Hash] = tx; + return CheckPrepareResponse(); + } + + private bool CheckPrepareResponse() + { if (context.TransactionHashes.Length == context.Transactions.Count) { // if we are the primary for this view, but acting as a backup because we recovered our own // previously sent prepare request, then we don't want to send a prepare response. if (context.IsPrimary || context.WatchOnly) return true; + // Check maximum block size via Native Contract policy + if (context.GetExpectedBlockSize() > NativeContract.Policy.GetMaxBlockSize(context.Snapshot)) + { + Log($"rejected block: {context.Block.Index}{Environment.NewLine} The size exceed the policy", LogLevel.Warning); + RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); + return false; + } + // Timeout extension due to prepare response sent // around 2*15/M=30.0/5 ~ 40% block time (for M=5) ExtendTimerByFactor(2); @@ -139,7 +153,7 @@ private void CheckPreparations() context.Save(); localNode.Tell(new LocalNode.SendDirectly { Inventory = payload }); // Set timer, so we will resend the commit in case of a networking issue - ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock)); + ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock)); CheckCommits(); } } @@ -155,7 +169,7 @@ private void InitializeConsensus(byte viewNumber) { if (isRecovering) { - ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (viewNumber + 1))); + ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << (viewNumber + 1))); } else { @@ -168,7 +182,7 @@ private void InitializeConsensus(byte viewNumber) } else { - ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (viewNumber + 1))); + ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << (viewNumber + 1))); } } @@ -188,7 +202,7 @@ private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) if (message.NewViewNumber <= expectedView) return; - Log($"{nameof(OnChangeViewReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} nv={message.NewViewNumber}"); + Log($"{nameof(OnChangeViewReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} nv={message.NewViewNumber} reason={message.Reason}"); context.ChangeViewPayloads[payload.ValidatorIndex] = payload; CheckExpectedView(message.NewViewNumber); } @@ -229,10 +243,10 @@ private void OnCommitReceived(ConsensusPayload payload, Commit commit) existingCommitPayload = payload; } - // this function increases existing timer (never decreases) with a value proportional to `maxDelayInBlockTimes`*`Blockchain.SecondsPerBlock` + // this function increases existing timer (never decreases) with a value proportional to `maxDelayInBlockTimes`*`Blockchain.MillisecondsPerBlock` private void ExtendTimerByFactor(int maxDelayInBlockTimes) { - TimeSpan nextDelay = expected_delay - (TimeProvider.Current.UtcNow - clock_started) + TimeSpan.FromMilliseconds(maxDelayInBlockTimes * Blockchain.SecondsPerBlock * 1000.0 / context.M); + TimeSpan nextDelay = expected_delay - (TimeProvider.Current.UtcNow - clock_started) + TimeSpan.FromMilliseconds(maxDelayInBlockTimes * Blockchain.MillisecondsPerBlock / context.M); if (!context.WatchOnly && !context.ViewChanging && !context.CommitSent && (nextDelay > TimeSpan.Zero)) ChangeTimer(nextDelay); } @@ -390,7 +404,7 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m if (context.RequestSentOrReceived || context.NotAcceptingPayloadsDueToViewChanging) return; if (payload.ValidatorIndex != context.Block.ConsensusData.PrimaryIndex || message.ViewNumber != context.ViewNumber) return; Log($"{nameof(OnPrepareRequestReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} tx={message.TransactionHashes.Length}"); - if (message.Timestamp <= context.PrevHeader.Timestamp || message.Timestamp > TimeProvider.Current.UtcNow.AddMinutes(10).ToTimestamp()) + if (message.Timestamp <= context.PrevHeader.Timestamp || message.Timestamp > TimeProvider.Current.UtcNow.AddMinutes(10).ToTimestampMS()) { Log($"Timestamp incorrect: {message.Timestamp}", LogLevel.Warning); return; @@ -419,6 +433,14 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m if (context.CommitPayloads[i]?.ConsensusMessage.ViewNumber == context.ViewNumber) if (!Crypto.Default.VerifySignature(hashData, context.CommitPayloads[i].GetDeserializedMessage().Signature, context.Validators[i].EncodePoint(false))) context.CommitPayloads[i] = null; + + if (context.TransactionHashes.Length == 0) + { + // There are no tx so we should act like if all the transactions were filled + CheckPrepareResponse(); + return; + } + Dictionary mempoolVerified = Blockchain.Singleton.MemPool.GetVerifiedTransactions().ToDictionary(p => p.Hash); List unverified = new List(); foreach (UInt256 hash in context.TransactionHashes) @@ -543,13 +565,13 @@ private void OnTimer(Timer timer) // Re-send commit periodically by sending recover message in case of a network issue. Log($"send recovery to resend commit"); localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryMessage() }); - ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << 1)); + ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << 1)); } else { var reason = ChangeViewReason.Timeout; - if (context.Block != null && context.TransactionHashes.Count() > context.Transactions.Count) + if (context.Block != null && context.TransactionHashes?.Count() > context.Transactions?.Count) { reason = ChangeViewReason.TxNotFound; } @@ -590,7 +612,7 @@ private void RequestChangeView(ChangeViewReason reason) // The latter may happen by nodes in higher views with, at least, `M` proofs byte expectedView = context.ViewNumber; expectedView++; - ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (expectedView + 1))); + ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << (expectedView + 1))); if ((context.CountCommitted + context.CountFailed) > context.F) { Log($"Skip requesting change view to nv={expectedView} because nc={context.CountCommitted} nf={context.CountFailed}"); @@ -622,7 +644,7 @@ private void SendPrepareRequest() foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, context.TransactionHashes)) localNode.Tell(Message.Create(MessageCommand.Inv, payload)); } - ChangeTimer(TimeSpan.FromSeconds((Blockchain.SecondsPerBlock << (context.ViewNumber + 1)) - (context.ViewNumber == 0 ? Blockchain.SecondsPerBlock : 0))); + ChangeTimer(TimeSpan.FromMilliseconds((Blockchain.MillisecondsPerBlock << (context.ViewNumber + 1)) - (context.ViewNumber == 0 ? Blockchain.MillisecondsPerBlock : 0))); } } diff --git a/neo/Consensus/PrepareRequest.cs b/neo/Consensus/PrepareRequest.cs index 299b0f6a72..2369b4f9f7 100644 --- a/neo/Consensus/PrepareRequest.cs +++ b/neo/Consensus/PrepareRequest.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Payloads; using System; using System.IO; @@ -8,12 +8,12 @@ namespace Neo.Consensus { public class PrepareRequest : ConsensusMessage { - public uint Timestamp; + public ulong Timestamp; public ulong Nonce; public UInt256[] TransactionHashes; public override int Size => base.Size - + sizeof(uint) //Timestamp + + sizeof(ulong) //Timestamp + sizeof(ulong) //Nonce + TransactionHashes.GetVarSize(); //TransactionHashes @@ -25,7 +25,7 @@ public PrepareRequest() public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); - Timestamp = reader.ReadUInt32(); + Timestamp = reader.ReadUInt64(); Nonce = reader.ReadUInt64(); TransactionHashes = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); if (TransactionHashes.Distinct().Count() != TransactionHashes.Length) diff --git a/neo/Consensus/PrepareResponse.cs b/neo/Consensus/PrepareResponse.cs index 93eb95833a..7c2956ccc2 100644 --- a/neo/Consensus/PrepareResponse.cs +++ b/neo/Consensus/PrepareResponse.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System.IO; namespace Neo.Consensus diff --git a/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs b/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs index 373e7e75a3..dfc46385c0 100644 --- a/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs +++ b/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Payloads; using System.IO; @@ -10,20 +10,20 @@ public class ChangeViewPayloadCompact : ISerializable { public ushort ValidatorIndex; public byte OriginalViewNumber; - public uint Timestamp; + public ulong Timestamp; public byte[] InvocationScript; int ISerializable.Size => sizeof(ushort) + //ValidatorIndex sizeof(byte) + //OriginalViewNumber - sizeof(uint) + //Timestamp + sizeof(ulong) + //Timestamp InvocationScript.GetVarSize(); //InvocationScript void ISerializable.Deserialize(BinaryReader reader) { ValidatorIndex = reader.ReadUInt16(); OriginalViewNumber = reader.ReadByte(); - Timestamp = reader.ReadUInt32(); + Timestamp = reader.ReadUInt64(); InvocationScript = reader.ReadVarBytes(1024); } diff --git a/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs b/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs index d9347b71bc..72962f92b3 100644 --- a/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs +++ b/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Payloads; using System.IO; diff --git a/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs b/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs index 8c0e52f59f..962ae69185 100644 --- a/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs +++ b/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Payloads; using System.IO; diff --git a/neo/Consensus/RecoveryMessage.cs b/neo/Consensus/RecoveryMessage.cs index afae45470b..a5e1c09ac6 100644 --- a/neo/Consensus/RecoveryMessage.cs +++ b/neo/Consensus/RecoveryMessage.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.SmartContract; diff --git a/neo/Consensus/RecoveryRequest.cs b/neo/Consensus/RecoveryRequest.cs index 971b49a324..6ea0d7c2f7 100644 --- a/neo/Consensus/RecoveryRequest.cs +++ b/neo/Consensus/RecoveryRequest.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Neo.Consensus { @@ -9,17 +9,17 @@ public class RecoveryRequest : ConsensusMessage /// they only respond once to a specific RecoveryRequest request. /// In this sense, it prevents replay of the RecoveryRequest message from the repeatedly broadcast of Recovery's messages. /// - public uint Timestamp; + public ulong Timestamp; public override int Size => base.Size - + sizeof(uint); //Timestamp + + sizeof(ulong); //Timestamp public RecoveryRequest() : base(ConsensusMessageType.RecoveryRequest) { } public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); - Timestamp = reader.ReadUInt32(); + Timestamp = reader.ReadUInt64(); } public override void Serialize(BinaryWriter writer) diff --git a/neo/Cryptography/Base58.cs b/neo/Cryptography/Base58.cs index fd5bf4d432..b64f12216b 100644 --- a/neo/Cryptography/Base58.cs +++ b/neo/Cryptography/Base58.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Numerics; using System.Text; diff --git a/neo/Cryptography/BloomFilter.cs b/neo/Cryptography/BloomFilter.cs index 637cd9fac5..c4b79d01f2 100644 --- a/neo/Cryptography/BloomFilter.cs +++ b/neo/Cryptography/BloomFilter.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Linq; namespace Neo.Cryptography diff --git a/neo/Cryptography/Crypto.cs b/neo/Cryptography/Crypto.cs index ae1c8cd168..c835e3eebb 100644 --- a/neo/Cryptography/Crypto.cs +++ b/neo/Cryptography/Crypto.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Security.Cryptography; diff --git a/neo/Cryptography/ECC/ECCurve.cs b/neo/Cryptography/ECC/ECCurve.cs index 9d43bcd25f..45d70d31f4 100644 --- a/neo/Cryptography/ECC/ECCurve.cs +++ b/neo/Cryptography/ECC/ECCurve.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Numerics; namespace Neo.Cryptography.ECC diff --git a/neo/Cryptography/ECC/ECDsa.cs b/neo/Cryptography/ECC/ECDsa.cs index a254b4248e..07373a7844 100644 --- a/neo/Cryptography/ECC/ECDsa.cs +++ b/neo/Cryptography/ECC/ECDsa.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Numerics; using System.Security.Cryptography; diff --git a/neo/Cryptography/ECC/ECFieldElement.cs b/neo/Cryptography/ECC/ECFieldElement.cs index e33cab4206..331e1e4408 100644 --- a/neo/Cryptography/ECC/ECFieldElement.cs +++ b/neo/Cryptography/ECC/ECFieldElement.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Numerics; diff --git a/neo/Cryptography/ECC/ECPoint.cs b/neo/Cryptography/ECC/ECPoint.cs index dbd556eb50..803aab9987 100644 --- a/neo/Cryptography/ECC/ECPoint.cs +++ b/neo/Cryptography/ECC/ECPoint.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.IO; using System.Linq; diff --git a/neo/Cryptography/Helper.cs b/neo/Cryptography/Helper.cs index ff1bb8d3d7..07c9ce74e9 100644 --- a/neo/Cryptography/Helper.cs +++ b/neo/Cryptography/Helper.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Payloads; using System; using System.Collections.Generic; diff --git a/neo/Cryptography/MerkleTree.cs b/neo/Cryptography/MerkleTree.cs index 8d73f56a23..0d8e617f3f 100644 --- a/neo/Cryptography/MerkleTree.cs +++ b/neo/Cryptography/MerkleTree.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/neo/Cryptography/MerkleTreeNode.cs b/neo/Cryptography/MerkleTreeNode.cs index 6bfc75c8d1..73b84b43e9 100644 --- a/neo/Cryptography/MerkleTreeNode.cs +++ b/neo/Cryptography/MerkleTreeNode.cs @@ -1,4 +1,4 @@ -namespace Neo.Cryptography +namespace Neo.Cryptography { internal class MerkleTreeNode { diff --git a/neo/Cryptography/Murmur3.cs b/neo/Cryptography/Murmur3.cs index e54c19ebc1..6296bb20be 100644 --- a/neo/Cryptography/Murmur3.cs +++ b/neo/Cryptography/Murmur3.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.CompilerServices; using System.Security.Cryptography; diff --git a/neo/Cryptography/RIPEMD160Managed.cs b/neo/Cryptography/RIPEMD160Managed.cs index 898780dc47..22ffafb835 100644 --- a/neo/Cryptography/RIPEMD160Managed.cs +++ b/neo/Cryptography/RIPEMD160Managed.cs @@ -1,4 +1,4 @@ -#if !NET47 +#if !NET47 using System; using System.Runtime.InteropServices; using System.Security; diff --git a/neo/Cryptography/SCrypt.cs b/neo/Cryptography/SCrypt.cs index 3022af296c..e40c2c83c5 100644 --- a/neo/Cryptography/SCrypt.cs +++ b/neo/Cryptography/SCrypt.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security.Cryptography; namespace Neo.Cryptography diff --git a/neo/Helper.cs b/neo/Helper.cs index 8c562740bd..1a513ec79e 100644 --- a/neo/Helper.cs +++ b/neo/Helper.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; using Neo.Plugins; using System; using System.Collections.Generic; @@ -140,15 +140,6 @@ internal static bool TestBit(this BigInteger i, int index) return (i & (BigInteger.One << index)) > BigInteger.Zero; } - public static DateTime ToDateTime(this uint timestamp) - { - return unixEpoch.AddSeconds(timestamp).ToLocalTime(); - } - - public static DateTime ToDateTime(this ulong timestamp) - { - return unixEpoch.AddSeconds(timestamp).ToLocalTime(); - } public static string ToHexString(this IEnumerable value) { @@ -181,6 +172,11 @@ public static uint ToTimestamp(this DateTime time) return (uint)(time.ToUniversalTime() - unixEpoch).TotalSeconds; } + public static ulong ToTimestampMS(this DateTime time) + { + return (ulong)(time.ToUniversalTime() - unixEpoch).TotalMilliseconds; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] unsafe internal static ushort ToUInt16(this byte[] value, int startIndex) { diff --git a/neo/IO/Actors/Idle.cs b/neo/IO/Actors/Idle.cs index 4bb8655bd8..bd4a2d05fa 100644 --- a/neo/IO/Actors/Idle.cs +++ b/neo/IO/Actors/Idle.cs @@ -1,4 +1,4 @@ -namespace Neo.IO.Actors +namespace Neo.IO.Actors { internal sealed class Idle { diff --git a/neo/IO/Actors/PriorityMailbox.cs b/neo/IO/Actors/PriorityMailbox.cs index c6c8a8fbe2..31a50c9a89 100644 --- a/neo/IO/Actors/PriorityMailbox.cs +++ b/neo/IO/Actors/PriorityMailbox.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Configuration; using Akka.Dispatch; using Akka.Dispatch.MessageQueues; diff --git a/neo/IO/Actors/PriorityMessageQueue.cs b/neo/IO/Actors/PriorityMessageQueue.cs index d22f7626fe..b7820b065a 100644 --- a/neo/IO/Actors/PriorityMessageQueue.cs +++ b/neo/IO/Actors/PriorityMessageQueue.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Dispatch; using Akka.Dispatch.MessageQueues; using System; diff --git a/neo/IO/ByteArrayComparer.cs b/neo/IO/ByteArrayComparer.cs new file mode 100644 index 0000000000..478c6e3c7d --- /dev/null +++ b/neo/IO/ByteArrayComparer.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Neo.IO +{ + internal class ByteArrayComparer : IComparer + { + public static readonly ByteArrayComparer Default = new ByteArrayComparer(); + + public int Compare(byte[] x, byte[] y) + { + int length = Math.Min(x.Length, y.Length); + for (int i = 0; i < length; i++) + { + int r = x[i].CompareTo(y[i]); + if (r != 0) return r; + } + return x.Length.CompareTo(y.Length); + } + } +} diff --git a/neo/IO/Caching/Cache.cs b/neo/IO/Caching/Cache.cs index 45556b8ae3..b5095231f3 100644 --- a/neo/IO/Caching/Cache.cs +++ b/neo/IO/Caching/Cache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -10,15 +10,15 @@ internal abstract class Cache : ICollection, IDisposable { protected class CacheItem { - public TKey Key; - public TValue Value; - public DateTime Time; + public readonly TKey Key; + public readonly TValue Value; + public readonly DateTime Time; public CacheItem(TKey key, TValue value) { this.Key = key; this.Value = value; - this.Time = DateTime.Now; + this.Time = TimeProvider.Current.UtcNow; } } @@ -60,13 +60,7 @@ public int Count } } - public bool IsReadOnly - { - get - { - return false; - } - } + public bool IsReadOnly => false; public Cache(int max_capacity) { @@ -97,7 +91,7 @@ private void AddInternal(TKey key, TValue item) { if (InnerDictionary.Count >= max_capacity) { - //TODO: 对PLINQ查询进行性能测试,以便确定此处使用何种算法更优(并行或串行) + //TODO: Perform a performance test on the PLINQ query to determine which algorithm is better here (parallel or not) foreach (CacheItem item_del in InnerDictionary.Values.AsParallel().OrderBy(p => p.Time).Take(InnerDictionary.Count - max_capacity + 1)) { RemoveInternal(item_del); diff --git a/neo/IO/Caching/CloneCache.cs b/neo/IO/Caching/CloneCache.cs index f13865a41b..0dd030fcb6 100644 --- a/neo/IO/Caching/CloneCache.cs +++ b/neo/IO/Caching/CloneCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Neo.IO.Caching diff --git a/neo/IO/Caching/CloneMetaCache.cs b/neo/IO/Caching/CloneMetaCache.cs index 6b354e256e..3cb0d83201 100644 --- a/neo/IO/Caching/CloneMetaCache.cs +++ b/neo/IO/Caching/CloneMetaCache.cs @@ -1,4 +1,4 @@ -namespace Neo.IO.Caching +namespace Neo.IO.Caching { internal class CloneMetaCache : MetaDataCache where T : class, ICloneable, ISerializable, new() diff --git a/neo/IO/Caching/DataCache.cs b/neo/IO/Caching/DataCache.cs index 5e9886d10c..dc97031d10 100644 --- a/neo/IO/Caching/DataCache.cs +++ b/neo/IO/Caching/DataCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -118,16 +118,58 @@ public void DeleteWhere(Func predicate) } } + /// + /// Find the entries that start with the `key_prefix` + /// + /// Must maintain the deserialized format of TKey + /// Entries found with the desired prefix public IEnumerable> Find(byte[] key_prefix = null) { + IEnumerable<(byte[], TKey, TValue)> cached; lock (dictionary) { - foreach (var pair in FindInternal(key_prefix ?? new byte[0])) - if (!dictionary.ContainsKey(pair.Key)) - yield return pair; - foreach (var pair in dictionary) - if (pair.Value.State != TrackState.Deleted && (key_prefix == null || pair.Key.ToArray().Take(key_prefix.Length).SequenceEqual(key_prefix))) - yield return new KeyValuePair(pair.Key, pair.Value.Item); + cached = dictionary + .Where(p => p.Value.State != TrackState.Deleted && (key_prefix == null || p.Key.ToArray().Take(key_prefix.Length).SequenceEqual(key_prefix))) + .Select(p => + ( + KeyBytes: p.Key.ToArray(), + p.Key, + p.Value.Item + )) + .OrderBy(p => p.KeyBytes, ByteArrayComparer.Default) + .ToArray(); + } + var uncached = FindInternal(key_prefix ?? new byte[0]) + .Where(p => !dictionary.ContainsKey(p.Key)) + .Select(p => + ( + KeyBytes: p.Key.ToArray(), + p.Key, + p.Value + )); + using (var e1 = cached.GetEnumerator()) + using (var e2 = uncached.GetEnumerator()) + { + (byte[] KeyBytes, TKey Key, TValue Item) i1, i2; + bool c1 = e1.MoveNext(); + bool c2 = e2.MoveNext(); + i1 = c1 ? e1.Current : default; + i2 = c2 ? e2.Current : default; + while (c1 || c2) + { + if (!c2 || (c1 && ByteArrayComparer.Default.Compare(i1.KeyBytes, i2.KeyBytes) < 0)) + { + yield return new KeyValuePair(i1.Key, i1.Item); + c1 = e1.MoveNext(); + i1 = c1 ? e1.Current : default; + } + else + { + yield return new KeyValuePair(i2.Key, i2.Item); + c2 = e2.MoveNext(); + i2 = c2 ? e2.Current : default; + } + } } } diff --git a/neo/IO/Caching/FIFOCache.cs b/neo/IO/Caching/FIFOCache.cs index 4aa7198b50..c96b11bf6e 100644 --- a/neo/IO/Caching/FIFOCache.cs +++ b/neo/IO/Caching/FIFOCache.cs @@ -1,4 +1,4 @@ -namespace Neo.IO.Caching +namespace Neo.IO.Caching { internal abstract class FIFOCache : Cache { diff --git a/neo/IO/Caching/FIFOSet.cs b/neo/IO/Caching/FIFOSet.cs index 7769cb5ad5..98733ce444 100644 --- a/neo/IO/Caching/FIFOSet.cs +++ b/neo/IO/Caching/FIFOSet.cs @@ -1,60 +1,65 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; - -namespace Neo.IO.Caching -{ - internal class FIFOSet : IEnumerable where T : IEquatable - { - private readonly int maxCapacity; - private readonly int removeCount; - private readonly OrderedDictionary dictionary; - - public FIFOSet(int maxCapacity, decimal batchSize = 0.1m) - { - if (maxCapacity <= 0) throw new ArgumentOutOfRangeException(nameof(maxCapacity)); - if (batchSize <= 0 || batchSize > 1) throw new ArgumentOutOfRangeException(nameof(batchSize)); - - this.maxCapacity = maxCapacity; - this.removeCount = Math.Max((int)(maxCapacity * batchSize), 1); - this.dictionary = new OrderedDictionary(maxCapacity); - } - - public bool Add(T item) - { - if (dictionary.Contains(item)) return false; - if (dictionary.Count >= maxCapacity) - { - if (removeCount == maxCapacity) - { - dictionary.Clear(); - } - else - { - for (int i = 0; i < removeCount; i++) - dictionary.RemoveAt(0); - } - } - dictionary.Add(item, null); - return true; - } - - public void ExceptWith(IEnumerable hashes) - { - foreach (var hash in hashes) - { - dictionary.Remove(hash); - } - } - - public IEnumerator GetEnumerator() - { - var entries = dictionary.Keys.Cast().ToArray(); - foreach (var entry in entries) yield return entry; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; + +namespace Neo.IO.Caching +{ + internal class FIFOSet : IEnumerable where T : IEquatable + { + private readonly int maxCapacity; + private readonly int removeCount; + private readonly OrderedDictionary dictionary; + + public FIFOSet(int maxCapacity, decimal batchSize = 0.1m) + { + if (maxCapacity <= 0) throw new ArgumentOutOfRangeException(nameof(maxCapacity)); + if (batchSize <= 0 || batchSize > 1) throw new ArgumentOutOfRangeException(nameof(batchSize)); + + this.maxCapacity = maxCapacity; + this.removeCount = Math.Max((int)(maxCapacity * batchSize), 1); + this.dictionary = new OrderedDictionary(maxCapacity); + } + + public bool Add(T item) + { + if (dictionary.Contains(item)) return false; + if (dictionary.Count >= maxCapacity) + { + if (removeCount == maxCapacity) + { + dictionary.Clear(); + } + else + { + for (int i = 0; i < removeCount; i++) + dictionary.RemoveAt(0); + } + } + dictionary.Add(item, null); + return true; + } + + public bool Contains(T item) + { + return dictionary.Contains(item); + } + + public void ExceptWith(IEnumerable hashes) + { + foreach (var hash in hashes) + { + dictionary.Remove(hash); + } + } + + public IEnumerator GetEnumerator() + { + var entries = dictionary.Keys.Cast().ToArray(); + foreach (var entry in entries) yield return entry; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/neo/IO/Caching/MetaDataCache.cs b/neo/IO/Caching/MetaDataCache.cs index fa3fed6215..f0c40cc59a 100644 --- a/neo/IO/Caching/MetaDataCache.cs +++ b/neo/IO/Caching/MetaDataCache.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Caching { diff --git a/neo/IO/Caching/OrderedDictionary.cs b/neo/IO/Caching/OrderedDictionary.cs index bf12785406..efc85b6305 100644 --- a/neo/IO/Caching/OrderedDictionary.cs +++ b/neo/IO/Caching/OrderedDictionary.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; diff --git a/neo/IO/Caching/ReflectionCache.cs b/neo/IO/Caching/ReflectionCache.cs index 800561c6d9..452856698c 100644 --- a/neo/IO/Caching/ReflectionCache.cs +++ b/neo/IO/Caching/ReflectionCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -77,4 +77,4 @@ public K CreateInstance(T key, K def = default(K)) return def; } } -} \ No newline at end of file +} diff --git a/neo/IO/Caching/ReflectionCacheAttribute.cs b/neo/IO/Caching/ReflectionCacheAttribute.cs index e1c1200278..b2ac3c3054 100644 --- a/neo/IO/Caching/ReflectionCacheAttribute.cs +++ b/neo/IO/Caching/ReflectionCacheAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Caching { @@ -18,4 +18,4 @@ public ReflectionCacheAttribute(Type type) Type = type; } } -} \ No newline at end of file +} diff --git a/neo/IO/Caching/RelayCache.cs b/neo/IO/Caching/RelayCache.cs index ec6e35bdce..0bc2884eda 100644 --- a/neo/IO/Caching/RelayCache.cs +++ b/neo/IO/Caching/RelayCache.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; namespace Neo.IO.Caching { diff --git a/neo/IO/Caching/TrackState.cs b/neo/IO/Caching/TrackState.cs index aa144aab34..cba7daec0c 100644 --- a/neo/IO/Caching/TrackState.cs +++ b/neo/IO/Caching/TrackState.cs @@ -1,4 +1,4 @@ -namespace Neo.IO.Caching +namespace Neo.IO.Caching { public enum TrackState : byte { diff --git a/neo/IO/Data/LevelDB/DB.cs b/neo/IO/Data/LevelDB/DB.cs index 559a109594..04e8f50e8f 100644 --- a/neo/IO/Data/LevelDB/DB.cs +++ b/neo/IO/Data/LevelDB/DB.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/Helper.cs b/neo/IO/Data/LevelDB/Helper.cs index 2b58690db0..d4c451f574 100644 --- a/neo/IO/Data/LevelDB/Helper.cs +++ b/neo/IO/Data/LevelDB/Helper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; diff --git a/neo/IO/Data/LevelDB/Iterator.cs b/neo/IO/Data/LevelDB/Iterator.cs index 11e72faf63..b3a6a0bfe0 100644 --- a/neo/IO/Data/LevelDB/Iterator.cs +++ b/neo/IO/Data/LevelDB/Iterator.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/LevelDBException.cs b/neo/IO/Data/LevelDB/LevelDBException.cs index 0fa0578c56..8804f1f7f2 100644 --- a/neo/IO/Data/LevelDB/LevelDBException.cs +++ b/neo/IO/Data/LevelDB/LevelDBException.cs @@ -1,4 +1,4 @@ -using System.Data.Common; +using System.Data.Common; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/Native.cs b/neo/IO/Data/LevelDB/Native.cs index fc3ccd6387..6a19ef4cfe 100644 --- a/neo/IO/Data/LevelDB/Native.cs +++ b/neo/IO/Data/LevelDB/Native.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; diff --git a/neo/IO/Data/LevelDB/Options.cs b/neo/IO/Data/LevelDB/Options.cs index 676627cb08..53dd6e488b 100644 --- a/neo/IO/Data/LevelDB/Options.cs +++ b/neo/IO/Data/LevelDB/Options.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/ReadOptions.cs b/neo/IO/Data/LevelDB/ReadOptions.cs index d27f0d7b59..9c198cfba1 100644 --- a/neo/IO/Data/LevelDB/ReadOptions.cs +++ b/neo/IO/Data/LevelDB/ReadOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/Slice.cs b/neo/IO/Data/LevelDB/Slice.cs index 95da8c6a44..1f9d927783 100644 --- a/neo/IO/Data/LevelDB/Slice.cs +++ b/neo/IO/Data/LevelDB/Slice.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using System; using System.Linq; using System.Runtime.InteropServices; diff --git a/neo/IO/Data/LevelDB/SliceBuilder.cs b/neo/IO/Data/LevelDB/SliceBuilder.cs index f00ce20454..d5888c6b5e 100644 --- a/neo/IO/Data/LevelDB/SliceBuilder.cs +++ b/neo/IO/Data/LevelDB/SliceBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; diff --git a/neo/IO/Data/LevelDB/Snapshot.cs b/neo/IO/Data/LevelDB/Snapshot.cs index 89a89cb55a..d651098388 100644 --- a/neo/IO/Data/LevelDB/Snapshot.cs +++ b/neo/IO/Data/LevelDB/Snapshot.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/WriteBatch.cs b/neo/IO/Data/LevelDB/WriteBatch.cs index eaa3e08bd6..b3a9782108 100644 --- a/neo/IO/Data/LevelDB/WriteBatch.cs +++ b/neo/IO/Data/LevelDB/WriteBatch.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Data/LevelDB/WriteOptions.cs b/neo/IO/Data/LevelDB/WriteOptions.cs index 8a74fb5340..8d120c3997 100644 --- a/neo/IO/Data/LevelDB/WriteOptions.cs +++ b/neo/IO/Data/LevelDB/WriteOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.IO.Data.LevelDB { diff --git a/neo/IO/Helper.cs b/neo/IO/Helper.cs index 647ae2cfb1..c985e676b2 100644 --- a/neo/IO/Helper.cs +++ b/neo/IO/Helper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -92,17 +92,16 @@ public static byte[] ReadBytesWithGrouping(this BinaryReader reader) { using (MemoryStream ms = new MemoryStream()) { - int padding = 0; + int count; do { byte[] group = reader.ReadBytes(GroupingSizeInBytes); - padding = reader.ReadByte(); - if (padding > GroupingSizeInBytes) + count = reader.ReadByte(); + if (count > GroupingSizeInBytes) throw new FormatException(); - int count = GroupingSizeInBytes - padding; if (count > 0) ms.Write(group, 0, count); - } while (padding == 0); + } while (count == GroupingSizeInBytes); return ms.ToArray(); } } @@ -200,7 +199,7 @@ public static void WriteBytesWithGrouping(this BinaryWriter writer, byte[] value while (remain >= GroupingSizeInBytes) { writer.Write(value, index, GroupingSizeInBytes); - writer.Write((byte)0); + writer.Write((byte)GroupingSizeInBytes); index += GroupingSizeInBytes; remain -= GroupingSizeInBytes; } @@ -209,7 +208,7 @@ public static void WriteBytesWithGrouping(this BinaryWriter writer, byte[] value int padding = GroupingSizeInBytes - remain; for (int i = 0; i < padding; i++) writer.Write((byte)0); - writer.Write((byte)padding); + writer.Write((byte)remain); } public static void WriteFixedString(this BinaryWriter writer, string value, int length) diff --git a/neo/IO/ICloneable.cs b/neo/IO/ICloneable.cs index 4df61c84b8..83b4d77725 100644 --- a/neo/IO/ICloneable.cs +++ b/neo/IO/ICloneable.cs @@ -1,4 +1,4 @@ -namespace Neo.IO +namespace Neo.IO { public interface ICloneable { diff --git a/neo/IO/ISerializable.cs b/neo/IO/ISerializable.cs index 28f1ec3e01..d03540deb2 100644 --- a/neo/IO/ISerializable.cs +++ b/neo/IO/ISerializable.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Neo.IO { diff --git a/neo/IO/Json/JArray.cs b/neo/IO/Json/JArray.cs index 3109b3b61c..6de623d29a 100644 --- a/neo/IO/Json/JArray.cs +++ b/neo/IO/Json/JArray.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; diff --git a/neo/IO/Json/JBoolean.cs b/neo/IO/Json/JBoolean.cs index 83a9111422..6b7c7a34c4 100644 --- a/neo/IO/Json/JBoolean.cs +++ b/neo/IO/Json/JBoolean.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace Neo.IO.Json diff --git a/neo/IO/Json/JNumber.cs b/neo/IO/Json/JNumber.cs index f9fa5e05e5..daac440094 100644 --- a/neo/IO/Json/JNumber.cs +++ b/neo/IO/Json/JNumber.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Text; @@ -101,13 +101,6 @@ public override string ToString() return AsString(); } - public DateTime ToTimestamp() - { - if (Value < 0 || Value > ulong.MaxValue) - throw new InvalidCastException(); - return ((ulong)Value).ToDateTime(); - } - public override T TryGetEnum(T defaultValue = default, bool ignoreCase = false) { Type enumType = typeof(T); diff --git a/neo/IO/Json/JObject.cs b/neo/IO/Json/JObject.cs index 40aa18f5da..cf6f68cbc7 100644 --- a/neo/IO/Json/JObject.cs +++ b/neo/IO/Json/JObject.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using System; using System.Collections.Generic; using System.IO; diff --git a/neo/IO/Json/JString.cs b/neo/IO/Json/JString.cs index 065e7b59f9..032470cf4e 100644 --- a/neo/IO/Json/JString.cs +++ b/neo/IO/Json/JString.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Text; diff --git a/neo/IO/Wrappers/SerializableWrapper.cs b/neo/IO/Wrappers/SerializableWrapper.cs index 868c9bcba2..098e4b9c7a 100644 --- a/neo/IO/Wrappers/SerializableWrapper.cs +++ b/neo/IO/Wrappers/SerializableWrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace Neo.IO.Wrappers diff --git a/neo/IO/Wrappers/UInt32Wrapper.cs b/neo/IO/Wrappers/UInt32Wrapper.cs index fa8406d50a..6e1fb71da1 100644 --- a/neo/IO/Wrappers/UInt32Wrapper.cs +++ b/neo/IO/Wrappers/UInt32Wrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace Neo.IO.Wrappers diff --git a/neo/Ledger/Blockchain.ApplicationExecuted.cs b/neo/Ledger/Blockchain.ApplicationExecuted.cs index 2f5d74fcb8..1bcc65563f 100644 --- a/neo/Ledger/Blockchain.ApplicationExecuted.cs +++ b/neo/Ledger/Blockchain.ApplicationExecuted.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; using System.Linq; diff --git a/neo/Ledger/Blockchain.cs b/neo/Ledger/Blockchain.cs index 5d9ad00ee9..50393d4c8e 100644 --- a/neo/Ledger/Blockchain.cs +++ b/neo/Ledger/Blockchain.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Configuration; using Neo.Cryptography.ECC; using Neo.IO; @@ -27,17 +27,17 @@ public class ImportCompleted { } public class FillMemoryPool { public IEnumerable Transactions; } public class FillCompleted { } - public static readonly uint SecondsPerBlock = ProtocolSettings.Default.SecondsPerBlock; + public static readonly uint MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock; public const uint DecrementInterval = 2000000; public const int MaxValidators = 1024; public static readonly uint[] GenerationAmount = { 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - public static readonly TimeSpan TimePerBlock = TimeSpan.FromSeconds(SecondsPerBlock); + public static readonly TimeSpan TimePerBlock = TimeSpan.FromMilliseconds(MillisecondsPerBlock); public static readonly ECPoint[] StandbyValidators = ProtocolSettings.Default.StandbyValidators.OfType().Select(p => ECPoint.DecodePoint(p.HexToBytes(), ECCurve.Secp256r1)).ToArray(); public static readonly Block GenesisBlock = new Block { PrevHash = UInt256.Zero, - Timestamp = (new DateTime(2016, 7, 15, 15, 8, 21, DateTimeKind.Utc)).ToTimestamp(), + Timestamp = (new DateTime(2016, 7, 15, 15, 8, 21, DateTimeKind.Utc)).ToTimestampMS(), Index = 0, NextConsensus = GetConsensusAddress(StandbyValidators), Witness = new Witness @@ -53,7 +53,7 @@ public class FillCompleted { } Transactions = new[] { DeployNativeContracts() } }; - private const int MemoryPoolMaxTransactions = 50_000; + private readonly static byte[] onPersistNativeContractScript; private const int MaxTxToReverifyPerIdle = 10; private static readonly object lockObj = new object(); private readonly NeoSystem system; @@ -61,15 +61,15 @@ public class FillCompleted { } private uint stored_header_count = 0; private readonly Dictionary block_cache = new Dictionary(); private readonly Dictionary> block_cache_unverified = new Dictionary>(); - internal readonly RelayCache RelayCache = new RelayCache(100); + internal readonly RelayCache ConsensusRelayCache = new RelayCache(100); private Snapshot currentSnapshot; public Store Store { get; } public MemoryPool MemPool { get; } public uint Height => currentSnapshot.Height; - public uint HeaderHeight => (uint)header_index.Count - 1; + public uint HeaderHeight => currentSnapshot.HeaderHeight; public UInt256 CurrentBlockHash => currentSnapshot.CurrentBlockHash; - public UInt256 CurrentHeaderHash => header_index[header_index.Count - 1]; + public UInt256 CurrentHeaderHash => currentSnapshot.CurrentHeaderHash; private static Blockchain singleton; public static Blockchain Singleton @@ -84,12 +84,21 @@ public static Blockchain Singleton static Blockchain() { GenesisBlock.RebuildMerkleRoot(); + + NativeContract[] contracts = { NativeContract.GAS, NativeContract.NEO }; + using (ScriptBuilder sb = new ScriptBuilder()) + { + foreach (NativeContract contract in contracts) + sb.EmitAppCall(contract.Hash, "onPersist"); + + onPersistNativeContractScript = sb.ToArray(); + } } public Blockchain(NeoSystem system, Store store) { this.system = system; - this.MemPool = new MemoryPool(system, MemoryPoolMaxTransactions); + this.MemPool = new MemoryPool(system, ProtocolSettings.Default.MemoryPoolMaxTransactions); this.Store = store; lock (lockObj) { @@ -154,6 +163,7 @@ private static Transaction DeployNativeContracts() Sender = (new[] { (byte)OpCode.PUSHT }).ToScriptHash(), SystemFee = 0, Attributes = new TransactionAttribute[0], + Cosigners = new Cosigner[0], Witnesses = new[] { new Witness @@ -229,7 +239,7 @@ private void OnFillMemoryPool(IEnumerable transactions) { if (Store.ContainsTransaction(tx.Hash)) continue; - if (!Plugin.CheckPolicy(tx)) + if (!NativeContract.Policy.CheckPolicy(tx, currentSnapshot)) continue; // First remove the tx if it is unverified in the pool. MemPool.TryRemoveUnVerified(tx.Hash, out _); @@ -283,7 +293,10 @@ private RelayResultReason OnNewBlock(Block block) block_cache_unverified.Remove(blockToPersist.Index); Persist(blockToPersist); - if (blocksPersisted++ < blocksToPersistList.Count - (2 + Math.Max(0, (15 - SecondsPerBlock)))) continue; + // 15000 is the default among of seconds per block, while MilliSecondsPerBlock is the current + uint extraBlocks = (15000 - MillisecondsPerBlock) / 1000; + + if (blocksPersisted++ < blocksToPersistList.Count - (2 + Math.Max(0, extraBlocks))) continue; // Empirically calibrated for relaying the most recent 2 blocks persisted with 15s network // Increase in the rate of 1 block per second in configurations with faster blocks @@ -325,7 +338,7 @@ private RelayResultReason OnNewConsensus(ConsensusPayload payload) { if (!payload.Verify(currentSnapshot)) return RelayResultReason.Invalid; system.Consensus?.Tell(payload); - RelayCache.Add(payload); + ConsensusRelayCache.Add(payload); system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = payload }); return RelayResultReason.Succeed; } @@ -351,7 +364,7 @@ private void OnNewHeaders(Header[] headers) system.TaskManager.Tell(new TaskManager.HeaderTaskCompleted(), Sender); } - private RelayResultReason OnNewTransaction(Transaction transaction) + private RelayResultReason OnNewTransaction(Transaction transaction, bool relay) { if (ContainsTransaction(transaction.Hash)) return RelayResultReason.AlreadyExists; @@ -359,13 +372,13 @@ private RelayResultReason OnNewTransaction(Transaction transaction) return RelayResultReason.OutOfMemory; if (!transaction.Verify(currentSnapshot, MemPool.GetVerifiedTransactions())) return RelayResultReason.Invalid; - if (!Plugin.CheckPolicy(transaction)) + if (!NativeContract.Policy.CheckPolicy(transaction, currentSnapshot)) return RelayResultReason.PolicyFail; if (!MemPool.TryAdd(transaction.Hash, transaction)) return RelayResultReason.OutOfMemory; - - system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = transaction }); + if (relay) + system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = transaction }); return RelayResultReason.Succeed; } @@ -392,8 +405,14 @@ protected override void OnReceive(object message) case Block block: Sender.Tell(OnNewBlock(block)); break; + case Transaction[] transactions: + { + // This message comes from a mempool's revalidation, already relayed + foreach (var tx in transactions) OnNewTransaction(tx, false); + break; + } case Transaction transaction: - Sender.Tell(OnNewTransaction(transaction)); + Sender.Tell(OnNewTransaction(transaction, true)); break; case ConsensusPayload payload: Sender.Tell(OnNewConsensus(payload)); @@ -413,15 +432,9 @@ private void Persist(Block block) snapshot.PersistingBlock = block; if (block.Index > 0) { - NativeContract[] contracts = { NativeContract.GAS, NativeContract.NEO }; using (ApplicationEngine engine = new ApplicationEngine(TriggerType.System, null, snapshot, 0, true)) { - using (ScriptBuilder sb = new ScriptBuilder()) - { - foreach (NativeContract contract in contracts) - sb.EmitAppCall(contract.Hash, "onPersist"); - engine.LoadScript(sb.ToArray()); - } + engine.LoadScript(onPersistNativeContractScript); if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); ApplicationExecuted application_executed = new ApplicationExecuted(engine); Context.System.EventStream.Publish(application_executed); @@ -452,13 +465,11 @@ private void Persist(Block block) all_application_executed.Add(application_executed); } } - snapshot.BlockHashIndex.GetAndChange().Hash = block.Hash; - snapshot.BlockHashIndex.GetAndChange().Index = block.Index; + snapshot.BlockHashIndex.GetAndChange().Set(block); if (block.Index == header_index.Count) { header_index.Add(block.Hash); - snapshot.HeaderHashIndex.GetAndChange().Hash = block.Hash; - snapshot.HeaderHashIndex.GetAndChange().Index = block.Index; + snapshot.HeaderHashIndex.GetAndChange().Set(block); } foreach (IPersistencePlugin plugin in Plugin.PersistencePlugins) plugin.OnPersist(snapshot, all_application_executed); diff --git a/neo/Ledger/ContractState.cs b/neo/Ledger/ContractState.cs index 8f97d4b363..f6c641d7d5 100644 --- a/neo/Ledger/ContractState.cs +++ b/neo/Ledger/ContractState.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Json; using Neo.SmartContract; using Neo.SmartContract.Manifest; @@ -64,5 +64,13 @@ public JObject ToJson() json["manifest"] = Manifest.ToJson(); return json; } + + public static ContractState FromJson(JObject json) + { + ContractState contractState = new ContractState(); + contractState.Script = json["script"].AsString().HexToBytes(); + contractState.Manifest = ContractManifest.FromJson(json["manifest"]); + return contractState; + } } } diff --git a/neo/Ledger/HashIndexState.cs b/neo/Ledger/HashIndexState.cs index f83aefd34f..fb7439a252 100644 --- a/neo/Ledger/HashIndexState.cs +++ b/neo/Ledger/HashIndexState.cs @@ -1,4 +1,5 @@ -using Neo.IO; +using Neo.IO; +using Neo.Network.P2P.Payloads; using System.IO; namespace Neo.Ledger @@ -36,5 +37,11 @@ void ISerializable.Serialize(BinaryWriter writer) writer.Write(Hash); writer.Write(Index); } + + internal void Set(BlockBase block) + { + Hash = block.Hash; + Index = block.Index; + } } } diff --git a/neo/Ledger/HeaderHashList.cs b/neo/Ledger/HeaderHashList.cs index c9e9878235..928a26e5d2 100644 --- a/neo/Ledger/HeaderHashList.cs +++ b/neo/Ledger/HeaderHashList.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System.IO; namespace Neo.Ledger diff --git a/neo/Ledger/MemoryPool.cs b/neo/Ledger/MemoryPool.cs index 96e17dc34f..a6af9fc721 100644 --- a/neo/Ledger/MemoryPool.cs +++ b/neo/Ledger/MemoryPool.cs @@ -21,10 +21,10 @@ public class MemoryPool : IReadOnlyCollection private const int BlocksTillRebroadcastHighPriorityPoolTx = 10; private int RebroadcastMultiplierThreshold => Capacity / 10; - private static readonly double MaxSecondsToReverifyTx = (double)Blockchain.SecondsPerBlock / 3; + private static readonly double MaxMillisecondsToReverifyTx = (double)Blockchain.MillisecondsPerBlock / 3; // These two are not expected to be hit, they are just safegaurds. - private static readonly double MaxSecondsToReverifyTxPerIdle = (double)Blockchain.SecondsPerBlock / 15; + private static readonly double MaxMillisecondsToReverifyTxPerIdle = (double)Blockchain.MillisecondsPerBlock / 15; private readonly NeoSystem _system; @@ -360,9 +360,14 @@ internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot) if (policyChanged) { + var tx = new List(); foreach (PoolItem item in _unverifiedSortedTransactions.Reverse()) - if(item.Tx.FeePerByte >= _feePerByte) - _system.Blockchain.Tell(item.Tx, ActorRefs.NoSender); + if (item.Tx.FeePerByte >= _feePerByte) + tx.Add(item.Tx); + + if (tx.Count > 0) + _system.Blockchain.Tell(tx.ToArray(), ActorRefs.NoSender); + _unverifiedTransactions.Clear(); _unverifiedSortedTransactions.Clear(); } @@ -378,7 +383,7 @@ internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot) return; ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, - _maxTxPerBlock, MaxSecondsToReverifyTx, snapshot); + _maxTxPerBlock, MaxMillisecondsToReverifyTx, snapshot); } internal void InvalidateAllTransactions() @@ -395,9 +400,9 @@ internal void InvalidateAllTransactions() } private int ReverifyTransactions(SortedSet verifiedSortedTxPool, - SortedSet unverifiedSortedTxPool, int count, double secondsTimeout, Snapshot snapshot) + SortedSet unverifiedSortedTxPool, int count, double millisecondsTimeout, Snapshot snapshot) { - DateTime reverifyCutOffTimeStamp = DateTime.UtcNow.AddSeconds(secondsTimeout); + DateTime reverifyCutOffTimeStamp = DateTime.UtcNow.AddMilliseconds(millisecondsTimeout); List reverifiedItems = new List(count); List invalidItems = new List(); @@ -421,8 +426,8 @@ internal void InvalidateAllTransactions() if (Count > RebroadcastMultiplierThreshold) blocksTillRebroadcast = blocksTillRebroadcast * Count / RebroadcastMultiplierThreshold; - var rebroadcastCutOffTime = DateTime.UtcNow.AddSeconds( - -Blockchain.SecondsPerBlock * blocksTillRebroadcast); + var rebroadcastCutOffTime = DateTime.UtcNow.AddMilliseconds( + -Blockchain.MillisecondsPerBlock * blocksTillRebroadcast); foreach (PoolItem item in reverifiedItems) { if (_unsortedTransactions.TryAdd(item.Tx.Hash, item)) @@ -477,7 +482,7 @@ internal bool ReVerifyTopUnverifiedTransactionsIfNeeded(int maxToVerify, Snapsho { int verifyCount = _sortedTransactions.Count > _maxTxPerBlock ? 1 : maxToVerify; ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions, - verifyCount, MaxSecondsToReverifyTxPerIdle, snapshot); + verifyCount, MaxMillisecondsToReverifyTxPerIdle, snapshot); } return _unverifiedTransactions.Count > 0; diff --git a/neo/Ledger/RelayResultReason.cs b/neo/Ledger/RelayResultReason.cs index e698d0ea34..7bf92afac6 100644 --- a/neo/Ledger/RelayResultReason.cs +++ b/neo/Ledger/RelayResultReason.cs @@ -1,4 +1,4 @@ -namespace Neo.Ledger +namespace Neo.Ledger { public enum RelayResultReason : byte { diff --git a/neo/Ledger/StorageFlags.cs b/neo/Ledger/StorageFlags.cs index a0d7034c1d..cace3c97ef 100644 --- a/neo/Ledger/StorageFlags.cs +++ b/neo/Ledger/StorageFlags.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.Ledger { diff --git a/neo/Ledger/StorageItem.cs b/neo/Ledger/StorageItem.cs index 37a69dba2d..49ea93d8a1 100644 --- a/neo/Ledger/StorageItem.cs +++ b/neo/Ledger/StorageItem.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System.IO; namespace Neo.Ledger diff --git a/neo/Ledger/StorageKey.cs b/neo/Ledger/StorageKey.cs index f975b7a422..c214260c21 100644 --- a/neo/Ledger/StorageKey.cs +++ b/neo/Ledger/StorageKey.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using System; using System.IO; @@ -13,6 +13,25 @@ public class StorageKey : IEquatable, ISerializable int ISerializable.Size => ScriptHash.Size + (Key.Length / 16 + 1) * 17; + internal static byte[] CreateSearchPrefix(UInt160 hash, byte[] prefix) + { + using (MemoryStream ms = new MemoryStream()) + { + int index = 0; + int remain = prefix.Length; + while (remain >= 16) + { + ms.Write(prefix, index, 16); + ms.WriteByte(16); + index += 16; + remain -= 16; + } + if (remain > 0) + ms.Write(prefix, index, remain); + return hash.ToArray().Concat(ms.ToArray()).ToArray(); + } + } + void ISerializable.Deserialize(BinaryReader reader) { ScriptHash = reader.ReadSerializable(); diff --git a/neo/Ledger/TransactionState.cs b/neo/Ledger/TransactionState.cs index c493bd631e..c8a479424a 100644 --- a/neo/Ledger/TransactionState.cs +++ b/neo/Ledger/TransactionState.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.VM; using System.IO; diff --git a/neo/Ledger/TrimmedBlock.cs b/neo/Ledger/TrimmedBlock.cs index 614ee89ac1..2a6f84672b 100644 --- a/neo/Ledger/TrimmedBlock.cs +++ b/neo/Ledger/TrimmedBlock.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Caching; using Neo.IO.Json; using Neo.Network.P2P.Payloads; diff --git a/neo/NeoSystem.cs b/neo/NeoSystem.cs index f53d6efa76..867d4e4201 100644 --- a/neo/NeoSystem.cs +++ b/neo/NeoSystem.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Neo.Consensus; using Neo.Ledger; using Neo.Network.P2P; diff --git a/neo/Network/P2P/Capabilities/FullNodeCapability.cs b/neo/Network/P2P/Capabilities/FullNodeCapability.cs index 7c2ae75845..ccaabbdb5a 100644 --- a/neo/Network/P2P/Capabilities/FullNodeCapability.cs +++ b/neo/Network/P2P/Capabilities/FullNodeCapability.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Neo.Network.P2P.Capabilities { diff --git a/neo/Network/P2P/Capabilities/NodeCapability.cs b/neo/Network/P2P/Capabilities/NodeCapability.cs index 1160dad912..034afb6be8 100644 --- a/neo/Network/P2P/Capabilities/NodeCapability.cs +++ b/neo/Network/P2P/Capabilities/NodeCapability.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.IO; diff --git a/neo/Network/P2P/Capabilities/NodeCapabilityType.cs b/neo/Network/P2P/Capabilities/NodeCapabilityType.cs index e8eb71850c..5ea0594d7b 100644 --- a/neo/Network/P2P/Capabilities/NodeCapabilityType.cs +++ b/neo/Network/P2P/Capabilities/NodeCapabilityType.cs @@ -1,4 +1,4 @@ -namespace Neo.Network.P2P.Capabilities +namespace Neo.Network.P2P.Capabilities { public enum NodeCapabilityType : byte { diff --git a/neo/Network/P2P/Capabilities/ServerCapability.cs b/neo/Network/P2P/Capabilities/ServerCapability.cs index a27573d438..0ce31e130e 100644 --- a/neo/Network/P2P/Capabilities/ServerCapability.cs +++ b/neo/Network/P2P/Capabilities/ServerCapability.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace Neo.Network.P2P.Capabilities diff --git a/neo/Network/P2P/ChannelsConfig.cs b/neo/Network/P2P/ChannelsConfig.cs index fe09c54996..2882876bd2 100644 --- a/neo/Network/P2P/ChannelsConfig.cs +++ b/neo/Network/P2P/ChannelsConfig.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; namespace Neo.Network.P2P { diff --git a/neo/Network/P2P/Connection.cs b/neo/Network/P2P/Connection.cs index d741f1d0ee..5a00a588ef 100644 --- a/neo/Network/P2P/Connection.cs +++ b/neo/Network/P2P/Connection.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.IO; using System; using System.Net; diff --git a/neo/Network/P2P/Helper.cs b/neo/Network/P2P/Helper.cs index 433725c45b..e3e7cece1b 100644 --- a/neo/Network/P2P/Helper.cs +++ b/neo/Network/P2P/Helper.cs @@ -1,4 +1,4 @@ -using K4os.Compression.LZ4; +using K4os.Compression.LZ4; using Neo.Network.P2P.Payloads; using System; using System.Buffers; diff --git a/neo/Network/P2P/LocalNode.cs b/neo/Network/P2P/LocalNode.cs index 6f30ed50cb..79ad7fb096 100644 --- a/neo/Network/P2P/LocalNode.cs +++ b/neo/Network/P2P/LocalNode.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/neo/Network/P2P/Message.cs b/neo/Network/P2P/Message.cs index 5be16b2be0..61ee4493b4 100644 --- a/neo/Network/P2P/Message.cs +++ b/neo/Network/P2P/Message.cs @@ -1,4 +1,4 @@ -using Akka.IO; +using Akka.IO; using Neo.Cryptography; using Neo.IO; using Neo.Network.P2P.Payloads; diff --git a/neo/Network/P2P/MessageCommand.cs b/neo/Network/P2P/MessageCommand.cs index ce0527e136..ed8dc6b96b 100644 --- a/neo/Network/P2P/MessageCommand.cs +++ b/neo/Network/P2P/MessageCommand.cs @@ -1,4 +1,4 @@ -namespace Neo.Network.P2P +namespace Neo.Network.P2P { public enum MessageCommand : byte { diff --git a/neo/Network/P2P/MessageFlags.cs b/neo/Network/P2P/MessageFlags.cs index 9af64e5fbc..4e8a34c1a7 100644 --- a/neo/Network/P2P/MessageFlags.cs +++ b/neo/Network/P2P/MessageFlags.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.Network.P2P { diff --git a/neo/Network/P2P/Payloads/AddrPayload.cs b/neo/Network/P2P/Payloads/AddrPayload.cs index 65337f0869..0a8f09c71e 100644 --- a/neo/Network/P2P/Payloads/AddrPayload.cs +++ b/neo/Network/P2P/Payloads/AddrPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.IO; diff --git a/neo/Network/P2P/Payloads/Block.cs b/neo/Network/P2P/Payloads/Block.cs index b63035e0ed..d339b536a0 100644 --- a/neo/Network/P2P/Payloads/Block.cs +++ b/neo/Network/P2P/Payloads/Block.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; using Neo.Ledger; @@ -12,7 +12,7 @@ namespace Neo.Network.P2P.Payloads public class Block : BlockBase, IInventory, IEquatable { public const int MaxContentsPerBlock = ushort.MaxValue; - public const int MaxTransactionsPerBlock = MaxContentsPerBlock; + public const int MaxTransactionsPerBlock = MaxContentsPerBlock - 1; public ConsensusData ConsensusData; public Transaction[] Transactions; @@ -41,13 +41,13 @@ public Header Header InventoryType IInventory.InventoryType => InventoryType.Block; public override int Size => base.Size - + IO.Helper.GetVarSize(Transactions.Length) //Count + + IO.Helper.GetVarSize(Transactions.Length + 1) //Count + ConsensusData.Size //ConsensusData + Transactions.Sum(p => p.Size); //Transactions public static UInt256 CalculateMerkleRoot(UInt256 consensusDataHash, params UInt256[] transactionHashes) { - List hashes = new List(transactionHashes.Length) { consensusDataHash }; + List hashes = new List(transactionHashes.Length + 1) { consensusDataHash }; hashes.AddRange(transactionHashes); return MerkleTree.ComputeRoot(hashes); } @@ -58,7 +58,7 @@ public override void Deserialize(BinaryReader reader) int count = (int)reader.ReadVarInt(MaxContentsPerBlock); if (count == 0) throw new FormatException(); ConsensusData = reader.ReadSerializable(); - Transactions = new Transaction[count]; + Transactions = new Transaction[count - 1]; for (int i = 0; i < Transactions.Length; i++) Transactions[i] = reader.ReadSerializable(); if (Transactions.Distinct().Count() != Transactions.Length) @@ -76,8 +76,7 @@ public bool Equals(Block other) public override bool Equals(object obj) { - if (!(obj is Block b)) return false; - return Equals(b); + return Equals(obj as Block); } public override int GetHashCode() @@ -93,7 +92,7 @@ public void RebuildMerkleRoot() public override void Serialize(BinaryWriter writer) { base.Serialize(writer); - writer.WriteVarInt(Transactions.Length); + writer.WriteVarInt(Transactions.Length + 1); writer.Write(ConsensusData); foreach (Transaction tx in Transactions) writer.Write(tx); @@ -107,6 +106,16 @@ public override JObject ToJson() return json; } + public new static Block FromJson(JObject json) + { + Block block = new Block(); + BlockBase blockBase = block; + blockBase.FromJson(json); + block.ConsensusData = ConsensusData.FromJson(json["consensus_data"]); + block.Transactions = ((JArray)json["tx"]).Select(p => Transaction.FromJson(p)).ToArray(); + return block; + } + public TrimmedBlock Trim() { return new TrimmedBlock diff --git a/neo/Network/P2P/Payloads/BlockBase.cs b/neo/Network/P2P/Payloads/BlockBase.cs index 9f56ca0d97..90b1531cd0 100644 --- a/neo/Network/P2P/Payloads/BlockBase.cs +++ b/neo/Network/P2P/Payloads/BlockBase.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; using Neo.Persistence; @@ -6,6 +6,7 @@ using Neo.Wallets; using System; using System.IO; +using System.Linq; namespace Neo.Network.P2P.Payloads { @@ -14,7 +15,7 @@ public abstract class BlockBase : IVerifiable public uint Version; public UInt256 PrevHash; public UInt256 MerkleRoot; - public uint Timestamp; + public ulong Timestamp; public uint Index; public UInt160 NextConsensus; public Witness Witness; @@ -32,7 +33,15 @@ public UInt256 Hash } } - public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + NextConsensus.Size + 1 + Witness.Size; + public virtual int Size => + sizeof(uint) + //Version + UInt256.Length + //PrevHash + UInt256.Length + //MerkleRoot + sizeof(ulong) + //Timestamp + sizeof(uint) + //Index + UInt160.Length + //NextConsensus + 1 + // + Witness.Size; //Witness Witness[] IVerifiable.Witnesses { @@ -59,7 +68,7 @@ void IVerifiable.DeserializeUnsigned(BinaryReader reader) Version = reader.ReadUInt32(); PrevHash = reader.ReadSerializable(); MerkleRoot = reader.ReadSerializable(); - Timestamp = reader.ReadUInt32(); + Timestamp = reader.ReadUInt64(); Index = reader.ReadUInt32(); NextConsensus = reader.ReadSerializable(); } @@ -103,6 +112,17 @@ public virtual JObject ToJson() return json; } + public void FromJson(JObject json) + { + Version = (uint)json["version"].AsNumber(); + PrevHash = UInt256.Parse(json["previousblockhash"].AsString()); + MerkleRoot = UInt256.Parse(json["merkleroot"].AsString()); + Timestamp = (ulong)json["time"].AsNumber(); + Index = (uint)json["index"].AsNumber(); + NextConsensus = json["nextconsensus"].AsString().ToScriptHash(); + Witness = ((JArray)json["witnesses"]).Select(p => Witness.FromJson(p)).FirstOrDefault(); + } + public virtual bool Verify(Snapshot snapshot) { Header prev_header = snapshot.GetHeader(PrevHash); diff --git a/neo/Network/P2P/Payloads/ConsensusData.cs b/neo/Network/P2P/Payloads/ConsensusData.cs index a48a8f1304..48d3d14ac8 100644 --- a/neo/Network/P2P/Payloads/ConsensusData.cs +++ b/neo/Network/P2P/Payloads/ConsensusData.cs @@ -1,7 +1,8 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; using Neo.Ledger; +using System.Globalization; using System.IO; namespace Neo.Network.P2P.Payloads @@ -45,5 +46,14 @@ public JObject ToJson() json["nonce"] = Nonce.ToString("x16"); return json; } + + public static ConsensusData FromJson(JObject json) + { + ConsensusData block = new ConsensusData(); + block.PrimaryIndex = (uint)json["primary"].AsNumber(); + block.Nonce = ulong.Parse(json["nonce"].AsString(), NumberStyles.HexNumber); + return block; + } + } } diff --git a/neo/Network/P2P/Payloads/ConsensusPayload.cs b/neo/Network/P2P/Payloads/ConsensusPayload.cs index a4c6e86ee6..062e5d9830 100644 --- a/neo/Network/P2P/Payloads/ConsensusPayload.cs +++ b/neo/Network/P2P/Payloads/ConsensusPayload.cs @@ -1,4 +1,4 @@ -using Neo.Consensus; +using Neo.Consensus; using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; diff --git a/neo/Network/P2P/Payloads/Cosigner.cs b/neo/Network/P2P/Payloads/Cosigner.cs new file mode 100644 index 0000000000..99672ecf4d --- /dev/null +++ b/neo/Network/P2P/Payloads/Cosigner.cs @@ -0,0 +1,76 @@ +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.IO.Json; +using System; +using System.IO; +using System.Linq; + +namespace Neo.Network.P2P.Payloads +{ + public class Cosigner : ISerializable + { + public UInt160 Account; + public WitnessScope Scopes; + public UInt160[] AllowedContracts; + public ECPoint[] AllowedGroups; + + public Cosigner() + { + this.Scopes = WitnessScope.Global; + } + + // This limits maximum number of AllowedContracts or AllowedGroups here + private int MaxSubitems = 16; + + public int Size => + /*Account*/ UInt160.Length + + /*Scopes*/ sizeof(WitnessScope) + + /*AllowedContracts*/ (Scopes.HasFlag(WitnessScope.CustomContracts) ? AllowedContracts.GetVarSize() : 0) + + /*AllowedGroups*/ (Scopes.HasFlag(WitnessScope.CustomGroups) ? AllowedGroups.GetVarSize() : 0); + + void ISerializable.Deserialize(BinaryReader reader) + { + Account = reader.ReadSerializable(); + Scopes = (WitnessScope)reader.ReadByte(); + AllowedContracts = Scopes.HasFlag(WitnessScope.CustomContracts) + ? reader.ReadSerializableArray(MaxSubitems) + : new UInt160[0]; + AllowedGroups = Scopes.HasFlag(WitnessScope.CustomGroups) + ? reader.ReadSerializableArray(MaxSubitems) + : new ECPoint[0]; + } + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(Account); + writer.Write((byte)Scopes); + if (Scopes.HasFlag(WitnessScope.CustomContracts)) + writer.Write(AllowedContracts); + if (Scopes.HasFlag(WitnessScope.CustomGroups)) + writer.Write(AllowedGroups); + } + + public JObject ToJson() + { + JObject json = new JObject(); + json["account"] = Account.ToString(); + json["scopes"] = Scopes; + if (Scopes.HasFlag(WitnessScope.CustomContracts)) + json["allowedContracts"] = AllowedContracts.Select(p => (JObject)p.ToString()).ToArray(); + if (Scopes.HasFlag(WitnessScope.CustomGroups)) + json["allowedGroups"] = AllowedGroups.Select(p => (JObject)p.ToString()).ToArray(); + return json; + } + + public static Cosigner FromJson(JObject json) + { + return new Cosigner + { + Account = UInt160.Parse(json["account"].AsString()), + Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), json["scopes"].AsString()), + AllowedContracts = ((JArray)json["allowedContracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray(), + AllowedGroups = ((JArray)json["allowedGroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() + }; + } + } +} diff --git a/neo/Network/P2P/Payloads/FilterAddPayload.cs b/neo/Network/P2P/Payloads/FilterAddPayload.cs index 71bddea5dc..3a7a4a9e6f 100644 --- a/neo/Network/P2P/Payloads/FilterAddPayload.cs +++ b/neo/Network/P2P/Payloads/FilterAddPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System.IO; namespace Neo.Network.P2P.Payloads diff --git a/neo/Network/P2P/Payloads/FilterLoadPayload.cs b/neo/Network/P2P/Payloads/FilterLoadPayload.cs index 5d337c8dac..dfba728fc0 100644 --- a/neo/Network/P2P/Payloads/FilterLoadPayload.cs +++ b/neo/Network/P2P/Payloads/FilterLoadPayload.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using System; using System.IO; diff --git a/neo/Network/P2P/Payloads/GetBlocksPayload.cs b/neo/Network/P2P/Payloads/GetBlocksPayload.cs index f7e39927f1..eccd66ed19 100644 --- a/neo/Network/P2P/Payloads/GetBlocksPayload.cs +++ b/neo/Network/P2P/Payloads/GetBlocksPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.IO; diff --git a/neo/Network/P2P/Payloads/Header.cs b/neo/Network/P2P/Payloads/Header.cs index 149f4a020f..eaef0dc160 100644 --- a/neo/Network/P2P/Payloads/Header.cs +++ b/neo/Network/P2P/Payloads/Header.cs @@ -1,6 +1,9 @@ -using Neo.Ledger; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Wallets; using System; using System.IO; +using System.Linq; namespace Neo.Network.P2P.Payloads { @@ -51,5 +54,14 @@ public TrimmedBlock Trim() Hashes = new UInt256[0] }; } + + public new static Header FromJson(JObject json) + { + Header header = new Header(); + BlockBase blockBase = header; + blockBase.FromJson(json); + return header; + } + } } diff --git a/neo/Network/P2P/Payloads/HeadersPayload.cs b/neo/Network/P2P/Payloads/HeadersPayload.cs index 54948a142d..3b13405d24 100644 --- a/neo/Network/P2P/Payloads/HeadersPayload.cs +++ b/neo/Network/P2P/Payloads/HeadersPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/neo/Network/P2P/Payloads/IInventory.cs b/neo/Network/P2P/Payloads/IInventory.cs index dcf3114a8b..26b62346d9 100644 --- a/neo/Network/P2P/Payloads/IInventory.cs +++ b/neo/Network/P2P/Payloads/IInventory.cs @@ -1,4 +1,4 @@ -using Neo.Persistence; +using Neo.Persistence; namespace Neo.Network.P2P.Payloads { diff --git a/neo/Network/P2P/Payloads/IVerifiable.cs b/neo/Network/P2P/Payloads/IVerifiable.cs index 50651ad9ff..8540d8a626 100644 --- a/neo/Network/P2P/Payloads/IVerifiable.cs +++ b/neo/Network/P2P/Payloads/IVerifiable.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Persistence; using System.IO; diff --git a/neo/Network/P2P/Payloads/InvPayload.cs b/neo/Network/P2P/Payloads/InvPayload.cs index a3bc82a4d9..613d280234 100644 --- a/neo/Network/P2P/Payloads/InvPayload.cs +++ b/neo/Network/P2P/Payloads/InvPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.Collections.Generic; using System.IO; diff --git a/neo/Network/P2P/Payloads/InventoryType.cs b/neo/Network/P2P/Payloads/InventoryType.cs index 58ad12471b..775eed958f 100644 --- a/neo/Network/P2P/Payloads/InventoryType.cs +++ b/neo/Network/P2P/Payloads/InventoryType.cs @@ -1,4 +1,4 @@ -namespace Neo.Network.P2P.Payloads +namespace Neo.Network.P2P.Payloads { public enum InventoryType : byte { diff --git a/neo/Network/P2P/Payloads/MerkleBlockPayload.cs b/neo/Network/P2P/Payloads/MerkleBlockPayload.cs index 11b9fb3a6e..0d00434cd4 100644 --- a/neo/Network/P2P/Payloads/MerkleBlockPayload.cs +++ b/neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using System.Collections; using System.IO; diff --git a/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs b/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs index 01508524a5..6fcda2f809 100644 --- a/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs +++ b/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Capabilities; using System; using System.IO; diff --git a/neo/Network/P2P/Payloads/PingPayload.cs b/neo/Network/P2P/Payloads/PingPayload.cs index 107dd501d5..680de9dd0a 100644 --- a/neo/Network/P2P/Payloads/PingPayload.cs +++ b/neo/Network/P2P/Payloads/PingPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.IO; diff --git a/neo/Network/P2P/Payloads/Transaction.cs b/neo/Network/P2P/Payloads/Transaction.cs index 923fe6447f..a2586e6da2 100644 --- a/neo/Network/P2P/Payloads/Transaction.cs +++ b/neo/Network/P2P/Payloads/Transaction.cs @@ -21,6 +21,10 @@ public class Transaction : IEquatable, IInventory /// Maximum number of attributes that can be contained within a transaction /// private const int MaxTransactionAttributes = 16; + /// + /// Maximum number of cosigners that can be contained within a transaction + /// + private const int MaxCosigners = 16; public byte Version; public uint Nonce; @@ -35,6 +39,7 @@ public class Transaction : IEquatable, IInventory public long NetworkFee; public uint ValidUntilBlock; public TransactionAttribute[] Attributes; + public Cosigner[] Cosigners { get; set; } public byte[] Script; public Witness[] Witnesses { get; set; } @@ -69,6 +74,7 @@ public UInt256 Hash public int Size => HeaderSize + Attributes.GetVarSize() + //Attributes + Cosigners.GetVarSize() + //Cosigners Script.GetVarSize() + //Script Witnesses.GetVarSize(); //Witnesses @@ -92,8 +98,8 @@ public void DeserializeUnsigned(BinaryReader reader) if (SystemFee + NetworkFee < SystemFee) throw new FormatException(); ValidUntilBlock = reader.ReadUInt32(); Attributes = reader.ReadSerializableArray(MaxTransactionAttributes); - var cosigners = Attributes.Where(p => p.Usage == TransactionAttributeUsage.Cosigner).Select(p => new UInt160(p.Data)).ToArray(); - if (cosigners.Distinct().Count() != cosigners.Length) throw new FormatException(); + Cosigners = reader.ReadSerializableArray(MaxCosigners); + if (Cosigners.Select(u => u.Account).Distinct().Count() != Cosigners.Length) throw new FormatException(); Script = reader.ReadVarBytes(ushort.MaxValue); if (Script.Length == 0) throw new FormatException(); } @@ -118,7 +124,7 @@ public override int GetHashCode() public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { var hashes = new HashSet { Sender }; - hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Cosigner).Select(p => new UInt160(p.Data))); + hashes.UnionWith(Cosigners.Select(p => p.Account)); return hashes.OrderBy(p => p).ToArray(); } @@ -157,6 +163,7 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer) writer.Write(NetworkFee); writer.Write(ValidUntilBlock); writer.Write(Attributes); + writer.Write(Cosigners); writer.WriteVarBytes(Script); } @@ -168,15 +175,32 @@ public JObject ToJson() json["version"] = Version; json["nonce"] = Nonce; json["sender"] = Sender.ToAddress(); - json["sys_fee"] = new BigDecimal(SystemFee, NativeContract.GAS.Decimals).ToString(); - json["net_fee"] = new BigDecimal(NetworkFee, NativeContract.GAS.Decimals).ToString(); + json["sys_fee"] = SystemFee.ToString(); + json["net_fee"] = NetworkFee.ToString(); json["valid_until_block"] = ValidUntilBlock; json["attributes"] = Attributes.Select(p => p.ToJson()).ToArray(); + json["cosigners"] = Cosigners.Select(p => p.ToJson()).ToArray(); json["script"] = Script.ToHexString(); json["witnesses"] = Witnesses.Select(p => p.ToJson()).ToArray(); return json; } + public static Transaction FromJson(JObject json) + { + Transaction tx = new Transaction(); + tx.Version = byte.Parse(json["version"].AsString()); + tx.Nonce = uint.Parse(json["nonce"].AsString()); + tx.Sender = json["sender"].AsString().ToScriptHash(); + tx.SystemFee = long.Parse(json["sys_fee"].AsString()); + tx.NetworkFee = long.Parse(json["net_fee"].AsString()); + tx.ValidUntilBlock = uint.Parse(json["valid_until_block"].AsString()); + tx.Attributes = ((JArray)json["attributes"]).Select(p => TransactionAttribute.FromJson(p)).ToArray(); + tx.Cosigners = ((JArray)json["cosigners"]).Select(p => Cosigner.FromJson(p)).ToArray(); + tx.Script = json["script"].AsString().HexToBytes(); + tx.Witnesses = ((JArray)json["witnesses"]).Select(p => Witness.FromJson(p)).ToArray(); + return tx; + } + bool IInventory.Verify(Snapshot snapshot) { return Verify(snapshot, Enumerable.Empty()); diff --git a/neo/Network/P2P/Payloads/TransactionAttribute.cs b/neo/Network/P2P/Payloads/TransactionAttribute.cs index 2f384fa046..5adc34db8e 100644 --- a/neo/Network/P2P/Payloads/TransactionAttribute.cs +++ b/neo/Network/P2P/Payloads/TransactionAttribute.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Json; using System; using System.IO; @@ -33,5 +33,13 @@ public JObject ToJson() json["data"] = Data.ToHexString(); return json; } + + public static TransactionAttribute FromJson(JObject json) + { + TransactionAttribute transactionAttribute = new TransactionAttribute(); + transactionAttribute.Usage = (TransactionAttributeUsage)(byte.Parse(json["usage"].AsString())); + transactionAttribute.Data = json["data"].AsString().HexToBytes(); + return transactionAttribute; + } } } diff --git a/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs index d878348c28..9bf5cc204c 100644 --- a/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs +++ b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs @@ -1,8 +1,7 @@ -namespace Neo.Network.P2P.Payloads +namespace Neo.Network.P2P.Payloads { public enum TransactionAttributeUsage : byte { - Cosigner = 0x20, Url = 0x81 } } diff --git a/neo/Network/P2P/Payloads/VersionPayload.cs b/neo/Network/P2P/Payloads/VersionPayload.cs index 754000e062..16c401b7db 100644 --- a/neo/Network/P2P/Payloads/VersionPayload.cs +++ b/neo/Network/P2P/Payloads/VersionPayload.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.Network.P2P.Capabilities; using System; using System.IO; diff --git a/neo/Network/P2P/Payloads/Witness.cs b/neo/Network/P2P/Payloads/Witness.cs index 237a954642..227d810a39 100644 --- a/neo/Network/P2P/Payloads/Witness.cs +++ b/neo/Network/P2P/Payloads/Witness.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Json; using Neo.SmartContract; using Neo.VM; @@ -28,8 +28,11 @@ public virtual UInt160 ScriptHash void ISerializable.Deserialize(BinaryReader reader) { - InvocationScript = reader.ReadVarBytes(65536); - VerificationScript = reader.ReadVarBytes(65536); + // This is designed to allow a MultiSig 10/10 (around 1003 bytes) ~1024 bytes + // Invocation = 10 * 64 + 10 = 650 ~ 664 (exact is 653) + InvocationScript = reader.ReadVarBytes(664); + // Verification = 10 * 33 + 10 = 340 ~ 360 (exact is 350) + VerificationScript = reader.ReadVarBytes(360); } void ISerializable.Serialize(BinaryWriter writer) @@ -45,5 +48,13 @@ public JObject ToJson() json["verification"] = VerificationScript.ToHexString(); return json; } + + public static Witness FromJson(JObject json) + { + Witness witness = new Witness(); + witness.InvocationScript = json["invocation"].AsString().HexToBytes(); + witness.VerificationScript = json["verification"].AsString().HexToBytes(); + return witness; + } } } diff --git a/neo/Network/P2P/Payloads/WitnessScope.cs b/neo/Network/P2P/Payloads/WitnessScope.cs new file mode 100644 index 0000000000..f35e550a34 --- /dev/null +++ b/neo/Network/P2P/Payloads/WitnessScope.cs @@ -0,0 +1,31 @@ +using System; + +namespace Neo.Network.P2P.Payloads +{ + [Flags] + public enum WitnessScope : byte + { + /// + /// Global allows this witness in all contexts (default Neo2 behavior) + /// This cannot be combined with other flags + /// + Global = 0x00, + + /// + /// CalledByEntry means that this condition must hold: EntryScriptHash == CallingScriptHash + /// No params is needed, as the witness/permission/signature given on first invocation will automatically expire if entering deeper internal invokes + /// This can be default safe choice for native NEO/GAS (previously used on Neo 2 as "attach" mode) + /// + CalledByEntry = 0x01, + + /// + /// Custom hash for contract-specific + /// + CustomContracts = 0x10, + + /// + /// Custom pubkey for group members + /// + CustomGroups = 0x20 + } +} diff --git a/neo/Network/P2P/Peer.cs b/neo/Network/P2P/Peer.cs index eb21095685..0cbd6cbaff 100644 --- a/neo/Network/P2P/Peer.cs +++ b/neo/Network/P2P/Peer.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/neo/Network/P2P/ProtocolHandler.cs b/neo/Network/P2P/ProtocolHandler.cs index 7f9821709b..aa7f0e06f2 100644 --- a/neo/Network/P2P/ProtocolHandler.cs +++ b/neo/Network/P2P/ProtocolHandler.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Configuration; using Neo.Cryptography; using Neo.IO; @@ -180,23 +180,20 @@ private void OnGetDataMessageReceived(InvPayload payload) UInt256[] hashes = payload.Hashes.Where(p => sentHashes.Add(p)).ToArray(); foreach (UInt256 hash in hashes) { - Blockchain.Singleton.RelayCache.TryGet(hash, out IInventory inventory); switch (payload.Type) { case InventoryType.TX: - if (inventory == null) - inventory = Blockchain.Singleton.GetTransaction(hash); - if (inventory is Transaction) - Context.Parent.Tell(Message.Create(MessageCommand.Transaction, inventory)); + Transaction tx = Blockchain.Singleton.GetTransaction(hash); + if (tx != null) + Context.Parent.Tell(Message.Create(MessageCommand.Transaction, tx)); break; case InventoryType.Block: - if (inventory == null) - inventory = Blockchain.Singleton.GetBlock(hash); - if (inventory is Block block) + Block block = Blockchain.Singleton.GetBlock(hash); + if (block != null) { if (bloom_filter == null) { - Context.Parent.Tell(Message.Create(MessageCommand.Block, inventory)); + Context.Parent.Tell(Message.Create(MessageCommand.Block, block)); } else { @@ -206,8 +203,8 @@ private void OnGetDataMessageReceived(InvPayload payload) } break; case InventoryType.Consensus: - if (inventory != null) - Context.Parent.Tell(Message.Create(MessageCommand.Consensus, inventory)); + if (Blockchain.Singleton.ConsensusRelayCache.TryGet(hash, out IInventory inventoryConsensus)) + Context.Parent.Tell(Message.Create(MessageCommand.Consensus, inventoryConsensus)); break; } } @@ -248,7 +245,7 @@ private void OnInventoryReceived(IInventory inventory) private void OnInvMessageReceived(InvPayload payload) { - UInt256[] hashes = payload.Hashes.Where(p => knownHashes.Add(p)).ToArray(); + UInt256[] hashes = payload.Hashes.Where(p => knownHashes.Add(p) && !sentHashes.Contains(p)).ToArray(); if (hashes.Length == 0) return; switch (payload.Type) { diff --git a/neo/Network/P2P/RemoteNode.cs b/neo/Network/P2P/RemoteNode.cs index 7700c896b2..ec2a16e047 100644 --- a/neo/Network/P2P/RemoteNode.cs +++ b/neo/Network/P2P/RemoteNode.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Configuration; using Akka.IO; using Neo.Cryptography; diff --git a/neo/Network/P2P/TaskManager.cs b/neo/Network/P2P/TaskManager.cs index cd96547df8..2c0f7ae07b 100644 --- a/neo/Network/P2P/TaskManager.cs +++ b/neo/Network/P2P/TaskManager.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Akka.Configuration; using Neo.IO.Actors; using Neo.IO.Caching; diff --git a/neo/Network/P2P/TaskSession.cs b/neo/Network/P2P/TaskSession.cs index 5617e0efa1..a1cf8a0ffc 100644 --- a/neo/Network/P2P/TaskSession.cs +++ b/neo/Network/P2P/TaskSession.cs @@ -1,4 +1,4 @@ -using Akka.Actor; +using Akka.Actor; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; using System; diff --git a/neo/Network/RPC/ContractClient.cs b/neo/Network/RPC/ContractClient.cs new file mode 100644 index 0000000000..a329e07674 --- /dev/null +++ b/neo/Network/RPC/ContractClient.cs @@ -0,0 +1,65 @@ +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.VM; +using Neo.Wallets; + +namespace Neo.Network.RPC +{ + /// + /// Contract related operations through RPC API + /// + public class ContractClient + { + protected readonly RpcClient rpcClient; + + /// + /// ContractClient Constructor + /// + /// the RPC client to call NEO RPC methods + public ContractClient(RpcClient rpc) + { + rpcClient = rpc; + } + + /// + /// Use RPC method to test invoke operation. + /// + /// contract script hash + /// contract operation + /// operation arguments + /// + public RpcInvokeResult TestInvoke(UInt160 scriptHash, string operation, params object[] args) + { + byte[] script = scriptHash.MakeScript(operation, args); + return rpcClient.InvokeScript(script); + } + + /// + /// Deploy Contract, return signed transaction + /// + /// contract script + /// contract manifest + /// sender KeyPair + /// transaction NetworkFee, set to be 0 if you don't need higher priority + /// + public Transaction DeployContract(byte[] contractScript, ContractManifest manifest, KeyPair key, long networkFee = 0) + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitSysCall(InteropService.Neo_Contract_Create, contractScript, manifest.ToString()); + script = sb.ToArray(); + } + + Transaction tx = new TransactionManager(rpcClient, Contract.CreateSignatureRedeemScript(key.PublicKey).ToScriptHash()) + .MakeTransaction(script, null, null, networkFee) + .AddSignature(key) + .Sign() + .Tx; + + return tx; + } + } +} diff --git a/neo/Network/RPC/Models/RpcBlock.cs b/neo/Network/RPC/Models/RpcBlock.cs new file mode 100644 index 0000000000..f71af51168 --- /dev/null +++ b/neo/Network/RPC/Models/RpcBlock.cs @@ -0,0 +1,37 @@ +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; + +namespace Neo.Network.RPC.Models +{ + public class RpcBlock + { + public Block Block { get; set; } + + public int? Confirmations { get; set; } + + public UInt256 NextBlockHash { get; set; } + + public JObject ToJson() + { + JObject json = Block.ToJson(); + if (Confirmations != null) + { + json["confirmations"] = Confirmations; + json["nextblockhash"] = NextBlockHash.ToString(); + } + return json; + } + + public static RpcBlock FromJson(JObject json) + { + RpcBlock block = new RpcBlock(); + block.Block = Block.FromJson(json); + if (json["confirmations"] != null) + { + block.Confirmations = (int)json["confirmations"].AsNumber(); + block.NextBlockHash = UInt256.Parse(json["nextblockhash"].AsString()); + } + return block; + } + } +} diff --git a/neo/Network/RPC/Models/RpcBlockHeader.cs b/neo/Network/RPC/Models/RpcBlockHeader.cs new file mode 100644 index 0000000000..2b9293ecaf --- /dev/null +++ b/neo/Network/RPC/Models/RpcBlockHeader.cs @@ -0,0 +1,37 @@ +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; + +namespace Neo.Network.RPC.Models +{ + public class RpcBlockHeader + { + public Header Header { get; set; } + + public int? Confirmations { get; set; } + + public UInt256 NextBlockHash { get; set; } + + public JObject ToJson() + { + JObject json = Header.ToJson(); + if (Confirmations != null) + { + json["confirmations"] = Confirmations; + json["nextblockhash"] = NextBlockHash.ToString(); + } + return json; + } + + public static RpcBlockHeader FromJson(JObject json) + { + RpcBlockHeader block = new RpcBlockHeader(); + block.Header = Header.FromJson(json); + if (json["confirmations"] != null) + { + block.Confirmations = (int)json["confirmations"].AsNumber(); + block.NextBlockHash = UInt256.Parse(json["nextblockhash"].AsString()); + } + return block; + } + } +} diff --git a/neo/Network/RPC/Models/RpcInvokeResult.cs b/neo/Network/RPC/Models/RpcInvokeResult.cs new file mode 100644 index 0000000000..c56307950a --- /dev/null +++ b/neo/Network/RPC/Models/RpcInvokeResult.cs @@ -0,0 +1,64 @@ +using Neo.IO.Json; +using Neo.SmartContract; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcInvokeResult + { + public string Script { get; set; } + + public string State { get; set; } + + public string GasConsumed { get; set; } + + public ContractParameter[] Stack { get; set; } + + public string Tx { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["script"] = Script; + json["state"] = State; + json["gas_consumed"] = GasConsumed; + json["stack"] = new JArray(Stack.Select(p => p.ToJson())); + json["tx"] = Tx; + return json; + } + + public static RpcInvokeResult FromJson(JObject json) + { + RpcInvokeResult invokeScriptResult = new RpcInvokeResult(); + invokeScriptResult.Script = json["script"].AsString(); + invokeScriptResult.State = json["state"].AsString(); + invokeScriptResult.GasConsumed = json["gas_consumed"].AsString(); + invokeScriptResult.Tx = json["tx"].AsString(); + invokeScriptResult.Stack = ((JArray)json["stack"]).Select(p => ContractParameter.FromJson(p)).ToArray(); + return invokeScriptResult; + } + } + + public class RpcStack + { + public string Type { get; set; } + + public string Value { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["type"] = Type; + json["value"] = Value; + return json; + } + + public static RpcStack FromJson(JObject json) + { + RpcStack stackJson = new RpcStack(); + stackJson.Type = json["type"].AsString(); + stackJson.Value = json["value"].AsString(); + return stackJson; + } + } +} diff --git a/neo/Network/RPC/Models/RpcNep5Balances.cs b/neo/Network/RPC/Models/RpcNep5Balances.cs new file mode 100644 index 0000000000..b471ab4545 --- /dev/null +++ b/neo/Network/RPC/Models/RpcNep5Balances.cs @@ -0,0 +1,57 @@ +using Neo.IO.Json; +using System.Linq; +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcNep5Balances + { + public string Address { get; set; } + + public RpcNep5Balance[] Balances { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["address"] = Address; + json["balance"] = Balances.Select(p => p.ToJson()).ToArray(); + return json; + } + + public static RpcNep5Balances FromJson(JObject json) + { + RpcNep5Balances nep5Balance = new RpcNep5Balances(); + nep5Balance.Address = json["address"].AsString(); + //List listBalance = new List(); + nep5Balance.Balances = ((JArray)json["balance"]).Select(p => RpcNep5Balance.FromJson(p)).ToArray(); + return nep5Balance; + } + } + + public class RpcNep5Balance + { + public UInt160 AssetHash { get; set; } + + public BigInteger Amount { get; set; } + + public uint LastUpdatedBlock { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["asset_hash"] = AssetHash.ToArray().ToHexString(); + json["amount"] = Amount.ToString(); + json["last_updated_block"] = LastUpdatedBlock.ToString(); + return json; + } + + public static RpcNep5Balance FromJson(JObject json) + { + RpcNep5Balance balance = new RpcNep5Balance(); + balance.AssetHash = UInt160.Parse(json["asset_hash"].AsString()); + balance.Amount = BigInteger.Parse(json["amount"].AsString()); + balance.LastUpdatedBlock = uint.Parse(json["last_updated_block"].AsString()); + return balance; + } + } +} diff --git a/neo/Network/RPC/Models/RpcPeers.cs b/neo/Network/RPC/Models/RpcPeers.cs new file mode 100644 index 0000000000..fac73842de --- /dev/null +++ b/neo/Network/RPC/Models/RpcPeers.cs @@ -0,0 +1,55 @@ +using Neo.IO.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcPeers + { + public RpcPeer[] Unconnected { get; set; } + + public RpcPeer[] Bad { get; set; } + + public RpcPeer[] Connected { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["unconnected"] = new JArray(Unconnected.Select(p => p.ToJson())); + json["bad"] = new JArray(Bad.Select(p => p.ToJson())); + json["connected"] = new JArray(Connected.Select(p => p.ToJson())); + return json; + } + + public static RpcPeers FromJson(JObject json) + { + RpcPeers result = new RpcPeers(); + result.Unconnected = ((JArray)json["unconnected"]).Select(p => RpcPeer.FromJson(p)).ToArray(); + result.Bad = ((JArray)json["bad"]).Select(p => RpcPeer.FromJson(p)).ToArray(); + result.Connected = ((JArray)json["connected"]).Select(p => RpcPeer.FromJson(p)).ToArray(); + return result; + } + } + + public class RpcPeer + { + public string Address { get; set; } + + public int Port { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["address"] = Address; + json["port"] = Port; + return json; + } + + public static RpcPeer FromJson(JObject json) + { + RpcPeer peer = new RpcPeer(); + peer.Address = json["address"].AsString(); + peer.Port = int.Parse(json["port"].AsString()); + return peer; + } + } +} diff --git a/neo/Network/RPC/Models/RpcPlugin.cs b/neo/Network/RPC/Models/RpcPlugin.cs new file mode 100644 index 0000000000..db03f70eb3 --- /dev/null +++ b/neo/Network/RPC/Models/RpcPlugin.cs @@ -0,0 +1,32 @@ +using Neo.IO.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcPlugin + { + public string Name { get; set; } + + public string Version { get; set; } + + public string[] Interfaces { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["name"] = Name; + json["version"] = Version; + json["interfaces"] = new JArray(Interfaces.Select(p => (JObject)p)); + return json; + } + + public static RpcPlugin FromJson(JObject json) + { + RpcPlugin plugin = new RpcPlugin(); + plugin.Name = json["name"].AsString(); + plugin.Version = json["version"].AsString(); + plugin.Interfaces = ((JArray)json["interfaces"]).Select(p => p.AsString()).ToArray(); + return plugin; + } + } +} diff --git a/neo/Network/RPC/Models/RpcRawMemPool.cs b/neo/Network/RPC/Models/RpcRawMemPool.cs new file mode 100644 index 0000000000..c5ebd63419 --- /dev/null +++ b/neo/Network/RPC/Models/RpcRawMemPool.cs @@ -0,0 +1,32 @@ +using Neo.IO.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcRawMemPool + { + public uint Height { get; set; } + + public string[] Verified { get; set; } + + public string[] UnVerified { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["height"] = Height; + json["verified"] = new JArray(Verified.Select(p => (JObject)p)); + json["unverified"] = new JArray(UnVerified.Select(p => (JObject)p)); + return json; + } + + public static RpcRawMemPool FromJson(JObject json) + { + RpcRawMemPool rawMemPool = new RpcRawMemPool(); + rawMemPool.Height = uint.Parse(json["height"].AsString()); + rawMemPool.Verified = ((JArray)json["verified"]).Select(p => p.AsString()).ToArray(); + rawMemPool.UnVerified = ((JArray)json["unverified"]).Select(p => p.AsString()).ToArray(); + return rawMemPool; + } + } +} diff --git a/neo/Network/RPC/Models/RpcRequest.cs b/neo/Network/RPC/Models/RpcRequest.cs new file mode 100644 index 0000000000..1970adedbf --- /dev/null +++ b/neo/Network/RPC/Models/RpcRequest.cs @@ -0,0 +1,37 @@ +using Neo.IO.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcRequest + { + public int Id { get; set; } + + public string Jsonrpc { get; set; } + + public string Method { get; set; } + + public JObject[] Params { get; set; } + + public static RpcRequest FromJson(JObject json) + { + return new RpcRequest + { + Id = (int)json["id"].AsNumber(), + Jsonrpc = json["jsonrpc"].AsString(), + Method = json["method"].AsString(), + Params = ((JArray)json["params"]).ToArray() + }; + } + + public JObject ToJson() + { + var json = new JObject(); + json["id"] = Id; + json["jsonrpc"] = Jsonrpc; + json["method"] = Method; + json["params"] = new JArray(Params); + return json; + } + } +} diff --git a/neo/Network/RPC/Models/RpcResponse.cs b/neo/Network/RPC/Models/RpcResponse.cs new file mode 100644 index 0000000000..e4ebcaed1b --- /dev/null +++ b/neo/Network/RPC/Models/RpcResponse.cs @@ -0,0 +1,72 @@ +using Neo.IO.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcResponse + { + public int? Id { get; set; } + + public string Jsonrpc { get; set; } + + public RpcResponseError Error { get; set; } + + public JObject Result { get; set; } + + public string RawResponse { get; set; } + + public static RpcResponse FromJson(JObject json) + { + var response = new RpcResponse + { + Id = (int?)json["id"]?.AsNumber(), + Jsonrpc = json["jsonrpc"].AsString(), + Result = json["result"] + }; + + if (json["error"] != null) + { + response.Error = RpcResponseError.FromJson(json["error"]); + } + + return response; + } + + public JObject ToJson() + { + var json = new JObject(); + json["id"] = Id; + json["jsonrpc"] = Jsonrpc; + json["error"] = Error.ToJson(); + json["result"] = Result; + return json; + } + } + + public class RpcResponseError + { + public int Code { get; set; } + + public string Message { get; set; } + + public JObject Data { get; set; } + + public static RpcResponseError FromJson(JObject json) + { + return new RpcResponseError + { + Code = (int)json["code"].AsNumber(), + Message = json["message"].AsString(), + Data = json["data"], + }; + } + + public JObject ToJson() + { + var json = new JObject(); + json["code"] = Code; + json["message"] = Message; + json["data"] = Data; + return json; + } + } +} diff --git a/neo/Network/RPC/Models/RpcTransaction.cs b/neo/Network/RPC/Models/RpcTransaction.cs new file mode 100644 index 0000000000..f41c04710a --- /dev/null +++ b/neo/Network/RPC/Models/RpcTransaction.cs @@ -0,0 +1,41 @@ +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; + +namespace Neo.Network.RPC.Models +{ + public class RpcTransaction + { + public Transaction Transaction { get; set; } + + public UInt256 BlockHash { get; set; } + + public int? Confirmations { get; set; } + + public uint? BlockTime { get; set; } + + public JObject ToJson() + { + JObject json = Transaction.ToJson(); + if (Confirmations != null) + { + json["blockhash"] = BlockHash.ToString(); + json["confirmations"] = Confirmations; + json["blocktime"] = BlockTime; + } + return json; + } + + public static RpcTransaction FromJson(JObject json) + { + RpcTransaction transaction = new RpcTransaction(); + transaction.Transaction = Transaction.FromJson(json); + if (json["confirmations"] != null) + { + transaction.BlockHash = UInt256.Parse(json["blockhash"].AsString()); + transaction.Confirmations = (int)json["confirmations"].AsNumber(); + transaction.BlockTime = (uint)json["blocktime"].AsNumber(); + } + return transaction; + } + } +} diff --git a/neo/Network/RPC/Models/RpcValidateAddressResult.cs b/neo/Network/RPC/Models/RpcValidateAddressResult.cs new file mode 100644 index 0000000000..5e0a704797 --- /dev/null +++ b/neo/Network/RPC/Models/RpcValidateAddressResult.cs @@ -0,0 +1,27 @@ +using Neo.IO.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcValidateAddressResult + { + public string Address { get; set; } + + public bool IsValid { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["address"] = Address; + json["isvalid"] = IsValid; + return json; + } + + public static RpcValidateAddressResult FromJson(JObject json) + { + RpcValidateAddressResult validateAddress = new RpcValidateAddressResult(); + validateAddress.Address = json["address"].AsString(); + validateAddress.IsValid = json["isvalid"].AsBoolean(); + return validateAddress; + } + } +} diff --git a/neo/Network/RPC/Models/RpcValidator.cs b/neo/Network/RPC/Models/RpcValidator.cs new file mode 100644 index 0000000000..f3116ed2e4 --- /dev/null +++ b/neo/Network/RPC/Models/RpcValidator.cs @@ -0,0 +1,32 @@ +using Neo.IO.Json; +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcValidator + { + public string PublicKey { get; set; } + + public BigInteger Votes { get; set; } + + public bool Active { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["publickey"] = PublicKey; + json["votes"] = Votes.ToString(); + json["active"] = Active; + return json; + } + + public static RpcValidator FromJson(JObject json) + { + RpcValidator validator = new RpcValidator(); + validator.PublicKey = json["publickey"].AsString(); + validator.Votes = BigInteger.Parse(json["votes"].AsString()); + validator.Active = json["active"].AsBoolean(); + return validator; + } + } +} diff --git a/neo/Network/RPC/Models/RpcVersion.cs b/neo/Network/RPC/Models/RpcVersion.cs new file mode 100644 index 0000000000..8163875b64 --- /dev/null +++ b/neo/Network/RPC/Models/RpcVersion.cs @@ -0,0 +1,35 @@ +using Neo.IO.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcVersion + { + public int TcpPort { get; set; } + + public int WsPort { get; set; } + + public uint Nonce { get; set; } + + public string UserAgent { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + json["topPort"] = TcpPort.ToString(); + json["wsPort"] = WsPort.ToString(); + json["nonce"] = Nonce.ToString(); + json["useragent"] = UserAgent; + return json; + } + + public static RpcVersion FromJson(JObject json) + { + RpcVersion version = new RpcVersion(); + version.TcpPort = int.Parse(json["tcpPort"].AsString()); + version.WsPort = int.Parse(json["wsPort"].AsString()); + version.Nonce = uint.Parse(json["nonce"].AsString()); + version.UserAgent = json["useragent"].AsString(); + return version; + } + } +} diff --git a/neo/Network/RPC/Nep5API.cs b/neo/Network/RPC/Nep5API.cs new file mode 100644 index 0000000000..abfad6c9ed --- /dev/null +++ b/neo/Network/RPC/Nep5API.cs @@ -0,0 +1,97 @@ +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.VM; +using Neo.Wallets; +using System.Linq; +using System.Numerics; + +namespace Neo.Network.RPC +{ + /// + /// Call NEP5 methods with RPC API + /// + public class Nep5API : ContractClient + { + /// + /// Nep5API Constructor + /// + /// the RPC client to call NEO RPC methods + public Nep5API(RpcClient rpcClient) : base(rpcClient) { } + + /// + /// Get balance of NEP5 token + /// + /// contract script hash + /// account script hash + /// + public BigInteger BalanceOf(UInt160 scriptHash, UInt160 account) + { + BigInteger balance = TestInvoke(scriptHash, "balanceOf", account).Stack.Single().ToStackItem().GetBigInteger(); + return balance; + } + + /// + /// Get name of NEP5 token + /// + /// contract script hash + /// + public string Name(UInt160 scriptHash) + { + return TestInvoke(scriptHash, "name").Stack.Single().ToStackItem().GetString(); + } + + /// + /// Get symbol of NEP5 token + /// + /// contract script hash + /// + public string Symbol(UInt160 scriptHash) + { + return TestInvoke(scriptHash, "symbol").Stack.Single().ToStackItem().GetString(); + } + + /// + /// Get decimals of NEP5 token + /// + /// contract script hash + /// + public uint Decimals(UInt160 scriptHash) + { + return (uint)TestInvoke(scriptHash, "decimals").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get total supply of NEP5 token + /// + /// contract script hash + /// + public BigInteger TotalSupply(UInt160 scriptHash) + { + return TestInvoke(scriptHash, "totalSupply").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get name of NEP5 token + /// + /// contract script hash + /// from KeyPair + /// to account script hash + /// transfer amount + /// netwotk fee, set to be 0 if you don't need higher priority + /// + public Transaction Transfer(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, long networkFee = 0) + { + var sender = Contract.CreateSignatureRedeemScript(fromKey.PublicKey).ToScriptHash(); + Cosigner[] cosigners = new[] { new Cosigner { Scopes = WitnessScope.CalledByEntry, Account = sender } }; + + byte[] script = scriptHash.MakeScript("transfer", sender, to, amount); + Transaction tx = new TransactionManager(rpcClient, sender) + .MakeTransaction(script, null, cosigners, networkFee) + .AddSignature(fromKey) + .Sign() + .Tx; + + return tx; + } + } +} diff --git a/neo/Network/RPC/PolicyAPI.cs b/neo/Network/RPC/PolicyAPI.cs new file mode 100644 index 0000000000..969c1c22f1 --- /dev/null +++ b/neo/Network/RPC/PolicyAPI.cs @@ -0,0 +1,57 @@ +using Neo.SmartContract.Native; +using Neo.VM; +using System.Linq; + +namespace Neo.Network.RPC +{ + /// + /// Get Policy info by RPC API + /// + public class PolicyAPI : ContractClient + { + readonly UInt160 scriptHash = NativeContract.Policy.Hash; + + /// + /// PolicyAPI Constructor + /// + /// the RPC client to call NEO RPC methods + public PolicyAPI(RpcClient rpcClient) : base(rpcClient) { } + + /// + /// Get Max Transactions Count Per Block + /// + /// + public uint GetMaxTransactionsPerBlock() + { + return (uint)TestInvoke(scriptHash, "getMaxTransactionsPerBlock").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get Max Block Size + /// + /// + public uint GetMaxBlockSize() + { + return (uint)TestInvoke(scriptHash, "getMaxBlockSize").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get Network Fee Per Byte + /// + /// + public long GetFeePerByte() + { + return (long)TestInvoke(scriptHash, "getFeePerByte").Stack.Single().ToStackItem().GetBigInteger(); + } + + /// + /// Get Ploicy Blocked Accounts + /// + /// + public UInt160[] GetBlockedAccounts() + { + var result = (VM.Types.Array)TestInvoke(scriptHash, "getBlockedAccounts").Stack.Single().ToStackItem(); + return result.Select(p => new UInt160(p.GetByteArray())).ToArray(); + } + } +} diff --git a/neo/Network/RPC/RpcClient.cs b/neo/Network/RPC/RpcClient.cs new file mode 100644 index 0000000000..cd0578953e --- /dev/null +++ b/neo/Network/RPC/RpcClient.cs @@ -0,0 +1,297 @@ +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.RPC.Models; +using System; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// The RPC client to call NEO RPC methods + /// + public class RpcClient : IDisposable + { + private readonly HttpClient httpClient; + + public RpcClient(string url) + { + httpClient = new HttpClient() { BaseAddress = new Uri(url) }; + } + + public RpcClient(HttpClient client) + { + httpClient = client; + } + + public void Dispose() + { + httpClient?.Dispose(); + } + + public async Task SendAsync(RpcRequest request) + { + var requestJson = request.ToJson().ToString(); + using (var result = await httpClient.PostAsync(httpClient.BaseAddress, new StringContent(requestJson, Encoding.UTF8))) + { + var content = await result.Content.ReadAsStringAsync(); + var response = RpcResponse.FromJson(JObject.Parse(content)); + response.RawResponse = content; + + if (response.Error != null) + { + throw new RpcException(response.Error.Code, response.Error.Message); + } + + return response; + } + } + + public RpcResponse Send(RpcRequest request) + { + try + { + return SendAsync(request).Result; + } + catch (AggregateException ex) + { + throw ex.GetBaseException(); + } + } + + public virtual JObject RpcSend(string method, params JObject[] paraArgs) + { + var request = new RpcRequest + { + Id = 1, + Jsonrpc = "2.0", + Method = method, + Params = paraArgs.Select(p => p).ToArray() + }; + return Send(request).Result; + } + + /// + /// Returns the hash of the tallest block in the main chain. + /// + public string GetBestBlockHash() + { + return RpcSend("getbestblockhash").AsString(); + } + + /// + /// Returns the hash of the tallest block in the main chain. + /// The serialized information of the block is returned, represented by a hexadecimal string. + /// + public string GetBlockHex(string hashOrIndex) + { + if (int.TryParse(hashOrIndex, out int index)) + { + return RpcSend("getblock", index).AsString(); + } + return RpcSend("getblock", hashOrIndex).AsString(); + } + + /// + /// Returns the hash of the tallest block in the main chain. + /// + public RpcBlock GetBlock(string hashOrIndex) + { + if (int.TryParse(hashOrIndex, out int index)) + { + return RpcBlock.FromJson(RpcSend("getblock", index, true)); + } + return RpcBlock.FromJson(RpcSend("getblock", hashOrIndex, true)); + } + + /// + /// Gets the number of blocks in the main chain. + /// + public uint GetBlockCount() + { + return (uint)RpcSend("getblockcount").AsNumber(); + } + + /// + /// Returns the hash value of the corresponding block, based on the specified index. + /// + public string GetBlockHash(int index) + { + return RpcSend("getblockhash", index).AsString(); + } + + /// + /// Returns the corresponding block header information according to the specified script hash. + /// + public string GetBlockHeaderHex(string hashOrIndex) + { + if (int.TryParse(hashOrIndex, out int index)) + { + return RpcSend("getblockheader", index).AsString(); + } + return RpcSend("getblockheader", hashOrIndex).AsString(); + } + + /// + /// Returns the corresponding block header information according to the specified script hash. + /// + public RpcBlockHeader GetBlockHeader(string hashOrIndex) + { + if (int.TryParse(hashOrIndex, out int index)) + { + return RpcBlockHeader.FromJson(RpcSend("getblockheader", index, true)); + } + return RpcBlockHeader.FromJson(RpcSend("getblockheader", hashOrIndex, true)); + } + + /// + /// Returns the system fees of the block, based on the specified index. + /// + public string GetBlockSysFee(int height) + { + return RpcSend("getblocksysfee", height).AsString(); + } + + /// + /// Gets the current number of connections for the node. + /// + public int GetConnectionCount() + { + return (int)RpcSend("getconnectioncount").AsNumber(); + } + + /// + /// Queries contract information, according to the contract script hash. + /// + public ContractState GetContractState(string hash) + { + return ContractState.FromJson(RpcSend("getcontractstate", hash)); + } + + /// + /// Gets the list of nodes that the node is currently connected/disconnected from. + /// + public RpcPeers GetPeers() + { + return RpcPeers.FromJson(RpcSend("getpeers")); + } + + /// + /// Obtains the list of unconfirmed transactions in memory. + /// + public string[] GetRawMempool() + { + return ((JArray)RpcSend("getrawmempool")).Select(p => p.AsString()).ToArray(); + } + + /// + /// Obtains the list of unconfirmed transactions in memory. + /// shouldGetUnverified = true + /// + public RpcRawMemPool GetRawMempoolBoth() + { + return RpcRawMemPool.FromJson(RpcSend("getrawmempool", true)); + } + + /// + /// Returns the corresponding transaction information, based on the specified hash value. + /// + public string GetRawTransactionHex(string txid) + { + return RpcSend("getrawtransaction", txid).AsString(); + } + + /// + /// Returns the corresponding transaction information, based on the specified hash value. + /// verbose = true + /// + public RpcTransaction GetRawTransaction(string txid) + { + return RpcTransaction.FromJson(RpcSend("getrawtransaction", txid, true)); + } + + /// + /// Returns the stored value, according to the contract script hash and the stored key. + /// + public string GetStorage(string script_hash, string key) + { + return RpcSend("getstorage", script_hash, key).AsString(); + } + + /// + /// Returns the block index in which the transaction is found. + /// + public uint GetTransactionHeight(string txid) + { + return uint.Parse(RpcSend("gettransactionheight", txid).AsString()); + } + + /// + /// Returns the current NEO consensus nodes information and voting status. + /// + public RpcValidator[] GetValidators() + { + return ((JArray)RpcSend("getvalidators")).Select(p => RpcValidator.FromJson(p)).ToArray(); + } + + /// + /// Returns the version information about the queried node. + /// + public RpcVersion GetVersion() + { + return RpcVersion.FromJson(RpcSend("getversion")); + } + + /// + /// Returns the result after calling a smart contract at scripthash with the given operation and parameters. + /// This RPC call does not affect the blockchain in any way. + /// + public RpcInvokeResult InvokeFunction(string address, string function, RpcStack[] stacks) + { + return RpcInvokeResult.FromJson(RpcSend("invokefunction", address, function, stacks.Select(p => p.ToJson()).ToArray())); + } + + /// + /// Returns the result after passing a script through the VM. + /// This RPC call does not affect the blockchain in any way. + /// + public RpcInvokeResult InvokeScript(byte[] script) + { + return RpcInvokeResult.FromJson(RpcSend("invokescript", script.ToHexString())); + } + + /// + /// Returns a list of plugins loaded by the node. + /// + public RpcPlugin[] ListPlugins() + { + return ((JArray)RpcSend("listplugins")).Select(p => RpcPlugin.FromJson(p)).ToArray(); + } + + /// + /// Broadcasts a transaction over the NEO network. + /// + public bool SendRawTransaction(byte[] rawTransaction) + { + return RpcSend("sendrawtransaction", rawTransaction.ToHexString()).AsBoolean(); + } + + /// + /// Broadcasts a raw block over the NEO network. + /// + public bool SubmitBlock(byte[] block) + { + return RpcSend("submitblock", block.ToHexString()).AsBoolean(); + } + + /// + /// Verifies that the address is a correct NEO address. + /// + public RpcValidateAddressResult ValidateAddress(string address) + { + return RpcValidateAddressResult.FromJson(RpcSend("validateaddress", address)); + } + } +} diff --git a/neo/Network/RPC/RpcException.cs b/neo/Network/RPC/RpcException.cs index 5a2120f083..b5030750ca 100644 --- a/neo/Network/RPC/RpcException.cs +++ b/neo/Network/RPC/RpcException.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.Network.RPC { diff --git a/neo/Network/RPC/RpcServer.cs b/neo/Network/RPC/RpcServer.cs index 6bfdd15618..c47e6644e7 100644 --- a/neo/Network/RPC/RpcServer.cs +++ b/neo/Network/RPC/RpcServer.cs @@ -31,6 +31,43 @@ namespace Neo.Network.RPC { public sealed class RpcServer : IDisposable { + private class CheckWitnessHashes : IVerifiable + { + private readonly UInt160[] _scriptHashesForVerifying; + public Witness[] Witnesses { get; set; } + public int Size { get; } + + public CheckWitnessHashes(UInt160[] scriptHashesForVerifying) + { + _scriptHashesForVerifying = scriptHashesForVerifying; + } + + public void Serialize(BinaryWriter writer) + { + throw new NotImplementedException(); + } + + public void Deserialize(BinaryReader reader) + { + throw new NotImplementedException(); + } + + public void DeserializeUnsigned(BinaryReader reader) + { + throw new NotImplementedException(); + } + + public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) + { + return _scriptHashesForVerifying; + } + + public void SerializeUnsigned(BinaryWriter writer) + { + throw new NotImplementedException(); + } + } + public Wallet Wallet { get; set; } public long MaxGasInvoke { get; } @@ -72,9 +109,9 @@ public void Dispose() } } - private JObject GetInvokeResult(byte[] script) + private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = null) { - ApplicationEngine engine = ApplicationEngine.Run(script, extraGAS: MaxGasInvoke); + ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: MaxGasInvoke); JObject json = new JObject(); json["script"] = script.ToHexString(); json["state"] = engine.State; @@ -202,7 +239,13 @@ private JObject Process(string method, JArray _params) case "invokescript": { byte[] script = _params[0].AsString().HexToBytes(); - return InvokeScript(script); + CheckWitnessHashes checkWitnessHashes = null; + if (_params.Count > 1) + { + UInt160[] scriptHashesForVerifying = _params.Skip(1).Select(u => UInt160.Parse(u.AsString())).ToArray(); + checkWitnessHashes = new CheckWitnessHashes(scriptHashesForVerifying); + } + return GetInvokeResult(script, checkWitnessHashes); } case "listplugins": { diff --git a/neo/Network/RPC/TransactionManager.cs b/neo/Network/RPC/TransactionManager.cs new file mode 100644 index 0000000000..7b4db697ab --- /dev/null +++ b/neo/Network/RPC/TransactionManager.cs @@ -0,0 +1,220 @@ +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; + +namespace Neo.Network.RPC +{ + /// + /// This class helps to create transaction with RPC API. + /// + public class TransactionManager + { + private static readonly Random rand = new Random(); + private readonly RpcClient rpcClient; + private readonly UInt160 sender; + + /// + /// The Transaction context to manage the witnesses + /// + private ContractParametersContext context; + + /// + /// The Transaction managed by this class + /// + public Transaction Tx { get; private set; } + + /// + /// TransactionManager Constructor + /// + /// the RPC client to call NEO RPC API + /// the account script hash of sender + public TransactionManager(RpcClient rpc, UInt160 sender) + { + rpcClient = rpc; + this.sender = sender; + } + + /// + /// Create an unsigned Transaction object with given parameters. + /// + /// Transaction Script + /// Transaction Attributes + /// Transaction Cosigners + /// Transaction NetworkFee, will set to estimate value(with only basic signatures) when networkFee is 0 + /// + public TransactionManager MakeTransaction(byte[] script, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null, long networkFee = 0) + { + uint height = rpcClient.GetBlockCount() - 1; + Tx = new Transaction + { + Version = 0, + Nonce = (uint)rand.Next(), + Script = script, + Sender = sender, + ValidUntilBlock = height + Transaction.MaxValidUntilBlockIncrement, + Attributes = attributes ?? new TransactionAttribute[0], + Cosigners = cosigners ?? new Cosigner[0], + Witnesses = new Witness[0] + }; + + RpcInvokeResult result = rpcClient.InvokeScript(script); + Tx.SystemFee = Math.Max(long.Parse(result.GasConsumed) - ApplicationEngine.GasFree, 0); + if (Tx.SystemFee > 0) + { + long d = (long)NativeContract.GAS.Factor; + long remainder = Tx.SystemFee % d; + if (remainder > 0) + Tx.SystemFee += d - remainder; + else if (remainder < 0) + Tx.SystemFee -= remainder; + } + + context = new ContractParametersContext(Tx); + + // set networkfee to estimate value when networkFee is 0 + Tx.NetworkFee = networkFee == 0 ? EstimateNetworkFee() : networkFee; + + var gasBalance = new Nep5API(rpcClient).BalanceOf(NativeContract.GAS.Hash, sender); + if (gasBalance >= Tx.SystemFee + Tx.NetworkFee) return this; + throw new InvalidOperationException($"Insufficient GAS in address: {sender.ToAddress()}"); + } + + /// + /// Estimate NetworkFee, assuming the witnesses are basic Signature Contract + /// + private long EstimateNetworkFee() + { + long networkFee = 0; + UInt160[] hashes = Tx.GetScriptHashesForVerifying(null); + int size = Transaction.HeaderSize + Tx.Attributes.GetVarSize() + Tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + + // assume the hashes are single Signature + foreach (var hash in hashes) + { + size += 166; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); + } + + networkFee += size * new PolicyAPI(rpcClient).GetFeePerByte(); + return networkFee; + } + + /// + /// Calculate NetworkFee with context items + /// + private long CalculateNetworkFee() + { + long networkFee = 0; + UInt160[] hashes = Tx.GetScriptHashesForVerifying(null); + int size = Transaction.HeaderSize + Tx.Attributes.GetVarSize() + Tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + foreach (UInt160 hash in hashes) + { + byte[] witness_script = context.GetScript(hash); + if (witness_script is null || witness_script.Length == 0) + { + try + { + witness_script = rpcClient.GetContractState(hash.ToString())?.Script; + } + catch { } + } + + if (witness_script is null) continue; + + networkFee += Wallet.CalculateNetWorkFee(witness_script, ref size); + } + networkFee += size * new PolicyAPI(rpcClient).GetFeePerByte(); + return networkFee; + } + + /// + /// Add Signature + /// + /// The KeyPair to sign transction + /// + public TransactionManager AddSignature(KeyPair key) + { + var contract = Contract.CreateSignatureContract(key.PublicKey); + + byte[] signature = Tx.Sign(key); + if (!context.AddSignature(contract, key.PublicKey, signature)) + { + throw new Exception("AddSignature failed!"); + } + + return this; + } + + /// + /// Add Multi-Signature + /// + /// The KeyPair to sign transction + /// The least count of signatures needed for multiple signature contract + /// The Public Keys construct the multiple signature contract + public TransactionManager AddMultiSig(KeyPair key, int m, params ECPoint[] publicKeys) + { + Contract contract = Contract.CreateMultiSigContract(m, publicKeys); + + byte[] signature = Tx.Sign(key); + if (!context.AddSignature(contract, key.PublicKey, signature)) + { + throw new Exception("AddMultiSig failed!"); + } + + return this; + } + + /// + /// Add Witness with contract + /// + /// The witness verification contract + /// The witness invocation parameters + public TransactionManager AddWitness(Contract contract, params object[] parameters) + { + if (!context.Add(contract, parameters)) + { + throw new Exception("AddWitness failed!"); + }; + return this; + } + + /// + /// Add Witness with scriptHash + /// + /// The witness verification contract hash + /// The witness invocation parameters + public TransactionManager AddWitness(UInt160 scriptHash, params object[] parameters) + { + var contract = Contract.Create(scriptHash); + return AddWitness(contract, parameters); + } + + /// + /// Verify Witness count and add witnesses + /// + public TransactionManager Sign() + { + // Verify witness count + if (!context.Completed) + { + throw new Exception($"Please add signature or witness first!"); + } + + // Calculate NetworkFee + long leastNetworkFee = CalculateNetworkFee(); + if (Tx.NetworkFee < leastNetworkFee) + { + throw new InvalidOperationException("Insufficient NetworkFee"); + } + + Tx.Witnesses = context.GetWitnesses(); + return this; + } + } +} diff --git a/neo/Persistence/CloneSnapshot.cs b/neo/Persistence/CloneSnapshot.cs index 9f86f2a1bf..0b79bd2739 100644 --- a/neo/Persistence/CloneSnapshot.cs +++ b/neo/Persistence/CloneSnapshot.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using Neo.IO.Wrappers; using Neo.Ledger; diff --git a/neo/Persistence/Helper.cs b/neo/Persistence/Helper.cs index 3cc1b92ca9..7f412e47d3 100644 --- a/neo/Persistence/Helper.cs +++ b/neo/Persistence/Helper.cs @@ -1,4 +1,4 @@ -using Neo.Ledger; +using Neo.Ledger; using Neo.Network.P2P.Payloads; namespace Neo.Persistence @@ -20,6 +20,7 @@ public static bool ContainsTransaction(this IPersistence persistence, UInt256 ha public static Block GetBlock(this IPersistence persistence, uint index) { + if (index == 0) return Blockchain.GenesisBlock; UInt256 hash = Blockchain.Singleton.GetBlockHash(index); if (hash == null) return null; return persistence.GetBlock(hash); @@ -35,6 +36,7 @@ public static Block GetBlock(this IPersistence persistence, UInt256 hash) public static Header GetHeader(this IPersistence persistence, uint index) { + if (index == 0) return Blockchain.GenesisBlock.Header; UInt256 hash = Blockchain.Singleton.GetBlockHash(index); if (hash == null) return null; return persistence.GetHeader(hash); diff --git a/neo/Persistence/IPersistence.cs b/neo/Persistence/IPersistence.cs index 34f1169f9b..0632b5d0fa 100644 --- a/neo/Persistence/IPersistence.cs +++ b/neo/Persistence/IPersistence.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using Neo.IO.Wrappers; using Neo.Ledger; diff --git a/neo/Persistence/LevelDB/DbCache.cs b/neo/Persistence/LevelDB/DbCache.cs index 86ff7a39ea..ae55b31326 100644 --- a/neo/Persistence/LevelDB/DbCache.cs +++ b/neo/Persistence/LevelDB/DbCache.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Caching; using Neo.IO.Data.LevelDB; using System; diff --git a/neo/Persistence/LevelDB/DbMetaDataCache.cs b/neo/Persistence/LevelDB/DbMetaDataCache.cs index e2c03ca2fd..0163d84e39 100644 --- a/neo/Persistence/LevelDB/DbMetaDataCache.cs +++ b/neo/Persistence/LevelDB/DbMetaDataCache.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Caching; using Neo.IO.Data.LevelDB; using System; diff --git a/neo/Persistence/LevelDB/DbSnapshot.cs b/neo/Persistence/LevelDB/DbSnapshot.cs index f7ade52ad8..365584fa7c 100644 --- a/neo/Persistence/LevelDB/DbSnapshot.cs +++ b/neo/Persistence/LevelDB/DbSnapshot.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using Neo.IO.Data.LevelDB; using Neo.IO.Wrappers; using Neo.Ledger; diff --git a/neo/Persistence/LevelDB/LevelDBStore.cs b/neo/Persistence/LevelDB/LevelDBStore.cs index 189f0cfbb5..805c1fe915 100644 --- a/neo/Persistence/LevelDB/LevelDBStore.cs +++ b/neo/Persistence/LevelDB/LevelDBStore.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using Neo.IO.Data.LevelDB; using Neo.IO.Wrappers; using Neo.Ledger; @@ -34,9 +34,9 @@ public void Dispose() db.Dispose(); } - public override byte[] Get(byte prefix, byte[] key) + public override byte[] Get(byte[] key) { - if (!db.TryGet(ReadOptions.Default, SliceBuilder.Begin(prefix).Add(key), out Slice slice)) + if (!db.TryGet(ReadOptions.Default, key, out Slice slice)) return null; return slice.ToArray(); } @@ -81,14 +81,14 @@ public override MetaDataCache GetHeaderHashIndex() return new DbMetaDataCache(db, null, null, Prefixes.IX_CurrentHeader); } - public override void Put(byte prefix, byte[] key, byte[] value) + public override void Put(byte[] key, byte[] value) { - db.Put(WriteOptions.Default, SliceBuilder.Begin(prefix).Add(key), value); + db.Put(WriteOptions.Default, key, value); } - public override void PutSync(byte prefix, byte[] key, byte[] value) + public override void PutSync(byte[] key, byte[] value) { - db.Put(new WriteOptions { Sync = true }, SliceBuilder.Begin(prefix).Add(key), value); + db.Put(new WriteOptions { Sync = true }, key, value); } } } diff --git a/neo/Persistence/LevelDB/Prefixes.cs b/neo/Persistence/LevelDB/Prefixes.cs index 605e9f1451..f40359a55a 100644 --- a/neo/Persistence/LevelDB/Prefixes.cs +++ b/neo/Persistence/LevelDB/Prefixes.cs @@ -1,4 +1,4 @@ -namespace Neo.Persistence.LevelDB +namespace Neo.Persistence.LevelDB { internal static class Prefixes { diff --git a/neo/Persistence/Snapshot.cs b/neo/Persistence/Snapshot.cs index f058562200..1e900800b0 100644 --- a/neo/Persistence/Snapshot.cs +++ b/neo/Persistence/Snapshot.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using Neo.IO.Wrappers; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/neo/Persistence/Store.cs b/neo/Persistence/Store.cs index 7b5b3b6435..eab5c182b9 100644 --- a/neo/Persistence/Store.cs +++ b/neo/Persistence/Store.cs @@ -1,4 +1,4 @@ -using Neo.IO.Caching; +using Neo.IO.Caching; using Neo.IO.Wrappers; using Neo.Ledger; @@ -14,7 +14,7 @@ public abstract class Store : IPersistence MetaDataCache IPersistence.BlockHashIndex => GetBlockHashIndex(); MetaDataCache IPersistence.HeaderHashIndex => GetHeaderHashIndex(); - public abstract byte[] Get(byte prefix, byte[] key); + public abstract byte[] Get(byte[] key); public abstract DataCache GetBlocks(); public abstract DataCache GetTransactions(); public abstract DataCache GetContracts(); @@ -22,8 +22,8 @@ public abstract class Store : IPersistence public abstract DataCache GetHeaderHashList(); public abstract MetaDataCache GetBlockHashIndex(); public abstract MetaDataCache GetHeaderHashIndex(); - public abstract void Put(byte prefix, byte[] key, byte[] value); - public abstract void PutSync(byte prefix, byte[] key, byte[] value); + public abstract void Put(byte[] key, byte[] value); + public abstract void PutSync(byte[] key, byte[] value); public abstract Snapshot GetSnapshot(); } diff --git a/neo/Plugins/ILogPlugin.cs b/neo/Plugins/ILogPlugin.cs index ff1052be2e..c0733f9143 100644 --- a/neo/Plugins/ILogPlugin.cs +++ b/neo/Plugins/ILogPlugin.cs @@ -1,4 +1,4 @@ -namespace Neo.Plugins +namespace Neo.Plugins { public interface ILogPlugin { diff --git a/neo/Plugins/IMemoryPoolTxObserverPlugin.cs b/neo/Plugins/IMemoryPoolTxObserverPlugin.cs index e596b9a7b5..3dd1cb3ef2 100644 --- a/neo/Plugins/IMemoryPoolTxObserverPlugin.cs +++ b/neo/Plugins/IMemoryPoolTxObserverPlugin.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Neo.Network.P2P.Payloads; namespace Neo.Plugins diff --git a/neo/Plugins/IP2PPlugin.cs b/neo/Plugins/IP2PPlugin.cs index 3029aaf849..e2043104fc 100644 --- a/neo/Plugins/IP2PPlugin.cs +++ b/neo/Plugins/IP2PPlugin.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P; +using Neo.Network.P2P; using Neo.Network.P2P.Payloads; namespace Neo.Plugins diff --git a/neo/Plugins/IPersistencePlugin.cs b/neo/Plugins/IPersistencePlugin.cs index af3cbe05b8..0f06ae51eb 100644 --- a/neo/Plugins/IPersistencePlugin.cs +++ b/neo/Plugins/IPersistencePlugin.cs @@ -1,4 +1,4 @@ -using System; +using System; using Neo.Persistence; using System.Collections.Generic; using static Neo.Ledger.Blockchain; diff --git a/neo/Plugins/IPolicyPlugin.cs b/neo/Plugins/IPolicyPlugin.cs deleted file mode 100644 index 812d418ee2..0000000000 --- a/neo/Plugins/IPolicyPlugin.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Neo.Network.P2P.Payloads; -using System.Collections.Generic; - -namespace Neo.Plugins -{ - public interface IPolicyPlugin - { - bool FilterForMemoryPool(Transaction tx); - IEnumerable FilterForBlock(IEnumerable transactions); - } -} diff --git a/neo/Plugins/IRpcPlugin.cs b/neo/Plugins/IRpcPlugin.cs index 19e5b71808..92e7a0fa48 100644 --- a/neo/Plugins/IRpcPlugin.cs +++ b/neo/Plugins/IRpcPlugin.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Neo.IO.Json; namespace Neo.Plugins diff --git a/neo/Plugins/LogLevel.cs b/neo/Plugins/LogLevel.cs index 72158c8aad..8f6e0b9df7 100644 --- a/neo/Plugins/LogLevel.cs +++ b/neo/Plugins/LogLevel.cs @@ -1,4 +1,4 @@ -namespace Neo.Plugins +namespace Neo.Plugins { public enum LogLevel : byte { diff --git a/neo/Plugins/MemoryPoolTxRemovalReason.cs b/neo/Plugins/MemoryPoolTxRemovalReason.cs index f7320dc60e..ddc7e10981 100644 --- a/neo/Plugins/MemoryPoolTxRemovalReason.cs +++ b/neo/Plugins/MemoryPoolTxRemovalReason.cs @@ -1,4 +1,4 @@ -namespace Neo.Plugins +namespace Neo.Plugins { public enum MemoryPoolTxRemovalReason : byte { @@ -11,4 +11,4 @@ public enum MemoryPoolTxRemovalReason : byte /// NoLongerValid, } -} \ No newline at end of file +} diff --git a/neo/Plugins/Plugin.cs b/neo/Plugins/Plugin.cs index 862ff6ae9a..aa04b0778d 100644 --- a/neo/Plugins/Plugin.cs +++ b/neo/Plugins/Plugin.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Neo.Network.P2P.Payloads; +using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; @@ -13,7 +12,6 @@ public abstract class Plugin { public static readonly List Plugins = new List(); private static readonly List Loggers = new List(); - internal static readonly List Policies = new List(); internal static readonly List RpcPlugins = new List(); internal static readonly List PersistencePlugins = new List(); internal static readonly List P2PPlugins = new List(); @@ -51,7 +49,6 @@ protected Plugin() if (this is ILogPlugin logger) Loggers.Add(logger); if (this is IP2PPlugin p2p) P2PPlugins.Add(p2p); - if (this is IPolicyPlugin policy) Policies.Add(policy); if (this is IRpcPlugin rpc) RpcPlugins.Add(rpc); if (this is IPersistencePlugin persistence) PersistencePlugins.Add(persistence); if (this is IMemoryPoolTxObserverPlugin txObserver) TxObserverPlugins.Add(txObserver); @@ -59,14 +56,6 @@ protected Plugin() Configure(); } - public static bool CheckPolicy(Transaction tx) - { - foreach (IPolicyPlugin plugin in Policies) - if (!plugin.FilterForMemoryPool(tx)) - return false; - return true; - } - public abstract void Configure(); protected virtual void OnPluginsLoaded() diff --git a/neo/ProtocolSettings.cs b/neo/ProtocolSettings.cs index ba4c192e8b..18c915d614 100644 --- a/neo/ProtocolSettings.cs +++ b/neo/ProtocolSettings.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; +using System; using System.Linq; using System.Threading; @@ -10,7 +11,8 @@ public class ProtocolSettings public byte AddressVersion { get; } public string[] StandbyValidators { get; } public string[] SeedList { get; } - public uint SecondsPerBlock { get; } + public uint MillisecondsPerBlock { get; } + public int MemoryPoolMaxTransactions { get; } static ProtocolSettings _default; @@ -69,7 +71,8 @@ private ProtocolSettings(IConfigurationSection section) "seed4.neo.org:10333", "seed5.neo.org:10333" }; - this.SecondsPerBlock = section.GetValue("SecondsPerBlock", 15u); + this.MillisecondsPerBlock = section.GetValue("MillisecondsPerBlock", 15000u); + this.MemoryPoolMaxTransactions = Math.Max(1, section.GetValue("MemoryPoolMaxTransactions", 50_000)); } } } diff --git a/neo/SmartContract/ApplicationEngine.OpCodePrices.cs b/neo/SmartContract/ApplicationEngine.OpCodePrices.cs index 05105e2261..271a76f935 100644 --- a/neo/SmartContract/ApplicationEngine.OpCodePrices.cs +++ b/neo/SmartContract/ApplicationEngine.OpCodePrices.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System.Collections.Generic; namespace Neo.SmartContract @@ -162,8 +162,6 @@ partial class ApplicationEngine [OpCode.MIN] = 200, [OpCode.MAX] = 200, [OpCode.WITHIN] = 200, - [OpCode.SHA1] = 300000, - [OpCode.SHA256] = 1000000, [OpCode.ARRAYSIZE] = 150, [OpCode.PACK] = 7000, [OpCode.UNPACK] = 7000, diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index ced43d379e..2eeeef52e0 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -15,7 +15,6 @@ public partial class ApplicationEngine : ExecutionEngine public const long GasFree = 0; private readonly long gas_amount; private readonly bool testMode; - private readonly RandomAccessStack hashes = new RandomAccessStack(); private readonly List notifications = new List(); private readonly List disposables = new List(); @@ -23,9 +22,9 @@ public partial class ApplicationEngine : ExecutionEngine public IVerifiable ScriptContainer { get; } public Snapshot Snapshot { get; } public long GasConsumed { get; private set; } = 0; - public UInt160 CurrentScriptHash => hashes.Count > 0 ? hashes.Peek() : null; - public UInt160 CallingScriptHash => hashes.Count > 1 ? hashes.Peek(1) : null; - public UInt160 EntryScriptHash => hashes.Count > 0 ? hashes.Peek(hashes.Count - 1) : null; + public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; + public UInt160 CallingScriptHash => InvocationStack.Count > 1 ? InvocationStack.Peek(1).GetState().ScriptHash : null; + public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash; public IReadOnlyList Notifications => notifications; internal Dictionary InvocationCounter { get; } = new Dictionary(); @@ -36,8 +35,6 @@ public ApplicationEngine(TriggerType trigger, IVerifiable container, Snapshot sn this.Trigger = trigger; this.ScriptContainer = container; this.Snapshot = snapshot; - ContextLoaded += ApplicationEngine_ContextLoaded; - ContextUnloaded += ApplicationEngine_ContextUnloaded; } internal T AddDisposable(T disposable) where T : IDisposable @@ -52,14 +49,16 @@ private bool AddGas(long gas) return testMode || GasConsumed <= gas_amount; } - private void ApplicationEngine_ContextLoaded(object sender, ExecutionContext e) + protected override void LoadContext(ExecutionContext context) { - hashes.Push(((byte[])e.Script).ToScriptHash()); - } + // Set default execution context state - private void ApplicationEngine_ContextUnloaded(object sender, ExecutionContext e) - { - hashes.Pop(); + context.SetState(new ExecutionContextState() + { + ScriptHash = ((byte[])context.Script).ToScriptHash() + }); + + base.LoadContext(context); } public override void Dispose() @@ -84,17 +83,17 @@ protected override bool PreExecuteInstruction() return AddGas(OpCodePrices[CurrentContext.CurrentInstruction.OpCode]); } - public static ApplicationEngine Run(byte[] script, Snapshot snapshot, - IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default) + private static Block CreateDummyBlock(Snapshot snapshot) { - snapshot.PersistingBlock = persistingBlock ?? snapshot.PersistingBlock ?? new Block + var currentBlock = snapshot.Blocks[snapshot.CurrentBlockHash]; + return new Block { Version = 0, PrevHash = snapshot.CurrentBlockHash, MerkleRoot = new UInt256(), - Timestamp = snapshot.Blocks[snapshot.CurrentBlockHash].Timestamp + Blockchain.SecondsPerBlock, + Timestamp = currentBlock.Timestamp + Blockchain.MillisecondsPerBlock, Index = snapshot.Height + 1, - NextConsensus = snapshot.Blocks[snapshot.CurrentBlockHash].NextConsensus, + NextConsensus = currentBlock.NextConsensus, Witness = new Witness { InvocationScript = new byte[0], @@ -103,6 +102,12 @@ protected override bool PreExecuteInstruction() ConsensusData = new ConsensusData(), Transactions = new Transaction[0] }; + } + + public static ApplicationEngine Run(byte[] script, Snapshot snapshot, + IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default) + { + snapshot.PersistingBlock = persistingBlock ?? snapshot.PersistingBlock ?? CreateDummyBlock(snapshot); ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, snapshot, extraGAS, testMode); engine.LoadScript(script); engine.Execute(); diff --git a/neo/SmartContract/ContainerPlaceholder.cs b/neo/SmartContract/ContainerPlaceholder.cs index 14e97ae848..a379ddd9d3 100644 --- a/neo/SmartContract/ContainerPlaceholder.cs +++ b/neo/SmartContract/ContainerPlaceholder.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System; namespace Neo.SmartContract diff --git a/neo/SmartContract/Contract.cs b/neo/SmartContract/Contract.cs index 8d23997006..58e32768d7 100644 --- a/neo/SmartContract/Contract.cs +++ b/neo/SmartContract/Contract.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.VM; using Neo.Wallets; using System; @@ -46,6 +46,20 @@ public static Contract Create(ContractParameterType[] parameterList, byte[] rede }; } + /// + /// Construct special Contract with empty Script, will get the Script with scriptHash from blockchain when doing the Verify + /// verification = snapshot.Contracts.TryGet(hashes[i])?.Script; + /// + public static Contract Create(UInt160 scriptHash, params ContractParameterType[] parameterList) + { + return new Contract + { + Script = new byte[0], + _scriptHash = scriptHash, + ParameterList = parameterList + }; + } + public static Contract CreateMultiSigContract(int m, params ECPoint[] publicKeys) { return new Contract diff --git a/neo/SmartContract/ContractParameter.cs b/neo/SmartContract/ContractParameter.cs index 5e78005e13..cae59229b8 100644 --- a/neo/SmartContract/ContractParameter.cs +++ b/neo/SmartContract/ContractParameter.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.IO.Json; using System; using System.Collections.Generic; diff --git a/neo/SmartContract/ContractParameterType.cs b/neo/SmartContract/ContractParameterType.cs index 67191852fa..748ff86654 100644 --- a/neo/SmartContract/ContractParameterType.cs +++ b/neo/SmartContract/ContractParameterType.cs @@ -1,4 +1,4 @@ -namespace Neo.SmartContract +namespace Neo.SmartContract { public enum ContractParameterType : byte { diff --git a/neo/SmartContract/ContractParametersContext.cs b/neo/SmartContract/ContractParametersContext.cs index 8fda2c8450..0388df342c 100644 --- a/neo/SmartContract/ContractParametersContext.cs +++ b/neo/SmartContract/ContractParametersContext.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.IO.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -72,16 +72,33 @@ public bool Completed } } + /// + /// Cache for public ScriptHashes field + /// private UInt160[] _ScriptHashes = null; + + /// + /// ScriptHashes are the verifiable ScriptHashes from Verifiable element + /// Equivalent to: Verifiable.GetScriptHashesForVerifying(Blockchain.Singleton.GetSnapshot()) + /// public IReadOnlyList ScriptHashes { get { if (_ScriptHashes == null) + { + // snapshot is not necessary for Transaction + if (Verifiable is Transaction) + { + _ScriptHashes = Verifiable.GetScriptHashesForVerifying(null); + return _ScriptHashes; + } + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { _ScriptHashes = Verifiable.GetScriptHashesForVerifying(snapshot); } + } return _ScriptHashes; } } @@ -100,6 +117,17 @@ public bool Add(Contract contract, int index, object parameter) return true; } + public bool Add(Contract contract, params object[] parameters) + { + ContextItem item = CreateItem(contract); + if (item == null) return false; + for (int index = 0; index < parameters.Length; index++) + { + item.Parameters[index].Value = parameters[index]; + } + return true; + } + public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) { if (contract.Script.IsMultiSigContract(out _, out _)) @@ -212,6 +240,13 @@ public IReadOnlyList GetParameters(UInt160 scriptHash) return item.Parameters; } + public byte[] GetScript(UInt160 scriptHash) + { + if (!ContextItems.TryGetValue(scriptHash, out ContextItem item)) + return null; + return item.Script; + } + public Witness[] GetWitnesses() { if (!Completed) throw new InvalidOperationException(); diff --git a/neo/SmartContract/Enumerators/ConcatenatedEnumerator.cs b/neo/SmartContract/Enumerators/ConcatenatedEnumerator.cs index 36e8c4fe97..2b75b1d46b 100644 --- a/neo/SmartContract/Enumerators/ConcatenatedEnumerator.cs +++ b/neo/SmartContract/Enumerators/ConcatenatedEnumerator.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; namespace Neo.SmartContract.Enumerators { diff --git a/neo/SmartContract/Enumerators/IEnumerator.cs b/neo/SmartContract/Enumerators/IEnumerator.cs index 27e30b5e18..4d1f11fe65 100644 --- a/neo/SmartContract/Enumerators/IEnumerator.cs +++ b/neo/SmartContract/Enumerators/IEnumerator.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System; namespace Neo.SmartContract.Enumerators diff --git a/neo/SmartContract/Enumerators/IteratorKeysWrapper.cs b/neo/SmartContract/Enumerators/IteratorKeysWrapper.cs index ef2e49d49f..d134eb884c 100644 --- a/neo/SmartContract/Enumerators/IteratorKeysWrapper.cs +++ b/neo/SmartContract/Enumerators/IteratorKeysWrapper.cs @@ -1,4 +1,4 @@ -using Neo.SmartContract.Iterators; +using Neo.SmartContract.Iterators; using Neo.VM; namespace Neo.SmartContract.Enumerators diff --git a/neo/SmartContract/Enumerators/IteratorValuesWrapper.cs b/neo/SmartContract/Enumerators/IteratorValuesWrapper.cs index fb69070d40..15e06f2223 100644 --- a/neo/SmartContract/Enumerators/IteratorValuesWrapper.cs +++ b/neo/SmartContract/Enumerators/IteratorValuesWrapper.cs @@ -1,4 +1,4 @@ -using Neo.SmartContract.Iterators; +using Neo.SmartContract.Iterators; using Neo.VM; namespace Neo.SmartContract.Enumerators diff --git a/neo/SmartContract/ExecutionContextState.cs b/neo/SmartContract/ExecutionContextState.cs new file mode 100644 index 0000000000..d26641f7dd --- /dev/null +++ b/neo/SmartContract/ExecutionContextState.cs @@ -0,0 +1,10 @@ +namespace Neo.SmartContract +{ + public class ExecutionContextState + { + /// + /// Script hash + /// + public UInt160 ScriptHash { get; set; } + } +} diff --git a/neo/SmartContract/Helper.cs b/neo/SmartContract/Helper.cs index 259e997b09..6a22249cd7 100644 --- a/neo/SmartContract/Helper.cs +++ b/neo/SmartContract/Helper.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; diff --git a/neo/SmartContract/InteropDescriptor.cs b/neo/SmartContract/InteropDescriptor.cs index c283e474de..6db599ea64 100644 --- a/neo/SmartContract/InteropDescriptor.cs +++ b/neo/SmartContract/InteropDescriptor.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System; namespace Neo.SmartContract diff --git a/neo/SmartContract/InteropService.NEO.cs b/neo/SmartContract/InteropService.NEO.cs index 5787cebdb4..7f0d857fe9 100644 --- a/neo/SmartContract/InteropService.NEO.cs +++ b/neo/SmartContract/InteropService.NEO.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO.Json; using Neo.Ledger; using Neo.Network.P2P; @@ -10,7 +10,6 @@ using Neo.VM; using Neo.VM.Types; using System; -using System.IO; using System.Linq; using VMArray = Neo.VM.Types.Array; @@ -345,22 +344,7 @@ private static bool Storage_Find(ApplicationEngine engine) StorageContext context = _interface.GetInterface(); if (!CheckStorageContext(engine, context)) return false; byte[] prefix = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - byte[] prefix_key; - using (MemoryStream ms = new MemoryStream()) - { - int index = 0; - int remain = prefix.Length; - while (remain >= 16) - { - ms.Write(prefix, index, 16); - ms.WriteByte(0); - index += 16; - remain -= 16; - } - if (remain > 0) - ms.Write(prefix, index, remain); - prefix_key = context.ScriptHash.ToArray().Concat(ms.ToArray()).ToArray(); - } + byte[] prefix_key = StorageKey.CreateSearchPrefix(context.ScriptHash, prefix); StorageIterator iterator = engine.AddDisposable(new StorageIterator(engine.Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.Take(prefix.Length).SequenceEqual(prefix)).GetEnumerator())); engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator)); return true; diff --git a/neo/SmartContract/InteropService.cs b/neo/SmartContract/InteropService.cs index fbb3a8655d..7aa7fd7284 100644 --- a/neo/SmartContract/InteropService.cs +++ b/neo/SmartContract/InteropService.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Ledger; @@ -54,7 +54,7 @@ public static partial class InteropService public static readonly uint System_Block_GetTransactions = Register("System.Block.GetTransactions", Block_GetTransactions, 0_00010000, TriggerType.Application); public static readonly uint System_Block_GetTransaction = Register("System.Block.GetTransaction", Block_GetTransaction, 0_00000400, TriggerType.Application); public static readonly uint System_Transaction_GetHash = Register("System.Transaction.GetHash", Transaction_GetHash, 0_00000400, TriggerType.All); - public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.Application); + public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application); public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application); public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application); public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application); @@ -77,6 +77,11 @@ public static long GetPrice(uint hash, RandomAccessStack stack) return methods[hash].GetPrice(stack); } + public static Dictionary SupportedMethods() + { + return methods.ToDictionary(p => p.Key, p => p.Value.Method); + } + private static long GetStoragePrice(RandomAccessStack stack) { return (stack.Peek(1).GetByteLength() + stack.Peek(2).GetByteLength()) * GasPerByte; @@ -143,8 +148,35 @@ private static bool Runtime_GetTrigger(ApplicationEngine engine) internal static bool CheckWitness(ApplicationEngine engine, UInt160 hash) { - var _hashes_for_verifying = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); - return _hashes_for_verifying.Contains(hash); + if (engine.ScriptContainer is Transaction tx) + { + Cosigner usage = tx.Cosigners.FirstOrDefault(p => p.Account.Equals(hash)); + if (usage is null) return false; + if (usage.Scopes == WitnessScope.Global) return true; + if (usage.Scopes.HasFlag(WitnessScope.CalledByEntry)) + { + if (engine.CallingScriptHash == engine.EntryScriptHash) + return true; + } + if (usage.Scopes.HasFlag(WitnessScope.CustomContracts)) + { + if (usage.AllowedContracts.Contains(engine.CurrentScriptHash)) + return true; + } + if (usage.Scopes.HasFlag(WitnessScope.CustomGroups)) + { + var contract = engine.Snapshot.Contracts[engine.CallingScriptHash]; + // check if current group is the required one + if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(usage.AllowedGroups).Any()) + return true; + } + return false; + } + + // only for non-Transaction types (Block, etc) + + var hashes_for_verifying = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); + return hashes_for_verifying.Contains(hash); } private static bool CheckWitness(ApplicationEngine engine, ECPoint pubkey) @@ -204,15 +236,17 @@ private static bool Runtime_Serialize(ApplicationEngine engine) private static bool Runtime_GetNotifications(ApplicationEngine engine) { - var data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - if (data.Length != UInt160.Length) return false; - if (!engine.CheckArraySize(engine.Notifications.Count)) return false; + byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + if ((data.Length != 0) && (data.Length != UInt160.Length)) return false; - var hash = new UInt160(data); IEnumerable notifications = engine.Notifications; - if (!hash.Equals(UInt160.Zero)) + if (data.Length == UInt160.Length) // must filter by scriptHash + { + var hash = new UInt160(data); notifications = notifications.Where(p => p.ScriptHash == hash); + } + if (!engine.CheckArraySize(notifications.Count())) return false; engine.CurrentContext.EvaluationStack.Push(notifications.Select(u => new VM.Types.Array(new StackItem[] { u.ScriptHash.ToArray(), u.State })).ToArray()); return true; } diff --git a/neo/SmartContract/Iterators/ArrayWrapper.cs b/neo/SmartContract/Iterators/ArrayWrapper.cs index 3284f1317f..883327bee0 100644 --- a/neo/SmartContract/Iterators/ArrayWrapper.cs +++ b/neo/SmartContract/Iterators/ArrayWrapper.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System; using System.Collections.Generic; diff --git a/neo/SmartContract/Iterators/IIterator.cs b/neo/SmartContract/Iterators/IIterator.cs index ca67ae0331..3438286615 100644 --- a/neo/SmartContract/Iterators/IIterator.cs +++ b/neo/SmartContract/Iterators/IIterator.cs @@ -1,4 +1,4 @@ -using Neo.SmartContract.Enumerators; +using Neo.SmartContract.Enumerators; using Neo.VM; namespace Neo.SmartContract.Iterators diff --git a/neo/SmartContract/Iterators/MapWrapper.cs b/neo/SmartContract/Iterators/MapWrapper.cs index d42f940fac..343b946982 100644 --- a/neo/SmartContract/Iterators/MapWrapper.cs +++ b/neo/SmartContract/Iterators/MapWrapper.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System.Collections.Generic; namespace Neo.SmartContract.Iterators diff --git a/neo/SmartContract/Iterators/StorageIterator.cs b/neo/SmartContract/Iterators/StorageIterator.cs index 2dd604d5d8..45c9d21ebd 100644 --- a/neo/SmartContract/Iterators/StorageIterator.cs +++ b/neo/SmartContract/Iterators/StorageIterator.cs @@ -1,4 +1,4 @@ -using Neo.Ledger; +using Neo.Ledger; using Neo.VM; using System.Collections.Generic; diff --git a/neo/SmartContract/JsonSerializer.cs b/neo/SmartContract/JsonSerializer.cs index d635c6c7b8..a4853fc39a 100644 --- a/neo/SmartContract/JsonSerializer.cs +++ b/neo/SmartContract/JsonSerializer.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using Neo.VM; using Neo.VM.Types; using System; diff --git a/neo/SmartContract/LogEventArgs.cs b/neo/SmartContract/LogEventArgs.cs index d124b6f9ab..8c007fe101 100644 --- a/neo/SmartContract/LogEventArgs.cs +++ b/neo/SmartContract/LogEventArgs.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; using System; namespace Neo.SmartContract diff --git a/neo/SmartContract/Manifest/ContractAbi.cs b/neo/SmartContract/Manifest/ContractAbi.cs index 033306f8e6..1d27ae4f1b 100644 --- a/neo/SmartContract/Manifest/ContractAbi.cs +++ b/neo/SmartContract/Manifest/ContractAbi.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System.Linq; namespace Neo.SmartContract.Manifest diff --git a/neo/SmartContract/Manifest/ContractEventDescriptor.cs b/neo/SmartContract/Manifest/ContractEventDescriptor.cs index b06b64f341..00748f9b09 100644 --- a/neo/SmartContract/Manifest/ContractEventDescriptor.cs +++ b/neo/SmartContract/Manifest/ContractEventDescriptor.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System.Linq; namespace Neo.SmartContract.Manifest diff --git a/neo/SmartContract/Manifest/ContractFeatures.cs b/neo/SmartContract/Manifest/ContractFeatures.cs index dd8336f695..9f94a9d3f1 100644 --- a/neo/SmartContract/Manifest/ContractFeatures.cs +++ b/neo/SmartContract/Manifest/ContractFeatures.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.SmartContract.Manifest { diff --git a/neo/SmartContract/Manifest/ContractGroup.cs b/neo/SmartContract/Manifest/ContractGroup.cs index fd73787a09..75359e782a 100644 --- a/neo/SmartContract/Manifest/ContractGroup.cs +++ b/neo/SmartContract/Manifest/ContractGroup.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO.Json; diff --git a/neo/SmartContract/Manifest/ContractManifest.cs b/neo/SmartContract/Manifest/ContractManifest.cs index 967a10e856..ebbcd61f2c 100644 --- a/neo/SmartContract/Manifest/ContractManifest.cs +++ b/neo/SmartContract/Manifest/ContractManifest.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.IO.Json; using System.IO; using System.Linq; diff --git a/neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/neo/SmartContract/Manifest/ContractMethodDescriptor.cs index c631aa0450..476ec546a6 100644 --- a/neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System; using System.Linq; diff --git a/neo/SmartContract/Manifest/ContractParameterDefinition.cs b/neo/SmartContract/Manifest/ContractParameterDefinition.cs index af07315a3a..3f184d6ba8 100644 --- a/neo/SmartContract/Manifest/ContractParameterDefinition.cs +++ b/neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System; namespace Neo.SmartContract.Manifest diff --git a/neo/SmartContract/Manifest/ContractPermission.cs b/neo/SmartContract/Manifest/ContractPermission.cs index b7d1d54bb9..890de9d200 100644 --- a/neo/SmartContract/Manifest/ContractPermission.cs +++ b/neo/SmartContract/Manifest/ContractPermission.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System; using System.Linq; diff --git a/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs b/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs index 7b64a66935..909ae27e42 100644 --- a/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs +++ b/neo/SmartContract/Manifest/ContractPermissionDescriptor.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.IO.Json; using System; diff --git a/neo/SmartContract/Manifest/WildCardContainer.cs b/neo/SmartContract/Manifest/WildCardContainer.cs index aab6ba838e..12dbac60fe 100644 --- a/neo/SmartContract/Manifest/WildCardContainer.cs +++ b/neo/SmartContract/Manifest/WildCardContainer.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System; using System.Collections; using System.Collections.Generic; diff --git a/neo/SmartContract/Native/ContractMethodAttribute.cs b/neo/SmartContract/Native/ContractMethodAttribute.cs index 393737d92e..42b414eed6 100644 --- a/neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/neo/SmartContract/Native/ContractMethodAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.SmartContract.Native { diff --git a/neo/SmartContract/Native/ContractMethodMetadata.cs b/neo/SmartContract/Native/ContractMethodMetadata.cs index f63344eec9..9267bb7db9 100644 --- a/neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/neo/SmartContract/Native/ContractMethodMetadata.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using System; using VMArray = Neo.VM.Types.Array; diff --git a/neo/SmartContract/Native/NativeContract.cs b/neo/SmartContract/Native/NativeContract.cs index 08971dcf6b..546743fc49 100644 --- a/neo/SmartContract/Native/NativeContract.cs +++ b/neo/SmartContract/Native/NativeContract.cs @@ -1,4 +1,4 @@ -#pragma warning disable IDE0060 +#pragma warning disable IDE0060 using Neo.IO; using Neo.Ledger; diff --git a/neo/SmartContract/Native/PolicyContract.cs b/neo/SmartContract/Native/PolicyContract.cs index 2109e8b65d..3ffe8199e5 100644 --- a/neo/SmartContract/Native/PolicyContract.cs +++ b/neo/SmartContract/Native/PolicyContract.cs @@ -1,8 +1,9 @@ -#pragma warning disable IDE0051 +#pragma warning disable IDE0051 #pragma warning disable IDE0060 using Neo.IO; using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract.Manifest; using Neo.VM; @@ -20,12 +21,21 @@ public sealed class PolicyContract : NativeContract private const byte Prefix_MaxTransactionsPerBlock = 23; private const byte Prefix_FeePerByte = 10; private const byte Prefix_BlockedAccounts = 15; + private const byte Prefix_MaxBlockSize = 16; public PolicyContract() { Manifest.Features = ContractFeatures.HasStorage; } + internal bool CheckPolicy(Transaction tx, Snapshot snapshot) + { + UInt160[] blockedAccounts = GetBlockedAccounts(snapshot); + if (blockedAccounts.Intersect(tx.GetScriptHashesForVerifying(snapshot)).Any()) + return false; + return true; + } + private bool CheckValidators(ApplicationEngine engine) { UInt256 prev_hash = engine.Snapshot.PersistingBlock.PrevHash; @@ -36,6 +46,10 @@ private bool CheckValidators(ApplicationEngine engine) internal override bool Initialize(ApplicationEngine engine) { if (!base.Initialize(engine)) return false; + engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_MaxBlockSize), new StorageItem + { + Value = BitConverter.GetBytes(1024u * 256u) + }); engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_MaxTransactionsPerBlock), new StorageItem { Value = BitConverter.GetBytes(512u) @@ -62,6 +76,17 @@ public uint GetMaxTransactionsPerBlock(Snapshot snapshot) return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxTransactionsPerBlock)].Value, 0); } + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] + private StackItem GetMaxBlockSize(ApplicationEngine engine, VMArray args) + { + return GetMaxBlockSize(engine.Snapshot); + } + + public uint GetMaxBlockSize(Snapshot snapshot) + { + return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxBlockSize)].Value, 0); + } + [ContractMethod(0_01000000, ContractParameterType.Integer, SafeMethod = true)] private StackItem GetFeePerByte(ApplicationEngine engine, VMArray args) { @@ -84,6 +109,17 @@ public UInt160[] GetBlockedAccounts(Snapshot snapshot) return snapshot.Storages[CreateStorageKey(Prefix_BlockedAccounts)].Value.AsSerializableArray(); } + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" })] + private StackItem SetMaxBlockSize(ApplicationEngine engine, VMArray args) + { + if (!CheckValidators(engine)) return false; + uint value = (uint)args[0].GetBigInteger(); + if (Network.P2P.Message.PayloadMaxSize <= value) return false; + StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxBlockSize)); + storage.Value = BitConverter.GetBytes(value); + return true; + } + [ContractMethod(0_03000000, ContractParameterType.Boolean, ParameterTypes = new[] { ContractParameterType.Integer }, ParameterNames = new[] { "value" })] private StackItem SetMaxTransactionsPerBlock(ApplicationEngine engine, VMArray args) { diff --git a/neo/SmartContract/Native/Tokens/GasToken.cs b/neo/SmartContract/Native/Tokens/GasToken.cs index fd39979670..7ef522dab7 100644 --- a/neo/SmartContract/Native/Tokens/GasToken.cs +++ b/neo/SmartContract/Native/Tokens/GasToken.cs @@ -1,4 +1,4 @@ -#pragma warning disable IDE0051 +#pragma warning disable IDE0051 using Neo.Cryptography.ECC; using Neo.Ledger; diff --git a/neo/SmartContract/Native/Tokens/NeoToken.cs b/neo/SmartContract/Native/Tokens/NeoToken.cs index 6de1ac1970..7b28bd0923 100644 --- a/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -93,7 +93,7 @@ private BigInteger CalculateBonus(Snapshot snapshot, BigInteger value, uint star } amount += (iend - istart) * Blockchain.GenerationAmount[ustart]; } - amount += GAS.GetSysFeeAmount(snapshot, end - 1) - (start == 0 ? 0 : GAS.GetSysFeeAmount(snapshot, start - 1)); + amount += (GAS.GetSysFeeAmount(snapshot, end - 1) - (start == 0 ? 0 : GAS.GetSysFeeAmount(snapshot, start - 1))) / GAS.Factor; return value * amount * GAS.Factor / TotalAmount; } @@ -201,7 +201,8 @@ private StackItem GetRegisteredValidators(ApplicationEngine engine, VMArray args public IEnumerable<(ECPoint PublicKey, BigInteger Votes)> GetRegisteredValidators(Snapshot snapshot) { - return snapshot.Storages.Find(new[] { Prefix_Validator }).Select(p => + byte[] prefix_key = StorageKey.CreateSearchPrefix(Hash, new[] { Prefix_Validator }); + return snapshot.Storages.Find(prefix_key).Select(p => ( p.Key.Key.Skip(1).ToArray().AsSerializable(), ValidatorState.FromByteArray(p.Value.Value).Votes diff --git a/neo/SmartContract/Native/Tokens/Nep5AccountState.cs b/neo/SmartContract/Native/Tokens/Nep5AccountState.cs index 2cc19f4619..aa62cf4bec 100644 --- a/neo/SmartContract/Native/Tokens/Nep5AccountState.cs +++ b/neo/SmartContract/Native/Tokens/Nep5AccountState.cs @@ -1,4 +1,4 @@ -using Neo.VM; +using Neo.VM; using Neo.VM.Types; using System.Numerics; @@ -19,7 +19,7 @@ public Nep5AccountState(byte[] data) public void FromByteArray(byte[] data) { - FromStruct((Struct)data.DeserializeStackItem(16, 32)); + FromStruct((Struct)data.DeserializeStackItem(16, 34)); } protected virtual void FromStruct(Struct @struct) diff --git a/neo/SmartContract/Native/Tokens/Nep5Token.cs b/neo/SmartContract/Native/Tokens/Nep5Token.cs index 6a2e1c78b3..a95885a665 100644 --- a/neo/SmartContract/Native/Tokens/Nep5Token.cs +++ b/neo/SmartContract/Native/Tokens/Nep5Token.cs @@ -1,4 +1,4 @@ -#pragma warning disable IDE0060 +#pragma warning disable IDE0060 using Neo.Ledger; using Neo.Persistence; diff --git a/neo/SmartContract/NefFile.cs b/neo/SmartContract/NefFile.cs new file mode 100644 index 0000000000..e0068b7213 --- /dev/null +++ b/neo/SmartContract/NefFile.cs @@ -0,0 +1,129 @@ +using Neo.Cryptography; +using Neo.IO; +using System; +using System.IO; + +namespace Neo.SmartContract +{ + /// + /// +------------+-----------+------------------------------------------------------------+ + /// | Field | Length | Comment | + /// +------------+-----------+------------------------------------------------------------+ + /// | Magic | 4 bytes | Magic header | + /// | Compiler | 32 bytes | Compiler used | + /// | Version | 16 bytes | Compiler version (Mayor, Minor, Build, Version) | + /// | ScriptHash | 20 bytes | ScriptHash for the script | + /// +------------+-----------+------------------------------------------------------------+ + /// | Checksum | 4 bytes | Sha256 of the header (CRC) | + /// +------------+-----------+------------------------------------------------------------+ + /// | Script | Var bytes | Var bytes for the payload | + /// +------------+-----------+------------------------------------------------------------+ + /// + public class NefFile : ISerializable + { + /// + /// NEO Executable Format 3 (NEF3) + /// + private const uint Magic = 0x3346454E; + + /// + /// Compiler + /// + public string Compiler { get; set; } + + /// + /// Version + /// + public Version Version { get; set; } + + /// + /// Script Hash + /// + public UInt160 ScriptHash { get; set; } + + /// + /// Checksum + /// + public uint CheckSum { get; set; } + + /// + /// Script + /// + public byte[] Script { get; set; } + + private const int HeaderSize = + sizeof(uint) + // Magic + 32 + // Compiler + (sizeof(int) * 4) + // Version + UInt160.Length + // ScriptHash + sizeof(uint); // Checksum + + public int Size => + HeaderSize + // Header + Script.GetVarSize(); // Script + + public void Serialize(BinaryWriter writer) + { + writer.Write(Magic); + writer.WriteFixedString(Compiler, 32); + + // Version + writer.Write(Version.Major); + writer.Write(Version.Minor); + writer.Write(Version.Build); + writer.Write(Version.Revision); + + writer.Write(ScriptHash); + writer.Write(CheckSum); + writer.WriteVarBytes(Script ?? new byte[0]); + } + + public void Deserialize(BinaryReader reader) + { + if (reader.ReadUInt32() != Magic) + { + throw new FormatException("Wrong magic"); + } + + Compiler = reader.ReadFixedString(32); + Version = new Version(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); + ScriptHash = reader.ReadSerializable(); + CheckSum = reader.ReadUInt32(); + + if (CheckSum != ComputeChecksum(this)) + { + throw new FormatException("CRC verification fail"); + } + + Script = reader.ReadVarBytes(1024 * 1024); + + if (Script.ToScriptHash() != ScriptHash) + { + throw new FormatException("ScriptHash is different"); + } + } + + /// + /// Compute checksum for a file + /// + /// File + /// Return checksum + public static uint ComputeChecksum(NefFile file) + { + using (var ms = new MemoryStream()) + using (var wr = new BinaryWriter(ms)) + { + file.Serialize(wr); + wr.Flush(); + + // Read header without CRC + + var buffer = new byte[HeaderSize - sizeof(uint)]; + ms.Seek(0, SeekOrigin.Begin); + ms.Read(buffer, 0, buffer.Length); + + return BitConverter.ToUInt32(buffer.Sha256(), 0); + } + } + } +} diff --git a/neo/SmartContract/NotifyEventArgs.cs b/neo/SmartContract/NotifyEventArgs.cs index f4e4e169b8..8fab961408 100644 --- a/neo/SmartContract/NotifyEventArgs.cs +++ b/neo/SmartContract/NotifyEventArgs.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; using Neo.VM; using System; diff --git a/neo/SmartContract/StackItemType.cs b/neo/SmartContract/StackItemType.cs index 94ee4f23ba..b47999e157 100644 --- a/neo/SmartContract/StackItemType.cs +++ b/neo/SmartContract/StackItemType.cs @@ -1,4 +1,4 @@ -namespace Neo.SmartContract +namespace Neo.SmartContract { internal enum StackItemType : byte { diff --git a/neo/SmartContract/StorageContext.cs b/neo/SmartContract/StorageContext.cs index 1c3336c9ac..abea2e343d 100644 --- a/neo/SmartContract/StorageContext.cs +++ b/neo/SmartContract/StorageContext.cs @@ -1,4 +1,4 @@ -namespace Neo.SmartContract +namespace Neo.SmartContract { internal class StorageContext { diff --git a/neo/SmartContract/WitnessWrapper.cs b/neo/SmartContract/WitnessWrapper.cs index f67690cad5..28019be0c2 100644 --- a/neo/SmartContract/WitnessWrapper.cs +++ b/neo/SmartContract/WitnessWrapper.cs @@ -1,4 +1,4 @@ -using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads; using Neo.Persistence; using System.Linq; diff --git a/neo/UInt160.cs b/neo/UInt160.cs index 0c6bea4b5a..0052d45569 100644 --- a/neo/UInt160.cs +++ b/neo/UInt160.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; diff --git a/neo/UInt256.cs b/neo/UInt256.cs index ec79ae21ea..72719f0e90 100644 --- a/neo/UInt256.cs +++ b/neo/UInt256.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; diff --git a/neo/UIntBase.cs b/neo/UIntBase.cs index 22431c4cae..af26e36d0a 100644 --- a/neo/UIntBase.cs +++ b/neo/UIntBase.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using System; using System.IO; using System.Linq; diff --git a/neo/VM/Helper.cs b/neo/VM/Helper.cs index 6a7a37d7d5..d63bebb89a 100644 --- a/neo/VM/Helper.cs +++ b/neo/VM/Helper.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.SmartContract; using Neo.VM.Types; @@ -162,6 +162,25 @@ public static ScriptBuilder EmitSysCall(this ScriptBuilder sb, uint method, para return sb.EmitSysCall(method); } + /// + /// Generate scripts to call a specific method from a specific contract. + /// + /// contract script hash + /// contract operation + /// operation arguments + /// + public static byte[] MakeScript(this UInt160 scriptHash, string operation, params object[] args) + { + using (ScriptBuilder sb = new ScriptBuilder()) + { + if (args.Length > 0) + sb.EmitAppCall(scriptHash, operation, args); + else + sb.EmitAppCall(scriptHash, operation); + return sb.ToArray(); + } + } + public static ContractParameter ToParameter(this StackItem item) { return ToParameter(item, null); @@ -228,5 +247,67 @@ private static ContractParameter ToParameter(StackItem item, List> context) + { + StackItem stackItem = null; + switch (parameter.Type) + { + case ContractParameterType.Array: + if (context is null) + context = new List>(); + else + stackItem = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter))?.Item1; + if (stackItem is null) + { + stackItem = ((IList)parameter.Value).Select(p => ToStackItem(p, context)).ToList(); + context.Add(new Tuple(stackItem, parameter)); + } + break; + case ContractParameterType.Map: + if (context is null) + context = new List>(); + else + stackItem = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter))?.Item1; + if (stackItem is null) + { + stackItem = new Map(((IList>)parameter.Value).ToDictionary(p => ToStackItem(p.Key, context), p => ToStackItem(p.Value, context))); + context.Add(new Tuple(stackItem, parameter)); + } + break; + case ContractParameterType.Boolean: + stackItem = (bool)parameter.Value; + break; + case ContractParameterType.ByteArray: + case ContractParameterType.Signature: + stackItem = (byte[])parameter.Value; + break; + case ContractParameterType.Integer: + stackItem = (BigInteger)parameter.Value; + break; + case ContractParameterType.Hash160: + stackItem = ((UInt160)parameter.Value).ToArray(); + break; + case ContractParameterType.Hash256: + stackItem = ((UInt256)parameter.Value).ToArray(); + break; + case ContractParameterType.PublicKey: + stackItem = ((ECPoint)parameter.Value).EncodePoint(true); + break; + case ContractParameterType.String: + stackItem = (string)parameter.Value; + break; + case ContractParameterType.InteropInterface: + break; + default: + throw new ArgumentException($"ContractParameterType({parameter.Type}) is not supported to StackItem."); + } + return stackItem; + } } } diff --git a/neo/Wallets/AssetDescriptor.cs b/neo/Wallets/AssetDescriptor.cs index 49144834bb..dca0b10b25 100644 --- a/neo/Wallets/AssetDescriptor.cs +++ b/neo/Wallets/AssetDescriptor.cs @@ -1,4 +1,4 @@ -using Neo.SmartContract; +using Neo.SmartContract; using Neo.VM; using System; diff --git a/neo/Wallets/Helper.cs b/neo/Wallets/Helper.cs index 4b26982db1..5960876fe2 100644 --- a/neo/Wallets/Helper.cs +++ b/neo/Wallets/Helper.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using System; diff --git a/neo/Wallets/KeyPair.cs b/neo/Wallets/KeyPair.cs index 0125d10f99..afea8e822a 100644 --- a/neo/Wallets/KeyPair.cs +++ b/neo/Wallets/KeyPair.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.SmartContract; using System; using System.Linq; diff --git a/neo/Wallets/NEP6/NEP6Account.cs b/neo/Wallets/NEP6/NEP6Account.cs index ddeace2523..990612d542 100644 --- a/neo/Wallets/NEP6/NEP6Account.cs +++ b/neo/Wallets/NEP6/NEP6Account.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using System; namespace Neo.Wallets.NEP6 diff --git a/neo/Wallets/NEP6/NEP6Contract.cs b/neo/Wallets/NEP6/NEP6Contract.cs index a3dd36f414..45f0b5e157 100644 --- a/neo/Wallets/NEP6/NEP6Contract.cs +++ b/neo/Wallets/NEP6/NEP6Contract.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using Neo.SmartContract; using System.Linq; diff --git a/neo/Wallets/NEP6/NEP6Wallet.cs b/neo/Wallets/NEP6/NEP6Wallet.cs index 9fe133d9ce..f76640e886 100644 --- a/neo/Wallets/NEP6/NEP6Wallet.cs +++ b/neo/Wallets/NEP6/NEP6Wallet.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; using Neo.SmartContract; using System; using System.Collections.Generic; @@ -216,9 +216,9 @@ public override WalletAccount Import(string wif) return account; } - public override WalletAccount Import(string nep2, string passphrase) + public override WalletAccount Import(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8) { - KeyPair key = new KeyPair(GetPrivateKeyFromNEP2(nep2, passphrase)); + KeyPair key = new KeyPair(GetPrivateKeyFromNEP2(nep2, passphrase, N, r, p)); NEP6Contract contract = new NEP6Contract { Script = Contract.CreateSignatureRedeemScript(key.PublicKey), diff --git a/neo/Wallets/NEP6/ScryptParameters.cs b/neo/Wallets/NEP6/ScryptParameters.cs index 018f0bfd46..a987159ad6 100644 --- a/neo/Wallets/NEP6/ScryptParameters.cs +++ b/neo/Wallets/NEP6/ScryptParameters.cs @@ -1,4 +1,4 @@ -using Neo.IO.Json; +using Neo.IO.Json; namespace Neo.Wallets.NEP6 { diff --git a/neo/Wallets/NEP6/WalletLocker.cs b/neo/Wallets/NEP6/WalletLocker.cs index 6f28779361..45574b5cfe 100644 --- a/neo/Wallets/NEP6/WalletLocker.cs +++ b/neo/Wallets/NEP6/WalletLocker.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Neo.Wallets.NEP6 { diff --git a/neo/Wallets/SQLite/Account.cs b/neo/Wallets/SQLite/Account.cs index 5c91556fb2..ce8ed1e01e 100644 --- a/neo/Wallets/SQLite/Account.cs +++ b/neo/Wallets/SQLite/Account.cs @@ -1,4 +1,4 @@ -namespace Neo.Wallets.SQLite +namespace Neo.Wallets.SQLite { internal class Account { diff --git a/neo/Wallets/SQLite/Address.cs b/neo/Wallets/SQLite/Address.cs index 79ef9e1031..a5abe4dcfc 100644 --- a/neo/Wallets/SQLite/Address.cs +++ b/neo/Wallets/SQLite/Address.cs @@ -1,4 +1,4 @@ -namespace Neo.Wallets.SQLite +namespace Neo.Wallets.SQLite { internal class Address { diff --git a/neo/Wallets/SQLite/Contract.cs b/neo/Wallets/SQLite/Contract.cs index a69c0f0968..96ba3c51a6 100644 --- a/neo/Wallets/SQLite/Contract.cs +++ b/neo/Wallets/SQLite/Contract.cs @@ -1,4 +1,4 @@ -namespace Neo.Wallets.SQLite +namespace Neo.Wallets.SQLite { internal class Contract { diff --git a/neo/Wallets/SQLite/Key.cs b/neo/Wallets/SQLite/Key.cs index 97c063a3f2..f493b7310d 100644 --- a/neo/Wallets/SQLite/Key.cs +++ b/neo/Wallets/SQLite/Key.cs @@ -1,4 +1,4 @@ -namespace Neo.Wallets.SQLite +namespace Neo.Wallets.SQLite { internal class Key { diff --git a/neo/Wallets/SQLite/UserWallet.cs b/neo/Wallets/SQLite/UserWallet.cs index c66734f172..efb477a61a 100644 --- a/neo/Wallets/SQLite/UserWallet.cs +++ b/neo/Wallets/SQLite/UserWallet.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Neo.Cryptography; using Neo.IO; using Neo.SmartContract; @@ -123,12 +123,12 @@ private void AddAccount(UserWalletAccount account, bool is_import) } //add address { - Address db_address = ctx.Addresses.FirstOrDefault(p => p.ScriptHash == account.Contract.ScriptHash.ToArray()); + Address db_address = ctx.Addresses.FirstOrDefault(p => p.ScriptHash == account.ScriptHash.ToArray()); if (db_address == null) { ctx.Addresses.Add(new Address { - ScriptHash = account.Contract.ScriptHash.ToArray() + ScriptHash = account.ScriptHash.ToArray() }); } } diff --git a/neo/Wallets/SQLite/UserWalletAccount.cs b/neo/Wallets/SQLite/UserWalletAccount.cs index 2d3ac74836..d586f59d36 100644 --- a/neo/Wallets/SQLite/UserWalletAccount.cs +++ b/neo/Wallets/SQLite/UserWalletAccount.cs @@ -1,4 +1,4 @@ -namespace Neo.Wallets.SQLite +namespace Neo.Wallets.SQLite { internal class UserWalletAccount : WalletAccount { diff --git a/neo/Wallets/SQLite/VerificationContract.cs b/neo/Wallets/SQLite/VerificationContract.cs index 41f8d44055..587a0fe468 100644 --- a/neo/Wallets/SQLite/VerificationContract.cs +++ b/neo/Wallets/SQLite/VerificationContract.cs @@ -1,4 +1,4 @@ -using Neo.IO; +using Neo.IO; using Neo.SmartContract; using Neo.VM; using System; diff --git a/neo/Wallets/SQLite/WalletDataContext.cs b/neo/Wallets/SQLite/WalletDataContext.cs index cba60cb00d..6d1ec957a8 100644 --- a/neo/Wallets/SQLite/WalletDataContext.cs +++ b/neo/Wallets/SQLite/WalletDataContext.cs @@ -1,4 +1,4 @@ -using Microsoft.Data.Sqlite; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace Neo.Wallets.SQLite diff --git a/neo/Wallets/TransferOutput.cs b/neo/Wallets/TransferOutput.cs index 6e45baa04b..9431f5616d 100644 --- a/neo/Wallets/TransferOutput.cs +++ b/neo/Wallets/TransferOutput.cs @@ -1,4 +1,4 @@ -namespace Neo.Wallets +namespace Neo.Wallets { public class TransferOutput { diff --git a/neo/Wallets/Wallet.cs b/neo/Wallets/Wallet.cs index fc490dbea7..5149c2b2fe 100644 --- a/neo/Wallets/Wallet.cs +++ b/neo/Wallets/Wallet.cs @@ -1,4 +1,4 @@ -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -200,9 +200,9 @@ public virtual WalletAccount Import(string wif) return account; } - public virtual WalletAccount Import(string nep2, string passphrase) + public virtual WalletAccount Import(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8) { - byte[] privateKey = GetPrivateKeyFromNEP2(nep2, passphrase); + byte[] privateKey = GetPrivateKeyFromNEP2(nep2, passphrase, N, r, p); WalletAccount account = CreateAccount(privateKey); Array.Clear(privateKey, 0, privateKey.Length); return account; @@ -223,7 +223,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null } using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { - HashSet cosigners = new HashSet(); + HashSet cosignerList = new HashSet(); byte[] script; List<(UInt160 Account, BigInteger Value)> balances_gas = null; using (ScriptBuilder sb = new ScriptBuilder()) @@ -250,7 +250,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null { balances = balances.OrderBy(p => p.Value).ToList(); var balances_used = FindPayingAccounts(balances, output.Value.Value); - cosigners.UnionWith(balances_used.Select(p => p.Account)); + cosignerList.UnionWith(balances_used.Select(p => p.Account)); foreach (var (account, value) in balances_used) { sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value); @@ -264,12 +264,20 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null } if (balances_gas is null) balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); - TransactionAttribute[] attributes = cosigners.Select(p => new TransactionAttribute { Usage = TransactionAttributeUsage.Cosigner, Data = p.ToArray() }).ToArray(); - return MakeTransaction(snapshot, attributes, script, balances_gas); + + var cosigners = cosignerList.Select(p => + new Cosigner() + { + // default access for transfers should be valid only for first invocation + Scopes = WitnessScope.CalledByEntry, + Account = new UInt160(p.ToArray()) + }).ToArray(); + + return MakeTransaction(snapshot, script, new TransactionAttribute[0], cosigners, balances_gas); } } - public Transaction MakeTransaction(TransactionAttribute[] attributes, byte[] script, UInt160 sender = null) + public Transaction MakeTransaction(byte[] script, UInt160 sender = null, TransactionAttribute[] attributes = null, Cosigner[] cosigners = null) { UInt160[] accounts; if (sender is null) @@ -285,11 +293,11 @@ public Transaction MakeTransaction(TransactionAttribute[] attributes, byte[] scr using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { var balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList(); - return MakeTransaction(snapshot, attributes, script, balances_gas); + return MakeTransaction(snapshot, script, attributes ?? new TransactionAttribute[0], cosigners ?? new Cosigner[0], balances_gas); } } - private Transaction MakeTransaction(Snapshot snapshot, TransactionAttribute[] attributes, byte[] script, List<(UInt160 Account, BigInteger Value)> balances_gas) + private Transaction MakeTransaction(Snapshot snapshot, byte[] script, TransactionAttribute[] attributes, Cosigner[] cosigners, List<(UInt160 Account, BigInteger Value)> balances_gas) { Random rand = new Random(); foreach (var (account, value) in balances_gas) @@ -301,9 +309,11 @@ private Transaction MakeTransaction(Snapshot snapshot, TransactionAttribute[] at Script = script, Sender = account, ValidUntilBlock = snapshot.Height + Transaction.MaxValidUntilBlockIncrement, - Attributes = attributes + Attributes = attributes, + Cosigners = cosigners }; - using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, tx, testMode: true)) + // will try to execute 'transfer' script to check if it works + using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot.Clone(), tx, testMode: true)) { if (engine.State.HasFlag(VMState.FAULT)) throw new InvalidOperationException($"Failed execution for '{script.ToHexString()}'"); @@ -318,33 +328,17 @@ private Transaction MakeTransaction(Snapshot snapshot, TransactionAttribute[] at tx.SystemFee -= remainder; } } + UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); - int size = Transaction.HeaderSize + attributes.GetVarSize() + script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + + // base size for transaction: includes const_header + attributes + cosigners with scopes + script + hashes + int size = Transaction.HeaderSize + attributes.GetVarSize() + cosigners.GetVarSize() + script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + foreach (UInt160 hash in hashes) { - script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script; - if (script is null) continue; - if (script.IsSignatureContract()) - { - size += 66 + script.GetVarSize(); - tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); - } - else if (script.IsMultiSigContract(out int m, out int n)) - { - int size_inv = 65 * m; - size += IO.Helper.GetVarSize(size_inv) + size_inv + script.GetVarSize(); - tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m; - using (ScriptBuilder sb = new ScriptBuilder()) - tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; - tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n; - using (ScriptBuilder sb = new ScriptBuilder()) - tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n; - } - else - { - //We can support more contract types in the future. - } + byte[] witness_script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script; + if (witness_script is null) continue; + tx.NetworkFee += CalculateNetWorkFee(witness_script, ref size); } tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); if (value >= tx.SystemFee + tx.NetworkFee) return tx; @@ -352,6 +346,35 @@ private Transaction MakeTransaction(Snapshot snapshot, TransactionAttribute[] at throw new InvalidOperationException("Insufficient GAS"); } + public static long CalculateNetWorkFee(byte[] witness_script, ref int size) + { + long networkFee = 0; + + if (witness_script.IsSignatureContract()) + { + size += 66 + witness_script.GetVarSize(); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null); + } + else if (witness_script.IsMultiSigContract(out int m, out int n)) + { + int size_inv = 65 * m; + size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize(); + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m; + using (ScriptBuilder sb = new ScriptBuilder()) + networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; + networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n; + using (ScriptBuilder sb = new ScriptBuilder()) + networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; + networkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n; + } + else + { + //We can support more contract types in the future. + } + + return networkFee; + } + public bool Sign(ContractParametersContext context) { bool fSuccess = false; diff --git a/neo/Wallets/WalletAccount.cs b/neo/Wallets/WalletAccount.cs index a427a9e50b..5e2408a1d0 100644 --- a/neo/Wallets/WalletAccount.cs +++ b/neo/Wallets/WalletAccount.cs @@ -1,4 +1,4 @@ -using Neo.SmartContract; +using Neo.SmartContract; namespace Neo.Wallets { diff --git a/neo/neo.csproj b/neo/neo.csproj index e811c280dd..724090c004 100644 --- a/neo/neo.csproj +++ b/neo/neo.csproj @@ -1,9 +1,9 @@ - + 2015-2019 The Neo Project Neo - 2.10.1 + 3.0.0-preview1 The Neo Project netstandard2.0;net47 true @@ -21,15 +21,16 @@ - - + + - + + - +