From c64748ecbac3baeb8045b16af0d518398a6ced24 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Fri, 14 Sep 2018 17:54:03 +0800 Subject: [PATCH] refactoring (#288) * refactoring * message priority * add failure reason for `sendrawtransaction` (neo-project/neo-cli#116) * fix bug in `sendrawtransaction` * improve RpcServer and Plugin system * add `unconnected` to RPC command `getpeers` * update dependencies: - Microsoft.AspNetCore.ResponseCompression v2.1.1 - Microsoft.AspNetCore.Server.Kestrel v2.1.1 - Microsoft.AspNetCore.Server.Kestrel.Https v2.1.1 - Microsoft.EntityFrameworkCore.Sqlite v2.1.1 - Microsoft.Extensions.Configuration.Json v2.1.1 * add WebSocket * fix bug in Snapshot.Dispose() * Hides the caught exceptions logs in `RemoteNode` * fix block stuck * fix bug in p2p connection * fix #305 * fix consensus * checked on Fixed8 mul * solve the problem of disconnecting from peers * fix bug in `System.Runtime.GetTime` * add RPC command: `getwalletheight` * fix p2p bug * Prevent error messages when exiting * optimize p2p network * allow user interface to send messages to plugins * change version to 2.9.0 * backward compatible for sendrawtransaction * Solve the problem of resynchronizing blocks * put unverified transactions in the memory pool back into the message queue after the block is persisted * minor change to consensus --- neo.UnitTests/README.md | 1 - neo.UnitTests/TestBlockchain.cs | 161 --- neo.UnitTests/TestMetaDataCache.cs | 10 +- neo.UnitTests/TestTransaction.cs | 5 +- neo.UnitTests/TestUtils.cs | 61 +- neo.UnitTests/TestVerifiable.cs | 7 +- neo.UnitTests/UT_AccountState.cs | 2 +- neo.UnitTests/UT_AssetState.cs | 3 +- neo.UnitTests/UT_Block.cs | 192 +--- neo.UnitTests/UT_ClaimTransaction.cs | 61 +- neo.UnitTests/UT_CoinReference.cs | 10 +- neo.UnitTests/UT_Culture.cs | 3 - neo.UnitTests/UT_Header.cs | 32 +- neo.UnitTests/UT_Helper.cs | 7 +- neo.UnitTests/UT_InvocationTransaction.cs | 11 +- neo.UnitTests/UT_IssueTransaction.cs | 117 --- neo.UnitTests/UT_MinerTransaction.cs | 13 +- neo.UnitTests/UT_SpentCoint.cs | 6 +- neo.UnitTests/UT_SpentCointState.cs | 2 +- neo.UnitTests/UT_StorageItem.cs | 2 +- neo.UnitTests/UT_StorageKey.cs | 5 +- neo.UnitTests/UT_TransactionAttribute.cs | 5 +- neo.UnitTests/UT_TransactionOutput.cs | 5 +- neo.UnitTests/UT_TransactionResult.cs | 6 +- neo.UnitTests/UT_UnspentCoinState.cs | 2 +- neo.UnitTests/UT_ValidatorState.cs | 2 +- neo.UnitTests/UT_Witness.cs | 2 +- neo/Consensus/ConsensusContext.cs | 24 +- neo/Consensus/ConsensusService.cs | 376 +++---- neo/Consensus/PrepareRequest.cs | 4 +- neo/Core/Block.cs | 196 ---- neo/Core/Blockchain.cs | 595 ----------- neo/Core/Helper.cs | 85 -- neo/Core/IVerifiable.cs | 35 - neo/Core/TransactionResult.cs | 17 - neo/Cryptography/Base58.cs | 13 - neo/Cryptography/ECC/ECCurve.cs | 15 - neo/Cryptography/ECC/ECDsa.cs | 24 - neo/Cryptography/ECC/ECPoint.cs | 46 - neo/Cryptography/Helper.cs | 38 +- neo/Cryptography/MerkleTree.cs | 8 - neo/Cryptography/ProtectedMemoryContext.cs | 39 - neo/Fixed8.cs | 2 +- neo/Helper.cs | 15 + neo/IO/Actors/PriorityMailbox.cs | 24 + neo/IO/Actors/PriorityMessageQueue.cs | 46 + neo/IO/Caching/CloneMetaCache.cs | 28 + neo/IO/Caching/DataCache.cs | 232 +++-- neo/IO/Caching/LRUCache.cs | 17 - neo/IO/Caching/MetaDataCache.cs | 29 +- neo/IO/Caching/RelayCache.cs | 2 +- neo/IO/Data/LevelDB/DbMetaDataCache.cs | 39 - neo/IO/ISerializable.cs | 12 - neo/IO/Json/JString.cs | 4 +- neo/IO/Wrappers/ByteWrapper.cs | 26 - neo/IO/Wrappers/SerializableWrapper.cs | 19 +- neo/IO/Wrappers/UInt32Wrapper.cs | 44 + .../LevelDB/ApplicationExecutedEventArgs.cs | 11 - .../Blockchains/LevelDB/LevelDBBlockchain.cs | 713 ------------- neo/{Core => Ledger}/AccountState.cs | 2 +- .../ApplicationExecutionResult.cs | 2 +- neo/{Core => Ledger}/AssetState.cs | 7 +- neo/Ledger/BlockState.cs | 51 + neo/Ledger/Blockchain.cs | 701 +++++++++++++ neo/{Core => Ledger}/CoinState.cs | 2 +- neo/{Core => Ledger}/ContractPropertyState.cs | 2 +- neo/{Core => Ledger}/ContractState.cs | 2 +- neo/Ledger/HashIndexState.cs | 51 + neo/Ledger/HeaderHashList.cs | 46 + neo/Ledger/RelayResultReason.cs | 11 + neo/{Core => Ledger}/SpentCoin.cs | 4 +- neo/{Core => Ledger}/SpentCoinState.cs | 2 +- neo/{Core => Ledger}/StateBase.cs | 2 +- neo/{Core => Ledger}/StorageItem.cs | 2 +- neo/{Core => Ledger}/StorageKey.cs | 6 +- neo/Ledger/TransactionState.cs | 52 + neo/Ledger/TrimmedBlock.cs | 76 ++ neo/{Core => Ledger}/UnspentCoinState.cs | 2 +- neo/{Core => Ledger}/ValidatorState.cs | 2 +- neo/{Core => Ledger}/ValidatorsCountState.cs | 2 +- neo/NeoSystem.cs | 64 ++ neo/Network/InventoryReceivingEventArgs.cs | 14 - neo/Network/InventoryType.cs | 21 - neo/Network/LocalNode.cs | 833 ---------------- neo/Network/Message.cs | 154 --- neo/Network/P2P/Connection.cs | 139 +++ neo/Network/P2P/Helper.cs | 19 + neo/Network/P2P/LocalNode.cs | 187 ++++ neo/Network/P2P/Message.cs | 63 ++ neo/Network/{ => P2P}/Payloads/AddrPayload.cs | 9 +- .../P2P/Payloads}/AssetType.cs | 5 +- neo/Network/P2P/Payloads/Block.cs | 125 +++ .../P2P/Payloads}/BlockBase.cs | 58 +- .../P2P/Payloads}/ClaimTransaction.cs | 36 +- .../P2P/Payloads}/CoinReference.cs | 33 +- .../{ => P2P}/Payloads/ConsensusPayload.cs | 33 +- .../P2P/Payloads}/ContractTransaction.cs | 5 +- .../P2P/Payloads}/EnrollmentTransaction.cs | 28 +- .../{ => P2P}/Payloads/FilterAddPayload.cs | 2 +- .../{ => P2P}/Payloads/FilterLoadPayload.cs | 2 +- .../{ => P2P}/Payloads/GetBlocksPayload.cs | 2 +- neo/{Core => Network/P2P/Payloads}/Header.cs | 35 +- .../{ => P2P}/Payloads/HeadersPayload.cs | 9 +- neo/Network/{ => P2P/Payloads}/IInventory.cs | 6 +- neo/Network/P2P/Payloads/IVerifiable.cs | 18 + neo/Network/{ => P2P}/Payloads/InvPayload.cs | 18 +- neo/Network/P2P/Payloads/InventoryType.cs | 9 + .../P2P/Payloads}/InvocationTransaction.cs | 7 +- .../P2P/Payloads}/IssueTransaction.cs | 32 +- .../{ => P2P}/Payloads/MerkleBlockPayload.cs | 7 +- .../P2P/Payloads}/MinerTransaction.cs | 20 +- .../Payloads/NetworkAddressWithTime.cs | 4 +- .../P2P/Payloads}/PublishTransaction.cs | 5 +- .../P2P/Payloads}/RegisterTransaction.cs | 48 +- .../P2P/Payloads}/StateDescriptor.cs | 17 +- .../P2P/Payloads}/StateTransaction.cs | 13 +- .../P2P/Payloads}/StateType.cs | 2 +- .../P2P/Payloads}/Transaction.cs | 153 +-- .../P2P/Payloads}/TransactionAttribute.cs | 15 +- .../Payloads}/TransactionAttributeUsage.cs | 20 +- .../P2P/Payloads}/TransactionOutput.cs | 21 +- neo/Network/P2P/Payloads/TransactionResult.cs | 8 + .../P2P/Payloads}/TransactionType.cs | 20 +- .../{ => P2P}/Payloads/VersionPayload.cs | 9 +- neo/{Core => Network/P2P/Payloads}/Witness.cs | 7 +- neo/Network/P2P/Peer.cs | 219 ++++ neo/Network/P2P/ProtocolHandler.cs | 328 ++++++ neo/Network/P2P/RemoteNode.cs | 243 +++++ neo/Network/P2P/TaskManager.cs | 225 +++++ neo/Network/P2P/TaskSession.cs | 24 + neo/Network/RPC/RpcServer.cs | 415 ++++++-- neo/Network/RemoteNode.cs | 543 ---------- neo/Network/TcpRemoteNode.cs | 117 --- neo/Network/UPnP.cs | 29 +- neo/Network/WebSocketRemoteNode.cs | 77 -- neo/Persistence/CloneSnapshot.cs | 42 + neo/Persistence/Helper.cs | 117 +++ neo/Persistence/IPersistence.cs | 24 + .../Data => Persistence}/LevelDB/DbCache.cs | 24 +- neo/Persistence/LevelDB/DbMetaDataCache.cs | 42 + neo/Persistence/LevelDB/DbSnapshot.cs | 62 ++ neo/Persistence/LevelDB/LevelDBStore.cs | 108 ++ .../LevelDB/Prefixes.cs} | 8 +- neo/Persistence/Snapshot.cs | 273 +++++ neo/Persistence/Store.cs | 40 + neo/Plugins/Helper.cs | 15 + neo/Plugins/ILogPlugin.cs | 7 + neo/Plugins/IPolicyPlugin.cs | 11 + neo/Plugins/IRpcPlugin.cs | 10 + neo/Plugins/LogLevel.cs | 11 + neo/Plugins/Plugin.cs | 33 +- neo/Plugins/PolicyPlugin.cs | 20 - neo/Plugins/RpcPlugin.cs | 20 - neo/Settings.cs | 2 +- neo/SmartContract/ApplicationEngine.cs | 63 +- neo/SmartContract/CachedScriptTable.cs | 26 - neo/SmartContract/Contract.cs | 10 +- neo/SmartContract/ContractParameterType.cs | 18 - .../ContractParametersContext.cs | 24 +- neo/SmartContract/Helper.cs | 54 + .../Iterators/StorageIterator.cs | 2 +- .../{StateReader.cs => NeoService.cs} | 937 +++++------------- neo/SmartContract/StandardService.cs | 633 ++++++++++++ neo/SmartContract/StateMachine.cs | 309 ------ neo/SmartContract/TriggerType.cs | 20 + neo/UIntBase.cs | 4 - neo/Wallets/AssetDescriptor.cs | 4 +- neo/Wallets/BalanceEventArgs.cs | 2 +- neo/Wallets/Coin.cs | 7 +- neo/Wallets/Helper.cs | 34 + neo/Wallets/KeyPair.cs | 66 +- .../Wallets/NEP6/NEP6Account.cs | 7 +- .../Wallets/NEP6/NEP6Contract.cs | 2 +- .../Wallets/NEP6/NEP6Wallet.cs | 18 +- .../Wallets/NEP6/ScryptParameters.cs | 2 +- .../Wallets/NEP6/WalletLocker.cs | 2 +- .../SQLite}/Account.cs | 2 +- .../SQLite}/Address.cs | 2 +- .../SQLite}/Contract.cs | 2 +- .../EntityFramework => Wallets/SQLite}/Key.cs | 2 +- .../SQLite}/UserWallet.cs | 62 +- .../SQLite}/UserWalletAccount.cs | 4 +- .../SQLite}/VerificationContract.cs | 26 +- .../SQLite}/WalletDataContext.cs | 8 +- neo/Wallets/TransferOutput.cs | 2 +- neo/Wallets/Wallet.cs | 35 +- neo/Wallets/WalletAccount.cs | 2 +- neo/Wallets/WalletIndexer.cs | 82 +- neo/neo.csproj | 22 +- neo/policy.json | 6 - 190 files changed, 5840 insertions(+), 6623 deletions(-) delete mode 100644 neo.UnitTests/TestBlockchain.cs delete mode 100644 neo.UnitTests/UT_IssueTransaction.cs delete mode 100644 neo/Core/Block.cs delete mode 100644 neo/Core/Blockchain.cs delete mode 100644 neo/Core/Helper.cs delete mode 100644 neo/Core/IVerifiable.cs delete mode 100644 neo/Core/TransactionResult.cs delete mode 100644 neo/Cryptography/ProtectedMemoryContext.cs create mode 100644 neo/IO/Actors/PriorityMailbox.cs create mode 100644 neo/IO/Actors/PriorityMessageQueue.cs create mode 100644 neo/IO/Caching/CloneMetaCache.cs delete mode 100644 neo/IO/Caching/LRUCache.cs delete mode 100644 neo/IO/Data/LevelDB/DbMetaDataCache.cs delete mode 100644 neo/IO/Wrappers/ByteWrapper.cs create mode 100644 neo/IO/Wrappers/UInt32Wrapper.cs delete mode 100644 neo/Implementations/Blockchains/LevelDB/ApplicationExecutedEventArgs.cs delete mode 100644 neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs rename neo/{Core => Ledger}/AccountState.cs (99%) rename neo/{Implementations/Blockchains/LevelDB => Ledger}/ApplicationExecutionResult.cs (90%) rename neo/{Core => Ledger}/AssetState.cs (97%) create mode 100644 neo/Ledger/BlockState.cs create mode 100644 neo/Ledger/Blockchain.cs rename neo/{Core => Ledger}/CoinState.cs (93%) rename neo/{Core => Ledger}/ContractPropertyState.cs (90%) rename neo/{Core => Ledger}/ContractState.cs (99%) create mode 100644 neo/Ledger/HashIndexState.cs create mode 100644 neo/Ledger/HeaderHashList.cs create mode 100644 neo/Ledger/RelayResultReason.cs rename neo/{Core => Ledger}/SpentCoin.cs (77%) rename neo/{Core => Ledger}/SpentCoinState.cs (98%) rename neo/{Core => Ledger}/StateBase.cs (97%) rename neo/{Core => Ledger}/StorageItem.cs (97%) rename neo/{Core => Ledger}/StorageKey.cs (90%) create mode 100644 neo/Ledger/TransactionState.cs create mode 100644 neo/Ledger/TrimmedBlock.cs rename neo/{Core => Ledger}/UnspentCoinState.cs (97%) rename neo/{Core => Ledger}/ValidatorState.cs (98%) rename neo/{Core => Ledger}/ValidatorsCountState.cs (98%) create mode 100644 neo/NeoSystem.cs delete mode 100644 neo/Network/InventoryReceivingEventArgs.cs delete mode 100644 neo/Network/InventoryType.cs delete mode 100644 neo/Network/LocalNode.cs delete mode 100644 neo/Network/Message.cs create mode 100644 neo/Network/P2P/Connection.cs create mode 100644 neo/Network/P2P/Helper.cs create mode 100644 neo/Network/P2P/LocalNode.cs create mode 100644 neo/Network/P2P/Message.cs rename neo/Network/{ => P2P}/Payloads/AddrPayload.cs (68%) rename neo/{Core => Network/P2P/Payloads}/AssetType.cs (78%) create mode 100644 neo/Network/P2P/Payloads/Block.cs rename neo/{Core => Network/P2P/Payloads}/BlockBase.cs (68%) rename neo/{Core => Network/P2P/Payloads}/ClaimTransaction.cs (68%) rename neo/{Core => Network/P2P/Payloads}/CoinReference.cs (57%) rename neo/Network/{ => P2P}/Payloads/ConsensusPayload.cs (76%) rename neo/{Core => Network/P2P/Payloads}/ContractTransaction.cs (77%) rename neo/{Core => Network/P2P/Payloads}/EnrollmentTransaction.cs (61%) rename neo/Network/{ => P2P}/Payloads/FilterAddPayload.cs (92%) rename neo/Network/{ => P2P}/Payloads/FilterLoadPayload.cs (96%) rename neo/Network/{ => P2P}/Payloads/GetBlocksPayload.cs (96%) rename neo/{Core => Network/P2P/Payloads}/Header.cs (61%) rename neo/Network/{ => P2P}/Payloads/HeadersPayload.cs (77%) rename neo/Network/{ => P2P/Payloads}/IInventory.cs (58%) create mode 100644 neo/Network/P2P/Payloads/IVerifiable.cs rename neo/Network/{ => P2P}/Payloads/InvPayload.cs (59%) create mode 100644 neo/Network/P2P/Payloads/InventoryType.cs rename neo/{Core => Network/P2P/Payloads}/InvocationTransaction.cs (89%) rename neo/{Core => Network/P2P/Payloads}/IssueTransaction.cs (68%) rename neo/Network/{ => P2P}/Payloads/MerkleBlockPayload.cs (93%) rename neo/{Core => Network/P2P/Payloads}/MinerTransaction.cs (67%) rename neo/Network/{ => P2P}/Payloads/NetworkAddressWithTime.cs (94%) rename neo/{Core => Network/P2P/Payloads}/PublishTransaction.cs (95%) rename neo/{Core => Network/P2P/Payloads}/RegisterTransaction.cs (63%) rename neo/{Core => Network/P2P/Payloads}/StateDescriptor.cs (88%) rename neo/{Core => Network/P2P/Payloads}/StateTransaction.cs (88%) rename neo/{Core => Network/P2P/Payloads}/StateType.cs (72%) rename neo/{Core => Network/P2P/Payloads}/Transaction.cs (72%) rename neo/{Core => Network/P2P/Payloads}/TransactionAttribute.cs (89%) rename neo/{Core => Network/P2P/Payloads}/TransactionAttributeUsage.cs (62%) rename neo/{Core => Network/P2P/Payloads}/TransactionOutput.cs (67%) create mode 100644 neo/Network/P2P/Payloads/TransactionResult.cs rename neo/{Core => Network/P2P/Payloads}/TransactionType.cs (64%) rename neo/Network/{ => P2P}/Payloads/VersionPayload.cs (92%) rename neo/{Core => Network/P2P/Payloads}/Witness.cs (89%) create mode 100644 neo/Network/P2P/Peer.cs create mode 100644 neo/Network/P2P/ProtocolHandler.cs create mode 100644 neo/Network/P2P/RemoteNode.cs create mode 100644 neo/Network/P2P/TaskManager.cs create mode 100644 neo/Network/P2P/TaskSession.cs delete mode 100644 neo/Network/RemoteNode.cs delete mode 100644 neo/Network/TcpRemoteNode.cs delete mode 100644 neo/Network/WebSocketRemoteNode.cs create mode 100644 neo/Persistence/CloneSnapshot.cs create mode 100644 neo/Persistence/Helper.cs create mode 100644 neo/Persistence/IPersistence.cs rename neo/{IO/Data => Persistence}/LevelDB/DbCache.cs (55%) create mode 100644 neo/Persistence/LevelDB/DbMetaDataCache.cs create mode 100644 neo/Persistence/LevelDB/DbSnapshot.cs create mode 100644 neo/Persistence/LevelDB/LevelDBStore.cs rename neo/{Implementations/Blockchains/LevelDB/DataEntryPrefix.cs => Persistence/LevelDB/Prefixes.cs} (74%) create mode 100644 neo/Persistence/Snapshot.cs create mode 100644 neo/Persistence/Store.cs create mode 100644 neo/Plugins/Helper.cs create mode 100644 neo/Plugins/ILogPlugin.cs create mode 100644 neo/Plugins/IPolicyPlugin.cs create mode 100644 neo/Plugins/IRpcPlugin.cs create mode 100644 neo/Plugins/LogLevel.cs delete mode 100644 neo/Plugins/PolicyPlugin.cs delete mode 100644 neo/Plugins/RpcPlugin.cs delete mode 100644 neo/SmartContract/CachedScriptTable.cs create mode 100644 neo/SmartContract/Helper.cs rename neo/SmartContract/{StateReader.cs => NeoService.cs} (50%) create mode 100644 neo/SmartContract/StandardService.cs delete mode 100644 neo/SmartContract/StateMachine.cs create mode 100644 neo/Wallets/Helper.cs rename neo/{Implementations => }/Wallets/NEP6/NEP6Account.cs (91%) rename neo/{Implementations => }/Wallets/NEP6/NEP6Contract.cs (96%) rename neo/{Implementations => }/Wallets/NEP6/NEP6Wallet.cs (97%) rename neo/{Implementations => }/Wallets/NEP6/ScryptParameters.cs (94%) rename neo/{Implementations => }/Wallets/NEP6/WalletLocker.cs (87%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/Account.cs (72%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/Address.cs (61%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/Contract.cs (82%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/Key.cs (68%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/UserWallet.cs (91%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/UserWalletAccount.cs (81%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/VerificationContract.cs (58%) rename neo/{Implementations/Wallets/EntityFramework => Wallets/SQLite}/WalletDataContext.cs (96%) delete mode 100644 neo/policy.json diff --git a/neo.UnitTests/README.md b/neo.UnitTests/README.md index 1b8a238e9c..48acf51de0 100644 --- a/neo.UnitTests/README.md +++ b/neo.UnitTests/README.md @@ -28,7 +28,6 @@ Coverage * Header.cs * Helper.cs * InvocationTransaction.cs - * IssueTransaction.cs * MinerTransaction.cs * SpentCoin.cs * SpentCoinState.cs diff --git a/neo.UnitTests/TestBlockchain.cs b/neo.UnitTests/TestBlockchain.cs deleted file mode 100644 index 3e7e222b46..0000000000 --- a/neo.UnitTests/TestBlockchain.cs +++ /dev/null @@ -1,161 +0,0 @@ -using Neo.Core; -using Neo.Cryptography.ECC; -using Neo.IO.Caching; -using System; -using System.Collections.Generic; - -namespace Neo.UnitTests -{ - public class TestBlockchain : Blockchain - { - private UInt256 _assetId; - - /// - /// Return true if haven't got valid handle - /// - public override bool IsDisposed => false; - - public TestBlockchain(UInt256 assetId) - { - _assetId = assetId; - } - - public override UInt256 CurrentBlockHash => throw new NotImplementedException(); - - public override UInt256 CurrentHeaderHash => throw new NotImplementedException(); - - public override uint HeaderHeight => throw new NotImplementedException(); - - public override uint Height => throw new NotImplementedException(); - - public override bool AddBlock(Block block) - { - throw new NotImplementedException(); - } - - public override bool ContainsBlock(UInt256 hash) - { - return true; // for verify in UT_Block - } - - public override bool ContainsTransaction(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override bool ContainsUnspent(UInt256 hash, ushort index) - { - throw new NotImplementedException(); - } - - public override MetaDataCache GetMetaData() - { - return new TestMetaDataCache(); - } - - public override DataCache GetStates() - { - return new TestDataCache(); - } - - public override void Dispose() - { - // do nothing - } - - public override AccountState GetAccountState(UInt160 script_hash) - { - throw new NotImplementedException(); - } - - public override AssetState GetAssetState(UInt256 asset_id) - { - if (asset_id == UInt256.Zero) return null; - UInt160 val = new UInt160(TestUtils.GetByteArray(20, asset_id.ToArray()[0])); - return new AssetState() { Issuer = val }; - } - - public override Block GetBlock(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override UInt256 GetBlockHash(uint height) - { - throw new NotImplementedException(); - } - - public override ContractState GetContract(UInt160 hash) - { - throw new NotImplementedException(); - } - - public override IEnumerable GetEnrollments() - { - ECPoint ecp = TestUtils.StandbyValidators[0]; - return new ValidatorState[] { new ValidatorState() { PublicKey = ecp } }; - } - - public override Header GetHeader(uint height) - { - throw new NotImplementedException(); - } - - public override Header GetHeader(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override Block GetNextBlock(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override UInt256 GetNextBlockHash(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override StorageItem GetStorageItem(StorageKey key) - { - throw new NotImplementedException(); - } - - public override long GetSysFeeAmount(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override Transaction GetTransaction(UInt256 hash, out int height) - { - height = 0; - // take part of the trans hash and use that for the scripthash of the testtransaction - return new TestTransaction(_assetId, TransactionType.ClaimTransaction, new UInt160(TestUtils.GetByteArray(20, hash.ToArray()[0]))); - } - - public override Dictionary GetUnclaimed(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override TransactionOutput GetUnspent(UInt256 hash, ushort index) - { - throw new NotImplementedException(); - } - - public override IEnumerable GetUnspent(UInt256 hash) - { - throw new NotImplementedException(); - } - - public override bool IsDoubleSpend(Transaction tx) - { - throw new NotImplementedException(); - } - - protected override void AddHeaders(IEnumerable
headers) - { - throw new NotImplementedException(); - } - } -} diff --git a/neo.UnitTests/TestMetaDataCache.cs b/neo.UnitTests/TestMetaDataCache.cs index b274b25c0a..55dca734f4 100644 --- a/neo.UnitTests/TestMetaDataCache.cs +++ b/neo.UnitTests/TestMetaDataCache.cs @@ -3,16 +3,24 @@ namespace Neo.UnitTests { - public class TestMetaDataCache : MetaDataCache where T : class, ISerializable, new() + public class TestMetaDataCache : MetaDataCache where T : class, ICloneable, ISerializable, new() { public TestMetaDataCache() : base(null) { } + protected override void AddInternal(T item) + { + } + protected override T TryGetInternal() { return null; } + + protected override void UpdateInternal(T item) + { + } } } diff --git a/neo.UnitTests/TestTransaction.cs b/neo.UnitTests/TestTransaction.cs index 6036a8d558..a507bbeb36 100644 --- a/neo.UnitTests/TestTransaction.cs +++ b/neo.UnitTests/TestTransaction.cs @@ -1,7 +1,4 @@ -using Neo.Core; -using System; -using System.Collections.Generic; -using System.Text; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { diff --git a/neo.UnitTests/TestUtils.cs b/neo.UnitTests/TestUtils.cs index ba3c595870..1e73f84cea 100644 --- a/neo.UnitTests/TestUtils.cs +++ b/neo.UnitTests/TestUtils.cs @@ -1,9 +1,7 @@ -using System; -using Neo.Core; -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; using Neo.VM; -using Neo.Wallets; -using Neo.SmartContract; +using System; namespace Neo.UnitTests { @@ -30,7 +28,7 @@ public static ClaimTransaction GetClaimTransaction() Attributes = new TransactionAttribute[0], Inputs = new CoinReference[0], Outputs = new TransactionOutput[0], - Scripts = new Witness[0] + Witnesses = new Witness[0] }; } @@ -42,48 +40,7 @@ public static MinerTransaction GetMinerTransaction() Attributes = new TransactionAttribute[0], Inputs = new CoinReference[0], Outputs = new TransactionOutput[0], - Scripts = new Witness[0] - }; - } - - public static IssueTransaction GetIssueTransaction(bool inputVal, decimal outputVal, UInt256 assetId) - { - TestUtils.SetupTestBlockchain(assetId); - - CoinReference[] inputsVal; - if (inputVal) - { - inputsVal = new[] - { - TestUtils.GetCoinReference(null) - }; - } - else - { - inputsVal = new CoinReference[0]; - } - - return new IssueTransaction - { - Attributes = new TransactionAttribute[0], - Inputs = inputsVal, - Outputs = new[] - { - new TransactionOutput - { - AssetId = assetId, - Value = Fixed8.FromDecimal(outputVal), - ScriptHash = Contract.CreateMultiSigRedeemScript(1, TestUtils.StandbyValidators).ToScriptHash() - } - }, - Scripts = new[] - { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSHT } - } - } + Witnesses = new Witness[0] }; } @@ -97,12 +54,6 @@ public static CoinReference GetCoinReference(UInt256 prevHash) }; } - public static void SetupTestBlockchain(UInt256 assetId) - { - Blockchain testBlockchain = new TestBlockchain(assetId); - Blockchain.RegisterBlockchain(testBlockchain); - } - public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out ulong consensusDataVal, out Witness scriptVal) { setupBlockBaseWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal); @@ -142,7 +93,7 @@ private static void setupBlockBaseWithValues(BlockBase bb, UInt256 val256, out U InvocationScript = new byte[0], VerificationScript = new[] { (byte)OpCode.PUSHT } }; - bb.Script = scriptVal; + bb.Witness = scriptVal; } } } diff --git a/neo.UnitTests/TestVerifiable.cs b/neo.UnitTests/TestVerifiable.cs index e02ce3b9ac..ee8877aee2 100644 --- a/neo.UnitTests/TestVerifiable.cs +++ b/neo.UnitTests/TestVerifiable.cs @@ -1,6 +1,7 @@ +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using System; using System.IO; -using Neo.Core; namespace Neo.UnitTests { @@ -8,7 +9,7 @@ public class TestVerifiable : IVerifiable { private string testStr = "testStr"; - public Witness[] Scripts { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public Witness[] Witnesses { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public int Size => throw new NotImplementedException(); @@ -27,7 +28,7 @@ public byte[] GetMessage() throw new NotImplementedException(); } - public UInt160[] GetScriptHashesForVerifying() + public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { throw new NotImplementedException(); } diff --git a/neo.UnitTests/UT_AccountState.cs b/neo.UnitTests/UT_AccountState.cs index 9e18dfef87..290db33040 100644 --- a/neo.UnitTests/UT_AccountState.cs +++ b/neo.UnitTests/UT_AccountState.cs @@ -1,8 +1,8 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.Cryptography.ECC; using Neo.IO; +using Neo.Ledger; using System.Collections.Generic; using System.IO; using System.Text; diff --git a/neo.UnitTests/UT_AssetState.cs b/neo.UnitTests/UT_AssetState.cs index 7dd1bbab95..a609bd86fb 100644 --- a/neo.UnitTests/UT_AssetState.cs +++ b/neo.UnitTests/UT_AssetState.cs @@ -1,8 +1,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.Cryptography.ECC; using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using System.Globalization; using System.IO; using System.Text; diff --git a/neo.UnitTests/UT_Block.cs b/neo.UnitTests/UT_Block.cs index fce6367bab..1007eabe9c 100644 --- a/neo.UnitTests/UT_Block.cs +++ b/neo.UnitTests/UT_Block.cs @@ -1,7 +1,8 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; using System.IO; @@ -54,7 +55,7 @@ public void Header_Get() uut.Header.Timestamp.Should().Be(timestampVal); uut.Header.Index.Should().Be(indexVal); uut.Header.ConsensusData.Should().Be(consensusDataVal); - uut.Header.Script.Should().Be(scriptVal); + uut.Header.Witness.Should().Be(scriptVal); } [TestMethod] @@ -75,7 +76,6 @@ public void Size_Get() private IssueTransaction getIssueTransaction(bool inputVal, decimal outputVal, UInt256 assetId) { - TestUtils.SetupTestBlockchain(assetId); CoinReference[] inputsVal; if (inputVal) @@ -104,7 +104,7 @@ private IssueTransaction getIssueTransaction(bool inputVal, decimal outputVal, U ScriptHash = Contract.CreateMultiSigRedeemScript(1, TestUtils.StandbyValidators).ToScriptHash() } }, - Scripts = new[] + Witnesses = new[] { new Witness { @@ -117,7 +117,6 @@ private IssueTransaction getIssueTransaction(bool inputVal, decimal outputVal, U private ContractTransaction getContractTransaction(bool inputVal, decimal outputVal, UInt256 assetId) { - TestUtils.SetupTestBlockchain(assetId); CoinReference[] inputsVal; if (inputVal) @@ -145,7 +144,7 @@ private ContractTransaction getContractTransaction(bool inputVal, decimal output ScriptHash = Contract.CreateMultiSigRedeemScript(1, TestUtils.StandbyValidators).ToScriptHash() } }, - Scripts = new[] + Witnesses = new[] { new Witness { @@ -275,63 +274,6 @@ public void CalculateNetFee_Out() Block.CalculateNetFee(uut.Transactions).Should().Be(Fixed8.FromDecimal(-100)); } - [TestMethod] - public void CalculateNetFee_In() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 0); - - uut.Transactions = new Transaction[1] { - getContractTransaction(true, 0, Blockchain.UtilityToken.Hash) - }; - - Block.CalculateNetFee(uut.Transactions).Should().Be(Fixed8.FromDecimal(50)); - } - - [TestMethod] - public void CalculateNetFee_In_And_Out() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 0); - - uut.Transactions = new Transaction[1] { - getContractTransaction(true, 100, Blockchain.UtilityToken.Hash) - }; - - Block.CalculateNetFee(uut.Transactions).Should().Be(Fixed8.FromDecimal(-50)); - } - - [TestMethod] - public void CalculateNetFee_SystemFee() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRootVal; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRootVal, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 0); - - uut.Transactions = new Transaction[1] { - TestUtils.GetIssueTransaction(true, 0, new UInt256(TestUtils.GetByteArray(32, 0x42))) - }; - - Block.CalculateNetFee(uut.Transactions).Should().Be(Fixed8.FromDecimal(-500)); - } - [TestMethod] public void Serialize() { @@ -398,9 +340,9 @@ private void assertStandardBlockTestVals(UInt256 val256, UInt256 merkRoot, UInt1 uut.Index.Should().Be(indexVal); uut.ConsensusData.Should().Be(consensusDataVal); uut.NextConsensus.Should().Be(val160); - uut.Script.InvocationScript.Length.Should().Be(0); - uut.Script.Size.Should().Be(scriptVal.Size); - uut.Script.VerificationScript[0].Should().Be(scriptVal.VerificationScript[0]); + uut.Witness.InvocationScript.Length.Should().Be(0); + uut.Witness.Size.Should().Be(scriptVal.Size); + uut.Witness.VerificationScript[0].Should().Be(scriptVal.VerificationScript[0]); if (testTransactions) { uut.Transactions.Length.Should().Be(1); @@ -456,71 +398,6 @@ public void Equals_SameHash() uut.Equals(newBlock).Should().BeTrue(); } - [TestMethod] - public void Trim() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 1); - - byte[] data = uut.Trim(); - 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, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251, 128, 171, 4, 253, 0, 0, 0, 0, 30, 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, 1, 0, 1, 81, 1, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251 }; - - data.Length.Should().Be(141); - for (int i = 0; i < 141; i++) - { - data[i].Should().Be(requiredData[i]); - } - } - - [TestMethod] - public void FromTrimmedData() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(new Block(), val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 1); - - 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, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251, 128, 171, 4, 253, 0, 0, 0, 0, 30, 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, 1, 0, 1, 81, 1, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251 }; - - uut = Block.FromTrimmedData(data, 0, x => TestUtils.GetMinerTransaction()); - - assertStandardBlockTestVals(val256, merkRoot, val160, timestampVal, indexVal, consensusDataVal, scriptVal, transactionsVal); - uut.Transactions[0].Should().Be(TestUtils.GetMinerTransaction()); - } - - [TestMethod] - public void FromTrimmedData_MultipleTx() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(new Block(), val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 3); - - 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, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251, 128, 171, 4, 253, 0, 0, 0, 0, 30, 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, 1, 0, 1, 81, 3, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251 }; - - uut = Block.FromTrimmedData(data, 0, x => TestUtils.GetMinerTransaction()); - - assertStandardBlockTestVals(val256, merkRoot, val160, timestampVal, indexVal, consensusDataVal, scriptVal, transactionsVal, testTransactions: false); - uut.Transactions.Length.Should().Be(3); - uut.Transactions[0].Should().Be(TestUtils.GetMinerTransaction()); - uut.Transactions[1].Should().Be(TestUtils.GetMinerTransaction()); - uut.Transactions[2].Should().Be(TestUtils.GetMinerTransaction()); - } - [TestMethod] public void RebuildMerkleRoot_Updates() { @@ -583,58 +460,5 @@ public void ToJson() ((JArray)txObj[0]["scripts"]).Count.Should().Be(0); txObj[0]["nonce"].AsNumber().Should().Be(2083236893); } - - [TestMethod] - public void Verify_CompletelyFalse() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 1); - - TestUtils.SetupTestBlockchain(UInt256.Zero); - - uut.Verify(false).Should().BeTrue(); - } - - [TestMethod] - public void Verify_CompletelyFalse_MinerTransaction_After_First() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 3); - - TestUtils.SetupTestBlockchain(UInt256.Zero); - - uut.Verify(false).Should().BeFalse(); - } - - [TestMethod] - public void Verify_CompletelyTrue_NextConsensus_Fail() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - Transaction[] transactionsVal; - TestUtils.SetupBlockWithValues(uut, val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal, out transactionsVal, 1); - // passing NextConsensus below - // uut.NextConsensus = new UInt160(new byte[] { 23, 52, 98, 203, 0, 206, 138, 37, 140, 16, 251, 231, 61, 120, 218, 200, 182, 125, 120, 73 }); - - TestUtils.SetupTestBlockchain(UInt256.Zero); - - uut.Verify(true).Should().BeFalse(); - } } } diff --git a/neo.UnitTests/UT_ClaimTransaction.cs b/neo.UnitTests/UT_ClaimTransaction.cs index 969b166abb..32353052f2 100644 --- a/neo.UnitTests/UT_ClaimTransaction.cs +++ b/neo.UnitTests/UT_ClaimTransaction.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { @@ -51,7 +47,7 @@ public void Size__Get_0_Claims() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; uut.Size.Should().Be(7); // 1, 1, 1, 1, 1, 1 + claims 1 } @@ -64,7 +60,7 @@ public void Size__Get_1_Claims() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; uut.Size.Should().Be(41); // 1, 1, 1, 1, 1, 1 + claims 35 } @@ -77,56 +73,11 @@ public void Size__Get_3_Claims() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; uut.Size.Should().Be(109); // 1, 1, 1, 1, 1, 1 + claims 103 } - [TestMethod] - public void GetScriptHashesForVerifying_0_Claims() - { - uut.Claims = new CoinReference[0]; - uut.Attributes = new TransactionAttribute[0]; - uut.Inputs = new CoinReference[0]; - uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; - - uut.GetScriptHashesForVerifying().Length.Should().Be(0); - } - - [TestMethod] - public void GetScriptHashesForVerifying_1_Claim() - { - CoinReference[] refs = new[] { TestUtils.GetCoinReference(new UInt256(TestUtils.GetByteArray(32, 0x42))) }; - uut.Claims = refs; - uut.Attributes = new TransactionAttribute[0]; - uut.Inputs = new CoinReference[0]; - uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; - - TestUtils.SetupTestBlockchain(UInt256.Zero); - - UInt160[] res = uut.GetScriptHashesForVerifying(); - res.Length.Should().Be(1); - } - - - [TestMethod] - public void GetScriptHashesForVerifying_2_Claim() - { - CoinReference[] refs = new[] { TestUtils.GetCoinReference(new UInt256(TestUtils.GetByteArray(32, 0x42))), TestUtils.GetCoinReference(new UInt256(TestUtils.GetByteArray(32, 0x48))) }; - uut.Claims = refs; - uut.Attributes = new TransactionAttribute[0]; - uut.Inputs = new CoinReference[0]; - uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; - - TestUtils.SetupTestBlockchain(UInt256.Zero); - - UInt160[] res = uut.GetScriptHashesForVerifying(); - res.Length.Should().Be(2); - } - [TestMethod] public void ToJson() { @@ -135,7 +86,7 @@ public void ToJson() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; JObject jObj = uut.ToJson(); jObj.Should().NotBeNull(); diff --git a/neo.UnitTests/UT_CoinReference.cs b/neo.UnitTests/UT_CoinReference.cs index e850285542..5777a9ea26 100644 --- a/neo.UnitTests/UT_CoinReference.cs +++ b/neo.UnitTests/UT_CoinReference.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; -using System.IO; using Neo.IO; using Neo.IO.Json; +using Neo.Network.P2P.Payloads; +using System.IO; +using System.Text; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_Culture.cs b/neo.UnitTests/UT_Culture.cs index f1b2fcb57b..28dc09aef4 100644 --- a/neo.UnitTests/UT_Culture.cs +++ b/neo.UnitTests/UT_Culture.cs @@ -1,11 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; -using System.Text; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_Header.cs b/neo.UnitTests/UT_Header.cs index a0a508f9d5..9819148241 100644 --- a/neo.UnitTests/UT_Header.cs +++ b/neo.UnitTests/UT_Header.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Network.P2P.Payloads; using System.IO; using System.Text; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; namespace Neo.UnitTests { @@ -68,9 +66,9 @@ private void assertStandardHeaderTestVals(UInt256 val256, UInt256 merkRoot, UInt uut.Index.Should().Be(indexVal); uut.ConsensusData.Should().Be(consensusDataVal); uut.NextConsensus.Should().Be(val160); - uut.Script.InvocationScript.Length.Should().Be(0); - uut.Script.Size.Should().Be(scriptVal.Size); - uut.Script.VerificationScript[0].Should().Be(scriptVal.VerificationScript[0]); + uut.Witness.InvocationScript.Length.Should().Be(0); + uut.Witness.Size.Should().Be(scriptVal.Size); + uut.Witness.VerificationScript[0].Should().Be(scriptVal.VerificationScript[0]); } [TestMethod] @@ -108,24 +106,6 @@ public void Equals_SameObject() uut.Equals((object)uut).Should().BeTrue(); } - [TestMethod] - public void FromTrimmedData() - { - UInt256 val256 = UInt256.Zero; - UInt256 merkRoot; - UInt160 val160; - uint timestampVal, indexVal; - ulong consensusDataVal; - Witness scriptVal; - TestUtils.SetupHeaderWithValues(new Header(), val256, out merkRoot, out val160, out timestampVal, out indexVal, out consensusDataVal, out scriptVal); - - 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, 214, 87, 42, 69, 155, 149, 217, 19, 107, 122, 113, 60, 84, 133, 202, 112, 159, 158, 250, 79, 8, 241, 194, 93, 215, 146, 103, 45, 43, 215, 91, 251, 128, 171, 4, 253, 0, 0, 0, 0, 30, 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, 1, 0, 1, 81, 0 }; - - uut = Header.FromTrimmedData(data, 0); - - assertStandardHeaderTestVals(val256, merkRoot, val160, timestampVal, indexVal, consensusDataVal, scriptVal); - } - [TestMethod] public void Serialize() { diff --git a/neo.UnitTests/UT_Helper.cs b/neo.UnitTests/UT_Helper.cs index fddcf06318..c836219b88 100644 --- a/neo.UnitTests/UT_Helper.cs +++ b/neo.UnitTests/UT_Helper.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; -using System.Text; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using FluentAssertions; -using Neo.Core; +using Neo.Network.P2P; +using Neo.SmartContract; using Neo.Wallets; namespace Neo.UnitTests diff --git a/neo.UnitTests/UT_InvocationTransaction.cs b/neo.UnitTests/UT_InvocationTransaction.cs index 77b665f92a..d97455790b 100644 --- a/neo.UnitTests/UT_InvocationTransaction.cs +++ b/neo.UnitTests/UT_InvocationTransaction.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { @@ -57,7 +54,7 @@ public void Size_Get() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; byte[] val = TestUtils.GetByteArray(32, 0x42); uut.Script = val; @@ -97,7 +94,7 @@ public void ToJson() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; JObject jObj = uut.ToJson(); jObj.Should().NotBeNull(); diff --git a/neo.UnitTests/UT_IssueTransaction.cs b/neo.UnitTests/UT_IssueTransaction.cs deleted file mode 100644 index efb6ae8580..0000000000 --- a/neo.UnitTests/UT_IssueTransaction.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; -using Neo.IO.Json; -using Neo.Wallets; -using Neo.VM; -using Neo.SmartContract; - -namespace Neo.UnitTests -{ - [TestClass] - public class UT_IssueTransaction - { - IssueTransaction uut; - - [TestInitialize] - public void TestSetup() - { - uut = new IssueTransaction(); - } - - [TestMethod] - public void SystemFee_Get() - { - uut.Version = 1; - uut.SystemFee.Should().Be(Fixed8.Zero); - } - - [TestMethod] - public void SystemFee_Get_Version_0_Share() - { - uut = TestUtils.GetIssueTransaction(false, 10, Blockchain.GoverningToken.Hash); - uut.Version = 0; - - uut.SystemFee.Should().Be(Fixed8.Zero); - } - - [TestMethod] - public void SystemFee_Get_Version_0_Coin() - { - uut = TestUtils.GetIssueTransaction(false, 10, Blockchain.UtilityToken.Hash); - uut.Version = 0; - - uut.SystemFee.Should().Be(Fixed8.Zero); - } - - [TestMethod] - public void SystemFee_Get_Version_0_OtherAsset() - { - uut = TestUtils.GetIssueTransaction(false, 10, new UInt256(TestUtils.GetByteArray(32,0x42))); - uut.Version = 0; - - uut.SystemFee.Should().Be(Fixed8.FromDecimal(500)); - } - - [TestMethod] - public void GetScriptHashesForVerifying() - { - TestUtils.SetupTestBlockchain(UInt256.Zero); - uut = TestUtils.GetIssueTransaction(false, 10, Blockchain.UtilityToken.Hash); - UInt160[] res = uut.GetScriptHashesForVerifying(); - res.Length.Should().Be(1); - res[0].Should().Be(new UInt160(TestUtils.GetByteArray(20, 0xe7))); - } - - [TestMethod] - public void GetScriptHashesForVerifying_ThrowsException_NullAsset() - { - TestUtils.SetupTestBlockchain(UInt256.Zero); - uut = TestUtils.GetIssueTransaction(false, 10, UInt256.Zero); - Action test = () => uut.GetScriptHashesForVerifying(); - test.ShouldThrow(); - } - - [TestMethod] - public void GetScriptHashesForVerifying_Ordered() - { - TestUtils.SetupTestBlockchain(UInt256.Zero); - uut = new IssueTransaction - { - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = new[] - { - new TransactionOutput - { - AssetId = Blockchain.UtilityToken.Hash, - Value = Fixed8.FromDecimal(10), - ScriptHash = Contract.CreateMultiSigRedeemScript(1, TestUtils.StandbyValidators).ToScriptHash() - }, - new TransactionOutput - { - AssetId = Blockchain.GoverningToken.Hash, - Value = Fixed8.FromDecimal(10), - ScriptHash = Contract.CreateMultiSigRedeemScript(1, TestUtils.StandbyValidators).ToScriptHash() - }, - }, - Scripts = new[] - { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSHT } - } - } - }; - UInt160[] res = uut.GetScriptHashesForVerifying(); - res.Length.Should().Be(2); - res[0].Should().Be(new UInt160(TestUtils.GetByteArray(20, 0x9b))); - res[1].Should().Be(new UInt160(TestUtils.GetByteArray(20, 0xe7))); - } - - } -} diff --git a/neo.UnitTests/UT_MinerTransaction.cs b/neo.UnitTests/UT_MinerTransaction.cs index 9c6110e6ad..5b0d2156c3 100644 --- a/neo.UnitTests/UT_MinerTransaction.cs +++ b/neo.UnitTests/UT_MinerTransaction.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using FluentAssertions; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; -using Neo.Wallets; -using Neo.VM; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { @@ -47,7 +42,7 @@ public void Size_Get() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; uut.Size.Should().Be(10); // 1, 1, 1, 1, 1, 1 + 4 } @@ -58,7 +53,7 @@ public void ToJson() uut.Attributes = new TransactionAttribute[0]; uut.Inputs = new CoinReference[0]; uut.Outputs = new TransactionOutput[0]; - uut.Scripts = new Witness[0]; + uut.Witnesses = new Witness[0]; uut.Nonce = 42; JObject jObj = uut.ToJson(); diff --git a/neo.UnitTests/UT_SpentCoint.cs b/neo.UnitTests/UT_SpentCoint.cs index 117ceb6d08..831eb10a1f 100644 --- a/neo.UnitTests/UT_SpentCoint.cs +++ b/neo.UnitTests/UT_SpentCoint.cs @@ -1,9 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; -using System.Collections.Generic; -using System.IO; -using System.Text; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_SpentCointState.cs b/neo.UnitTests/UT_SpentCointState.cs index c728c4bcf7..bb28ebecf3 100644 --- a/neo.UnitTests/UT_SpentCointState.cs +++ b/neo.UnitTests/UT_SpentCointState.cs @@ -1,6 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; +using Neo.Ledger; using System.Collections.Generic; using System.IO; using System.Text; diff --git a/neo.UnitTests/UT_StorageItem.cs b/neo.UnitTests/UT_StorageItem.cs index ec36b90762..262cc33e50 100644 --- a/neo.UnitTests/UT_StorageItem.cs +++ b/neo.UnitTests/UT_StorageItem.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO; +using Neo.Ledger; using System.IO; using System.Text; diff --git a/neo.UnitTests/UT_StorageKey.cs b/neo.UnitTests/UT_StorageKey.cs index 037e0c3309..1ba8d61d26 100644 --- a/neo.UnitTests/UT_StorageKey.cs +++ b/neo.UnitTests/UT_StorageKey.cs @@ -1,9 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; -using System.Collections.Generic; -using System.IO; -using System.Text; +using Neo.Ledger; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_TransactionAttribute.cs b/neo.UnitTests/UT_TransactionAttribute.cs index c5ff05c7b4..bf79f6ac01 100644 --- a/neo.UnitTests/UT_TransactionAttribute.cs +++ b/neo.UnitTests/UT_TransactionAttribute.cs @@ -1,10 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; -using System.Collections.Generic; -using System.IO; -using System.Text; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_TransactionOutput.cs b/neo.UnitTests/UT_TransactionOutput.cs index 20862110e7..00929a3a99 100644 --- a/neo.UnitTests/UT_TransactionOutput.cs +++ b/neo.UnitTests/UT_TransactionOutput.cs @@ -1,10 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; -using System.Collections.Generic; -using System.IO; -using System.Text; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_TransactionResult.cs b/neo.UnitTests/UT_TransactionResult.cs index da6165807e..685b653d09 100644 --- a/neo.UnitTests/UT_TransactionResult.cs +++ b/neo.UnitTests/UT_TransactionResult.cs @@ -1,10 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; -using Neo.IO.Json; -using System.Collections.Generic; -using System.IO; -using System.Text; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { diff --git a/neo.UnitTests/UT_UnspentCoinState.cs b/neo.UnitTests/UT_UnspentCoinState.cs index e8a723808b..08f70093e0 100644 --- a/neo.UnitTests/UT_UnspentCoinState.cs +++ b/neo.UnitTests/UT_UnspentCoinState.cs @@ -1,6 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; +using Neo.Ledger; using System.IO; using System.Text; diff --git a/neo.UnitTests/UT_ValidatorState.cs b/neo.UnitTests/UT_ValidatorState.cs index 1c393bc756..11a78050b3 100644 --- a/neo.UnitTests/UT_ValidatorState.cs +++ b/neo.UnitTests/UT_ValidatorState.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.Cryptography.ECC; +using Neo.Ledger; using System.IO; using System.Text; diff --git a/neo.UnitTests/UT_Witness.cs b/neo.UnitTests/UT_Witness.cs index 48708da55d..26dc8b84dd 100644 --- a/neo.UnitTests/UT_Witness.cs +++ b/neo.UnitTests/UT_Witness.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Core; using Neo.IO.Json; +using Neo.Network.P2P.Payloads; namespace Neo.UnitTests { diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index ee714dd421..a8ba393bae 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -1,21 +1,24 @@ -using Neo.Core; -using Neo.Cryptography; +using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; -using Neo.Network.Payloads; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.Wallets; +using System; using System.Collections.Generic; using System.Linq; namespace Neo.Consensus { - internal class ConsensusContext + internal class ConsensusContext : IDisposable { public const uint Version = 0; public ConsensusState State; public UInt256 PrevHash; public uint BlockIndex; public byte ViewNumber; + public Snapshot Snapshot; public ECPoint[] Validators; public int MyIndex; public uint PrimaryIndex; @@ -46,6 +49,11 @@ public void ChangeView(byte view_number) _header = null; } + public void Dispose() + { + Snapshot?.Dispose(); + } + public ConsensusPayload MakeChangeView() { return MakePayload(new ChangeView @@ -111,11 +119,13 @@ public ConsensusPayload MakePrepareResponse(byte[] signature) public void Reset(Wallet wallet) { + Snapshot?.Dispose(); + Snapshot = Blockchain.Singleton.GetSnapshot(); State = ConsensusState.Initial; - PrevHash = Blockchain.Default.CurrentBlockHash; - BlockIndex = Blockchain.Default.Height + 1; + PrevHash = Snapshot.CurrentBlockHash; + BlockIndex = Snapshot.Height + 1; ViewNumber = 0; - Validators = Blockchain.Default.GetValidators(); + Validators = Snapshot.GetValidators(); MyIndex = -1; PrimaryIndex = BlockIndex % (uint)Validators.Length; TransactionHashes = null; diff --git a/neo/Consensus/ConsensusService.cs b/neo/Consensus/ConsensusService.cs index 260cd7a00d..3746355181 100644 --- a/neo/Consensus/ConsensusService.cs +++ b/neo/Consensus/ConsensusService.cs @@ -1,52 +1,53 @@ -using Neo.Core; +using Akka.Actor; +using Akka.Configuration; using Neo.Cryptography; using Neo.IO; -using Neo.Network; -using Neo.Network.Payloads; +using Neo.IO.Actors; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.Plugins; using Neo.SmartContract; using Neo.Wallets; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Neo.Consensus { - public class ConsensusService : IDisposable + public sealed class ConsensusService : UntypedActor { - private ConsensusContext context = new ConsensusContext(); - private LocalNode localNode; - private Wallet wallet; - private Timer timer; - private uint timer_height; - private byte timer_view; + public class Start { } + internal class Timer { public uint Height; public byte ViewNumber; } + + private readonly ConsensusContext context = new ConsensusContext(); + private readonly NeoSystem system; + private readonly Wallet wallet; private DateTime block_received_time; - private bool started = false; - public ConsensusService(LocalNode localNode, Wallet wallet) + public ConsensusService(NeoSystem system, Wallet wallet) { - this.localNode = localNode; + this.system = system; this.wallet = wallet; - this.timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite); } private bool AddTransaction(Transaction tx, bool verify) { - if (Blockchain.Default.ContainsTransaction(tx.Hash) || - (verify && !tx.Verify(context.Transactions.Values)) || + if (context.Snapshot.ContainsTransaction(tx.Hash) || + (verify && !tx.Verify(context.Snapshot, context.Transactions.Values)) || !CheckPolicy(tx)) { - Log($"reject tx: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}"); + Log($"reject tx: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); RequestChangeView(); return false; } context.Transactions[tx.Hash] = tx; if (context.TransactionHashes.Length == context.Transactions.Count) { - if (Blockchain.GetConsensusAddress(Blockchain.Default.GetValidators(context.Transactions.Values).ToArray()).Equals(context.NextConsensus)) + if (Blockchain.GetConsensusAddress(context.Snapshot.GetValidators(context.Transactions.Values).ToArray()).Equals(context.NextConsensus)) { - Log($"send perpare response"); + Log($"send prepare response"); context.State |= ConsensusState.SignatureSent; context.Signatures[context.MyIndex] = context.MakeHeader().Sign(context.KeyPair); SignAndRelay(context.MakePrepareResponse(context.Signatures[context.MyIndex])); @@ -61,11 +62,13 @@ private bool AddTransaction(Transaction tx, bool verify) return true; } - private void Blockchain_PersistUnlocked(object sender, Block block) + private void ChangeTimer(TimeSpan delay) { - Log($"persist block: {block.Hash}"); - block_received_time = DateTime.Now; - InitializeConsensus(0); + Context.System.Scheduler.ScheduleTellOnce(delay, Self, new Timer + { + Height = context.BlockIndex, + ViewNumber = context.ViewNumber + }, ActorRefs.NoSender); } private void CheckExpectedView(byte view_number) @@ -79,7 +82,7 @@ private void CheckExpectedView(byte view_number) private bool CheckPolicy(Transaction tx) { - foreach (PolicyPlugin plugin in PolicyPlugin.Instances) + foreach (IPolicyPlugin plugin in Plugin.Policies) if (!plugin.CheckPolicy(tx)) return false; return true; @@ -98,31 +101,18 @@ private void CheckSignatures() sc.AddSignature(contract, context.Validators[i], context.Signatures[i]); j++; } - sc.Verifiable.Scripts = sc.GetScripts(); + sc.Verifiable.Witnesses = sc.GetWitnesses(); block.Transactions = context.TransactionHashes.Select(p => context.Transactions[p]).ToArray(); Log($"relay block: {block.Hash}"); - if (!localNode.Relay(block)) - Log($"reject block: {block.Hash}"); + system.LocalNode.Tell(new LocalNode.Relay { Inventory = block }); context.State |= ConsensusState.BlockSent; } } - public void Dispose() - { - Log("OnStop"); - if (timer != null) timer.Dispose(); - if (started) - { - Blockchain.PersistUnlocked -= Blockchain_PersistUnlocked; - LocalNode.InventoryReceiving -= LocalNode_InventoryReceiving; - LocalNode.InventoryReceived -= LocalNode_InventoryReceived; - } - } - private void FillContext() { - IEnumerable mem_pool = LocalNode.GetMemoryPool().Where(p => CheckPolicy(p)); - foreach (PolicyPlugin plugin in PolicyPlugin.Instances) + IEnumerable mem_pool = Blockchain.Singleton.GetMemoryPool().Where(p => CheckPolicy(p)); + foreach (IPolicyPlugin plugin in Plugin.Policies) mem_pool = plugin.Filter(mem_pool); List transactions = mem_pool.ToList(); Fixed8 amount_netfee = Block.CalculateNetFee(transactions); @@ -141,9 +131,9 @@ private void FillContext() Attributes = new TransactionAttribute[0], Inputs = new CoinReference[0], Outputs = outputs, - Scripts = new Witness[0] + Witnesses = new Witness[0] }; - if (Blockchain.Default.GetTransaction(tx.Hash) == null) + if (!context.Snapshot.ContainsTransaction(tx.Hash)) { context.Nonce = nonce; transactions.Insert(0, tx); @@ -152,7 +142,7 @@ private void FillContext() } context.TransactionHashes = transactions.Select(p => p.Hash).ToArray(); context.Transactions = transactions.ToDictionary(p => p.Hash); - context.NextConsensus = Blockchain.GetConsensusAddress(Blockchain.Default.GetValidators(transactions).ToArray()); + context.NextConsensus = Blockchain.GetConsensusAddress(context.Snapshot.GetValidators(transactions).ToArray()); } private static ulong GetNonce() @@ -165,117 +155,31 @@ private static ulong GetNonce() private void InitializeConsensus(byte view_number) { - lock (context) + if (view_number == 0) + context.Reset(wallet); + else + context.ChangeView(view_number); + if (context.MyIndex < 0) return; + Log($"initialize: height={context.BlockIndex} view={view_number} index={context.MyIndex} role={(context.MyIndex == context.PrimaryIndex ? ConsensusState.Primary : ConsensusState.Backup)}"); + if (context.MyIndex == context.PrimaryIndex) { - if (view_number == 0) - context.Reset(wallet); + context.State |= ConsensusState.Primary; + TimeSpan span = DateTime.Now - block_received_time; + if (span >= Blockchain.TimePerBlock) + ChangeTimer(TimeSpan.Zero); else - context.ChangeView(view_number); - if (context.MyIndex < 0) return; - Log($"initialize: height={context.BlockIndex} view={view_number} index={context.MyIndex} role={(context.MyIndex == context.PrimaryIndex ? ConsensusState.Primary : ConsensusState.Backup)}"); - if (context.MyIndex == context.PrimaryIndex) - { - context.State |= ConsensusState.Primary; - if (!context.State.HasFlag(ConsensusState.SignatureSent)) - { - FillContext(); - } - if (context.TransactionHashes.Length > 1) - { - InvPayload invPayload = InvPayload.Create(InventoryType.TX, context.TransactionHashes.Skip(1).ToArray()); - foreach (RemoteNode node in localNode.GetRemoteNodes()) - node.EnqueueMessage("inv", invPayload); - } - timer_height = context.BlockIndex; - timer_view = view_number; - TimeSpan span = DateTime.Now - block_received_time; - if (span >= Blockchain.TimePerBlock) - timer.Change(0, Timeout.Infinite); - else - timer.Change(Blockchain.TimePerBlock - span, Timeout.InfiniteTimeSpan); - } - else - { - context.State = ConsensusState.Backup; - timer_height = context.BlockIndex; - timer_view = view_number; - timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (view_number + 1)), Timeout.InfiniteTimeSpan); - } + ChangeTimer(Blockchain.TimePerBlock - span); } - } - - private void LocalNode_InventoryReceived(object sender, IInventory inventory) - { - ConsensusPayload payload = inventory as ConsensusPayload; - if (payload != null) + else { - lock (context) - { - if (payload.ValidatorIndex == context.MyIndex) return; - - if (payload.Version != ConsensusContext.Version) - return; - if (payload.PrevHash != context.PrevHash || payload.BlockIndex != context.BlockIndex) - { - // Request blocks - - if (Blockchain.Default?.Height + 1 < payload.BlockIndex) - { - Log($"chain sync: expected={payload.BlockIndex} current: {Blockchain.Default?.Height} nodes={localNode.RemoteNodeCount}"); - - localNode.RequestGetBlocks(); - } - - return; - } - - if (payload.ValidatorIndex >= context.Validators.Length) return; - ConsensusMessage message; - try - { - message = ConsensusMessage.DeserializeFrom(payload.Data); - } - catch - { - return; - } - if (message.ViewNumber != context.ViewNumber && message.Type != ConsensusMessageType.ChangeView) - return; - switch (message.Type) - { - case ConsensusMessageType.ChangeView: - OnChangeViewReceived(payload, (ChangeView)message); - break; - case ConsensusMessageType.PrepareRequest: - OnPrepareRequestReceived(payload, (PrepareRequest)message); - break; - case ConsensusMessageType.PrepareResponse: - OnPrepareResponseReceived(payload, (PrepareResponse)message); - break; - } - } - } - } - - private void LocalNode_InventoryReceiving(object sender, InventoryReceivingEventArgs e) - { - Transaction tx = e.Inventory as Transaction; - if (tx != null) - { - lock (context) - { - if (!context.State.HasFlag(ConsensusState.Backup) || !context.State.HasFlag(ConsensusState.RequestReceived) || context.State.HasFlag(ConsensusState.SignatureSent) || context.State.HasFlag(ConsensusState.ViewChanging)) - return; - if (context.Transactions.ContainsKey(tx.Hash)) return; - if (!context.TransactionHashes.Contains(tx.Hash)) return; - AddTransaction(tx, true); - e.Cancel = true; - } + context.State = ConsensusState.Backup; + ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (view_number + 1))); } } - protected virtual void Log(string message) + private void Log(string message, LogLevel level = LogLevel.Info) { + Plugin.Log(nameof(ConsensusService), level, message); } private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) @@ -287,15 +191,61 @@ private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) CheckExpectedView(message.NewViewNumber); } + private void OnConsensusPayload(ConsensusPayload payload) + { + if (payload.ValidatorIndex == context.MyIndex) return; + if (payload.Version != ConsensusContext.Version) + return; + if (payload.PrevHash != context.PrevHash || payload.BlockIndex != context.BlockIndex) + { + if (context.Snapshot.Height + 1 < payload.BlockIndex) + { + Log($"chain sync: expected={payload.BlockIndex} current: {context.Snapshot.Height} nodes={LocalNode.Singleton.ConnectedCount}", LogLevel.Warning); + } + return; + } + if (payload.ValidatorIndex >= context.Validators.Length) return; + ConsensusMessage message; + try + { + message = ConsensusMessage.DeserializeFrom(payload.Data); + } + catch + { + return; + } + if (message.ViewNumber != context.ViewNumber && message.Type != ConsensusMessageType.ChangeView) + return; + switch (message.Type) + { + case ConsensusMessageType.ChangeView: + OnChangeViewReceived(payload, (ChangeView)message); + break; + case ConsensusMessageType.PrepareRequest: + OnPrepareRequestReceived(payload, (PrepareRequest)message); + break; + case ConsensusMessageType.PrepareResponse: + OnPrepareResponseReceived(payload, (PrepareResponse)message); + break; + } + } + + private void OnPersistCompleted(Block block) + { + Log($"persist block: {block.Hash}"); + block_received_time = DateTime.Now; + InitializeConsensus(0); + } + private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest message) { Log($"{nameof(OnPrepareRequestReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} tx={message.TransactionHashes.Length}"); if (!context.State.HasFlag(ConsensusState.Backup) || context.State.HasFlag(ConsensusState.RequestReceived)) return; if (payload.ValidatorIndex != context.PrimaryIndex) return; - if (payload.Timestamp <= Blockchain.Default.GetHeader(context.PrevHash).Timestamp || payload.Timestamp > DateTime.Now.AddMinutes(10).ToTimestamp()) + if (payload.Timestamp <= context.Snapshot.GetHeader(context.PrevHash).Timestamp || payload.Timestamp > DateTime.Now.AddMinutes(10).ToTimestamp()) { - Log($"Timestamp incorrect: {payload.Timestamp}"); + Log($"Timestamp incorrect: {payload.Timestamp}", LogLevel.Warning); return; } context.State |= ConsensusState.RequestReceived; @@ -307,7 +257,7 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m if (!Crypto.Default.VerifySignature(context.MakeHeader().GetHashData(), message.Signature, context.Validators[payload.ValidatorIndex].EncodePoint(false))) return; context.Signatures = new byte[context.Validators.Length][]; context.Signatures[payload.ValidatorIndex] = message.Signature; - Dictionary mempool = LocalNode.GetMemoryPool().ToDictionary(p => p.Hash); + Dictionary mempool = Blockchain.Singleton.GetMemoryPool().ToDictionary(p => p.Hash); foreach (UInt256 hash in context.TransactionHashes.Skip(1)) { if (mempool.TryGetValue(hash, out Transaction tx)) @@ -318,10 +268,10 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m if (context.Transactions.Count < context.TransactionHashes.Length) { UInt256[] hashes = context.TransactionHashes.Where(i => !context.Transactions.ContainsKey(i)).ToArray(); - LocalNode.AllowHashes(hashes); - InvPayload msg = InvPayload.Create(InventoryType.TX, hashes); - foreach (RemoteNode node in localNode.GetRemoteNodes()) - node.EnqueueMessage("getdata", msg); + system.TaskManager.Tell(new TaskManager.RestartTasks + { + Payload = InvPayload.Create(InventoryType.TX, hashes) + }); } } @@ -336,29 +286,82 @@ private void OnPrepareResponseReceived(ConsensusPayload payload, PrepareResponse CheckSignatures(); } - private void OnTimeout(object state) + protected override void OnReceive(object message) + { + switch (message) + { + case Start _: + OnStart(); + break; + case Timer timer: + OnTimer(timer); + break; + case ConsensusPayload payload: + OnConsensusPayload(payload); + break; + case Transaction transaction: + OnTransaction(transaction); + break; + case Blockchain.PersistCompleted completed: + OnPersistCompleted(completed.Block); + break; + } + } + + private void OnStart() + { + Log("OnStart"); + InitializeConsensus(0); + } + + private void OnTimer(Timer timer) { - lock (context) + if (timer.Height != context.BlockIndex || timer.ViewNumber != context.ViewNumber) return; + Log($"timeout: height={timer.Height} view={timer.ViewNumber} state={context.State}"); + if (context.State.HasFlag(ConsensusState.Primary) && !context.State.HasFlag(ConsensusState.RequestSent)) { - if (timer_height != context.BlockIndex || timer_view != context.ViewNumber) return; - Log($"timeout: height={timer_height} view={timer_view} state={context.State}"); - if (context.State.HasFlag(ConsensusState.Primary) && !context.State.HasFlag(ConsensusState.RequestSent)) + Log($"send prepare request: height={timer.Height} view={timer.ViewNumber}"); + context.State |= ConsensusState.RequestSent; + if (!context.State.HasFlag(ConsensusState.SignatureSent)) { - Log($"send perpare request: height={timer_height} view={timer_view}"); - context.State |= ConsensusState.RequestSent; - if (!context.State.HasFlag(ConsensusState.SignatureSent)) - { - context.Timestamp = Math.Max(DateTime.Now.ToTimestamp(), Blockchain.Default.GetHeader(context.PrevHash).Timestamp + 1); - context.Signatures[context.MyIndex] = context.MakeHeader().Sign(context.KeyPair); - } - SignAndRelay(context.MakePrepareRequest()); - timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (timer_view + 1)), Timeout.InfiniteTimeSpan); + FillContext(); + context.Timestamp = Math.Max(DateTime.Now.ToTimestamp(), context.Snapshot.GetHeader(context.PrevHash).Timestamp + 1); + context.Signatures[context.MyIndex] = context.MakeHeader().Sign(context.KeyPair); } - else if ((context.State.HasFlag(ConsensusState.Primary) && context.State.HasFlag(ConsensusState.RequestSent)) || context.State.HasFlag(ConsensusState.Backup)) + SignAndRelay(context.MakePrepareRequest()); + if (context.TransactionHashes.Length > 1) { - RequestChangeView(); + foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, context.TransactionHashes.Skip(1).ToArray())) + system.LocalNode.Tell(Message.Create("inv", payload)); } + ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (timer.ViewNumber + 1))); } + else if ((context.State.HasFlag(ConsensusState.Primary) && context.State.HasFlag(ConsensusState.RequestSent)) || context.State.HasFlag(ConsensusState.Backup)) + { + RequestChangeView(); + } + } + + private void OnTransaction(Transaction transaction) + { + if (transaction.Type == TransactionType.MinerTransaction) return; + if (!context.State.HasFlag(ConsensusState.Backup) || !context.State.HasFlag(ConsensusState.RequestReceived) || context.State.HasFlag(ConsensusState.SignatureSent) || context.State.HasFlag(ConsensusState.ViewChanging)) + return; + if (context.Transactions.ContainsKey(transaction.Hash)) return; + if (!context.TransactionHashes.Contains(transaction.Hash)) return; + AddTransaction(transaction, true); + } + + protected override void PostStop() + { + Log("OnStop"); + context.Dispose(); + base.PostStop(); + } + + public static Props Props(NeoSystem system, Wallet wallet) + { + return Akka.Actor.Props.Create(() => new ConsensusService(system, wallet)).WithMailbox("consensus-service-mailbox"); } private void RequestChangeView() @@ -366,7 +369,7 @@ private void RequestChangeView() context.State |= ConsensusState.ViewChanging; context.ExpectedView[context.MyIndex]++; Log($"request change view: height={context.BlockIndex} view={context.ViewNumber} nv={context.ExpectedView[context.MyIndex]} state={context.State}"); - timer.Change(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (context.ExpectedView[context.MyIndex] + 1)), Timeout.InfiniteTimeSpan); + ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock << (context.ExpectedView[context.MyIndex] + 1))); SignAndRelay(context.MakeChangeView()); CheckExpectedView(context.ExpectedView[context.MyIndex]); } @@ -383,18 +386,29 @@ private void SignAndRelay(ConsensusPayload payload) { return; } - sc.Verifiable.Scripts = sc.GetScripts(); - localNode.RelayDirectly(payload); + sc.Verifiable.Witnesses = sc.GetWitnesses(); + system.LocalNode.Tell(new LocalNode.SendDirectly { Inventory = payload }); } + } - public void Start() + internal class ConsensusServiceMailbox : PriorityMailbox + { + public ConsensusServiceMailbox(Akka.Actor.Settings settings, Config config) + : base(settings, config) { - Log("OnStart"); - started = true; - Blockchain.PersistUnlocked += Blockchain_PersistUnlocked; - LocalNode.InventoryReceiving += LocalNode_InventoryReceiving; - LocalNode.InventoryReceived += LocalNode_InventoryReceived; - InitializeConsensus(0); + } + + protected override bool IsHighPriority(object message) + { + switch (message) + { + case ConsensusPayload _: + case ConsensusService.Timer _: + case Blockchain.PersistCompleted _: + return true; + default: + return false; + } } } } diff --git a/neo/Consensus/PrepareRequest.cs b/neo/Consensus/PrepareRequest.cs index a9e9032a54..c88f7b76a1 100644 --- a/neo/Consensus/PrepareRequest.cs +++ b/neo/Consensus/PrepareRequest.cs @@ -1,5 +1,5 @@ -using Neo.Core; -using Neo.IO; +using Neo.IO; +using Neo.Network.P2P.Payloads; using System; using System.IO; using System.Linq; diff --git a/neo/Core/Block.cs b/neo/Core/Block.cs deleted file mode 100644 index ead1e9e462..0000000000 --- a/neo/Core/Block.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Neo.Cryptography; -using Neo.IO; -using Neo.IO.Json; -using Neo.Network; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Neo.Core -{ - /// - /// 区块或区块头 - /// - public class Block : BlockBase, IInventory, IEquatable - { - /// - /// 交易列表 - /// - public Transaction[] Transactions; - - private Header _header = null; - /// - /// 该区块的区块头 - /// - public Header Header - { - get - { - if (_header == null) - { - _header = new Header - { - PrevHash = PrevHash, - MerkleRoot = MerkleRoot, - Timestamp = Timestamp, - Index = Index, - ConsensusData = ConsensusData, - NextConsensus = NextConsensus, - Script = Script - }; - } - return _header; - } - } - - /// - /// 资产清单的类型 - /// - InventoryType IInventory.InventoryType => InventoryType.Block; - - public override int Size => base.Size + Transactions.GetVarSize(); - - public static Fixed8 CalculateNetFee(IEnumerable transactions) - { - Transaction[] ts = transactions.Where(p => p.Type != TransactionType.MinerTransaction && p.Type != TransactionType.ClaimTransaction).ToArray(); - Fixed8 amount_in = ts.SelectMany(p => p.References.Values.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value); - Fixed8 amount_out = ts.SelectMany(p => p.Outputs.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value); - Fixed8 amount_sysfee = ts.Sum(p => p.SystemFee); - return amount_in - amount_out - amount_sysfee; - } - - /// - /// 反序列化 - /// - /// 数据来源 - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - Transactions = new Transaction[reader.ReadVarInt(0x10000)]; - if (Transactions.Length == 0) throw new FormatException(); - for (int i = 0; i < Transactions.Length; i++) - { - Transactions[i] = Transaction.DeserializeFrom(reader); - } - if (MerkleTree.ComputeRoot(Transactions.Select(p => p.Hash).ToArray()) != MerkleRoot) - throw new FormatException(); - } - - /// - /// 比较当前区块与指定区块是否相等 - /// - /// 要比较的区块 - /// 返回对象是否相等 - public bool Equals(Block other) - { - if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) return false; - return Hash.Equals(other.Hash); - } - - /// - /// 比较当前区块与指定区块是否相等 - /// - /// 要比较的区块 - /// 返回对象是否相等 - public override bool Equals(object obj) - { - return Equals(obj as Block); - } - - public static Block FromTrimmedData(byte[] data, int index, Func txSelector) - { - Block block = new Block(); - using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) - using (BinaryReader reader = new BinaryReader(ms)) - { - ((IVerifiable)block).DeserializeUnsigned(reader); - reader.ReadByte(); block.Script = reader.ReadSerializable(); - block.Transactions = new Transaction[reader.ReadVarInt(0x10000000)]; - for (int i = 0; i < block.Transactions.Length; i++) - { - block.Transactions[i] = txSelector(reader.ReadSerializable()); - } - } - return block; - } - - /// - /// 获得区块的HashCode - /// - /// 返回区块的HashCode - public override int GetHashCode() - { - return Hash.GetHashCode(); - } - - /// - /// 根据区块中所有交易的Hash生成MerkleRoot - /// - public void RebuildMerkleRoot() - { - MerkleRoot = MerkleTree.ComputeRoot(Transactions.Select(p => p.Hash).ToArray()); - } - - /// - /// 序列化 - /// - /// 存放序列化后的数据 - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(Transactions); - } - - /// - /// 变成json对象 - /// - /// 返回json对象 - public override JObject ToJson() - { - JObject json = base.ToJson(); - json["tx"] = Transactions.Select(p => p.ToJson()).ToArray(); - return json; - } - - /// - /// 把区块对象变为只包含区块头和交易Hash的字节数组,去除交易数据 - /// - /// 返回只包含区块头和交易Hash的字节数组 - public byte[] Trim() - { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms)) - { - ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write((byte)1); writer.Write(Script); - writer.Write(Transactions.Select(p => p.Hash).ToArray()); - writer.Flush(); - return ms.ToArray(); - } - } - - /// - /// 验证该区块是否合法 - /// - /// 是否同时验证区块中的每一笔交易 - /// 返回该区块的合法性,返回true即为合法,否则,非法。 - public bool Verify(bool completely) - { - if (!Verify()) return false; - if (Transactions[0].Type != TransactionType.MinerTransaction || Transactions.Skip(1).Any(p => p.Type == TransactionType.MinerTransaction)) - return false; - if (completely) - { - if (NextConsensus != Blockchain.GetConsensusAddress(Blockchain.Default.GetValidators(Transactions).ToArray())) - return false; - foreach (Transaction tx in Transactions) - if (!tx.Verify(Transactions.Where(p => !p.Hash.Equals(tx.Hash)))) return false; - Transaction tx_gen = Transactions.FirstOrDefault(p => p.Type == TransactionType.MinerTransaction); - if (tx_gen?.Outputs.Sum(p => p.Value) != CalculateNetFee(Transactions)) return false; - } - return true; - } - } -} diff --git a/neo/Core/Blockchain.cs b/neo/Core/Blockchain.cs deleted file mode 100644 index f8c17672ab..0000000000 --- a/neo/Core/Blockchain.cs +++ /dev/null @@ -1,595 +0,0 @@ -using Neo.Cryptography; -using Neo.Cryptography.ECC; -using Neo.IO; -using Neo.IO.Caching; -using Neo.SmartContract; -using Neo.VM; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace Neo.Core -{ - /// - /// 实现区块链功能的基类 - /// - public abstract class Blockchain : IDisposable, IScriptTable - { - public static event EventHandler PersistCompleted; - public static event EventHandler PersistUnlocked; - - public CancellationTokenSource VerificationCancellationToken { get; protected set; } = new CancellationTokenSource(); - public object PersistLock { get; } = new object(); - - /// - /// 产生每个区块的时间间隔,已秒为单位 - /// - public static readonly uint SecondsPerBlock = Settings.Default.SecondsPerBlock; - public const uint DecrementInterval = 2000000; - public const uint MaxValidators = 1024; - public static readonly uint[] GenerationAmount = { 8, 7, 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 ECPoint[] StandbyValidators = Settings.Default.StandbyValidators.OfType().Select(p => ECPoint.DecodePoint(p.HexToBytes(), ECCurve.Secp256r1)).ToArray(); - - /// - /// Return true if haven't got valid handle - /// - public abstract bool IsDisposed { get; } - -#pragma warning disable CS0612 - public static readonly RegisterTransaction GoverningToken = new RegisterTransaction - { - AssetType = AssetType.GoverningToken, - Name = "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", - Amount = Fixed8.FromDecimal(100000000), - Precision = 0, - Owner = ECCurve.Secp256r1.Infinity, - Admin = (new[] { (byte)OpCode.PUSHT }).ToScriptHash(), - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = new TransactionOutput[0], - Scripts = new Witness[0] - }; - - public static readonly RegisterTransaction UtilityToken = new RegisterTransaction - { - AssetType = AssetType.UtilityToken, - Name = "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]", - Amount = Fixed8.FromDecimal(GenerationAmount.Sum(p => p) * DecrementInterval), - Precision = 8, - Owner = ECCurve.Secp256r1.Infinity, - Admin = (new[] { (byte)OpCode.PUSHF }).ToScriptHash(), - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = new TransactionOutput[0], - Scripts = new Witness[0] - }; -#pragma warning restore CS0612 - - /// - /// 创世区块 - /// - public static readonly Block GenesisBlock = new Block - { - PrevHash = UInt256.Zero, - Timestamp = (new DateTime(2016, 7, 15, 15, 8, 21, DateTimeKind.Utc)).ToTimestamp(), - Index = 0, - ConsensusData = 2083236893, //向比特币致敬 - NextConsensus = GetConsensusAddress(StandbyValidators), - Script = new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSHT } - }, - Transactions = new Transaction[] - { - new MinerTransaction - { - Nonce = 2083236893, - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = new TransactionOutput[0], - Scripts = new Witness[0] - }, - GoverningToken, - UtilityToken, - new IssueTransaction - { - Attributes = new TransactionAttribute[0], - Inputs = new CoinReference[0], - Outputs = new[] - { - new TransactionOutput - { - AssetId = GoverningToken.Hash, - Value = GoverningToken.Amount, - ScriptHash = Contract.CreateMultiSigRedeemScript(StandbyValidators.Length / 2 + 1, StandbyValidators).ToScriptHash() - } - }, - Scripts = new[] - { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSHT } - } - } - } - } - }; - - /// - /// 当前最新区块散列值 - /// - public abstract UInt256 CurrentBlockHash { get; } - /// - /// 当前最新区块头的散列值 - /// - public abstract UInt256 CurrentHeaderHash { get; } - /// - /// 默认的区块链实例 - /// - public static Blockchain Default { get; private set; } = null; - /// - /// 区块头高度 - /// - public abstract uint HeaderHeight { get; } - /// - /// 区块高度 - /// - public abstract uint Height { get; } - - static Blockchain() - { - GenesisBlock.RebuildMerkleRoot(); - } - - /// - /// 将指定的区块添加到区块链中 - /// - /// 要添加的区块 - /// 返回是否添加成功 - public abstract bool AddBlock(Block block); - - /// - /// 将指定的区块头添加到区块头链中 - /// - /// 要添加的区块头列表 - protected internal abstract void AddHeaders(IEnumerable
headers); - - public static Fixed8 CalculateBonus(IEnumerable inputs, bool ignoreClaimed = true) - { - List unclaimed = new List(); - foreach (var group in inputs.GroupBy(p => p.PrevHash)) - { - Dictionary claimable = Default.GetUnclaimed(group.Key); - if (claimable == null || claimable.Count == 0) - if (ignoreClaimed) - continue; - else - throw new ArgumentException(); - foreach (CoinReference claim in group) - { - if (!claimable.TryGetValue(claim.PrevIndex, out SpentCoin claimed)) - if (ignoreClaimed) - continue; - else - throw new ArgumentException(); - unclaimed.Add(claimed); - } - } - return CalculateBonusInternal(unclaimed); - } - - public static Fixed8 CalculateBonus(IEnumerable inputs, uint height_end) - { - List unclaimed = new List(); - foreach (var group in inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx = Default.GetTransaction(group.Key, out int height_start); - if (tx == null) throw new ArgumentException(); - if (height_start == height_end) continue; - foreach (CoinReference claim in group) - { - if (claim.PrevIndex >= tx.Outputs.Length || !tx.Outputs[claim.PrevIndex].AssetId.Equals(GoverningToken.Hash)) - throw new ArgumentException(); - unclaimed.Add(new SpentCoin - { - Output = tx.Outputs[claim.PrevIndex], - StartHeight = (uint)height_start, - EndHeight = height_end - }); - } - } - return CalculateBonusInternal(unclaimed); - } - - private static Fixed8 CalculateBonusInternal(IEnumerable unclaimed) - { - Fixed8 amount_claimed = Fixed8.Zero; - foreach (var group in unclaimed.GroupBy(p => new { p.StartHeight, p.EndHeight })) - { - uint amount = 0; - uint ustart = group.Key.StartHeight / DecrementInterval; - if (ustart < GenerationAmount.Length) - { - uint istart = group.Key.StartHeight % DecrementInterval; - uint uend = group.Key.EndHeight / DecrementInterval; - uint iend = group.Key.EndHeight % DecrementInterval; - if (uend >= GenerationAmount.Length) - { - uend = (uint)GenerationAmount.Length; - iend = 0; - } - if (iend == 0) - { - uend--; - iend = DecrementInterval; - } - while (ustart < uend) - { - amount += (DecrementInterval - istart) * GenerationAmount[ustart]; - ustart++; - istart = 0; - } - amount += (iend - istart) * GenerationAmount[ustart]; - } - amount += (uint)(Default.GetSysFeeAmount(group.Key.EndHeight - 1) - (group.Key.StartHeight == 0 ? 0 : Default.GetSysFeeAmount(group.Key.StartHeight - 1))); - amount_claimed += group.Sum(p => p.Value) / 100000000 * amount; - } - return amount_claimed; - } - - /// - /// 判断区块链中是否包含指定的区块 - /// - /// 区块编号 - /// 如果包含指定区块则返回true - public abstract bool ContainsBlock(UInt256 hash); - - /// - /// 判断区块链中是否包含指定的交易 - /// - /// 交易编号 - /// 如果包含指定交易则返回true - public abstract bool ContainsTransaction(UInt256 hash); - - public bool ContainsUnspent(CoinReference input) - { - return ContainsUnspent(input.PrevHash, input.PrevIndex); - } - - public abstract bool ContainsUnspent(UInt256 hash, ushort index); - - public abstract MetaDataCache GetMetaData() where T : class, ISerializable, new(); - - public abstract DataCache GetStates() - where TKey : IEquatable, ISerializable, new() - where TValue : StateBase, ICloneable, new(); - - public abstract void Dispose(); - - public abstract AccountState GetAccountState(UInt160 script_hash); - - public abstract AssetState GetAssetState(UInt256 asset_id); - - /// - /// 根据指定的高度,返回对应的区块信息 - /// - /// 区块高度 - /// 返回对应的区块信息 - public Block GetBlock(uint height) - { - UInt256 hash = GetBlockHash(height); - if (hash == null) return null; - return GetBlock(hash); - } - - /// - /// 根据指定的散列值,返回对应的区块信息 - /// - /// 散列值 - /// 返回对应的区块信息 - public abstract Block GetBlock(UInt256 hash); - - /// - /// 根据指定的高度,返回对应区块的散列值 - /// - /// 区块高度 - /// 返回对应区块的散列值 - public abstract UInt256 GetBlockHash(uint height); - - public abstract ContractState GetContract(UInt160 hash); - - public abstract IEnumerable GetEnrollments(); - - /// - /// 根据指定的高度,返回对应的区块头信息 - /// - /// 区块高度 - /// 返回对应的区块头信息 - public abstract Header GetHeader(uint height); - - /// - /// 根据指定的散列值,返回对应的区块头信息 - /// - /// 散列值 - /// 返回对应的区块头信息 - public abstract Header GetHeader(UInt256 hash); - - /// - /// 获取记账人的合约地址 - /// - /// 记账人的公钥列表 - /// 返回记账人的合约地址 - public static UInt160 GetConsensusAddress(ECPoint[] validators) - { - return Contract.CreateMultiSigRedeemScript(validators.Length - (validators.Length - 1) / 3, validators).ToScriptHash(); - } - - private List _validators = new List(); - /// - /// 获取下一个区块的记账人列表 - /// - /// 返回一组公钥,表示下一个区块的记账人列表 - public ECPoint[] GetValidators() - { - lock (_validators) - { - if (_validators.Count == 0) - { - _validators.AddRange(GetValidators(Enumerable.Empty())); - } - return _validators.ToArray(); - } - } - - public virtual IEnumerable GetValidators(IEnumerable others) - { - DataCache accounts = GetStates(); - DataCache validators = GetStates(); - MetaDataCache validators_count = GetMetaData(); - foreach (Transaction tx in others) - { - foreach (TransactionOutput output in tx.Outputs) - { - AccountState account = accounts.GetAndChange(output.ScriptHash, () => new AccountState(output.ScriptHash)); - if (account.Balances.ContainsKey(output.AssetId)) - account.Balances[output.AssetId] += output.Value; - else - account.Balances[output.AssetId] = output.Value; - if (output.AssetId.Equals(GoverningToken.Hash) && account.Votes.Length > 0) - { - foreach (ECPoint pubkey in account.Votes) - validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += output.Value; - validators_count.GetAndChange().Votes[account.Votes.Length - 1] += output.Value; - } - } - foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx_prev = GetTransaction(group.Key, out int height); - foreach (CoinReference input in group) - { - TransactionOutput out_prev = tx_prev.Outputs[input.PrevIndex]; - AccountState account = accounts.GetAndChange(out_prev.ScriptHash); - if (out_prev.AssetId.Equals(GoverningToken.Hash)) - { - if (account.Votes.Length > 0) - { - foreach (ECPoint pubkey in account.Votes) - { - ValidatorState validator = validators.GetAndChange(pubkey); - validator.Votes -= out_prev.Value; - if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) - validators.Delete(pubkey); - } - validators_count.GetAndChange().Votes[account.Votes.Length - 1] -= out_prev.Value; - } - } - account.Balances[out_prev.AssetId] -= out_prev.Value; - } - } - switch (tx) - { -#pragma warning disable CS0612 - case EnrollmentTransaction tx_enrollment: - validators.GetAndChange(tx_enrollment.PublicKey, () => new ValidatorState(tx_enrollment.PublicKey)).Registered = true; - break; -#pragma warning restore CS0612 - case StateTransaction tx_state: - foreach (StateDescriptor descriptor in tx_state.Descriptors) - switch (descriptor.Type) - { - case StateType.Account: - ProcessAccountStateDescriptor(descriptor, accounts, validators, validators_count); - break; - case StateType.Validator: - ProcessValidatorStateDescriptor(descriptor, validators); - break; - } - break; - } - } - int count = (int)validators_count.Get().Votes.Select((p, i) => new - { - Count = i, - Votes = p - }).Where(p => p.Votes > Fixed8.Zero).ToArray().WeightedFilter(0.25, 0.75, p => p.Votes.GetData(), (p, w) => new - { - p.Count, - Weight = w - }).WeightedAverage(p => p.Count, p => p.Weight); - count = Math.Max(count, StandbyValidators.Length); - HashSet sv = new HashSet(StandbyValidators); - ECPoint[] pubkeys = validators.Find().Select(p => p.Value).Where(p => (p.Registered && p.Votes > Fixed8.Zero) || sv.Contains(p.PublicKey)).OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(count).ToArray(); - IEnumerable result; - if (pubkeys.Length == count) - { - result = pubkeys; - } - else - { - HashSet hashSet = new HashSet(pubkeys); - for (int i = 0; i < StandbyValidators.Length && hashSet.Count < count; i++) - hashSet.Add(StandbyValidators[i]); - result = hashSet; - } - return result.OrderBy(p => p); - } - - /// - /// 根据指定的散列值,返回下一个区块的信息 - /// - /// 散列值 - /// 返回下一个区块的信息> - public abstract Block GetNextBlock(UInt256 hash); - - /// - /// 根据指定的散列值,返回下一个区块的散列值 - /// - /// 散列值 - /// 返回下一个区块的散列值 - public abstract UInt256 GetNextBlockHash(UInt256 hash); - - byte[] IScriptTable.GetScript(byte[] script_hash) - { - return GetContract(new UInt160(script_hash)).Script; - } - - public abstract StorageItem GetStorageItem(StorageKey key); - - /// - /// 根据指定的区块高度,返回对应区块及之前所有区块中包含的系统费用的总量 - /// - /// 区块高度 - /// 返回对应的系统费用的总量 - public virtual long GetSysFeeAmount(uint height) - { - return GetSysFeeAmount(GetBlockHash(height)); - } - - /// - /// 根据指定的区块散列值,返回对应区块及之前所有区块中包含的系统费用的总量 - /// - /// 散列值 - /// 返回系统费用的总量 - public abstract long GetSysFeeAmount(UInt256 hash); - - /// - /// 根据指定的散列值,返回对应的交易信息 - /// - /// 散列值 - /// 返回对应的交易信息 - public Transaction GetTransaction(UInt256 hash) - { - return GetTransaction(hash, out _); - } - - /// - /// 根据指定的散列值,返回对应的交易信息与该交易所在区块的高度 - /// - /// 交易散列值 - /// 返回该交易所在区块的高度 - /// 返回对应的交易信息 - public abstract Transaction GetTransaction(UInt256 hash, out int height); - - public abstract Dictionary GetUnclaimed(UInt256 hash); - - /// - /// 根据指定的散列值和索引,获取对应的未花费的资产 - /// - /// 交易散列值 - /// 输出的索引 - /// 返回一个交易输出,表示一个未花费的资产 - public abstract TransactionOutput GetUnspent(UInt256 hash, ushort index); - - public abstract IEnumerable GetUnspent(UInt256 hash); - - /// - /// 判断交易是否双花 - /// - /// 交易 - /// 返回交易是否双花 - public abstract bool IsDoubleSpend(Transaction tx); - - /// - /// 当区块被写入到硬盘后调用 - /// - /// 区块 - protected void OnPersistCompleted(Block block) - { - PersistCompleted?.Invoke(this, block); - } - - protected void OnPersistUnlocked(Block block) - { - lock (_validators) - { - _validators.Clear(); - } - PersistUnlocked?.Invoke(this, block); - } - - protected void ProcessAccountStateDescriptor(StateDescriptor descriptor, DataCache accounts, DataCache validators, MetaDataCache validators_count) - { - UInt160 hash = new UInt160(descriptor.Key); - AccountState account = accounts.GetAndChange(hash, () => new AccountState(hash)); - switch (descriptor.Field) - { - case "Votes": - Fixed8 balance = account.GetBalance(GoverningToken.Hash); - foreach (ECPoint pubkey in account.Votes) - { - ValidatorState validator = validators.GetAndChange(pubkey); - validator.Votes -= balance; - if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) - validators.Delete(pubkey); - } - ECPoint[] votes = descriptor.Value.AsSerializableArray().Distinct().ToArray(); - if (votes.Length != account.Votes.Length) - { - ValidatorsCountState count_state = validators_count.GetAndChange(); - if (account.Votes.Length > 0) - count_state.Votes[account.Votes.Length - 1] -= balance; - if (votes.Length > 0) - count_state.Votes[votes.Length - 1] += balance; - } - account.Votes = votes; - foreach (ECPoint pubkey in account.Votes) - validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += balance; - break; - } - } - - protected void ProcessValidatorStateDescriptor(StateDescriptor descriptor, DataCache validators) - { - ECPoint pubkey = ECPoint.DecodePoint(descriptor.Key, ECCurve.Secp256r1); - ValidatorState validator = validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)); - switch (descriptor.Field) - { - case "Registered": - validator.Registered = BitConverter.ToBoolean(descriptor.Value, 0); - break; - } - } - - /// - /// 注册默认的区块链实例 - /// - /// 区块链实例 - /// 返回注册后的区块链实例 - public static Blockchain RegisterBlockchain(Blockchain blockchain) - { - if (Default != null) Default.Dispose(); - Default = blockchain ?? throw new ArgumentNullException(); - return blockchain; - } - } -} diff --git a/neo/Core/Helper.cs b/neo/Core/Helper.cs deleted file mode 100644 index 7683b2b952..0000000000 --- a/neo/Core/Helper.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Neo.Cryptography; -using Neo.SmartContract; -using Neo.VM; -using Neo.Wallets; -using System; -using System.IO; -using System.Linq; - -namespace Neo.Core -{ - /// - /// 包含一系列签名与验证的扩展方法 - /// - public static class Helper - { - public static byte[] GetHashData(this IVerifiable verifiable) - { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms)) - { - verifiable.SerializeUnsigned(writer); - writer.Flush(); - return ms.ToArray(); - } - } - - /// - /// 根据传入的账户信息,对可签名的对象进行签名 - /// - /// 要签名的数据 - /// 用于签名的账户 - /// 返回签名后的结果 - public static byte[] Sign(this IVerifiable verifiable, KeyPair key) - { - using (key.Decrypt()) - { - return Crypto.Default.Sign(verifiable.GetHashData(), key.PrivateKey, key.PublicKey.EncodePoint(false).Skip(1).ToArray()); - } - } - - public static UInt160 ToScriptHash(this byte[] script) - { - return new UInt160(Crypto.Default.Hash160(script)); - } - - internal static bool VerifyScripts(this IVerifiable verifiable) - { - UInt160[] hashes; - try - { - hashes = verifiable.GetScriptHashesForVerifying(); - } - catch (InvalidOperationException) - { - return false; - } - if (hashes.Length != verifiable.Scripts.Length) return false; - for (int i = 0; i < hashes.Length; i++) - { - byte[] verification = verifiable.Scripts[i].VerificationScript; - if (verification.Length == 0) - { - using (ScriptBuilder sb = new ScriptBuilder()) - { - sb.EmitAppCall(hashes[i].ToArray()); - verification = sb.ToArray(); - } - } - else - { - if (hashes[i] != verifiable.Scripts[i].ScriptHash) return false; - } - using (StateReader service = new StateReader()) - { - ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, Blockchain.Default, service, Fixed8.Zero); - engine.LoadScript(verification); - engine.LoadScript(verifiable.Scripts[i].InvocationScript); - if (!engine.Execute()) return false; - if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; - } - } - return true; - } - } -} diff --git a/neo/Core/IVerifiable.cs b/neo/Core/IVerifiable.cs deleted file mode 100644 index dcf9fc72b4..0000000000 --- a/neo/Core/IVerifiable.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Neo.IO; -using Neo.VM; -using System.IO; - -namespace Neo.Core -{ - /// - /// 为需要签名的数据提供一个接口 - /// - public interface IVerifiable : ISerializable, IScriptContainer - { - /// - /// 用于验证该对象的脚本列表 - /// - Witness[] Scripts { get; set; } - - /// - /// 反序列化未签名的数据 - /// - /// 数据来源 - void DeserializeUnsigned(BinaryReader reader); - - /// - /// 获得需要校验的脚本Hash值 - /// - /// 返回需要校验的脚本Hash值 - UInt160[] GetScriptHashesForVerifying(); - - /// - /// 序列化未签名的数据 - /// - /// 存放序列化后的结果 - void SerializeUnsigned(BinaryWriter writer); - } -} diff --git a/neo/Core/TransactionResult.cs b/neo/Core/TransactionResult.cs deleted file mode 100644 index 92448610fa..0000000000 --- a/neo/Core/TransactionResult.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Neo.Core -{ - /// - /// 交易结果,表示交易中资产的变化量 - /// - public class TransactionResult - { - /// - /// 资产编号 - /// - public UInt256 AssetId; - /// - /// 该资产的变化量 - /// - public Fixed8 Amount; - } -} diff --git a/neo/Cryptography/Base58.cs b/neo/Cryptography/Base58.cs index 1d8107c1ff..bb5dd42df9 100644 --- a/neo/Cryptography/Base58.cs +++ b/neo/Cryptography/Base58.cs @@ -7,16 +7,8 @@ namespace Neo.Cryptography { public static class Base58 { - /// - /// base58编码的字母表 - /// public const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - /// - /// 解码 - /// - /// 要解码的字符串 - /// 返回解码后的字节数组 public static byte[] Decode(string input) { BigInteger bi = BigInteger.Zero; @@ -40,11 +32,6 @@ public static byte[] Decode(string input) return tmp; } - /// - /// 编码 - /// - /// 要编码的字节数组 - /// 返回编码后的字符串 public static string Encode(byte[] input) { BigInteger value = new BigInteger(new byte[1].Concat(input).Reverse().ToArray()); diff --git a/neo/Cryptography/ECC/ECCurve.cs b/neo/Cryptography/ECC/ECCurve.cs index aa4a956f76..9d43bcd25f 100644 --- a/neo/Cryptography/ECC/ECCurve.cs +++ b/neo/Cryptography/ECC/ECCurve.cs @@ -3,22 +3,13 @@ namespace Neo.Cryptography.ECC { - /// - /// ECC椭圆曲线参数 - /// public class ECCurve { internal readonly BigInteger Q; internal readonly ECFieldElement A; internal readonly ECFieldElement B; internal readonly BigInteger N; - /// - /// 无穷远点 - /// public readonly ECPoint Infinity; - /// - /// 基点 - /// public readonly ECPoint G; private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G) @@ -31,9 +22,6 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G this.G = ECPoint.DecodePoint(G, this); } - /// - /// 曲线secp256k1 - /// public static readonly ECCurve Secp256k1 = new ECCurve ( BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", NumberStyles.AllowHexSpecifier), @@ -43,9 +31,6 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G ("04" + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8").HexToBytes() ); - /// - /// 曲线secp256r1 - /// public static readonly ECCurve Secp256r1 = new ECCurve ( BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", NumberStyles.AllowHexSpecifier), diff --git a/neo/Cryptography/ECC/ECDsa.cs b/neo/Cryptography/ECC/ECDsa.cs index 8e0f028e99..a254b4248e 100644 --- a/neo/Cryptography/ECC/ECDsa.cs +++ b/neo/Cryptography/ECC/ECDsa.cs @@ -5,30 +5,18 @@ namespace Neo.Cryptography.ECC { - /// - /// 提供椭圆曲线数字签名算法(ECDSA)的功能 - /// public class ECDsa { private readonly byte[] privateKey; private readonly ECPoint publicKey; private readonly ECCurve curve; - /// - /// 根据指定的私钥和曲线参数来创建新的ECDsa对象,该对象可用于签名 - /// - /// 私钥 - /// 椭圆曲线参数 public ECDsa(byte[] privateKey, ECCurve curve) : this(curve.G * privateKey) { this.privateKey = privateKey; } - /// - /// 根据指定的公钥来创建新的ECDsa对象,该对象可用于验证签名 - /// - /// 公钥 public ECDsa(ECPoint publicKey) { this.publicKey = publicKey; @@ -46,11 +34,6 @@ private BigInteger CalculateE(BigInteger n, byte[] message) return trunc; } - /// - /// 生成椭圆曲线数字签名 - /// - /// 要签名的消息 - /// 返回签名的数字编码(r,s) public BigInteger[] GenerateSignature(byte[] message) { if (privateKey == null) throw new InvalidOperationException(); @@ -109,13 +92,6 @@ private static ECPoint SumOfTwoMultiplies(ECPoint P, BigInteger k, ECPoint Q, Bi return R; } - /// - /// 验证签名的合法性 - /// - /// 要验证的消息 - /// 签名的数字编码 - /// 签名的数字编码 - /// 返回验证的结果 public bool VerifySignature(byte[] message, BigInteger r, BigInteger s) { if (r.Sign < 1 || s.Sign < 1 || r.CompareTo(curve.N) >= 0 || s.CompareTo(curve.N) >= 0) diff --git a/neo/Cryptography/ECC/ECPoint.cs b/neo/Cryptography/ECC/ECPoint.cs index ba529f8b02..99d2b099e1 100644 --- a/neo/Cryptography/ECC/ECPoint.cs +++ b/neo/Cryptography/ECC/ECPoint.cs @@ -12,9 +12,6 @@ public class ECPoint : IComparable, IEquatable, ISerializable internal ECFieldElement X, Y; internal readonly ECCurve Curve; - /// - /// 判断是否为无穷远点 - /// public bool IsInfinity { get { return X == null && Y == null; } @@ -36,11 +33,6 @@ internal ECPoint(ECFieldElement x, ECFieldElement y, ECCurve curve) this.Curve = curve; } - /// - /// 与另一对象进行比较 - /// - /// 另一对象 - /// 返回比较的结果 public int CompareTo(ECPoint other) { if (ReferenceEquals(this, other)) return 0; @@ -49,12 +41,6 @@ public int CompareTo(ECPoint other) return Y.CompareTo(other.Y); } - /// - /// 从字节数组中解码 - /// - /// 要解码的字节数组 - /// 曲线参数 - /// public static ECPoint DecodePoint(byte[] encoded, ECCurve curve) { ECPoint p = null; @@ -127,12 +113,6 @@ void ISerializable.Deserialize(BinaryReader reader) Y = p.Y; } - /// - /// 反序列化 - /// - /// 数据来源 - /// 椭圆曲线参数 - /// public static ECPoint DeserializeFrom(BinaryReader reader, ECCurve curve) { int expectedLength = (curve.Q.GetBitLength() + 7) / 8; @@ -156,11 +136,6 @@ public static ECPoint DeserializeFrom(BinaryReader reader, ECCurve curve) } } - /// - /// 将对象编码到字节数组 - /// - /// 是否为压缩格式的编码 - /// 返回编码后的字节数组 public byte[] EncodePoint(bool commpressed) { if (IsInfinity) return new byte[1]; @@ -181,11 +156,6 @@ public byte[] EncodePoint(bool commpressed) return data; } - /// - /// 比较与另一个对象是否相等 - /// - /// 另一个对象 - /// 返回比较的结果 public bool Equals(ECPoint other) { if (ReferenceEquals(this, other)) return true; @@ -195,22 +165,11 @@ public bool Equals(ECPoint other) return X.Equals(other.X) && Y.Equals(other.Y); } - /// - /// 比较与另一个对象是否相等 - /// - /// 另一个对象 - /// 返回比较的结果 public override bool Equals(object obj) { return Equals(obj as ECPoint); } - /// - /// 从指定的字节数组中解析出公钥,这个字节数组可以是任意形式的公钥编码、或者包含私钥的内容 - /// - /// 要解析的字节数组 - /// 椭圆曲线参数 - /// 返回解析出的公钥 public static ECPoint FromBytes(byte[] pubkey, ECCurve curve) { switch (pubkey.Length) @@ -229,10 +188,6 @@ public static ECPoint FromBytes(byte[] pubkey, ECCurve curve) } } - /// - /// 获取HashCode - /// - /// 返回HashCode public override int GetHashCode() { return X.GetHashCode() + Y.GetHashCode(); @@ -428,7 +383,6 @@ private static sbyte[] WindowNaf(sbyte width, BigInteger k) throw new ArgumentException(); if (p.IsInfinity) return p; - //BigInteger的内存无法被保护,可能会有安全隐患。此处的k需要重写一个SecureBigInteger类来代替 BigInteger k = new BigInteger(n.Reverse().Concat(new byte[1]).ToArray()); if (k.Sign == 0) return p.Curve.Infinity; diff --git a/neo/Cryptography/Helper.cs b/neo/Cryptography/Helper.cs index 539ecf3419..878195d873 100644 --- a/neo/Cryptography/Helper.cs +++ b/neo/Cryptography/Helper.cs @@ -1,4 +1,6 @@ -using System; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -9,9 +11,6 @@ namespace Neo.Cryptography { - /// - /// 包含一系列密码学算法的扩展方法 - /// public static class Helper { private static ThreadLocal _sha256 = new ThreadLocal(() => SHA256.Create()); @@ -92,11 +91,6 @@ public static string Base58CheckEncode(this byte[] data) return Base58.Encode(buffer); } - /// - /// 求字节数组的ripemd160散列值 - /// - /// 字节数组 - /// 返回该散列值 public static byte[] RIPEMD160(this IEnumerable value) { return _ripemd160.Value.ComputeHash(value.ToArray()); @@ -110,28 +104,30 @@ public static uint Murmur32(this IEnumerable value, uint seed) } } - /// - /// 求字节数组的sha256散列值 - /// - /// 字节数组 - /// 返回该散列值 public static byte[] Sha256(this IEnumerable value) { return _sha256.Value.ComputeHash(value.ToArray()); } - /// - /// 求字节数组的sha256散列值 - /// - /// 字节数组 - /// 偏移量,散列计算时从该偏移量处开始 - /// 要计算散列值的字节数量 - /// 返回该散列值 public static byte[] Sha256(this byte[] value, int offset, int count) { return _sha256.Value.ComputeHash(value, offset, count); } + internal static bool Test(this BloomFilter filter, Transaction tx) + { + if (filter.Check(tx.Hash.ToArray())) return true; + if (tx.Outputs.Any(p => filter.Check(p.ScriptHash.ToArray()))) return true; + if (tx.Inputs.Any(p => filter.Check(p.ToArray()))) return true; + if (tx.Witnesses.Any(p => filter.Check(p.ScriptHash.ToArray()))) + return true; +#pragma warning disable CS0612 + if (tx is RegisterTransaction asset) + if (filter.Check(asset.Admin.ToArray())) return true; +#pragma warning restore CS0612 + return false; + } + internal static byte[] ToAesKey(this string password) { using (SHA256 sha256 = SHA256.Create()) diff --git a/neo/Cryptography/MerkleTree.cs b/neo/Cryptography/MerkleTree.cs index 76b2ac198a..4f433aac88 100644 --- a/neo/Cryptography/MerkleTree.cs +++ b/neo/Cryptography/MerkleTree.cs @@ -5,9 +5,6 @@ namespace Neo.Cryptography { - /// - /// 哈希树 - /// public class MerkleTree { private MerkleTreeNode root; @@ -48,11 +45,6 @@ private static MerkleTreeNode Build(MerkleTreeNode[] leaves) return Build(parents); //TailCall } - /// - /// 计算根节点的值 - /// - /// 子节点列表 - /// 返回计算的结果 public static UInt256 ComputeRoot(UInt256[] hashes) { if (hashes.Length == 0) throw new ArgumentException(); diff --git a/neo/Cryptography/ProtectedMemoryContext.cs b/neo/Cryptography/ProtectedMemoryContext.cs deleted file mode 100644 index b9453a1a3b..0000000000 --- a/neo/Cryptography/ProtectedMemoryContext.cs +++ /dev/null @@ -1,39 +0,0 @@ -#if NET47 -using System; -using System.Collections.Generic; -using System.Security.Cryptography; - -namespace Neo.Cryptography -{ - internal class ProtectedMemoryContext : IDisposable - { - private static Dictionary counts = new Dictionary(); - private byte[] memory; - private MemoryProtectionScope scope; - - public ProtectedMemoryContext(byte[] memory, MemoryProtectionScope scope) - { - this.memory = memory; - this.scope = scope; - if (counts.ContainsKey(memory)) - { - counts[memory]++; - } - else - { - counts.Add(memory, 1); - ProtectedMemory.Unprotect(memory, scope); - } - } - - void IDisposable.Dispose() - { - if (--counts[memory] == 0) - { - counts.Remove(memory); - ProtectedMemory.Protect(memory, scope); - } - } - } -} -#endif \ No newline at end of file diff --git a/neo/Fixed8.cs b/neo/Fixed8.cs index 2fcd08d46a..7edf94ea41 100644 --- a/neo/Fixed8.cs +++ b/neo/Fixed8.cs @@ -233,7 +233,7 @@ public static explicit operator long(Fixed8 value) public static Fixed8 operator *(Fixed8 x, long y) { - x.value *= y; + x.value = checked(x.value * y); return x; } diff --git a/neo/Helper.cs b/neo/Helper.cs index 6bb5173834..7672d078b3 100644 --- a/neo/Helper.cs +++ b/neo/Helper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; @@ -216,6 +217,20 @@ unsafe internal static ulong ToUInt64(this byte[] value, int startIndex) } } + internal static IPAddress Unmap(this IPAddress address) + { + if (address.IsIPv4MappedToIPv6) + address = address.MapToIPv4(); + return address; + } + + internal static IPEndPoint Unmap(this IPEndPoint endPoint) + { + if (!endPoint.Address.IsIPv4MappedToIPv6) + return endPoint; + return new IPEndPoint(endPoint.Address.Unmap(), endPoint.Port); + } + internal static long WeightedAverage(this IEnumerable source, Func valueSelector, Func weightSelector) { long sum_weight = 0; diff --git a/neo/IO/Actors/PriorityMailbox.cs b/neo/IO/Actors/PriorityMailbox.cs new file mode 100644 index 0000000000..fa345302ea --- /dev/null +++ b/neo/IO/Actors/PriorityMailbox.cs @@ -0,0 +1,24 @@ +using Akka.Actor; +using Akka.Configuration; +using Akka.Dispatch; +using Akka.Dispatch.MessageQueues; +using System.Collections; + +namespace Neo.IO.Actors +{ + internal abstract class PriorityMailbox : MailboxType, IProducesMessageQueue + { + public PriorityMailbox(Akka.Actor.Settings settings, Config config) + : base(settings, config) + { + } + + public override IMessageQueue Create(IActorRef owner, ActorSystem system) + { + return new PriorityMessageQueue(ShallDrop, IsHighPriority); + } + + protected virtual bool IsHighPriority(object message) => false; + protected virtual bool ShallDrop(object message, IEnumerable queue) => false; + } +} diff --git a/neo/IO/Actors/PriorityMessageQueue.cs b/neo/IO/Actors/PriorityMessageQueue.cs new file mode 100644 index 0000000000..c166d76f08 --- /dev/null +++ b/neo/IO/Actors/PriorityMessageQueue.cs @@ -0,0 +1,46 @@ +using Akka.Actor; +using Akka.Dispatch; +using Akka.Dispatch.MessageQueues; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Linq; + +namespace Neo.IO.Actors +{ + internal class PriorityMessageQueue : IMessageQueue, IUnboundedMessageQueueSemantics + { + private readonly ConcurrentQueue high = new ConcurrentQueue(); + private readonly ConcurrentQueue low = new ConcurrentQueue(); + private readonly Func dropper; + private readonly Func priority_generator; + + public bool HasMessages => !high.IsEmpty || !low.IsEmpty; + public int Count => high.Count + low.Count; + + public PriorityMessageQueue(Func dropper, Func priority_generator) + { + this.dropper = dropper; + this.priority_generator = priority_generator; + } + + public void CleanUp(IActorRef owner, IMessageQueue deadletters) + { + } + + public void Enqueue(IActorRef receiver, Envelope envelope) + { + if (dropper(envelope.Message, high.Concat(low).Select(p => p.Message))) + return; + ConcurrentQueue queue = priority_generator(envelope.Message) ? high : low; + queue.Enqueue(envelope); + } + + public bool TryDequeue(out Envelope envelope) + { + if (high.TryDequeue(out envelope)) return true; + if (low.TryDequeue(out envelope)) return true; + return false; + } + } +} diff --git a/neo/IO/Caching/CloneMetaCache.cs b/neo/IO/Caching/CloneMetaCache.cs new file mode 100644 index 0000000000..f44600a336 --- /dev/null +++ b/neo/IO/Caching/CloneMetaCache.cs @@ -0,0 +1,28 @@ +namespace Neo.IO.Caching +{ + internal class CloneMetaCache : MetaDataCache + where T : class, ICloneable, ISerializable, new() + { + private MetaDataCache innerCache; + + public CloneMetaCache(MetaDataCache innerCache) + : base(null) + { + this.innerCache = innerCache; + } + + protected override void AddInternal(T item) + { + } + + protected override T TryGetInternal() + { + return innerCache.Get().Clone(); + } + + protected override void UpdateInternal(T item) + { + innerCache.GetAndChange().FromReplica(item); + } + } +} diff --git a/neo/IO/Caching/DataCache.cs b/neo/IO/Caching/DataCache.cs index 0ae9f6ba26..71f9da6317 100644 --- a/neo/IO/Caching/DataCache.cs +++ b/neo/IO/Caching/DataCache.cs @@ -15,41 +15,47 @@ protected internal class Trackable public TrackState State; } - private Dictionary dictionary = new Dictionary(); + private readonly Dictionary dictionary = new Dictionary(); public TValue this[TKey key] { get { - if (dictionary.TryGetValue(key, out Trackable trackable)) - { - if (trackable.State == TrackState.Deleted) - throw new KeyNotFoundException(); - } - else + lock (dictionary) { - trackable = new Trackable + if (dictionary.TryGetValue(key, out Trackable trackable)) { - Key = key, - Item = GetInternal(key), - State = TrackState.None - }; - dictionary.Add(key, trackable); + if (trackable.State == TrackState.Deleted) + throw new KeyNotFoundException(); + } + else + { + trackable = new Trackable + { + Key = key, + Item = GetInternal(key), + State = TrackState.None + }; + dictionary.Add(key, trackable); + } + return trackable.Item; } - return trackable.Item; } } public void Add(TKey key, TValue value) { - if (dictionary.TryGetValue(key, out Trackable trackable) && trackable.State != TrackState.Deleted) - throw new ArgumentException(); - dictionary[key] = new Trackable + lock (dictionary) { - Key = key, - Item = value, - State = trackable == null ? TrackState.Added : TrackState.Changed - }; + if (dictionary.TryGetValue(key, out Trackable trackable) && trackable.State != TrackState.Deleted) + throw new ArgumentException(); + dictionary[key] = new Trackable + { + Key = key, + Item = value, + State = trackable == null ? TrackState.Added : TrackState.Changed + }; + } } protected abstract void AddInternal(TKey key, TValue value); @@ -78,23 +84,26 @@ public DataCache CreateSnapshot() public void Delete(TKey key) { - if (dictionary.TryGetValue(key, out Trackable trackable)) + lock (dictionary) { - if (trackable.State == TrackState.Added) - dictionary.Remove(key); + if (dictionary.TryGetValue(key, out Trackable trackable)) + { + if (trackable.State == TrackState.Added) + dictionary.Remove(key); + else + trackable.State = TrackState.Deleted; + } else - trackable.State = TrackState.Deleted; - } - else - { - TValue item = TryGetInternal(key); - if (item == null) return; - dictionary.Add(key, new Trackable { - Key = key, - Item = item, - State = TrackState.Deleted - }); + TValue item = TryGetInternal(key); + if (item == null) return; + dictionary.Add(key, new Trackable + { + Key = key, + Item = item, + State = TrackState.Deleted + }); + } } } @@ -102,113 +111,132 @@ public void Delete(TKey key) public void DeleteWhere(Func predicate) { - foreach (Trackable trackable in dictionary.Where(p => p.Value.State != TrackState.Deleted && predicate(p.Key, p.Value.Item)).Select(p => p.Value)) - trackable.State = TrackState.Deleted; + lock (dictionary) + { + foreach (Trackable trackable in dictionary.Where(p => p.Value.State != TrackState.Deleted && predicate(p.Key, p.Value.Item)).Select(p => p.Value)) + trackable.State = TrackState.Deleted; + } } public IEnumerable> Find(byte[] key_prefix = null) { - 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); + 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); + } } protected abstract IEnumerable> FindInternal(byte[] key_prefix); protected internal IEnumerable GetChangeSet() { - return dictionary.Values.Where(p => p.State != TrackState.None); + lock (dictionary) + { + foreach (Trackable trackable in dictionary.Values.Where(p => p.State != TrackState.None)) + yield return trackable; + } } protected abstract TValue GetInternal(TKey key); public TValue GetAndChange(TKey key, Func factory = null) { - if (dictionary.TryGetValue(key, out Trackable trackable)) - { - if (trackable.State == TrackState.Deleted) - { - if (factory == null) throw new KeyNotFoundException(); - trackable.Item = factory(); - trackable.State = TrackState.Changed; - } - else if (trackable.State == TrackState.None) - { - trackable.State = TrackState.Changed; - } - } - else + lock (dictionary) { - trackable = new Trackable - { - Key = key, - Item = TryGetInternal(key) - }; - if (trackable.Item == null) + if (dictionary.TryGetValue(key, out Trackable trackable)) { - if (factory == null) throw new KeyNotFoundException(); - trackable.Item = factory(); - trackable.State = TrackState.Added; + if (trackable.State == TrackState.Deleted) + { + if (factory == null) throw new KeyNotFoundException(); + trackable.Item = factory(); + trackable.State = TrackState.Changed; + } + else if (trackable.State == TrackState.None) + { + trackable.State = TrackState.Changed; + } } else { - trackable.State = TrackState.Changed; + trackable = new Trackable + { + Key = key, + Item = TryGetInternal(key) + }; + if (trackable.Item == null) + { + if (factory == null) throw new KeyNotFoundException(); + trackable.Item = factory(); + trackable.State = TrackState.Added; + } + else + { + trackable.State = TrackState.Changed; + } + dictionary.Add(key, trackable); } - dictionary.Add(key, trackable); + return trackable.Item; } - return trackable.Item; } public TValue GetOrAdd(TKey key, Func factory) { - if (dictionary.TryGetValue(key, out Trackable trackable)) - { - if (trackable.State == TrackState.Deleted) - { - trackable.Item = factory(); - trackable.State = TrackState.Changed; - } - } - else + lock (dictionary) { - trackable = new Trackable - { - Key = key, - Item = TryGetInternal(key) - }; - if (trackable.Item == null) + if (dictionary.TryGetValue(key, out Trackable trackable)) { - trackable.Item = factory(); - trackable.State = TrackState.Added; + if (trackable.State == TrackState.Deleted) + { + trackable.Item = factory(); + trackable.State = TrackState.Changed; + } } else { - trackable.State = TrackState.None; + trackable = new Trackable + { + Key = key, + Item = TryGetInternal(key) + }; + if (trackable.Item == null) + { + trackable.Item = factory(); + trackable.State = TrackState.Added; + } + else + { + trackable.State = TrackState.None; + } + dictionary.Add(key, trackable); } - dictionary.Add(key, trackable); + return trackable.Item; } - return trackable.Item; } public TValue TryGet(TKey key) { - if (dictionary.TryGetValue(key, out Trackable trackable)) + lock (dictionary) { - if (trackable.State == TrackState.Deleted) return null; - return trackable.Item; + if (dictionary.TryGetValue(key, out Trackable trackable)) + { + if (trackable.State == TrackState.Deleted) return null; + return trackable.Item; + } + TValue value = TryGetInternal(key); + if (value == null) return null; + dictionary.Add(key, new Trackable + { + Key = key, + Item = value, + State = TrackState.None + }); + return value; } - TValue value = TryGetInternal(key); - if (value == null) return null; - dictionary.Add(key, new Trackable - { - Key = key, - Item = value, - State = TrackState.None - }); - return value; } protected abstract TValue TryGetInternal(TKey key); diff --git a/neo/IO/Caching/LRUCache.cs b/neo/IO/Caching/LRUCache.cs deleted file mode 100644 index fa3b7a03d8..0000000000 --- a/neo/IO/Caching/LRUCache.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Neo.IO.Caching -{ - internal abstract class LRUCache : Cache - { - public LRUCache(int max_capacity) - : base(max_capacity) - { - } - - protected override void OnAccess(CacheItem item) - { - item.Time = DateTime.Now; - } - } -} diff --git a/neo/IO/Caching/MetaDataCache.cs b/neo/IO/Caching/MetaDataCache.cs index 20615cc287..fa3fed6215 100644 --- a/neo/IO/Caching/MetaDataCache.cs +++ b/neo/IO/Caching/MetaDataCache.cs @@ -2,19 +2,40 @@ namespace Neo.IO.Caching { - public abstract class MetaDataCache where T : class, ISerializable, new() + public abstract class MetaDataCache + where T : class, ICloneable, ISerializable, new() { - protected T Item; - protected TrackState State; - private Func factory; + private T Item; + private TrackState State; + private readonly Func factory; + protected abstract void AddInternal(T item); protected abstract T TryGetInternal(); + protected abstract void UpdateInternal(T item); protected MetaDataCache(Func factory) { this.factory = factory; } + public void Commit() + { + switch (State) + { + case TrackState.Added: + AddInternal(Item); + break; + case TrackState.Changed: + UpdateInternal(Item); + break; + } + } + + public MetaDataCache CreateSnapshot() + { + return new CloneMetaCache(this); + } + public T Get() { if (Item == null) diff --git a/neo/IO/Caching/RelayCache.cs b/neo/IO/Caching/RelayCache.cs index 0c7805074c..ec6e35bdce 100644 --- a/neo/IO/Caching/RelayCache.cs +++ b/neo/IO/Caching/RelayCache.cs @@ -1,4 +1,4 @@ -using Neo.Network; +using Neo.Network.P2P.Payloads; namespace Neo.IO.Caching { diff --git a/neo/IO/Data/LevelDB/DbMetaDataCache.cs b/neo/IO/Data/LevelDB/DbMetaDataCache.cs deleted file mode 100644 index 5c41943b5e..0000000000 --- a/neo/IO/Data/LevelDB/DbMetaDataCache.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Neo.IO.Caching; -using System; - -namespace Neo.IO.Data.LevelDB -{ - public class DbMetaDataCache : MetaDataCache where T : class, ISerializable, new() - { - private DB db; - private byte prefix; - - public DbMetaDataCache(DB db, byte prefix, Func factory = null) - : base(factory) - { - this.db = db; - this.prefix = prefix; - } - - public void Commit(WriteBatch batch) - { - switch (State) - { - case TrackState.Added: - case TrackState.Changed: - batch.Put(prefix, Item.ToArray()); - break; - case TrackState.Deleted: - batch.Delete(prefix); - break; - } - } - - protected override T TryGetInternal() - { - if (!db.TryGet(ReadOptions.Default, prefix, out Slice slice)) - return null; - return slice.ToArray().AsSerializable(); - } - } -} diff --git a/neo/IO/ISerializable.cs b/neo/IO/ISerializable.cs index b91b18c86d..28f1ec3e01 100644 --- a/neo/IO/ISerializable.cs +++ b/neo/IO/ISerializable.cs @@ -2,23 +2,11 @@ namespace Neo.IO { - /// - /// 为序列化提供一个接口 - /// public interface ISerializable { int Size { get; } - /// - /// 序列化 - /// - /// 存放序列化后的结果 void Serialize(BinaryWriter writer); - - /// - /// 反序列化 - /// - /// 数据来源 void Deserialize(BinaryReader reader); } } diff --git a/neo/IO/Json/JString.cs b/neo/IO/Json/JString.cs index 29b5ab3802..3aec381fb4 100644 --- a/neo/IO/Json/JString.cs +++ b/neo/IO/Json/JString.cs @@ -13,9 +13,7 @@ public class JString : JObject public JString(string value) { - if (value == null) - throw new ArgumentNullException(); - this.Value = value; + this.Value = value ?? throw new ArgumentNullException(); } public override bool AsBoolean() diff --git a/neo/IO/Wrappers/ByteWrapper.cs b/neo/IO/Wrappers/ByteWrapper.cs deleted file mode 100644 index d576a2efa9..0000000000 --- a/neo/IO/Wrappers/ByteWrapper.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.IO; - -namespace Neo.IO.Wrappers -{ - internal class ByteWrapper : SerializableWrapper - { - private byte value; - - public override int Size => sizeof(byte); - - public ByteWrapper(byte value) - { - this.value = value; - } - - public override void Deserialize(BinaryReader reader) - { - value = reader.ReadByte(); - } - - public override void Serialize(BinaryWriter writer) - { - writer.Write(value); - } - } -} diff --git a/neo/IO/Wrappers/SerializableWrapper.cs b/neo/IO/Wrappers/SerializableWrapper.cs index 7bc6ae3b3c..868c9bcba2 100644 --- a/neo/IO/Wrappers/SerializableWrapper.cs +++ b/neo/IO/Wrappers/SerializableWrapper.cs @@ -3,20 +3,25 @@ namespace Neo.IO.Wrappers { - public abstract class SerializableWrapper : ISerializable + public abstract class SerializableWrapper : IEquatable, IEquatable>, ISerializable + where T : struct, IEquatable { + protected T value; + public abstract int Size { get; } public abstract void Deserialize(BinaryReader reader); - public abstract void Serialize(BinaryWriter writer); - public static implicit operator SerializableWrapper(byte value) + public bool Equals(T other) { - return new ByteWrapper(value); + return value.Equals(other); } - } - public abstract class SerializableWrapper : SerializableWrapper where T : IEquatable - { + public bool Equals(SerializableWrapper other) + { + return value.Equals(other.value); + } + + public abstract void Serialize(BinaryWriter writer); } } diff --git a/neo/IO/Wrappers/UInt32Wrapper.cs b/neo/IO/Wrappers/UInt32Wrapper.cs new file mode 100644 index 0000000000..fa8406d50a --- /dev/null +++ b/neo/IO/Wrappers/UInt32Wrapper.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; + +namespace Neo.IO.Wrappers +{ + public sealed class UInt32Wrapper : SerializableWrapper, IEquatable + { + public override int Size => sizeof(uint); + + public UInt32Wrapper() + { + } + + private UInt32Wrapper(uint value) + { + this.value = value; + } + + public override void Deserialize(BinaryReader reader) + { + value = reader.ReadUInt32(); + } + + public bool Equals(UInt32Wrapper other) + { + return value == other.value; + } + + public override void Serialize(BinaryWriter writer) + { + writer.Write(value); + } + + public static implicit operator UInt32Wrapper(uint value) + { + return new UInt32Wrapper(value); + } + + public static implicit operator uint(UInt32Wrapper wrapper) + { + return wrapper.value; + } + } +} diff --git a/neo/Implementations/Blockchains/LevelDB/ApplicationExecutedEventArgs.cs b/neo/Implementations/Blockchains/LevelDB/ApplicationExecutedEventArgs.cs deleted file mode 100644 index 017430be76..0000000000 --- a/neo/Implementations/Blockchains/LevelDB/ApplicationExecutedEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Neo.Core; -using System; - -namespace Neo.Implementations.Blockchains.LevelDB -{ - public class ApplicationExecutedEventArgs : EventArgs - { - public Transaction Transaction { get; internal set; } - public ApplicationExecutionResult[] ExecutionResults { get; internal set; } - } -} diff --git a/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs b/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs deleted file mode 100644 index 5654d4eb75..0000000000 --- a/neo/Implementations/Blockchains/LevelDB/LevelDBBlockchain.cs +++ /dev/null @@ -1,713 +0,0 @@ -using Neo.Core; -using Neo.Cryptography; -using Neo.Cryptography.ECC; -using Neo.IO; -using Neo.IO.Caching; -using Neo.IO.Data.LevelDB; -using Neo.SmartContract; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using Iterator = Neo.IO.Data.LevelDB.Iterator; - -namespace Neo.Implementations.Blockchains.LevelDB -{ - public class LevelDBBlockchain : Blockchain - { - public static event EventHandler ApplicationExecuted; - - private DB db; - private Thread thread_persistence; - private ReaderWriterLockSlim headerIndexRwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private List header_index = new List(); - private ReaderWriterLockSlim headerCacheRwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private Dictionary header_cache = new Dictionary(); - private Dictionary block_cache = new Dictionary(); - private uint current_block_height = 0; - private uint stored_header_count = 0; - private AutoResetEvent new_block_event = new AutoResetEvent(false); - private bool disposed = false; - - public override UInt256 CurrentBlockHash => header_index[(int)current_block_height]; - public override UInt256 CurrentHeaderHash => header_index[header_index.Count - 1]; - public override uint HeaderHeight => (uint)header_index.Count - 1; - public override uint Height => current_block_height; - public bool VerifyBlocks { get; set; } = true; - - /// - /// Return true if haven't got valid handle - /// - public override bool IsDisposed => disposed; - - public LevelDBBlockchain(string path) - { - header_index.Add(GenesisBlock.Hash); - Version version; - Slice value; - db = DB.Open(path, new Options { CreateIfMissing = true }); - if (db.TryGet(ReadOptions.Default, SliceBuilder.Begin(DataEntryPrefix.SYS_Version), out value) && Version.TryParse(value.ToString(), out version) && version >= Version.Parse("2.7.4")) - { - ReadOptions options = new ReadOptions { FillCache = false }; - value = db.Get(options, SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentBlock)); - UInt256 current_header_hash = new UInt256(value.ToArray().Take(32).ToArray()); - this.current_block_height = value.ToArray().ToUInt32(32); - uint current_header_height = current_block_height; - if (db.TryGet(options, SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentHeader), out value)) - { - current_header_hash = new UInt256(value.ToArray().Take(32).ToArray()); - current_header_height = value.ToArray().ToUInt32(32); - } - foreach (UInt256 hash in db.Find(options, SliceBuilder.Begin(DataEntryPrefix.IX_HeaderHashList), (k, v) => - { - using (MemoryStream ms = new MemoryStream(v.ToArray(), false)) - using (BinaryReader r = new BinaryReader(ms)) - { - return new - { - Index = k.ToArray().ToUInt32(1), - Hashes = r.ReadSerializableArray() - }; - } - }).OrderBy(p => p.Index).SelectMany(p => p.Hashes).ToArray()) - { - if (!hash.Equals(GenesisBlock.Hash)) - { - header_index.Add(hash); - } - stored_header_count++; - } - if (stored_header_count == 0) - { - Header[] headers = db.Find(options, SliceBuilder.Begin(DataEntryPrefix.DATA_Block), (k, v) => Header.FromTrimmedData(v.ToArray(), sizeof(long))).OrderBy(p => p.Index).ToArray(); - for (int i = 1; i < headers.Length; i++) - { - header_index.Add(headers[i].Hash); - } - } - else if (current_header_height >= stored_header_count) - { - for (UInt256 hash = current_header_hash; hash != header_index[(int)stored_header_count - 1];) - { - Header header = Header.FromTrimmedData(db.Get(options, SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(hash)).ToArray(), sizeof(long)); - header_index.Insert((int)stored_header_count, hash); - hash = header.PrevHash; - } - } - } - else - { - WriteBatch batch = new WriteBatch(); - ReadOptions options = new ReadOptions { FillCache = false }; - using (Iterator it = db.NewIterator(options)) - { - for (it.SeekToFirst(); it.Valid(); it.Next()) - { - batch.Delete(it.Key()); - } - } - db.Write(WriteOptions.Default, batch); - Persist(GenesisBlock); - db.Put(WriteOptions.Default, SliceBuilder.Begin(DataEntryPrefix.SYS_Version), GetType().GetTypeInfo().Assembly.GetName().Version.ToString()); - } - thread_persistence = new Thread(PersistBlocks); - thread_persistence.Name = "LevelDBBlockchain.PersistBlocks"; - thread_persistence.Priority = ThreadPriority.AboveNormal; - thread_persistence.Start(); - } - - public override bool AddBlock(Block block) - { - lock (block_cache) - { - if (!block_cache.ContainsKey(block.Hash)) - { - block_cache.Add(block.Hash, block); - } - } - headerIndexRwLock.EnterWriteLock(); - try - { - if (block.Index - 1 >= header_index.Count) return false; - if (block.Index == header_index.Count) - { - if (VerifyBlocks && !block.Verify()) return false; - WriteBatch batch = new WriteBatch(); - OnAddHeader(block.Header, batch); - db.Write(WriteOptions.Default, batch); - } - if (block.Index < header_index.Count) - new_block_event.Set(); - } - finally - { - headerIndexRwLock.ExitWriteLock(); - } - return true; - } - - public void AddBlockDirectly(Block block) - { - if (block.Index != Height + 1) - throw new InvalidOperationException(); - if (block.Index == header_index.Count) - { - WriteBatch batch = new WriteBatch(); - OnAddHeader(block.Header, batch); - db.Write(WriteOptions.Default, batch); - } - - lock (PersistLock) - { - Persist(block); - OnPersistCompleted(block); - } - } - - protected internal override void AddHeaders(IEnumerable
headers) - { - headerIndexRwLock.EnterWriteLock(); - try - { - headerCacheRwLock.EnterWriteLock(); - try - { - WriteBatch batch = new WriteBatch(); - foreach (Header header in headers) - { - if (header.Index - 1 >= header_index.Count) break; - if (header.Index < header_index.Count) continue; - if (VerifyBlocks && !header.Verify()) break; - OnAddHeader(header, batch); - header_cache.Add(header.Hash, header); - } - db.Write(WriteOptions.Default, batch); - header_cache.Clear(); - } - finally - { - headerCacheRwLock.ExitWriteLock(); - } - - } - finally - { - headerIndexRwLock.ExitWriteLock(); - } - } - - public override bool ContainsBlock(UInt256 hash) - { - return GetHeader(hash)?.Index <= current_block_height; - } - - public override bool ContainsTransaction(UInt256 hash) - { - Slice value; - return db.TryGet(ReadOptions.Default, SliceBuilder.Begin(DataEntryPrefix.DATA_Transaction).Add(hash), out value); - } - - public override bool ContainsUnspent(UInt256 hash, ushort index) - { - UnspentCoinState state = db.TryGet(ReadOptions.Default, DataEntryPrefix.ST_Coin, hash); - if (state == null) return false; - if (index >= state.Items.Length) return false; - return !state.Items[index].HasFlag(CoinState.Spent); - } - - public override void Dispose() - { - disposed = true; - new_block_event.Set(); - if (!thread_persistence.ThreadState.HasFlag(ThreadState.Unstarted)) - thread_persistence.Join(); - new_block_event.Dispose(); - if (db != null) - { - db.Dispose(); - db = null; - } - headerCacheRwLock.Dispose(); - headerIndexRwLock.Dispose(); - } - - public override AccountState GetAccountState(UInt160 script_hash) - { - return db.TryGet(ReadOptions.Default, DataEntryPrefix.ST_Account, script_hash); - } - - public override AssetState GetAssetState(UInt256 asset_id) - { - return db.TryGet(ReadOptions.Default, DataEntryPrefix.ST_Asset, asset_id); - } - - public override Block GetBlock(UInt256 hash) - { - return GetBlockInternal(ReadOptions.Default, hash); - } - - public override UInt256 GetBlockHash(uint height) - { - if (current_block_height < height) return null; - headerIndexRwLock.EnterReadLock(); - try - { - if (header_index.Count <= height) return null; - return header_index[(int) height]; - } - finally - { - headerIndexRwLock.ExitReadLock(); - } - } - - private Block GetBlockInternal(ReadOptions options, UInt256 hash) - { - Slice value; - if (!db.TryGet(options, SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(hash), out value)) - return null; - int height; - Block block = Block.FromTrimmedData(value.ToArray(), sizeof(long), p => GetTransaction(options, p, out height)); - if (block.Transactions.Length == 0) return null; - return block; - } - - public override ContractState GetContract(UInt160 hash) - { - return db.TryGet(ReadOptions.Default, DataEntryPrefix.ST_Contract, hash); - } - - public override IEnumerable GetEnrollments() - { - HashSet sv = new HashSet(StandbyValidators); - return db.Find(ReadOptions.Default, DataEntryPrefix.ST_Validator).Where(p => p.Registered || sv.Contains(p.PublicKey)); - } - - public override Header GetHeader(uint height) - { - UInt256 hash; - headerIndexRwLock.EnterReadLock(); - try - { - if (header_index.Count <= height) return null; - hash = header_index[(int) height]; - } - finally - { - headerIndexRwLock.ExitReadLock(); - } - return GetHeader(hash); - } - - public override Header GetHeader(UInt256 hash) - { - headerCacheRwLock.EnterReadLock(); - try - { - if (header_cache.TryGetValue(hash, out Header header)) - return header; - } - finally - { - headerCacheRwLock.ExitReadLock(); - } - Slice value; - if (!db.TryGet(ReadOptions.Default, SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(hash), out value)) - return null; - return Header.FromTrimmedData(value.ToArray(), sizeof(long)); - } - - public override Block GetNextBlock(UInt256 hash) - { - return GetBlockInternal(ReadOptions.Default, GetNextBlockHash(hash)); - } - - public override UInt256 GetNextBlockHash(UInt256 hash) - { - Header header = GetHeader(hash); - if (header == null) return null; - headerIndexRwLock.EnterReadLock(); - try - { - if (header.Index + 1 >= header_index.Count) - return null; - return header_index[(int) header.Index + 1]; - } - finally - { - headerIndexRwLock.ExitReadLock(); - } - } - - public override StorageItem GetStorageItem(StorageKey key) - { - return db.TryGet(ReadOptions.Default, DataEntryPrefix.ST_Storage, key); - } - - public override long GetSysFeeAmount(UInt256 hash) - { - Slice value; - if (!db.TryGet(ReadOptions.Default, SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(hash), out value)) - return 0; - return value.ToArray().ToInt64(0); - } - - public override MetaDataCache GetMetaData() - { - Type t = typeof(T); - if (t == typeof(ValidatorsCountState)) return new DbMetaDataCache(db, DataEntryPrefix.IX_ValidatorsCount); - throw new NotSupportedException(); - } - - public override DataCache GetStates() - { - Type t = typeof(TValue); - if (t == typeof(AccountState)) return new DbCache(db, DataEntryPrefix.ST_Account); - if (t == typeof(UnspentCoinState)) return new DbCache(db, DataEntryPrefix.ST_Coin); - if (t == typeof(SpentCoinState)) return new DbCache(db, DataEntryPrefix.ST_SpentCoin); - if (t == typeof(ValidatorState)) return new DbCache(db, DataEntryPrefix.ST_Validator); - if (t == typeof(AssetState)) return new DbCache(db, DataEntryPrefix.ST_Asset); - if (t == typeof(ContractState)) return new DbCache(db, DataEntryPrefix.ST_Contract); - if (t == typeof(StorageItem)) return new DbCache(db, DataEntryPrefix.ST_Storage); - throw new NotSupportedException(); - } - - public override Transaction GetTransaction(UInt256 hash, out int height) - { - return GetTransaction(ReadOptions.Default, hash, out height); - } - - private Transaction GetTransaction(ReadOptions options, UInt256 hash, out int height) - { - Slice value; - if (db.TryGet(options, SliceBuilder.Begin(DataEntryPrefix.DATA_Transaction).Add(hash), out value)) - { - byte[] data = value.ToArray(); - height = data.ToInt32(0); - return Transaction.DeserializeFrom(data, sizeof(uint)); - } - else - { - height = -1; - return null; - } - } - - public override Dictionary GetUnclaimed(UInt256 hash) - { - int height; - Transaction tx = GetTransaction(ReadOptions.Default, hash, out height); - if (tx == null) return null; - SpentCoinState state = db.TryGet(ReadOptions.Default, DataEntryPrefix.ST_SpentCoin, hash); - if (state != null) - { - return state.Items.ToDictionary(p => p.Key, p => new SpentCoin - { - Output = tx.Outputs[p.Key], - StartHeight = (uint)height, - EndHeight = p.Value - }); - } - else - { - return new Dictionary(); - } - } - - public override TransactionOutput GetUnspent(UInt256 hash, ushort index) - { - ReadOptions options = new ReadOptions(); - using (options.Snapshot = db.GetSnapshot()) - { - UnspentCoinState state = db.TryGet(options, DataEntryPrefix.ST_Coin, hash); - if (state == null) return null; - if (index >= state.Items.Length) return null; - if (state.Items[index].HasFlag(CoinState.Spent)) return null; - int height; - return GetTransaction(options, hash, out height).Outputs[index]; - } - } - - public override IEnumerable GetUnspent(UInt256 hash) - { - ReadOptions options = new ReadOptions(); - using (options.Snapshot = db.GetSnapshot()) - { - List outputs = new List(); - UnspentCoinState state = db.TryGet(options, DataEntryPrefix.ST_Coin, hash); - if (state != null) - { - int height; - Transaction tx = GetTransaction(options, hash, out height); - for (int i = 0; i < state.Items.Length; i++) - { - if (!state.Items[i].HasFlag(CoinState.Spent)) - { - outputs.Add(tx.Outputs[i]); - } - - } - } - return outputs; - } - } - - public override bool IsDoubleSpend(Transaction tx) - { - if (tx.Inputs.Length == 0) return false; - ReadOptions options = new ReadOptions(); - using (options.Snapshot = db.GetSnapshot()) - { - foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) - { - UnspentCoinState state = db.TryGet(options, DataEntryPrefix.ST_Coin, group.Key); - if (state == null) return true; - if (group.Any(p => p.PrevIndex >= state.Items.Length || state.Items[p.PrevIndex].HasFlag(CoinState.Spent))) - return true; - } - } - return false; - } - - private void OnAddHeader(Header header, WriteBatch batch) - { - header_index.Add(header.Hash); - while ((int)header.Index - 2000 >= stored_header_count) - { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter w = new BinaryWriter(ms)) - { - w.Write(header_index.Skip((int)stored_header_count).Take(2000).ToArray()); - w.Flush(); - batch.Put(SliceBuilder.Begin(DataEntryPrefix.IX_HeaderHashList).Add(stored_header_count), ms.ToArray()); - } - stored_header_count += 2000; - } - batch.Put(SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(header.Hash), SliceBuilder.Begin().Add(0L).Add(header.ToArray())); - batch.Put(SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentHeader), SliceBuilder.Begin().Add(header.Hash).Add(header.Index)); - } - - private void Persist(Block block) - { - WriteBatch batch = new WriteBatch(); - DbCache accounts = new DbCache(db, DataEntryPrefix.ST_Account, batch); - DbCache unspentcoins = new DbCache(db, DataEntryPrefix.ST_Coin, batch); - DbCache spentcoins = new DbCache(db, DataEntryPrefix.ST_SpentCoin, batch); - DbCache validators = new DbCache(db, DataEntryPrefix.ST_Validator, batch); - DbCache assets = new DbCache(db, DataEntryPrefix.ST_Asset, batch); - DbCache contracts = new DbCache(db, DataEntryPrefix.ST_Contract, batch); - DbCache storages = new DbCache(db, DataEntryPrefix.ST_Storage, batch); - DbMetaDataCache validators_count = new DbMetaDataCache(db, DataEntryPrefix.IX_ValidatorsCount); - CachedScriptTable script_table = new CachedScriptTable(contracts); - long amount_sysfee = GetSysFeeAmount(block.PrevHash) + (long)block.Transactions.Sum(p => p.SystemFee); - batch.Put(SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(block.Hash), SliceBuilder.Begin().Add(amount_sysfee).Add(block.Trim())); - foreach (Transaction tx in block.Transactions) - { - batch.Put(SliceBuilder.Begin(DataEntryPrefix.DATA_Transaction).Add(tx.Hash), SliceBuilder.Begin().Add(block.Index).Add(tx.ToArray())); - unspentcoins.Add(tx.Hash, new UnspentCoinState - { - Items = Enumerable.Repeat(CoinState.Confirmed, tx.Outputs.Length).ToArray() - }); - foreach (TransactionOutput output in tx.Outputs) - { - AccountState account = accounts.GetAndChange(output.ScriptHash, () => new AccountState(output.ScriptHash)); - if (account.Balances.ContainsKey(output.AssetId)) - account.Balances[output.AssetId] += output.Value; - else - account.Balances[output.AssetId] = output.Value; - if (output.AssetId.Equals(GoverningToken.Hash) && account.Votes.Length > 0) - { - foreach (ECPoint pubkey in account.Votes) - validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += output.Value; - validators_count.GetAndChange().Votes[account.Votes.Length - 1] += output.Value; - } - } - foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) - { - Transaction tx_prev = GetTransaction(ReadOptions.Default, group.Key, out int height); - foreach (CoinReference input in group) - { - unspentcoins.GetAndChange(input.PrevHash).Items[input.PrevIndex] |= CoinState.Spent; - TransactionOutput out_prev = tx_prev.Outputs[input.PrevIndex]; - AccountState account = accounts.GetAndChange(out_prev.ScriptHash); - if (out_prev.AssetId.Equals(GoverningToken.Hash)) - { - spentcoins.GetAndChange(input.PrevHash, () => new SpentCoinState - { - TransactionHash = input.PrevHash, - TransactionHeight = (uint)height, - Items = new Dictionary() - }).Items.Add(input.PrevIndex, block.Index); - if (account.Votes.Length > 0) - { - foreach (ECPoint pubkey in account.Votes) - { - ValidatorState validator = validators.GetAndChange(pubkey); - validator.Votes -= out_prev.Value; - if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) - validators.Delete(pubkey); - } - validators_count.GetAndChange().Votes[account.Votes.Length - 1] -= out_prev.Value; - } - } - account.Balances[out_prev.AssetId] -= out_prev.Value; - } - } - List execution_results = new List(); - switch (tx) - { -#pragma warning disable CS0612 - case RegisterTransaction tx_register: - assets.Add(tx.Hash, new AssetState - { - AssetId = tx_register.Hash, - AssetType = tx_register.AssetType, - Name = tx_register.Name, - Amount = tx_register.Amount, - Available = Fixed8.Zero, - Precision = tx_register.Precision, - Fee = Fixed8.Zero, - FeeAddress = new UInt160(), - Owner = tx_register.Owner, - Admin = tx_register.Admin, - Issuer = tx_register.Admin, - Expiration = block.Index + 2 * 2000000, - IsFrozen = false - }); - break; -#pragma warning restore CS0612 - case IssueTransaction _: - foreach (TransactionResult result in tx.GetTransactionResults().Where(p => p.Amount < Fixed8.Zero)) - assets.GetAndChange(result.AssetId).Available -= result.Amount; - break; - case ClaimTransaction _: - foreach (CoinReference input in ((ClaimTransaction)tx).Claims) - { - if (spentcoins.TryGet(input.PrevHash)?.Items.Remove(input.PrevIndex) == true) - spentcoins.GetAndChange(input.PrevHash); - } - break; -#pragma warning disable CS0612 - case EnrollmentTransaction tx_enrollment: - validators.GetAndChange(tx_enrollment.PublicKey, () => new ValidatorState(tx_enrollment.PublicKey)).Registered = true; - break; -#pragma warning restore CS0612 - case StateTransaction tx_state: - foreach (StateDescriptor descriptor in tx_state.Descriptors) - switch (descriptor.Type) - { - case StateType.Account: - ProcessAccountStateDescriptor(descriptor, accounts, validators, validators_count); - break; - case StateType.Validator: - ProcessValidatorStateDescriptor(descriptor, validators); - break; - } - break; -#pragma warning disable CS0612 - case PublishTransaction tx_publish: - contracts.GetOrAdd(tx_publish.ScriptHash, () => new ContractState - { - Script = tx_publish.Script, - ParameterList = tx_publish.ParameterList, - ReturnType = tx_publish.ReturnType, - ContractProperties = (ContractPropertyState)Convert.ToByte(tx_publish.NeedStorage), - Name = tx_publish.Name, - CodeVersion = tx_publish.CodeVersion, - Author = tx_publish.Author, - Email = tx_publish.Email, - Description = tx_publish.Description - }); - break; -#pragma warning restore CS0612 - case InvocationTransaction tx_invocation: - using (StateMachine service = new StateMachine(block, accounts, assets, contracts, storages)) - { - ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, tx_invocation, script_table, service, tx_invocation.Gas); - engine.LoadScript(tx_invocation.Script); - if (engine.Execute()) - { - service.Commit(); - } - execution_results.Add(new ApplicationExecutionResult - { - Trigger = TriggerType.Application, - ScriptHash = tx_invocation.Script.ToScriptHash(), - VMState = engine.State, - GasConsumed = engine.GasConsumed, - Stack = engine.ResultStack.ToArray(), - Notifications = service.Notifications.ToArray() - }); - } - break; - } - if (execution_results.Count > 0) - ApplicationExecuted?.Invoke(this, new ApplicationExecutedEventArgs - { - Transaction = tx, - ExecutionResults = execution_results.ToArray() - }); - } - accounts.DeleteWhere((k, v) => !v.IsFrozen && v.Votes.Length == 0 && v.Balances.All(p => p.Value <= Fixed8.Zero)); - accounts.Commit(); - unspentcoins.DeleteWhere((k, v) => v.Items.All(p => p.HasFlag(CoinState.Spent))); - unspentcoins.Commit(); - spentcoins.DeleteWhere((k, v) => v.Items.Count == 0); - spentcoins.Commit(); - validators.Commit(); - assets.Commit(); - contracts.Commit(); - storages.Commit(); - validators_count.Commit(batch); - batch.Put(SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentBlock), SliceBuilder.Begin().Add(block.Hash).Add(block.Index)); - db.Write(WriteOptions.Default, batch); - current_block_height = block.Index; - } - - private void PersistBlocks() - { - while (!disposed) - { - new_block_event.WaitOne(); - while (!disposed) - { - UInt256 hash; - headerIndexRwLock.EnterReadLock(); - try - { - if (header_index.Count <= current_block_height + 1) break; - hash = header_index[(int) current_block_height + 1]; - } - finally - { - headerIndexRwLock.ExitReadLock(); - } - Block block; - lock (block_cache) - { - if (!block_cache.TryGetValue(hash, out block)) - break; - } - - VerificationCancellationToken.Cancel(); - lock (PersistLock) - { - Persist(block); - OnPersistCompleted(block); - // Reset cancellation token. - VerificationCancellationToken = new CancellationTokenSource(); - } - - lock (block_cache) - { - block_cache.Remove(hash); - } - - OnPersistUnlocked(block); - } - } - } - } -} diff --git a/neo/Core/AccountState.cs b/neo/Ledger/AccountState.cs similarity index 99% rename from neo/Core/AccountState.cs rename to neo/Ledger/AccountState.cs index 75e50b54ad..d34738cfb7 100644 --- a/neo/Core/AccountState.cs +++ b/neo/Ledger/AccountState.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Ledger { public class AccountState : StateBase, ICloneable { diff --git a/neo/Implementations/Blockchains/LevelDB/ApplicationExecutionResult.cs b/neo/Ledger/ApplicationExecutionResult.cs similarity index 90% rename from neo/Implementations/Blockchains/LevelDB/ApplicationExecutionResult.cs rename to neo/Ledger/ApplicationExecutionResult.cs index 59407d04ba..40bd89aacb 100644 --- a/neo/Implementations/Blockchains/LevelDB/ApplicationExecutionResult.cs +++ b/neo/Ledger/ApplicationExecutionResult.cs @@ -1,7 +1,7 @@ using Neo.SmartContract; using Neo.VM; -namespace Neo.Implementations.Blockchains.LevelDB +namespace Neo.Ledger { public class ApplicationExecutionResult { diff --git a/neo/Core/AssetState.cs b/neo/Ledger/AssetState.cs similarity index 97% rename from neo/Core/AssetState.cs rename to neo/Ledger/AssetState.cs index f18529fdbc..76889d5cde 100644 --- a/neo/Core/AssetState.cs +++ b/neo/Ledger/AssetState.cs @@ -1,6 +1,7 @@ using Neo.Cryptography.ECC; using Neo.IO; using Neo.IO.Json; +using Neo.Network.P2P.Payloads; using Neo.VM; using Neo.Wallets; using System; @@ -9,7 +10,7 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Ledger { public class AssetState : StateBase, ICloneable { @@ -164,8 +165,8 @@ public override JObject ToJson() json["available"] = Available.ToString(); json["precision"] = Precision; json["owner"] = Owner.ToString(); - json["admin"] = Wallet.ToAddress(Admin); - json["issuer"] = Wallet.ToAddress(Issuer); + json["admin"] = Admin.ToAddress(); + json["issuer"] = Issuer.ToAddress(); json["expiration"] = Expiration; json["frozen"] = IsFrozen; return json; diff --git a/neo/Ledger/BlockState.cs b/neo/Ledger/BlockState.cs new file mode 100644 index 0000000000..a0d759ca6e --- /dev/null +++ b/neo/Ledger/BlockState.cs @@ -0,0 +1,51 @@ +using Neo.IO; +using Neo.IO.Json; +using System.IO; + +namespace Neo.Ledger +{ + public class BlockState : StateBase, ICloneable + { + public long SystemFeeAmount; + public TrimmedBlock TrimmedBlock; + + public override int Size => base.Size + sizeof(long) + TrimmedBlock.Size; + + BlockState ICloneable.Clone() + { + return new BlockState + { + SystemFeeAmount = SystemFeeAmount, + TrimmedBlock = TrimmedBlock + }; + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + SystemFeeAmount = reader.ReadInt64(); + TrimmedBlock = reader.ReadSerializable(); + } + + void ICloneable.FromReplica(BlockState replica) + { + SystemFeeAmount = replica.SystemFeeAmount; + TrimmedBlock = replica.TrimmedBlock; + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(SystemFeeAmount); + writer.Write(TrimmedBlock); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["sysfee_amount"] = SystemFeeAmount.ToString(); + json["trimmed"] = TrimmedBlock.ToJson(); + return json; + } + } +} diff --git a/neo/Ledger/Blockchain.cs b/neo/Ledger/Blockchain.cs new file mode 100644 index 0000000000..f62b455047 --- /dev/null +++ b/neo/Ledger/Blockchain.cs @@ -0,0 +1,701 @@ +using Akka.Actor; +using Akka.Configuration; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.IO.Actors; +using Neo.IO.Caching; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.VM; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading; + +namespace Neo.Ledger +{ + public sealed class Blockchain : UntypedActor + { + public class Register { } + public class ApplicationExecuted { public Transaction Transaction; public ApplicationExecutionResult[] ExecutionResults; } + public class PersistCompleted { public Block Block; } + public class Import { public IEnumerable Blocks; } + public class ImportCompleted { } + + public static readonly uint SecondsPerBlock = Settings.Default.SecondsPerBlock; + public const uint DecrementInterval = 2000000; + public const uint MaxValidators = 1024; + public static readonly uint[] GenerationAmount = { 8, 7, 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 ECPoint[] StandbyValidators = Settings.Default.StandbyValidators.OfType().Select(p => ECPoint.DecodePoint(p.HexToBytes(), ECCurve.Secp256r1)).ToArray(); + +#pragma warning disable CS0612 + public static readonly RegisterTransaction GoverningToken = new RegisterTransaction + { + AssetType = AssetType.GoverningToken, + Name = "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", + Amount = Fixed8.FromDecimal(100000000), + Precision = 0, + Owner = ECCurve.Secp256r1.Infinity, + Admin = (new[] { (byte)OpCode.PUSHT }).ToScriptHash(), + Attributes = new TransactionAttribute[0], + Inputs = new CoinReference[0], + Outputs = new TransactionOutput[0], + Witnesses = new Witness[0] + }; + + public static readonly RegisterTransaction UtilityToken = new RegisterTransaction + { + AssetType = AssetType.UtilityToken, + Name = "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]", + Amount = Fixed8.FromDecimal(GenerationAmount.Sum(p => p) * DecrementInterval), + Precision = 8, + Owner = ECCurve.Secp256r1.Infinity, + Admin = (new[] { (byte)OpCode.PUSHF }).ToScriptHash(), + Attributes = new TransactionAttribute[0], + Inputs = new CoinReference[0], + Outputs = new TransactionOutput[0], + Witnesses = new Witness[0] + }; +#pragma warning restore CS0612 + + public static readonly Block GenesisBlock = new Block + { + PrevHash = UInt256.Zero, + Timestamp = (new DateTime(2016, 7, 15, 15, 8, 21, DateTimeKind.Utc)).ToTimestamp(), + Index = 0, + ConsensusData = 2083236893, //向比特币致敬 + NextConsensus = GetConsensusAddress(StandbyValidators), + Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new[] { (byte)OpCode.PUSHT } + }, + Transactions = new Transaction[] + { + new MinerTransaction + { + Nonce = 2083236893, + Attributes = new TransactionAttribute[0], + Inputs = new CoinReference[0], + Outputs = new TransactionOutput[0], + Witnesses = new Witness[0] + }, + GoverningToken, + UtilityToken, + new IssueTransaction + { + Attributes = new TransactionAttribute[0], + Inputs = new CoinReference[0], + Outputs = new[] + { + new TransactionOutput + { + AssetId = GoverningToken.Hash, + Value = GoverningToken.Amount, + ScriptHash = Contract.CreateMultiSigRedeemScript(StandbyValidators.Length / 2 + 1, StandbyValidators).ToScriptHash() + } + }, + Witnesses = new[] + { + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new[] { (byte)OpCode.PUSHT } + } + } + } + } + }; + + private readonly NeoSystem system; + private readonly List header_index = new List(); + private uint stored_header_count = 0; + private readonly Dictionary block_cache = new Dictionary(); + private readonly Dictionary block_cache_unverified = new Dictionary(); + private readonly ConcurrentDictionary mem_pool = new ConcurrentDictionary(); + internal readonly RelayCache RelayCache = new RelayCache(100); + private readonly HashSet subscribers = new HashSet(); + private Snapshot currentSnapshot; + + public Store Store { get; } + public uint Height => currentSnapshot.Height; + public uint HeaderHeight => (uint)header_index.Count - 1; + public UInt256 CurrentBlockHash => currentSnapshot.CurrentBlockHash; + public UInt256 CurrentHeaderHash => header_index[header_index.Count - 1]; + + private static Blockchain singleton; + public static Blockchain Singleton + { + get + { + while (singleton == null) Thread.Sleep(10); + return singleton; + } + } + + static Blockchain() + { + GenesisBlock.RebuildMerkleRoot(); + } + + public Blockchain(NeoSystem system, Store store) + { + this.system = system; + this.Store = store; + lock (GetType()) + { + if (singleton != null) + throw new InvalidOperationException(); + header_index.AddRange(store.GetHeaderHashList().Find().OrderBy(p => (uint)p.Key).SelectMany(p => p.Value.Hashes)); + stored_header_count += (uint)header_index.Count; + if (stored_header_count == 0) + { + header_index.AddRange(store.GetBlocks().Find().OrderBy(p => p.Value.TrimmedBlock.Index).Select(p => p.Key)); + } + else + { + HashIndexState hashIndex = store.GetHeaderHashIndex().Get(); + if (hashIndex.Index >= stored_header_count) + { + DataCache cache = store.GetBlocks(); + for (UInt256 hash = hashIndex.Hash; hash != header_index[(int)stored_header_count - 1];) + { + header_index.Insert((int)stored_header_count, hash); + hash = cache[hash].TrimmedBlock.PrevHash; + } + } + } + if (header_index.Count == 0) + Persist(GenesisBlock); + else + UpdateCurrentSnapshot(); + singleton = this; + } + } + + public bool ContainsBlock(UInt256 hash) + { + if (block_cache.ContainsKey(hash)) return true; + return Store.ContainsBlock(hash); + } + + public bool ContainsTransaction(UInt256 hash) + { + if (mem_pool.ContainsKey(hash)) return true; + return Store.ContainsTransaction(hash); + } + + private void Distribute(object message) + { + foreach (IActorRef subscriber in subscribers) + subscriber.Tell(message); + } + + public Block GetBlock(UInt256 hash) + { + if (block_cache.TryGetValue(hash, out Block block)) + return block; + return Store.GetBlock(hash); + } + + public UInt256 GetBlockHash(uint index) + { + if (header_index.Count <= index) return null; + return header_index[(int)index]; + } + + public static UInt160 GetConsensusAddress(ECPoint[] validators) + { + return Contract.CreateMultiSigRedeemScript(validators.Length - (validators.Length - 1) / 3, validators).ToScriptHash(); + } + + public IEnumerable GetMemoryPool() + { + return mem_pool.Values; + } + + public Snapshot GetSnapshot() + { + return Store.GetSnapshot(); + } + + public Transaction GetTransaction(UInt256 hash) + { + if (mem_pool.TryGetValue(hash, out Transaction transaction)) + return transaction; + return Store.GetTransaction(hash); + } + + private void OnImport(IEnumerable blocks) + { + foreach (Block block in blocks) + { + if (block.Index <= Height) continue; + if (block.Index != Height + 1) + throw new InvalidOperationException(); + Persist(block); + SaveHeaderHashList(); + } + Sender.Tell(new ImportCompleted()); + } + + private RelayResultReason OnNewBlock(Block block) + { + if (block.Index <= Height) + return RelayResultReason.AlreadyExists; + if (block_cache.ContainsKey(block.Hash)) + return RelayResultReason.AlreadyExists; + if (block.Index - 1 >= header_index.Count) + { + block_cache_unverified[block.Index] = block; + return RelayResultReason.UnableToVerify; + } + if (block.Index == header_index.Count) + { + if (!block.Verify(currentSnapshot)) + return RelayResultReason.Invalid; + } + else + { + if (!block.Hash.Equals(header_index[(int)block.Index])) + return RelayResultReason.Invalid; + } + if (block.Index == Height + 1) + { + Block block_persist = block; + while (true) + { + block_cache_unverified.Remove(block_persist.Index); + Persist(block_persist); + if (block_persist == block) + if (block.Index + 100 >= header_index.Count) + system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = block }); + if (block_persist.Index + 1 >= header_index.Count) break; + UInt256 hash = header_index[(int)block_persist.Index + 1]; + if (!block_cache.TryGetValue(hash, out block_persist)) break; + } + SaveHeaderHashList(); + if (block_cache_unverified.TryGetValue(Height + 1, out block_persist)) + Self.Tell(block_persist, ActorRefs.NoSender); + } + else + { + block_cache.Add(block.Hash, block); + if (block.Index + 100 >= header_index.Count) + system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = block }); + if (block.Index == header_index.Count) + { + header_index.Add(block.Hash); + using (Snapshot snapshot = GetSnapshot()) + { + snapshot.Blocks.Add(block.Hash, new BlockState + { + SystemFeeAmount = 0, + TrimmedBlock = block.Header.Trim() + }); + snapshot.HeaderHashIndex.GetAndChange().Hash = block.Hash; + snapshot.HeaderHashIndex.GetAndChange().Index = block.Index; + SaveHeaderHashList(snapshot); + snapshot.Commit(); + } + UpdateCurrentSnapshot(); + } + } + return RelayResultReason.Succeed; + } + + private RelayResultReason OnNewConsensus(ConsensusPayload payload) + { + if (!payload.Verify(currentSnapshot)) return RelayResultReason.Invalid; + system.Consensus?.Tell(payload); + RelayCache.Add(payload); + system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = payload }); + return RelayResultReason.Succeed; + } + + private void OnNewHeaders(Header[] headers) + { + using (Snapshot snapshot = GetSnapshot()) + { + foreach (Header header in headers) + { + if (header.Index - 1 >= header_index.Count) break; + if (header.Index < header_index.Count) continue; + if (!header.Verify(snapshot)) break; + header_index.Add(header.Hash); + snapshot.Blocks.Add(header.Hash, new BlockState + { + SystemFeeAmount = 0, + TrimmedBlock = header.Trim() + }); + snapshot.HeaderHashIndex.GetAndChange().Hash = header.Hash; + snapshot.HeaderHashIndex.GetAndChange().Index = header.Index; + } + SaveHeaderHashList(snapshot); + snapshot.Commit(); + } + UpdateCurrentSnapshot(); + system.TaskManager.Tell(new TaskManager.HeaderTaskCompleted(), Sender); + } + + private RelayResultReason OnNewTransaction(Transaction transaction) + { + const int MemoryPoolSize = 50000; + if (transaction.Type == TransactionType.MinerTransaction) + return RelayResultReason.Invalid; + if (ContainsTransaction(transaction.Hash)) + return RelayResultReason.AlreadyExists; + if (!transaction.Verify(currentSnapshot, mem_pool.Values)) + return RelayResultReason.Invalid; + mem_pool.TryAdd(transaction.Hash, transaction); + if (mem_pool.Count > MemoryPoolSize) + { + UInt256[] delete = mem_pool.Values.AsParallel() + .OrderBy(p => p.NetworkFee / p.Size) + .ThenBy(p => p.NetworkFee) + .ThenBy(p => new BigInteger(p.Hash.ToArray())) + .Take(mem_pool.Count - MemoryPoolSize) + .Select(p => p.Hash) + .ToArray(); + foreach (UInt256 hash in delete) + mem_pool.TryRemove(hash, out _); + } + if (!mem_pool.ContainsKey(transaction.Hash)) + return RelayResultReason.OutOfMemory; + system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = transaction }); + return RelayResultReason.Succeed; + } + + private void OnPersistCompleted(Block block) + { + block_cache.Remove(block.Hash); + foreach (Transaction tx in block.Transactions) + mem_pool.TryRemove(tx.Hash, out _); + foreach (Transaction tx in mem_pool.Values) + Self.Tell(tx, ActorRefs.NoSender); + mem_pool.Clear(); + PersistCompleted completed = new PersistCompleted { Block = block }; + system.Consensus?.Tell(completed); + Distribute(completed); + } + + protected override void OnReceive(object message) + { + switch (message) + { + case Register _: + OnRegister(); + break; + case Import import: + OnImport(import.Blocks); + break; + case Header[] headers: + OnNewHeaders(headers); + break; + case Block block: + Sender.Tell(OnNewBlock(block)); + break; + case Transaction transaction: + Sender.Tell(OnNewTransaction(transaction)); + break; + case ConsensusPayload payload: + Sender.Tell(OnNewConsensus(payload)); + break; + case Terminated terminated: + subscribers.Remove(terminated.ActorRef); + break; + } + } + + private void OnRegister() + { + subscribers.Add(Sender); + Context.Watch(Sender); + } + + private void Persist(Block block) + { + using (Snapshot snapshot = GetSnapshot()) + { + snapshot.PersistingBlock = block; + snapshot.Blocks.Add(block.Hash, new BlockState + { + SystemFeeAmount = snapshot.GetSysFeeAmount(block.PrevHash) + (long)block.Transactions.Sum(p => p.SystemFee), + TrimmedBlock = block.Trim() + }); + foreach (Transaction tx in block.Transactions) + { + snapshot.Transactions.Add(tx.Hash, new TransactionState + { + BlockIndex = block.Index, + Transaction = tx + }); + snapshot.UnspentCoins.Add(tx.Hash, new UnspentCoinState + { + Items = Enumerable.Repeat(CoinState.Confirmed, tx.Outputs.Length).ToArray() + }); + foreach (TransactionOutput output in tx.Outputs) + { + AccountState account = snapshot.Accounts.GetAndChange(output.ScriptHash, () => new AccountState(output.ScriptHash)); + if (account.Balances.ContainsKey(output.AssetId)) + account.Balances[output.AssetId] += output.Value; + else + account.Balances[output.AssetId] = output.Value; + if (output.AssetId.Equals(GoverningToken.Hash) && account.Votes.Length > 0) + { + foreach (ECPoint pubkey in account.Votes) + snapshot.Validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += output.Value; + snapshot.ValidatorsCount.GetAndChange().Votes[account.Votes.Length - 1] += output.Value; + } + } + foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) + { + TransactionState tx_prev = snapshot.Transactions[group.Key]; + foreach (CoinReference input in group) + { + snapshot.UnspentCoins.GetAndChange(input.PrevHash).Items[input.PrevIndex] |= CoinState.Spent; + TransactionOutput out_prev = tx_prev.Transaction.Outputs[input.PrevIndex]; + AccountState account = snapshot.Accounts.GetAndChange(out_prev.ScriptHash); + if (out_prev.AssetId.Equals(GoverningToken.Hash)) + { + snapshot.SpentCoins.GetAndChange(input.PrevHash, () => new SpentCoinState + { + TransactionHash = input.PrevHash, + TransactionHeight = tx_prev.BlockIndex, + Items = new Dictionary() + }).Items.Add(input.PrevIndex, block.Index); + if (account.Votes.Length > 0) + { + foreach (ECPoint pubkey in account.Votes) + { + ValidatorState validator = snapshot.Validators.GetAndChange(pubkey); + validator.Votes -= out_prev.Value; + if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) + snapshot.Validators.Delete(pubkey); + } + snapshot.ValidatorsCount.GetAndChange().Votes[account.Votes.Length - 1] -= out_prev.Value; + } + } + account.Balances[out_prev.AssetId] -= out_prev.Value; + } + } + List execution_results = new List(); + switch (tx) + { +#pragma warning disable CS0612 + case RegisterTransaction tx_register: + snapshot.Assets.Add(tx.Hash, new AssetState + { + AssetId = tx_register.Hash, + AssetType = tx_register.AssetType, + Name = tx_register.Name, + Amount = tx_register.Amount, + Available = Fixed8.Zero, + Precision = tx_register.Precision, + Fee = Fixed8.Zero, + FeeAddress = new UInt160(), + Owner = tx_register.Owner, + Admin = tx_register.Admin, + Issuer = tx_register.Admin, + Expiration = block.Index + 2 * 2000000, + IsFrozen = false + }); + break; +#pragma warning restore CS0612 + case IssueTransaction _: + foreach (TransactionResult result in tx.GetTransactionResults().Where(p => p.Amount < Fixed8.Zero)) + snapshot.Assets.GetAndChange(result.AssetId).Available -= result.Amount; + break; + case ClaimTransaction _: + foreach (CoinReference input in ((ClaimTransaction)tx).Claims) + { + if (snapshot.SpentCoins.TryGet(input.PrevHash)?.Items.Remove(input.PrevIndex) == true) + snapshot.SpentCoins.GetAndChange(input.PrevHash); + } + break; +#pragma warning disable CS0612 + case EnrollmentTransaction tx_enrollment: + snapshot.Validators.GetAndChange(tx_enrollment.PublicKey, () => new ValidatorState(tx_enrollment.PublicKey)).Registered = true; + break; +#pragma warning restore CS0612 + case StateTransaction tx_state: + foreach (StateDescriptor descriptor in tx_state.Descriptors) + switch (descriptor.Type) + { + case StateType.Account: + ProcessAccountStateDescriptor(descriptor, snapshot); + break; + case StateType.Validator: + ProcessValidatorStateDescriptor(descriptor, snapshot); + break; + } + break; +#pragma warning disable CS0612 + case PublishTransaction tx_publish: + snapshot.Contracts.GetOrAdd(tx_publish.ScriptHash, () => new ContractState + { + Script = tx_publish.Script, + ParameterList = tx_publish.ParameterList, + ReturnType = tx_publish.ReturnType, + ContractProperties = (ContractPropertyState)Convert.ToByte(tx_publish.NeedStorage), + Name = tx_publish.Name, + CodeVersion = tx_publish.CodeVersion, + Author = tx_publish.Author, + Email = tx_publish.Email, + Description = tx_publish.Description + }); + break; +#pragma warning restore CS0612 + case InvocationTransaction tx_invocation: + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, tx_invocation, snapshot.Clone(), tx_invocation.Gas)) + { + engine.LoadScript(tx_invocation.Script); + if (engine.Execute()) + { + engine.Service.Commit(); + } + execution_results.Add(new ApplicationExecutionResult + { + Trigger = TriggerType.Application, + ScriptHash = tx_invocation.Script.ToScriptHash(), + VMState = engine.State, + GasConsumed = engine.GasConsumed, + Stack = engine.ResultStack.ToArray(), + Notifications = engine.Service.Notifications.ToArray() + }); + } + break; + } + if (execution_results.Count > 0) + Distribute(new ApplicationExecuted + { + Transaction = tx, + ExecutionResults = execution_results.ToArray() + }); + } + snapshot.BlockHashIndex.GetAndChange().Hash = block.Hash; + snapshot.BlockHashIndex.GetAndChange().Index = block.Index; + if (block.Index == header_index.Count) + { + header_index.Add(block.Hash); + snapshot.HeaderHashIndex.GetAndChange().Hash = block.Hash; + snapshot.HeaderHashIndex.GetAndChange().Index = block.Index; + } + snapshot.Commit(); + } + UpdateCurrentSnapshot(); + OnPersistCompleted(block); + } + + protected override void PostStop() + { + base.PostStop(); + currentSnapshot?.Dispose(); + } + + internal static void ProcessAccountStateDescriptor(StateDescriptor descriptor, Snapshot snapshot) + { + UInt160 hash = new UInt160(descriptor.Key); + AccountState account = snapshot.Accounts.GetAndChange(hash, () => new AccountState(hash)); + switch (descriptor.Field) + { + case "Votes": + Fixed8 balance = account.GetBalance(GoverningToken.Hash); + foreach (ECPoint pubkey in account.Votes) + { + ValidatorState validator = snapshot.Validators.GetAndChange(pubkey); + validator.Votes -= balance; + if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) + snapshot.Validators.Delete(pubkey); + } + ECPoint[] votes = descriptor.Value.AsSerializableArray().Distinct().ToArray(); + if (votes.Length != account.Votes.Length) + { + ValidatorsCountState count_state = snapshot.ValidatorsCount.GetAndChange(); + if (account.Votes.Length > 0) + count_state.Votes[account.Votes.Length - 1] -= balance; + if (votes.Length > 0) + count_state.Votes[votes.Length - 1] += balance; + } + account.Votes = votes; + foreach (ECPoint pubkey in account.Votes) + snapshot.Validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += balance; + break; + } + } + + internal static void ProcessValidatorStateDescriptor(StateDescriptor descriptor, Snapshot snapshot) + { + ECPoint pubkey = ECPoint.DecodePoint(descriptor.Key, ECCurve.Secp256r1); + ValidatorState validator = snapshot.Validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)); + switch (descriptor.Field) + { + case "Registered": + validator.Registered = BitConverter.ToBoolean(descriptor.Value, 0); + break; + } + } + + public static Props Props(NeoSystem system, Store store) + { + return Akka.Actor.Props.Create(() => new Blockchain(system, store)).WithMailbox("blockchain-mailbox"); + } + + private void SaveHeaderHashList(Snapshot snapshot = null) + { + if ((header_index.Count - stored_header_count < 2000)) + return; + bool snapshot_created = snapshot == null; + if (snapshot_created) snapshot = GetSnapshot(); + try + { + while (header_index.Count - stored_header_count >= 2000) + { + snapshot.HeaderHashList.Add(stored_header_count, new HeaderHashList + { + Hashes = header_index.Skip((int)stored_header_count).Take(2000).ToArray() + }); + stored_header_count += 2000; + } + if (snapshot_created) snapshot.Commit(); + } + finally + { + if (snapshot_created) snapshot.Dispose(); + } + } + + private void UpdateCurrentSnapshot() + { + Interlocked.Exchange(ref currentSnapshot, GetSnapshot())?.Dispose(); + } + } + + internal class BlockchainMailbox : PriorityMailbox + { + public BlockchainMailbox(Akka.Actor.Settings settings, Config config) + : base(settings, config) + { + } + + protected override bool IsHighPriority(object message) + { + switch (message) + { + case Header[] _: + case Block _: + case ConsensusPayload _: + case Terminated _: + return true; + default: + return false; + } + } + } +} diff --git a/neo/Core/CoinState.cs b/neo/Ledger/CoinState.cs similarity index 93% rename from neo/Core/CoinState.cs rename to neo/Ledger/CoinState.cs index 85300e5763..4d9d697b5d 100644 --- a/neo/Core/CoinState.cs +++ b/neo/Ledger/CoinState.cs @@ -1,6 +1,6 @@ using System; -namespace Neo.Core +namespace Neo.Ledger { [Flags] public enum CoinState : byte diff --git a/neo/Core/ContractPropertyState.cs b/neo/Ledger/ContractPropertyState.cs similarity index 90% rename from neo/Core/ContractPropertyState.cs rename to neo/Ledger/ContractPropertyState.cs index f09b38cc9a..036c005189 100644 --- a/neo/Core/ContractPropertyState.cs +++ b/neo/Ledger/ContractPropertyState.cs @@ -1,6 +1,6 @@ using System; -namespace Neo.Core +namespace Neo.Ledger { [Flags] public enum ContractPropertyState : byte diff --git a/neo/Core/ContractState.cs b/neo/Ledger/ContractState.cs similarity index 99% rename from neo/Core/ContractState.cs rename to neo/Ledger/ContractState.cs index a677298241..2f2250fa0a 100644 --- a/neo/Core/ContractState.cs +++ b/neo/Ledger/ContractState.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Ledger { public class ContractState : StateBase, ICloneable { diff --git a/neo/Ledger/HashIndexState.cs b/neo/Ledger/HashIndexState.cs new file mode 100644 index 0000000000..abf1420f09 --- /dev/null +++ b/neo/Ledger/HashIndexState.cs @@ -0,0 +1,51 @@ +using Neo.IO; +using Neo.IO.Json; +using System.IO; + +namespace Neo.Ledger +{ + public class HashIndexState : StateBase, ICloneable + { + public UInt256 Hash = UInt256.Zero; + public uint Index = uint.MaxValue; + + public override int Size => base.Size + Hash.Size + sizeof(uint); + + HashIndexState ICloneable.Clone() + { + return new HashIndexState + { + Hash = Hash, + Index = Index + }; + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + Hash = reader.ReadSerializable(); + Index = reader.ReadUInt32(); + } + + void ICloneable.FromReplica(HashIndexState replica) + { + Hash = replica.Hash; + Index = replica.Index; + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Hash); + writer.Write(Index); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["hash"] = Hash.ToString(); + json["index"] = Index; + return json; + } + } +} diff --git a/neo/Ledger/HeaderHashList.cs b/neo/Ledger/HeaderHashList.cs new file mode 100644 index 0000000000..c3c7a05da8 --- /dev/null +++ b/neo/Ledger/HeaderHashList.cs @@ -0,0 +1,46 @@ +using Neo.IO; +using Neo.IO.Json; +using System.IO; +using System.Linq; + +namespace Neo.Ledger +{ + public class HeaderHashList : StateBase, ICloneable + { + public UInt256[] Hashes; + + public override int Size => base.Size + Hashes.GetVarSize(); + + HeaderHashList ICloneable.Clone() + { + return new HeaderHashList + { + Hashes = Hashes + }; + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + Hashes = reader.ReadSerializableArray(); + } + + void ICloneable.FromReplica(HeaderHashList replica) + { + Hashes = replica.Hashes; + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Hashes); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["hashes"] = Hashes.Select(p => (JObject)p.ToString()).ToArray(); + return json; + } + } +} diff --git a/neo/Ledger/RelayResultReason.cs b/neo/Ledger/RelayResultReason.cs new file mode 100644 index 0000000000..9365ab5d61 --- /dev/null +++ b/neo/Ledger/RelayResultReason.cs @@ -0,0 +1,11 @@ +namespace Neo.Ledger +{ + public enum RelayResultReason : byte + { + Succeed, + AlreadyExists, + OutOfMemory, + UnableToVerify, + Invalid + } +} diff --git a/neo/Core/SpentCoin.cs b/neo/Ledger/SpentCoin.cs similarity index 77% rename from neo/Core/SpentCoin.cs rename to neo/Ledger/SpentCoin.cs index ba6a900aa7..a9cf058d7e 100644 --- a/neo/Core/SpentCoin.cs +++ b/neo/Ledger/SpentCoin.cs @@ -1,4 +1,6 @@ -namespace Neo.Core +using Neo.Network.P2P.Payloads; + +namespace Neo.Ledger { public class SpentCoin { diff --git a/neo/Core/SpentCoinState.cs b/neo/Ledger/SpentCoinState.cs similarity index 98% rename from neo/Core/SpentCoinState.cs rename to neo/Ledger/SpentCoinState.cs index 9f1e8b4049..62443c28d6 100644 --- a/neo/Core/SpentCoinState.cs +++ b/neo/Ledger/SpentCoinState.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; -namespace Neo.Core +namespace Neo.Ledger { public class SpentCoinState : StateBase, ICloneable { diff --git a/neo/Core/StateBase.cs b/neo/Ledger/StateBase.cs similarity index 97% rename from neo/Core/StateBase.cs rename to neo/Ledger/StateBase.cs index ca1bf5b3f0..9801c6aa4f 100644 --- a/neo/Core/StateBase.cs +++ b/neo/Ledger/StateBase.cs @@ -4,7 +4,7 @@ using System; using System.IO; -namespace Neo.Core +namespace Neo.Ledger { public abstract class StateBase : IInteropInterface, ISerializable { diff --git a/neo/Core/StorageItem.cs b/neo/Ledger/StorageItem.cs similarity index 97% rename from neo/Core/StorageItem.cs rename to neo/Ledger/StorageItem.cs index 257dbf936b..1d220b2e00 100644 --- a/neo/Core/StorageItem.cs +++ b/neo/Ledger/StorageItem.cs @@ -1,7 +1,7 @@ using Neo.IO; using System.IO; -namespace Neo.Core +namespace Neo.Ledger { public class StorageItem : StateBase, ICloneable { diff --git a/neo/Core/StorageKey.cs b/neo/Ledger/StorageKey.cs similarity index 90% rename from neo/Core/StorageKey.cs rename to neo/Ledger/StorageKey.cs index bb569a656c..891fb081ec 100644 --- a/neo/Core/StorageKey.cs +++ b/neo/Ledger/StorageKey.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Ledger { public class StorageKey : IEquatable, ISerializable { @@ -21,7 +21,7 @@ void ISerializable.Deserialize(BinaryReader reader) public bool Equals(StorageKey other) { - if (ReferenceEquals(other, null)) + if (other is null) return false; if (ReferenceEquals(this, other)) return true; @@ -30,7 +30,7 @@ public bool Equals(StorageKey other) public override bool Equals(object obj) { - if (ReferenceEquals(obj, null)) return false; + if (obj is null) return false; if (!(obj is StorageKey)) return false; return Equals((StorageKey)obj); } diff --git a/neo/Ledger/TransactionState.cs b/neo/Ledger/TransactionState.cs new file mode 100644 index 0000000000..5c72da7b8f --- /dev/null +++ b/neo/Ledger/TransactionState.cs @@ -0,0 +1,52 @@ +using Neo.IO; +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; +using System.IO; + +namespace Neo.Ledger +{ + public class TransactionState : StateBase, ICloneable + { + public uint BlockIndex; + public Transaction Transaction; + + public override int Size => base.Size + sizeof(uint) + Transaction.Size; + + TransactionState ICloneable.Clone() + { + return new TransactionState + { + BlockIndex = BlockIndex, + Transaction = Transaction + }; + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + BlockIndex = reader.ReadUInt32(); + Transaction = Transaction.DeserializeFrom(reader); + } + + void ICloneable.FromReplica(TransactionState replica) + { + BlockIndex = replica.BlockIndex; + Transaction = replica.Transaction; + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(BlockIndex); + writer.Write(Transaction); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["height"] = BlockIndex; + json["tx"] = Transaction.ToJson(); + return json; + } + } +} diff --git a/neo/Ledger/TrimmedBlock.cs b/neo/Ledger/TrimmedBlock.cs new file mode 100644 index 0000000000..b5b9eb0db5 --- /dev/null +++ b/neo/Ledger/TrimmedBlock.cs @@ -0,0 +1,76 @@ +using Neo.IO; +using Neo.IO.Caching; +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; +using System.IO; +using System.Linq; + +namespace Neo.Ledger +{ + public class TrimmedBlock : BlockBase + { + public UInt256[] Hashes; + + public bool IsBlock => Hashes.Length > 0; + + public Block GetBlock(DataCache cache) + { + return new Block + { + Version = Version, + PrevHash = PrevHash, + MerkleRoot = MerkleRoot, + Timestamp = Timestamp, + Index = Index, + ConsensusData = ConsensusData, + NextConsensus = NextConsensus, + Witness = Witness, + Transactions = Hashes.Select(p => cache[p].Transaction).ToArray() + }; + } + + private Header _header = null; + public Header Header + { + get + { + if (_header == null) + { + _header = new Header + { + Version = Version, + PrevHash = PrevHash, + MerkleRoot = MerkleRoot, + Timestamp = Timestamp, + Index = Index, + ConsensusData = ConsensusData, + NextConsensus = NextConsensus, + Witness = Witness + }; + } + return _header; + } + } + + public override int Size => base.Size + Hashes.GetVarSize(); + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + Hashes = reader.ReadSerializableArray(); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Hashes); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["hashes"] = Hashes.Select(p => (JObject)p.ToString()).ToArray(); + return json; + } + } +} diff --git a/neo/Core/UnspentCoinState.cs b/neo/Ledger/UnspentCoinState.cs similarity index 97% rename from neo/Core/UnspentCoinState.cs rename to neo/Ledger/UnspentCoinState.cs index 76caa7d447..640f13b739 100644 --- a/neo/Core/UnspentCoinState.cs +++ b/neo/Ledger/UnspentCoinState.cs @@ -2,7 +2,7 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Ledger { public class UnspentCoinState : StateBase, ICloneable { diff --git a/neo/Core/ValidatorState.cs b/neo/Ledger/ValidatorState.cs similarity index 98% rename from neo/Core/ValidatorState.cs rename to neo/Ledger/ValidatorState.cs index 1292cbbcdb..e4afd22151 100644 --- a/neo/Core/ValidatorState.cs +++ b/neo/Ledger/ValidatorState.cs @@ -2,7 +2,7 @@ using Neo.IO; using System.IO; -namespace Neo.Core +namespace Neo.Ledger { public class ValidatorState : StateBase, ICloneable { diff --git a/neo/Core/ValidatorsCountState.cs b/neo/Ledger/ValidatorsCountState.cs similarity index 98% rename from neo/Core/ValidatorsCountState.cs rename to neo/Ledger/ValidatorsCountState.cs index 9ad96c330a..07b172de5d 100644 --- a/neo/Core/ValidatorsCountState.cs +++ b/neo/Ledger/ValidatorsCountState.cs @@ -1,7 +1,7 @@ using Neo.IO; using System.IO; -namespace Neo.Core +namespace Neo.Ledger { public class ValidatorsCountState : StateBase, ICloneable { diff --git a/neo/NeoSystem.cs b/neo/NeoSystem.cs new file mode 100644 index 0000000000..1c921448b7 --- /dev/null +++ b/neo/NeoSystem.cs @@ -0,0 +1,64 @@ +using Akka.Actor; +using Neo.Consensus; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.RPC; +using Neo.Persistence; +using Neo.Plugins; +using Neo.Wallets; +using System; + +namespace Neo +{ + public class NeoSystem : IDisposable + { + public readonly ActorSystem ActorSystem = ActorSystem.Create(nameof(NeoSystem), + $"akka {{ log-dead-letters = off }}" + + $"blockchain-mailbox {{ mailbox-type: \"{typeof(BlockchainMailbox).AssemblyQualifiedName}\" }}" + + $"task-manager-mailbox {{ mailbox-type: \"{typeof(TaskManagerMailbox).AssemblyQualifiedName}\" }}" + + $"remote-node-mailbox {{ mailbox-type: \"{typeof(RemoteNodeMailbox).AssemblyQualifiedName}\" }}" + + $"protocol-handler-mailbox {{ mailbox-type: \"{typeof(ProtocolHandlerMailbox).AssemblyQualifiedName}\" }}" + + $"consensus-service-mailbox {{ mailbox-type: \"{typeof(ConsensusServiceMailbox).AssemblyQualifiedName}\" }}"); + public readonly IActorRef Blockchain; + public readonly IActorRef LocalNode; + internal readonly IActorRef TaskManager; + internal IActorRef Consensus; + private RpcServer rpcServer; + + public NeoSystem(Store store) + { + this.Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this, store)); + this.LocalNode = ActorSystem.ActorOf(Network.P2P.LocalNode.Props(this)); + this.TaskManager = ActorSystem.ActorOf(Network.P2P.TaskManager.Props(this)); + Plugin.LoadPlugins(this); + } + + public void Dispose() + { + rpcServer?.Dispose(); + ActorSystem.Stop(LocalNode); + ActorSystem.Dispose(); + } + + public void StartConsensus(Wallet wallet) + { + Consensus = ActorSystem.ActorOf(ConsensusService.Props(this, wallet)); + Consensus.Tell(new ConsensusService.Start()); + } + + public void StartNode(int port = 0, int ws_port = 0) + { + LocalNode.Tell(new Peer.Start + { + Port = port, + WsPort = ws_port + }); + } + + public void StartRpc(int port, Wallet wallet = null, string sslCert = null, string password = null) + { + rpcServer = new RpcServer(this, wallet); + rpcServer.Start(port, sslCert, password); + } + } +} diff --git a/neo/Network/InventoryReceivingEventArgs.cs b/neo/Network/InventoryReceivingEventArgs.cs deleted file mode 100644 index 020d06678f..0000000000 --- a/neo/Network/InventoryReceivingEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.ComponentModel; - -namespace Neo.Network -{ - public class InventoryReceivingEventArgs : CancelEventArgs - { - public IInventory Inventory { get; } - - public InventoryReceivingEventArgs(IInventory inventory) - { - this.Inventory = inventory; - } - } -} diff --git a/neo/Network/InventoryType.cs b/neo/Network/InventoryType.cs deleted file mode 100644 index 4d50374401..0000000000 --- a/neo/Network/InventoryType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Neo.Network -{ - /// - /// 定义清单中的对象类型 - /// - public enum InventoryType : byte - { - /// - /// 交易 - /// - TX = 0x01, - /// - /// 区块 - /// - Block = 0x02, - /// - /// 共识数据 - /// - Consensus = 0xe0 - } -} diff --git a/neo/Network/LocalNode.cs b/neo/Network/LocalNode.cs deleted file mode 100644 index 42199c0edc..0000000000 --- a/neo/Network/LocalNode.cs +++ /dev/null @@ -1,833 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Neo.Core; -using Neo.IO; -using Neo.IO.Caching; -using Neo.Network.Payloads; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Net.WebSockets; -using System.Numerics; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Neo.Network -{ - public class LocalNode : IDisposable - { - public static event EventHandler InventoryReceiving; - public static event EventHandler InventoryReceived; - - public const uint ProtocolVersion = 0; - private const int ConnectedMax = 10; - private const int DesiredAvailablePeers = (int)(ConnectedMax * 1.5); - private const int UnconnectedMax = 1000; - public const int MemoryPoolSize = 50000; - internal static readonly TimeSpan HashesExpiration = TimeSpan.FromSeconds(30); - private DateTime LastBlockReceived = DateTime.UtcNow; - - private static readonly ReaderWriterLockSlim MemPoolReadWriteLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private static readonly Dictionary mem_pool = new Dictionary(); - private readonly HashSet temp_pool = new HashSet(); - internal static readonly Dictionary KnownHashes = new Dictionary(); - internal readonly RelayCache RelayCache = new RelayCache(100); - - private static readonly HashSet unconnectedPeers = new HashSet(); - private static readonly HashSet badPeers = new HashSet(); - internal readonly List connectedPeers = new List(); - - internal static readonly HashSet LocalAddresses = new HashSet(); - internal ushort Port; - internal readonly uint Nonce; - private TcpListener listener; - private IWebHost ws_host; - private Thread connectThread; - private Thread poolThread; - private readonly AutoResetEvent new_tx_event = new AutoResetEvent(false); - private int started = 0; - private int disposed = 0; - private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - - public bool GlobalMissionsEnabled { get; set; } = true; - public int RemoteNodeCount => connectedPeers.Count; - public bool ServiceEnabled { get; set; } = true; - public bool UpnpEnabled { get; set; } = false; - public string UserAgent { get; set; } - - static LocalNode() - { - LocalAddresses.UnionWith(NetworkInterface.GetAllNetworkInterfaces().SelectMany(p => p.GetIPProperties().UnicastAddresses).Select(p => p.Address.MapToIPv6())); - } - - public LocalNode() - { - Random rand = new Random(); - this.Nonce = (uint)rand.Next(); - this.connectThread = new Thread(ConnectToPeersLoop) - { - IsBackground = true, - Name = "LocalNode.ConnectToPeersLoop" - }; - if (Blockchain.Default != null) - { - this.poolThread = new Thread(AddTransactionLoop) - { - IsBackground = true, - Name = "LocalNode.AddTransactionLoop" - }; - } - this.UserAgent = string.Format("/NEO:{0}/", Assembly.GetExecutingAssembly().GetVersion()); - Blockchain.PersistCompleted += Blockchain_PersistCompleted; - } - - private async void AcceptPeers() - { -#if !NET47 - //There is a bug in .NET Core 2.0 that blocks async method which returns void. - await Task.Yield(); -#endif - while (!cancellationTokenSource.IsCancellationRequested) - { - Socket socket; - try - { - socket = await listener.AcceptSocketAsync(); - } - catch (ObjectDisposedException) - { - break; - } - catch (SocketException) - { - continue; - } - TcpRemoteNode remoteNode = new TcpRemoteNode(this, socket); - OnConnected(remoteNode); - } - } - - private static bool AddTransaction(Transaction tx) - { - if (Blockchain.Default == null) return false; - lock (Blockchain.Default.PersistLock) - { - MemPoolReadWriteLock.EnterWriteLock(); - try - { - if (mem_pool.ContainsKey(tx.Hash)) return false; - if (Blockchain.Default.ContainsTransaction(tx.Hash)) return false; - if (!tx.Verify(mem_pool.Values)) return false; - mem_pool.Add(tx.Hash, tx); - CheckMemPool(); - } - finally - { - MemPoolReadWriteLock.ExitWriteLock(); - } - } - return true; - } - - private void AddTransactionLoop() - { - while (!cancellationTokenSource.IsCancellationRequested) - { - new_tx_event.WaitOne(); - - Transaction[] transactions; - lock (temp_pool) - { - if (temp_pool.Count == 0) continue; - transactions = temp_pool.ToArray(); - temp_pool.Clear(); - } - ConcurrentBag verified = new ConcurrentBag(); - lock (Blockchain.Default.PersistLock) - { - MemPoolReadWriteLock.EnterWriteLock(); - try - { - transactions = transactions.Where(p => !mem_pool.ContainsKey(p.Hash) && !Blockchain.Default.ContainsTransaction(p.Hash)).ToArray(); - - if (transactions.Length == 0) - continue; - - Transaction[] tmpool = mem_pool.Values.Concat(transactions).ToArray(); - - ParallelOptions po = new ParallelOptions(); - po.CancellationToken = Blockchain.Default.VerificationCancellationToken.Token; - po.MaxDegreeOfParallelism = System.Environment.ProcessorCount; - - try - { - Parallel.ForEach(transactions.AsParallel(), po, tx => - { - if (tx.Verify(tmpool)) - verified.Add(tx); - }); - } - catch (OperationCanceledException) - { - lock (temp_pool) - { - foreach (Transaction tx in transactions) - temp_pool.Add(tx); - } - - continue; - } - - if (verified.Count == 0) continue; - - foreach (Transaction tx in verified) - mem_pool.Add(tx.Hash, tx); - - CheckMemPool(); - } - finally - { - MemPoolReadWriteLock.ExitWriteLock(); - } - } - RelayDirectly(verified); - if (InventoryReceived != null) - foreach (Transaction tx in verified) - InventoryReceived(this, tx); - } - } - - public static void AllowHashes(IEnumerable hashes) - { - lock (KnownHashes) - { - foreach (UInt256 hash in hashes) - KnownHashes.Remove(hash); - } - } - - private void Blockchain_PersistCompleted(object sender, Block block) - { - Transaction[] remain; - var millisSinceLastBlock = TimeSpan.FromTicks(DateTimeOffset.UtcNow.Ticks) - .Subtract(TimeSpan.FromTicks(LastBlockReceived.Ticks)).TotalMilliseconds; - - MemPoolReadWriteLock.EnterWriteLock(); - try - { - // Remove the transactions that made it into the block - foreach (Transaction tx in block.Transactions) - mem_pool.Remove(tx.Hash); - if (mem_pool.Count == 0) return; - - remain = mem_pool.Values.ToArray(); - mem_pool.Clear(); - - if (millisSinceLastBlock > 10000) - { - ConcurrentBag verified = new ConcurrentBag(); - // Reverify the remaining transactions in the mem_pool - remain.AsParallel().ForAll(tx => - { - if (tx.Verify(remain)) - verified.Add(tx); - }); - - // Note, when running - foreach (Transaction tx in verified) - mem_pool.Add(tx.Hash, tx); - } - } - finally - { - MemPoolReadWriteLock.ExitWriteLock(); - } - LastBlockReceived = DateTime.UtcNow; - - lock (temp_pool) - { - if (millisSinceLastBlock > 10000) - { - if (temp_pool.Count > 0) - new_tx_event.Set(); - } - else - { - temp_pool.UnionWith(remain); - new_tx_event.Set(); - } - } - } - - private static bool CheckKnownHashes(UInt256 hash) - { - DateTime now = DateTime.UtcNow; - lock (KnownHashes) - { - if (KnownHashes.TryGetValue(hash, out DateTime time)) - { - if (now - time <= HashesExpiration) - return false; - } - KnownHashes[hash] = now; - if (KnownHashes.Count > 1000000) - { - UInt256[] expired = KnownHashes.Where(p => now - p.Value > HashesExpiration).Select(p => p.Key).ToArray(); - foreach (UInt256 key in expired) - KnownHashes.Remove(key); - } - return true; - } - } - - private static void CheckMemPool() - { - if (mem_pool.Count <= MemoryPoolSize) return; - - UInt256[] hashes = mem_pool.Values.AsParallel() - .OrderBy(p => p.NetworkFee / p.Size) - .ThenBy(p => p.NetworkFee) - .ThenBy(p => new BigInteger(p.Hash.ToArray())) - .Take(mem_pool.Count - MemoryPoolSize) - .Select(p => p.Hash) - .ToArray(); - - foreach (UInt256 hash in hashes) - mem_pool.Remove(hash); - } - - public async Task GetIPEndpointFromHostPortAsync(string hostNameOrAddress, int port) - { - if (IPAddress.TryParse(hostNameOrAddress, out IPAddress ipAddress)) - { - ipAddress = ipAddress.MapToIPv6(); - } - else - { - IPHostEntry entry; - try - { - entry = await Dns.GetHostEntryAsync(hostNameOrAddress); - } - catch (SocketException) - { - return null; - } - ipAddress = entry.AddressList.FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork || p.IsIPv6Teredo)?.MapToIPv6(); - if (ipAddress == null) return null; - } - - return new IPEndPoint(ipAddress, port); - } - - public async Task ConnectToPeerAsync(string hostNameOrAddress, int port) - { - IPEndPoint ipEndpoint = await GetIPEndpointFromHostPortAsync(hostNameOrAddress, port); - - if (ipEndpoint == null) return; - await ConnectToPeerAsync(ipEndpoint); - } - - public async Task ConnectToPeerAsync(IPEndPoint remoteEndpoint) - { - if (remoteEndpoint.Port == Port && LocalAddresses.Contains(remoteEndpoint.Address.MapToIPv6())) return; - lock (unconnectedPeers) - { - unconnectedPeers.Remove(remoteEndpoint); - } - lock (connectedPeers) - { - if (connectedPeers.Any(p => remoteEndpoint.Equals(p.ListenerEndpoint))) - return; - } - TcpRemoteNode remoteNode = new TcpRemoteNode(this, remoteEndpoint); - if (await remoteNode.ConnectAsync()) - { - OnConnected(remoteNode); - } - } - - private IEnumerable GetIPEndPointsFromSeedList(int seedsToTake) - { - if (seedsToTake > 0) - { - Random rand = new Random(); - foreach (string hostAndPort in Settings.Default.SeedList.OrderBy(p => rand.Next())) - { - if (seedsToTake == 0) break; - string[] p = hostAndPort.Split(':'); - IPEndPoint seed; - try - { - seed = GetIPEndpointFromHostPortAsync(p[0], int.Parse(p[1])).Result; - } - catch (AggregateException) - { - continue; - } - if (seed == null) continue; - seedsToTake--; - yield return seed; - } - } - } - - private void ConnectToPeersLoop() - { - Dictionary tasksDict = new Dictionary(); - DateTime lastSufficientPeersTimestamp = DateTime.UtcNow; - Dictionary currentlyConnectingIPs = new Dictionary(); - - void connectToPeers(IEnumerable ipEndPoints) - { - foreach (var ipEndPoint in ipEndPoints) - { - // Protect from the case same IP is in the endpoint array twice - if (currentlyConnectingIPs.ContainsKey(ipEndPoint.Address)) - continue; - - var connectTask = ConnectToPeerAsync(ipEndPoint); - - // Completed tasks that run synchronously may use a non-unique cached task object. - if (connectTask.IsCompleted) - continue; - - tasksDict.Add(connectTask, ipEndPoint.Address); - currentlyConnectingIPs.Add(ipEndPoint.Address, connectTask); - } - } - - while (!cancellationTokenSource.IsCancellationRequested) - { - int connectedCount = connectedPeers.Count; - int unconnectedCount = unconnectedPeers.Count; - if (connectedCount < ConnectedMax) - { - if (unconnectedCount > 0) - { - IPEndPoint[] endpoints; - lock (unconnectedPeers) - { - endpoints = unconnectedPeers.Where(x => !currentlyConnectingIPs.ContainsKey(x.Address)) - .Take(ConnectedMax - connectedCount).ToArray(); - } - - connectToPeers(endpoints); - } - - if (connectedCount > 0) - { - if (unconnectedCount + connectedCount < DesiredAvailablePeers) - { - lock (connectedPeers) - { - foreach (RemoteNode node in connectedPeers) - node.RequestPeers(); - } - - if (lastSufficientPeersTimestamp < DateTime.UtcNow.AddSeconds(-180)) - { - IEnumerable endpoints = GetIPEndPointsFromSeedList(2); - connectToPeers(endpoints); - lastSufficientPeersTimestamp = DateTime.UtcNow; - } - } - else - { - lastSufficientPeersTimestamp = DateTime.UtcNow; - } - } - else - { - IEnumerable endpoints = GetIPEndPointsFromSeedList(5); - connectToPeers(endpoints); - lastSufficientPeersTimestamp = DateTime.UtcNow; - } - } - - try - { - var tasksArray = tasksDict.Keys.ToArray(); - if (tasksArray.Length > 0) - { - Task.WaitAny(tasksArray, 5000, cancellationTokenSource.Token); - - foreach (var task in tasksArray) - { - if (!task.IsCompleted) continue; - if (tasksDict.TryGetValue(task, out IPAddress ip)) - currentlyConnectingIPs.Remove(ip); - // Clean-up task no longer running. - tasksDict.Remove(task); - task.Dispose(); - } - } - } - catch (OperationCanceledException) - { - break; - } - - for (int i = 0; i < 50 && !cancellationTokenSource.IsCancellationRequested; i++) - { - Thread.Sleep(100); - } - } - } - - public static bool ContainsTransaction(UInt256 hash) - { - MemPoolReadWriteLock.EnterReadLock(); - try - { - return mem_pool.ContainsKey(hash); - } - finally - { - MemPoolReadWriteLock.ExitReadLock(); - } - } - - public void Dispose() - { - if (Interlocked.Exchange(ref disposed, 1) == 0) - { - cancellationTokenSource.Cancel(); - if (started > 0) - { - // Ensure any outstanding calls to Blockchain_PersistCompleted are not in progress - lock (Blockchain.Default.PersistLock) - { - Blockchain.PersistCompleted -= Blockchain_PersistCompleted; - } - - if (listener != null) listener.Stop(); - if (!connectThread.ThreadState.HasFlag(ThreadState.Unstarted)) connectThread.Join(); - lock (unconnectedPeers) - { - if (unconnectedPeers.Count < UnconnectedMax) - { - lock (connectedPeers) - { - unconnectedPeers.UnionWith(connectedPeers.Select(p => p.ListenerEndpoint).Where(p => p != null).Take(UnconnectedMax - unconnectedPeers.Count)); - } - } - } - RemoteNode[] nodes; - lock (connectedPeers) - { - nodes = connectedPeers.ToArray(); - } - Task.WaitAll(nodes.Select(p => Task.Run(() => p.Disconnect(false))).ToArray()); - - new_tx_event.Set(); - if (poolThread?.ThreadState.HasFlag(ThreadState.Unstarted) == false) - poolThread.Join(); - - new_tx_event.Dispose(); - } - MemPoolReadWriteLock.Dispose(); - } - } - - public static Transaction[] GetMemoryPool() - { - MemPoolReadWriteLock.EnterReadLock(); - try - { - return mem_pool.Values.ToArray(); - } - finally - { - MemPoolReadWriteLock.ExitReadLock(); - } - } - - public RemoteNode[] GetRemoteNodes() - { - lock (connectedPeers) - { - return connectedPeers.ToArray(); - } - } - - public static Transaction GetTransaction(UInt256 hash) - { - MemPoolReadWriteLock.EnterReadLock(); - try - { - if (!mem_pool.TryGetValue(hash, out Transaction tx)) - return null; - return tx; - } - finally - { - MemPoolReadWriteLock.ExitReadLock(); - } - } - - internal void RequestGetBlocks() - { - RemoteNode[] nodes = GetRemoteNodes(); - - GetBlocksPayload payload = GetBlocksPayload.Create(Blockchain.Default.CurrentBlockHash); - - foreach (RemoteNode node in nodes) - node.EnqueueMessage("getblocks", payload); - } - - private static bool IsIntranetAddress(IPAddress address) - { - byte[] data = address.MapToIPv4().GetAddressBytes(); - Array.Reverse(data); - uint value = data.ToUInt32(0); - return (value & 0xff000000) == 0x0a000000 || (value & 0xff000000) == 0x7f000000 || (value & 0xfff00000) == 0xac100000 || (value & 0xffff0000) == 0xc0a80000 || (value & 0xffff0000) == 0xa9fe0000; - } - - public static void LoadState(Stream stream) - { - lock (unconnectedPeers) - { - unconnectedPeers.Clear(); - using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, true)) - { - int count = reader.ReadInt32(); - for (int i = 0; i < count; i++) - { - IPAddress address = new IPAddress(reader.ReadBytes(4)); - int port = reader.ReadUInt16(); - unconnectedPeers.Add(new IPEndPoint(address.MapToIPv6(), port)); - } - } - } - } - - private void OnConnected(RemoteNode remoteNode) - { - lock (connectedPeers) - { - connectedPeers.Add(remoteNode); - } - remoteNode.Disconnected += RemoteNode_Disconnected; - remoteNode.InventoryReceived += RemoteNode_InventoryReceived; - remoteNode.PeersReceived += RemoteNode_PeersReceived; - remoteNode.StartProtocol(); - } - - private async Task ProcessWebSocketAsync(HttpContext context) - { - if (!context.WebSockets.IsWebSocketRequest) return; - WebSocket ws = await context.WebSockets.AcceptWebSocketAsync(); - WebSocketRemoteNode remoteNode = new WebSocketRemoteNode(this, ws, new IPEndPoint(context.Connection.RemoteIpAddress, context.Connection.RemotePort)); - OnConnected(remoteNode); - } - - public bool Relay(IInventory inventory) - { - if (inventory is MinerTransaction) return false; - if (!CheckKnownHashes(inventory.Hash)) return false; - InventoryReceivingEventArgs args = new InventoryReceivingEventArgs(inventory); - InventoryReceiving?.Invoke(this, args); - if (args.Cancel) return false; - if (inventory is Block block) - { - if (Blockchain.Default == null) return false; - if (Blockchain.Default.ContainsBlock(block.Hash)) return false; - if (!Blockchain.Default.AddBlock(block)) return false; - } - else if (inventory is Transaction) - { - if (!AddTransaction((Transaction)inventory)) return false; - } - else //if (inventory is Consensus) - { - if (!inventory.Verify()) return false; - } - bool relayed = RelayDirectly(inventory); - InventoryReceived?.Invoke(this, inventory); - return relayed; - } - - public bool RelayDirectly(IInventory inventory) - { - bool relayed = false; - lock (connectedPeers) - { - RelayCache.Add(inventory); - foreach (RemoteNode node in connectedPeers) - relayed |= node.Relay(inventory); - } - return relayed; - } - - private void RelayDirectly(IReadOnlyCollection transactions) - { - lock (connectedPeers) - { - foreach (RemoteNode node in connectedPeers) - node.Relay(transactions); - } - } - - private void RemoteNode_Disconnected(object sender, bool error) - { - RemoteNode remoteNode = (RemoteNode)sender; - remoteNode.Disconnected -= RemoteNode_Disconnected; - remoteNode.InventoryReceived -= RemoteNode_InventoryReceived; - remoteNode.PeersReceived -= RemoteNode_PeersReceived; - if (error && remoteNode.ListenerEndpoint != null) - { - lock (badPeers) - { - badPeers.Add(remoteNode.ListenerEndpoint); - } - } - lock (unconnectedPeers) - { - lock (connectedPeers) - { - if (remoteNode.ListenerEndpoint != null) - { - unconnectedPeers.Remove(remoteNode.ListenerEndpoint); - } - connectedPeers.Remove(remoteNode); - } - } - } - - private void RemoteNode_InventoryReceived(object sender, IInventory inventory) - { - if (inventory is Transaction tx && tx.Type != TransactionType.ClaimTransaction && tx.Type != TransactionType.IssueTransaction) - { - if (Blockchain.Default == null) return; - if (!CheckKnownHashes(inventory.Hash)) return; - InventoryReceivingEventArgs args = new InventoryReceivingEventArgs(inventory); - InventoryReceiving?.Invoke(this, args); - if (args.Cancel) return; - lock (temp_pool) - { - temp_pool.Add(tx); - } - new_tx_event.Set(); - } - else - { - Relay(inventory); - } - } - - private void RemoteNode_PeersReceived(object sender, IPEndPoint[] peers) - { - lock (unconnectedPeers) - { - if (unconnectedPeers.Count < UnconnectedMax) - { - lock (badPeers) - { - lock (connectedPeers) - { - unconnectedPeers.UnionWith(peers); - unconnectedPeers.ExceptWith(badPeers); - unconnectedPeers.ExceptWith(connectedPeers.Select(p => p.ListenerEndpoint)); - } - } - } - } - } - - public IPEndPoint[] GetUnconnectedPeers() - { - lock (unconnectedPeers) - { - return unconnectedPeers.ToArray(); - } - } - - public IPEndPoint[] GetBadPeers() - { - lock (badPeers) - { - return badPeers.ToArray(); - } - } - - public static void SaveState(Stream stream) - { - IPEndPoint[] peers; - lock (unconnectedPeers) - { - peers = unconnectedPeers.Take(UnconnectedMax).ToArray(); - } - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII, true)) - { - writer.Write(peers.Length); - foreach (IPEndPoint endpoint in peers) - { - writer.Write(endpoint.Address.MapToIPv4().GetAddressBytes()); - writer.Write((ushort)endpoint.Port); - } - } - } - - public void Start(int port = 0, int ws_port = 0) - { - if (Interlocked.Exchange(ref started, 1) == 0) - { - Task.Run(async () => - { - if ((port > 0 || ws_port > 0) - && UpnpEnabled - && LocalAddresses.All(p => !p.IsIPv4MappedToIPv6 || IsIntranetAddress(p)) - && await UPnP.DiscoverAsync()) - { - try - { - LocalAddresses.Add((await UPnP.GetExternalIPAsync()).MapToIPv6()); - if (port > 0) - await UPnP.ForwardPortAsync(port, ProtocolType.Tcp, "NEO"); - if (ws_port > 0) - await UPnP.ForwardPortAsync(ws_port, ProtocolType.Tcp, "NEO WebSocket"); - } - catch { } - } - connectThread.Start(); - poolThread?.Start(); - if (port > 0) - { - listener = new TcpListener(IPAddress.Any, port); - listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); - try - { - listener.Start(); - Port = (ushort)port; - AcceptPeers(); - } - catch (SocketException) { } - } - if (ws_port > 0) - { - ws_host = new WebHostBuilder().UseKestrel().UseUrls($"http://*:{ws_port}").Configure(app => app.UseWebSockets().Run(ProcessWebSocketAsync)).Build(); - ws_host.Start(); - } - }); - } - } - - public void SynchronizeMemoryPool() - { - lock (connectedPeers) - { - foreach (RemoteNode node in connectedPeers) - node.RequestMemoryPool(); - } - } - } -} diff --git a/neo/Network/Message.cs b/neo/Network/Message.cs deleted file mode 100644 index cb20b39cc5..0000000000 --- a/neo/Network/Message.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Neo.Cryptography; -using Neo.IO; -using System; -using System.IO; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Neo.Network -{ - public class Message : ISerializable - { - private const int PayloadMaxSize = 0x02000000; - - public static readonly uint Magic = Settings.Default.Magic; - public string Command; - public uint Checksum; - public byte[] Payload; - - public int Size => sizeof(uint) + 12 + sizeof(int) + sizeof(uint) + Payload.Length; - - public static Message Create(string command, ISerializable payload = null) - { - return Create(command, payload == null ? new byte[0] : payload.ToArray()); - } - - public static Message Create(string command, byte[] payload) - { - return new Message - { - Command = command, - Checksum = GetChecksum(payload), - Payload = payload - }; - } - - void ISerializable.Deserialize(BinaryReader reader) - { - if (reader.ReadUInt32() != Magic) - throw new FormatException(); - this.Command = reader.ReadFixedString(12); - uint length = reader.ReadUInt32(); - if (length > PayloadMaxSize) - throw new FormatException(); - this.Checksum = reader.ReadUInt32(); - this.Payload = reader.ReadBytes((int)length); - if (GetChecksum(Payload) != Checksum) - throw new FormatException(); - } - - public static async Task DeserializeFromAsync(Stream stream, CancellationToken cancellationToken) - { - uint payload_length; - byte[] buffer = await FillBufferAsync(stream, 24, cancellationToken); - Message message = new Message(); - using (MemoryStream ms = new MemoryStream(buffer, false)) - using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8)) - { - if (reader.ReadUInt32() != Magic) - throw new FormatException(); - message.Command = reader.ReadFixedString(12); - payload_length = reader.ReadUInt32(); - if (payload_length > PayloadMaxSize) - throw new FormatException(); - message.Checksum = reader.ReadUInt32(); - } - if (payload_length > 0) - message.Payload = await FillBufferAsync(stream, (int)payload_length, cancellationToken); - else - message.Payload = new byte[0]; - if (GetChecksum(message.Payload) != message.Checksum) - throw new FormatException(); - return message; - } - - public static async Task DeserializeFromAsync(WebSocket socket, CancellationToken cancellationToken) - { - uint payload_length; - byte[] buffer = await FillBufferAsync(socket, 24, cancellationToken); - Message message = new Message(); - using (MemoryStream ms = new MemoryStream(buffer, false)) - using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8)) - { - if (reader.ReadUInt32() != Magic) - throw new FormatException(); - message.Command = reader.ReadFixedString(12); - payload_length = reader.ReadUInt32(); - if (payload_length > PayloadMaxSize) - throw new FormatException(); - message.Checksum = reader.ReadUInt32(); - } - if (payload_length > 0) - message.Payload = await FillBufferAsync(socket, (int)payload_length, cancellationToken); - else - message.Payload = new byte[0]; - if (GetChecksum(message.Payload) != message.Checksum) - throw new FormatException(); - return message; - } - - private static async Task FillBufferAsync(Stream stream, int buffer_size, CancellationToken cancellationToken) - { - const int MAX_SIZE = 1024; - byte[] buffer = new byte[buffer_size < MAX_SIZE ? buffer_size : MAX_SIZE]; - using (MemoryStream ms = new MemoryStream()) - { - while (buffer_size > 0) - { - int count = buffer_size < MAX_SIZE ? buffer_size : MAX_SIZE; - count = await stream.ReadAsync(buffer, 0, count, cancellationToken); - if (count <= 0) throw new IOException(); - ms.Write(buffer, 0, count); - buffer_size -= count; - } - return ms.ToArray(); - } - } - - private static async Task FillBufferAsync(WebSocket socket, int buffer_size, CancellationToken cancellationToken) - { - const int MAX_SIZE = 1024; - byte[] buffer = new byte[buffer_size < MAX_SIZE ? buffer_size : MAX_SIZE]; - using (MemoryStream ms = new MemoryStream()) - { - while (buffer_size > 0) - { - int count = buffer_size < MAX_SIZE ? buffer_size : MAX_SIZE; - ArraySegment segment = new ArraySegment(buffer, 0, count); - WebSocketReceiveResult result = await socket.ReceiveAsync(segment, cancellationToken); - if (result.Count <= 0 || result.MessageType != WebSocketMessageType.Binary) - throw new IOException(); - ms.Write(buffer, 0, result.Count); - buffer_size -= result.Count; - } - return ms.ToArray(); - } - } - - private static uint GetChecksum(byte[] value) - { - return Crypto.Default.Hash256(value).ToUInt32(0); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - writer.Write(Magic); - writer.WriteFixedString(Command, 12); - writer.Write(Payload.Length); - writer.Write(Checksum); - writer.Write(Payload); - } - } -} diff --git a/neo/Network/P2P/Connection.cs b/neo/Network/P2P/Connection.cs new file mode 100644 index 0000000000..e4c8becec6 --- /dev/null +++ b/neo/Network/P2P/Connection.cs @@ -0,0 +1,139 @@ +using Akka.Actor; +using Akka.IO; +using System; +using System.Net; +using System.Net.WebSockets; +using System.Threading; + +namespace Neo.Network.P2P +{ + public abstract class Connection : UntypedActor + { + internal class Timer { public static Timer Instance = new Timer(); } + internal class Ack : Tcp.Event { public static Ack Instance = new Ack(); } + + public IPEndPoint Remote { get; } + public IPEndPoint Local { get; } + public abstract int ListenerPort { get; } + + private ICancelable timer; + private readonly IActorRef tcp; + private readonly WebSocket ws; + private bool disconnected = false; + + protected Connection(object connection, IPEndPoint remote, IPEndPoint local) + { + this.Remote = remote; + this.Local = local; + this.timer = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromSeconds(10), Self, Timer.Instance, ActorRefs.NoSender); + switch (connection) + { + case IActorRef tcp: + this.tcp = tcp; + break; + case WebSocket ws: + this.ws = ws; + WsReceive(); + break; + } + } + + private void WsReceive() + { + byte[] buffer = new byte[512]; + ArraySegment segment = new ArraySegment(buffer); + ws.ReceiveAsync(segment, CancellationToken.None).PipeTo(Self, + success: p => + { + switch (p.MessageType) + { + case WebSocketMessageType.Binary: + return new Tcp.Received(ByteString.FromBytes(buffer, 0, p.Count)); + case WebSocketMessageType.Close: + return Tcp.PeerClosed.Instance; + default: + ws.Abort(); + return Tcp.Aborted.Instance; + } + }, + failure: ex => new Tcp.ErrorClosed(ex.Message)); + } + + public void Disconnect(bool abort = false) + { + disconnected = true; + if (tcp != null) + { + tcp.Tell(abort ? (Tcp.CloseCommand)Tcp.Abort.Instance : Tcp.Close.Instance); + } + else + { + ws.Abort(); + } + Context.Stop(Self); + } + + protected virtual void OnAck() + { + } + + protected abstract void OnData(ByteString data); + + protected override void OnReceive(object message) + { + switch (message) + { + case Timer _: + Disconnect(true); + break; + case Ack _: + OnAck(); + break; + case Tcp.Received received: + OnReceived(received.Data); + break; + case Tcp.ConnectionClosed _: + Context.Stop(Self); + break; + } + } + + private void OnReceived(ByteString data) + { + timer.CancelIfNotNull(); + timer = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromMinutes(1), Self, Timer.Instance, ActorRefs.NoSender); + try + { + OnData(data); + } + catch + { + Disconnect(true); + } + } + + protected override void PostStop() + { + if (!disconnected) + tcp?.Tell(Tcp.Close.Instance); + timer.CancelIfNotNull(); + ws?.Dispose(); + base.PostStop(); + } + + protected void SendData(ByteString data) + { + if (tcp != null) + { + tcp.Tell(Tcp.Write.Create(data, Ack.Instance)); + } + else + { + ArraySegment segment = new ArraySegment(data.ToArray()); + ws.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).PipeTo(Self, + success: () => Ack.Instance, + failure: ex => new Tcp.ErrorClosed(ex.Message)); + } + } + } +} diff --git a/neo/Network/P2P/Helper.cs b/neo/Network/P2P/Helper.cs new file mode 100644 index 0000000000..43ded70d6a --- /dev/null +++ b/neo/Network/P2P/Helper.cs @@ -0,0 +1,19 @@ +using Neo.Network.P2P.Payloads; +using System.IO; + +namespace Neo.Network.P2P +{ + public static class Helper + { + public static byte[] GetHashData(this IVerifiable verifiable) + { + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter writer = new BinaryWriter(ms)) + { + verifiable.SerializeUnsigned(writer); + writer.Flush(); + return ms.ToArray(); + } + } + } +} diff --git a/neo/Network/P2P/LocalNode.cs b/neo/Network/P2P/LocalNode.cs new file mode 100644 index 0000000000..2e12a046b4 --- /dev/null +++ b/neo/Network/P2P/LocalNode.cs @@ -0,0 +1,187 @@ +using Akka.Actor; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Threading; + +namespace Neo.Network.P2P +{ + public class LocalNode : Peer + { + public class Relay { public IInventory Inventory; } + internal class RelayDirectly { public IInventory Inventory; } + internal class SendDirectly { public IInventory Inventory; } + + public const uint ProtocolVersion = 0; + protected override int ConnectedMax => 10; + protected override int UnconnectedMax => 1000; + + private readonly NeoSystem system; + internal readonly ConcurrentDictionary RemoteNodes = new ConcurrentDictionary(); + + public int ConnectedCount => RemoteNodes.Count; + public int UnconnectedCount => UnconnectedPeers.Count; + public static readonly uint Nonce; + public static string UserAgent { get; set; } + + private static LocalNode singleton { get; set; } + public static LocalNode Singleton + { + get + { + while (singleton == null) Thread.Sleep(10); + return singleton; + } + } + + static LocalNode() + { + Random rand = new Random(); + Nonce = (uint)rand.Next(); + UserAgent = $"/{Assembly.GetExecutingAssembly().GetName().Name}:{Assembly.GetExecutingAssembly().GetVersion()}/"; + } + + public LocalNode(NeoSystem system) + { + lock (GetType()) + { + if (singleton != null) + throw new InvalidOperationException(); + this.system = system; + singleton = this; + } + } + + private void BroadcastMessage(string command, ISerializable payload = null) + { + BroadcastMessage(Message.Create(command, payload)); + } + + private void BroadcastMessage(Message message) + { + Connections.Tell(message); + } + + private static IPEndPoint GetIPEndpointFromHostPort(string hostNameOrAddress, int port) + { + if (IPAddress.TryParse(hostNameOrAddress, out IPAddress ipAddress)) + return new IPEndPoint(ipAddress, port); + IPHostEntry entry; + try + { + entry = Dns.GetHostEntry(hostNameOrAddress); + } + catch (SocketException) + { + return null; + } + ipAddress = entry.AddressList.FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork || p.IsIPv6Teredo); + if (ipAddress == null) return null; + return new IPEndPoint(ipAddress, port); + } + + private static IEnumerable GetIPEndPointsFromSeedList(int seedsToTake) + { + if (seedsToTake > 0) + { + Random rand = new Random(); + foreach (string hostAndPort in Settings.Default.SeedList.OrderBy(p => rand.Next())) + { + if (seedsToTake == 0) break; + string[] p = hostAndPort.Split(':'); + IPEndPoint seed; + try + { + seed = GetIPEndpointFromHostPort(p[0], int.Parse(p[1])); + } + catch (AggregateException) + { + continue; + } + if (seed == null) continue; + seedsToTake--; + yield return seed; + } + } + } + + public IEnumerable GetRemoteNodes() + { + return RemoteNodes.Values; + } + + public IEnumerable GetUnconnectedPeers() + { + return UnconnectedPeers; + } + + protected override void NeedMorePeers(int count) + { + count = Math.Max(count, 5); + if (ConnectedPeers.Count > 0) + { + BroadcastMessage("getaddr"); + } + else + { + AddPeers(GetIPEndPointsFromSeedList(count)); + } + } + + protected override void OnReceive(object message) + { + base.OnReceive(message); + switch (message) + { + case Message msg: + BroadcastMessage(msg); + break; + case Relay relay: + OnRelay(relay.Inventory); + break; + case RelayDirectly relay: + OnRelayDirectly(relay.Inventory); + break; + case SendDirectly send: + OnSendDirectly(send.Inventory); + break; + case RelayResultReason _: + break; + } + } + + private void OnRelay(IInventory inventory) + { + if (inventory is Transaction transaction) + system.Consensus?.Tell(transaction); + system.Blockchain.Tell(inventory); + } + + private void OnRelayDirectly(IInventory inventory) + { + Connections.Tell(new RemoteNode.Relay { Inventory = inventory }); + } + + private void OnSendDirectly(IInventory inventory) + { + Connections.Tell(inventory); + } + + public static Props Props(NeoSystem system) + { + return Akka.Actor.Props.Create(() => new LocalNode(system)); + } + + protected override Props ProtocolProps(object connection, IPEndPoint remote, IPEndPoint local) + { + return RemoteNode.Props(system, connection, remote, local); + } + } +} diff --git a/neo/Network/P2P/Message.cs b/neo/Network/P2P/Message.cs new file mode 100644 index 0000000000..fc96e4a54c --- /dev/null +++ b/neo/Network/P2P/Message.cs @@ -0,0 +1,63 @@ +using Neo.Cryptography; +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Network.P2P +{ + public class Message : ISerializable + { + public const int HeaderSize = sizeof(uint) + 12 + sizeof(int) + sizeof(uint); + public const int PayloadMaxSize = 0x02000000; + + public static readonly uint Magic = Settings.Default.Magic; + public string Command; + public uint Checksum; + public byte[] Payload; + + public int Size => HeaderSize + Payload.Length; + + public static Message Create(string command, ISerializable payload = null) + { + return Create(command, payload == null ? new byte[0] : payload.ToArray()); + } + + public static Message Create(string command, byte[] payload) + { + return new Message + { + Command = command, + Checksum = GetChecksum(payload), + Payload = payload + }; + } + + void ISerializable.Deserialize(BinaryReader reader) + { + if (reader.ReadUInt32() != Magic) + throw new FormatException(); + this.Command = reader.ReadFixedString(12); + uint length = reader.ReadUInt32(); + if (length > PayloadMaxSize) + throw new FormatException(); + this.Checksum = reader.ReadUInt32(); + this.Payload = reader.ReadBytes((int)length); + if (GetChecksum(Payload) != Checksum) + throw new FormatException(); + } + + private static uint GetChecksum(byte[] value) + { + return Crypto.Default.Hash256(value).ToUInt32(0); + } + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(Magic); + writer.WriteFixedString(Command, 12); + writer.Write(Payload.Length); + writer.Write(Checksum); + writer.Write(Payload); + } + } +} diff --git a/neo/Network/Payloads/AddrPayload.cs b/neo/Network/P2P/Payloads/AddrPayload.cs similarity index 68% rename from neo/Network/Payloads/AddrPayload.cs rename to neo/Network/P2P/Payloads/AddrPayload.cs index 5c659de4a5..65337f0869 100644 --- a/neo/Network/Payloads/AddrPayload.cs +++ b/neo/Network/P2P/Payloads/AddrPayload.cs @@ -1,10 +1,13 @@ using Neo.IO; +using System; using System.IO; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class AddrPayload : ISerializable { + public const int MaxCountToSend = 200; + public NetworkAddressWithTime[] AddressList; public int Size => AddressList.GetVarSize(); @@ -19,7 +22,9 @@ public static AddrPayload Create(params NetworkAddressWithTime[] addresses) void ISerializable.Deserialize(BinaryReader reader) { - this.AddressList = reader.ReadSerializableArray(200); + AddressList = reader.ReadSerializableArray(MaxCountToSend); + if (AddressList.Length == 0) + throw new FormatException(); } void ISerializable.Serialize(BinaryWriter writer) diff --git a/neo/Core/AssetType.cs b/neo/Network/P2P/Payloads/AssetType.cs similarity index 78% rename from neo/Core/AssetType.cs rename to neo/Network/P2P/Payloads/AssetType.cs index b70f517d26..e6e4560988 100644 --- a/neo/Core/AssetType.cs +++ b/neo/Network/P2P/Payloads/AssetType.cs @@ -1,8 +1,5 @@ -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 资产类别 - /// public enum AssetType : byte { CreditFlag = 0x40, diff --git a/neo/Network/P2P/Payloads/Block.cs b/neo/Network/P2P/Payloads/Block.cs new file mode 100644 index 0000000000..cde786f407 --- /dev/null +++ b/neo/Network/P2P/Payloads/Block.cs @@ -0,0 +1,125 @@ +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Neo.Network.P2P.Payloads +{ + public class Block : BlockBase, IInventory, IEquatable + { + public Transaction[] Transactions; + + private Header _header = null; + public Header Header + { + get + { + if (_header == null) + { + _header = new Header + { + PrevHash = PrevHash, + MerkleRoot = MerkleRoot, + Timestamp = Timestamp, + Index = Index, + ConsensusData = ConsensusData, + NextConsensus = NextConsensus, + Witness = Witness + }; + } + return _header; + } + } + + InventoryType IInventory.InventoryType => InventoryType.Block; + + public override int Size => base.Size + Transactions.GetVarSize(); + + public static Fixed8 CalculateNetFee(IEnumerable transactions) + { + Transaction[] ts = transactions.Where(p => p.Type != TransactionType.MinerTransaction && p.Type != TransactionType.ClaimTransaction).ToArray(); + Fixed8 amount_in = ts.SelectMany(p => p.References.Values.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value); + Fixed8 amount_out = ts.SelectMany(p => p.Outputs.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value); + Fixed8 amount_sysfee = ts.Sum(p => p.SystemFee); + return amount_in - amount_out - amount_sysfee; + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + Transactions = new Transaction[reader.ReadVarInt(0x10000)]; + if (Transactions.Length == 0) throw new FormatException(); + for (int i = 0; i < Transactions.Length; i++) + { + Transactions[i] = Transaction.DeserializeFrom(reader); + if (i == 0) + { + if (Transactions[0].Type != TransactionType.MinerTransaction) + throw new FormatException(); + } + else + { + if (Transactions[i].Type == TransactionType.MinerTransaction) + throw new FormatException(); + } + } + if (MerkleTree.ComputeRoot(Transactions.Select(p => p.Hash).ToArray()) != MerkleRoot) + throw new FormatException(); + } + + public bool Equals(Block other) + { + if (ReferenceEquals(this, other)) return true; + if (other is null) return false; + return Hash.Equals(other.Hash); + } + + public override bool Equals(object obj) + { + return Equals(obj as Block); + } + + public override int GetHashCode() + { + return Hash.GetHashCode(); + } + + public void RebuildMerkleRoot() + { + MerkleRoot = MerkleTree.ComputeRoot(Transactions.Select(p => p.Hash).ToArray()); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Transactions); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["tx"] = Transactions.Select(p => p.ToJson()).ToArray(); + return json; + } + + public TrimmedBlock Trim() + { + return new TrimmedBlock + { + Version = Version, + PrevHash = PrevHash, + MerkleRoot = MerkleRoot, + Timestamp = Timestamp, + Index = Index, + ConsensusData = ConsensusData, + NextConsensus = NextConsensus, + Witness = Witness, + Hashes = Transactions.Select(p => p.Hash).ToArray() + }; + } + } +} diff --git a/neo/Core/BlockBase.cs b/neo/Network/P2P/Payloads/BlockBase.cs similarity index 68% rename from neo/Core/BlockBase.cs rename to neo/Network/P2P/Payloads/BlockBase.cs index 9b64c43a10..ed37394e82 100644 --- a/neo/Core/BlockBase.cs +++ b/neo/Network/P2P/Payloads/BlockBase.cs @@ -1,44 +1,26 @@ using Neo.Cryptography; using Neo.IO; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; +using Neo.SmartContract; using Neo.VM; using Neo.Wallets; using System; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public abstract class BlockBase : IVerifiable { - /// - /// 区块版本 - /// public uint Version; - /// - /// 前一个区块的散列值 - /// public UInt256 PrevHash; - /// - /// 该区块中所有交易的Merkle树的根 - /// public UInt256 MerkleRoot; - /// - /// 时间戳 - /// public uint Timestamp; - /// - /// 区块高度 - /// public uint Index; public ulong ConsensusData; - /// - /// 下一个区块的记账合约的散列值 - /// public UInt160 NextConsensus; - /// - /// 用于验证该区块的脚本 - /// - public Witness Script; + public Witness Witness; private UInt256 _hash = null; public UInt256 Hash @@ -53,26 +35,26 @@ public UInt256 Hash } } - Witness[] IVerifiable.Scripts + Witness[] IVerifiable.Witnesses { get { - return new[] { Script }; + return new[] { Witness }; } set { if (value.Length != 1) throw new ArgumentException(); - Script = value[0]; + Witness = value[0]; } } - public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + sizeof(ulong) + NextConsensus.Size + 1 + Script.Size; + public virtual int Size => sizeof(uint) + PrevHash.Size + MerkleRoot.Size + sizeof(uint) + sizeof(uint) + sizeof(ulong) + NextConsensus.Size + 1 + Witness.Size; public virtual void Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); if (reader.ReadByte() != 1) throw new FormatException(); - Script = reader.ReadSerializable(); + Witness = reader.ReadSerializable(); } void IVerifiable.DeserializeUnsigned(BinaryReader reader) @@ -91,11 +73,11 @@ byte[] IScriptContainer.GetMessage() return this.GetHashData(); } - UInt160[] IVerifiable.GetScriptHashesForVerifying() + UInt160[] IVerifiable.GetScriptHashesForVerifying(Snapshot snapshot) { if (PrevHash == UInt256.Zero) - return new[] { Script.ScriptHash }; - Header prev_header = Blockchain.Default.GetHeader(PrevHash); + return new[] { Witness.ScriptHash }; + Header prev_header = snapshot.GetHeader(PrevHash); if (prev_header == null) throw new InvalidOperationException(); return new UInt160[] { prev_header.NextConsensus }; } @@ -103,7 +85,7 @@ UInt160[] IVerifiable.GetScriptHashesForVerifying() public virtual void Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write((byte)1); writer.Write(Script); + writer.Write((byte)1); writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -128,20 +110,18 @@ public virtual JObject ToJson() json["time"] = Timestamp; json["index"] = Index; json["nonce"] = ConsensusData.ToString("x16"); - json["nextconsensus"] = Wallet.ToAddress(NextConsensus); - json["script"] = Script.ToJson(); + json["nextconsensus"] = NextConsensus.ToAddress(); + json["script"] = Witness.ToJson(); return json; } - public bool Verify() + public virtual bool Verify(Snapshot snapshot) { - if (Hash == Blockchain.GenesisBlock.Hash) return true; - if (Blockchain.Default.ContainsBlock(Hash)) return true; - Header prev_header = Blockchain.Default.GetHeader(PrevHash); + Header prev_header = snapshot.GetHeader(PrevHash); if (prev_header == null) return false; if (prev_header.Index + 1 != Index) return false; if (prev_header.Timestamp >= Timestamp) return false; - if (!this.VerifyScripts()) return false; + if (!this.VerifyWitnesses(snapshot)) return false; return true; } } diff --git a/neo/Core/ClaimTransaction.cs b/neo/Network/P2P/Payloads/ClaimTransaction.cs similarity index 68% rename from neo/Core/ClaimTransaction.cs rename to neo/Network/P2P/Payloads/ClaimTransaction.cs index d544c88d95..e87cba06b8 100644 --- a/neo/Core/ClaimTransaction.cs +++ b/neo/Network/P2P/Payloads/ClaimTransaction.cs @@ -1,11 +1,13 @@ using Neo.IO; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public class ClaimTransaction : Transaction { @@ -20,10 +22,6 @@ public ClaimTransaction() { } - /// - /// 反序列化交易中的额外数据 - /// - /// 数据来源 protected override void DeserializeExclusiveData(BinaryReader reader) { if (Version != 0) throw new FormatException(); @@ -31,16 +29,12 @@ protected override void DeserializeExclusiveData(BinaryReader reader) if (Claims.Length == 0) throw new FormatException(); } - /// - /// 获得需要校验的脚本Hash - /// - /// 返回需要校验的脚本Hash - public override UInt160[] GetScriptHashesForVerifying() + public override UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { - HashSet hashes = new HashSet(base.GetScriptHashesForVerifying()); + HashSet hashes = new HashSet(base.GetScriptHashesForVerifying(snapshot)); foreach (var group in Claims.GroupBy(p => p.PrevHash)) { - Transaction tx = Blockchain.Default.GetTransaction(group.Key); + Transaction tx = snapshot.GetTransaction(group.Key); if (tx == null) throw new InvalidOperationException(); foreach (CoinReference claim in group) { @@ -51,19 +45,11 @@ public override UInt160[] GetScriptHashesForVerifying() return hashes.OrderBy(p => p).ToArray(); } - /// - /// 序列化交易中的额外数据 - /// - /// 存放序列化后的结果 protected override void SerializeExclusiveData(BinaryWriter writer) { writer.Write(Claims); } - /// - /// 变成json对象 - /// - /// 返回json对象 public override JObject ToJson() { JObject json = base.ToJson(); @@ -71,13 +57,9 @@ public override JObject ToJson() return json; } - /// - /// 验证交易 - /// - /// 返回验证结果 - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { - if (!base.Verify(mempool)) return false; + if (!base.Verify(snapshot, mempool)) return false; if (Claims.Length != Claims.Distinct().Count()) return false; if (mempool.OfType().Where(p => p != this).SelectMany(p => p.Claims).Intersect(Claims).Count() > 0) @@ -86,7 +68,7 @@ public override bool Verify(IEnumerable mempool) if (result == null || result.Amount > Fixed8.Zero) return false; try { - return Blockchain.CalculateBonus(Claims, false) == -result.Amount; + return snapshot.CalculateBonus(Claims, false) == -result.Amount; } catch (ArgumentException) { diff --git a/neo/Core/CoinReference.cs b/neo/Network/P2P/Payloads/CoinReference.cs similarity index 57% rename from neo/Core/CoinReference.cs rename to neo/Network/P2P/Payloads/CoinReference.cs index 4145fbe20e..3f148a1617 100644 --- a/neo/Core/CoinReference.cs +++ b/neo/Network/P2P/Payloads/CoinReference.cs @@ -4,20 +4,11 @@ using System; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 交易输入 - /// public class CoinReference : IEquatable, IInteropInterface, ISerializable { - /// - /// 引用交易的散列值 - /// public UInt256 PrevHash; - /// - /// 引用交易输出的索引 - /// public ushort PrevIndex; public int Size => PrevHash.Size + sizeof(ushort); @@ -28,35 +19,21 @@ void ISerializable.Deserialize(BinaryReader reader) PrevIndex = reader.ReadUInt16(); } - /// - /// 比较当前对象与指定对象是否相等 - /// - /// 要比较的对象 - /// 返回对象是否相等 public bool Equals(CoinReference other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; return PrevHash.Equals(other.PrevHash) && PrevIndex.Equals(other.PrevIndex); } - /// - /// 比较当前对象与指定对象是否相等 - /// - /// 要比较的对象 - /// 返回对象是否相等 public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) return true; - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (!(obj is CoinReference)) return false; return Equals((CoinReference)obj); } - /// - /// 获得对象的HashCode - /// - /// 返回对象的HashCode public override int GetHashCode() { return PrevHash.GetHashCode() + PrevIndex.GetHashCode(); @@ -68,10 +45,6 @@ void ISerializable.Serialize(BinaryWriter writer) writer.Write(PrevIndex); } - /// - /// 将交易输入转变为json对象 - /// - /// 返回json对象 public JObject ToJson() { JObject json = new JObject(); diff --git a/neo/Network/Payloads/ConsensusPayload.cs b/neo/Network/P2P/Payloads/ConsensusPayload.cs similarity index 76% rename from neo/Network/Payloads/ConsensusPayload.cs rename to neo/Network/P2P/Payloads/ConsensusPayload.cs index ab79f31d4f..b961d41944 100644 --- a/neo/Network/Payloads/ConsensusPayload.cs +++ b/neo/Network/P2P/Payloads/ConsensusPayload.cs @@ -1,13 +1,13 @@ -using Neo.Core; -using Neo.Cryptography; +using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; +using Neo.Persistence; using Neo.SmartContract; using Neo.VM; using System; using System.IO; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class ConsensusPayload : IInventory { @@ -17,7 +17,7 @@ public class ConsensusPayload : IInventory public ushort ValidatorIndex; public uint Timestamp; public byte[] Data; - public Witness Script; + public Witness Witness; private UInt256 _hash = null; UInt256 IInventory.Hash @@ -34,26 +34,26 @@ UInt256 IInventory.Hash InventoryType IInventory.InventoryType => InventoryType.Consensus; - Witness[] IVerifiable.Scripts + Witness[] IVerifiable.Witnesses { get { - return new[] { Script }; + return new[] { Witness }; } set { if (value.Length != 1) throw new ArgumentException(); - Script = value[0]; + Witness = value[0]; } } - public int Size => sizeof(uint) + PrevHash.Size + sizeof(uint) + sizeof(ushort) + sizeof(uint) + Data.GetVarSize() + 1 + Script.Size; + public int Size => sizeof(uint) + PrevHash.Size + sizeof(uint) + sizeof(ushort) + sizeof(uint) + Data.GetVarSize() + 1 + Witness.Size; void ISerializable.Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); if (reader.ReadByte() != 1) throw new FormatException(); - Script = reader.ReadSerializable(); + Witness = reader.ReadSerializable(); } void IVerifiable.DeserializeUnsigned(BinaryReader reader) @@ -71,11 +71,9 @@ byte[] IScriptContainer.GetMessage() return this.GetHashData(); } - UInt160[] IVerifiable.GetScriptHashesForVerifying() + UInt160[] IVerifiable.GetScriptHashesForVerifying(Snapshot snapshot) { - if (Blockchain.Default == null) - throw new InvalidOperationException(); - ECPoint[] validators = Blockchain.Default.GetValidators(); + ECPoint[] validators = snapshot.GetValidators(); if (validators.Length <= ValidatorIndex) throw new InvalidOperationException(); return new[] { Contract.CreateSignatureRedeemScript(validators[ValidatorIndex]).ToScriptHash() }; @@ -84,7 +82,7 @@ UInt160[] IVerifiable.GetScriptHashesForVerifying() void ISerializable.Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write((byte)1); writer.Write(Script); + writer.Write((byte)1); writer.Write(Witness); } void IVerifiable.SerializeUnsigned(BinaryWriter writer) @@ -97,12 +95,11 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer) writer.WriteVarBytes(Data); } - public bool Verify() + public bool Verify(Snapshot snapshot) { - if (Blockchain.Default == null) return false; - if (BlockIndex <= Blockchain.Default.Height) + if (BlockIndex <= snapshot.Height) return false; - return this.VerifyScripts(); + return this.VerifyWitnesses(snapshot); } } } diff --git a/neo/Core/ContractTransaction.cs b/neo/Network/P2P/Payloads/ContractTransaction.cs similarity index 77% rename from neo/Core/ContractTransaction.cs rename to neo/Network/P2P/Payloads/ContractTransaction.cs index 252976fd69..241795acb6 100644 --- a/neo/Core/ContractTransaction.cs +++ b/neo/Network/P2P/Payloads/ContractTransaction.cs @@ -1,11 +1,8 @@ using System; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 合约交易,这是最常用的一种交易 - /// public class ContractTransaction : Transaction { public ContractTransaction() diff --git a/neo/Core/EnrollmentTransaction.cs b/neo/Network/P2P/Payloads/EnrollmentTransaction.cs similarity index 61% rename from neo/Core/EnrollmentTransaction.cs rename to neo/Network/P2P/Payloads/EnrollmentTransaction.cs index 89af83b426..0cdfaeb381 100644 --- a/neo/Core/EnrollmentTransaction.cs +++ b/neo/Network/P2P/Payloads/EnrollmentTransaction.cs @@ -1,20 +1,18 @@ using Neo.Cryptography.ECC; using Neo.IO; using Neo.IO.Json; +using Neo.Persistence; using Neo.SmartContract; using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { [Obsolete] public class EnrollmentTransaction : Transaction { - /// - /// 记账人的公钥 - /// public ECPoint PublicKey; private UInt160 _script_hash = null; @@ -37,38 +35,22 @@ public EnrollmentTransaction() { } - /// - /// 序列化交易中的额外数据 - /// - /// 数据来源 protected override void DeserializeExclusiveData(BinaryReader reader) { if (Version != 0) throw new FormatException(); PublicKey = ECPoint.DeserializeFrom(reader, ECCurve.Secp256r1); } - /// - /// 获取需要校验的脚本Hash - /// - /// 返回需要校验的脚本Hash - public override UInt160[] GetScriptHashesForVerifying() + public override UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { - return base.GetScriptHashesForVerifying().Union(new UInt160[] { ScriptHash }).OrderBy(p => p).ToArray(); + return base.GetScriptHashesForVerifying(snapshot).Union(new UInt160[] { ScriptHash }).OrderBy(p => p).ToArray(); } - /// - /// 序列化交易中的额外数据 - /// - /// 存放序列化后的结果 protected override void SerializeExclusiveData(BinaryWriter writer) { writer.Write(PublicKey); } - /// - /// 变成json对象 - /// - /// 返回json对象 public override JObject ToJson() { JObject json = base.ToJson(); @@ -76,7 +58,7 @@ public override JObject ToJson() return json; } - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { return false; } diff --git a/neo/Network/Payloads/FilterAddPayload.cs b/neo/Network/P2P/Payloads/FilterAddPayload.cs similarity index 92% rename from neo/Network/Payloads/FilterAddPayload.cs rename to neo/Network/P2P/Payloads/FilterAddPayload.cs index 661170feda..71bddea5dc 100644 --- a/neo/Network/Payloads/FilterAddPayload.cs +++ b/neo/Network/P2P/Payloads/FilterAddPayload.cs @@ -1,7 +1,7 @@ using Neo.IO; using System.IO; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class FilterAddPayload : ISerializable { diff --git a/neo/Network/Payloads/FilterLoadPayload.cs b/neo/Network/P2P/Payloads/FilterLoadPayload.cs similarity index 96% rename from neo/Network/Payloads/FilterLoadPayload.cs rename to neo/Network/P2P/Payloads/FilterLoadPayload.cs index 11c33cef8e..5d337c8dac 100644 --- a/neo/Network/Payloads/FilterLoadPayload.cs +++ b/neo/Network/P2P/Payloads/FilterLoadPayload.cs @@ -3,7 +3,7 @@ using System; using System.IO; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class FilterLoadPayload : ISerializable { diff --git a/neo/Network/Payloads/GetBlocksPayload.cs b/neo/Network/P2P/Payloads/GetBlocksPayload.cs similarity index 96% rename from neo/Network/Payloads/GetBlocksPayload.cs rename to neo/Network/P2P/Payloads/GetBlocksPayload.cs index 9f281c6f70..af3bfb7238 100644 --- a/neo/Network/Payloads/GetBlocksPayload.cs +++ b/neo/Network/P2P/Payloads/GetBlocksPayload.cs @@ -1,7 +1,7 @@ using Neo.IO; using System.IO; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class GetBlocksPayload : ISerializable { diff --git a/neo/Core/Header.cs b/neo/Network/P2P/Payloads/Header.cs similarity index 61% rename from neo/Core/Header.cs rename to neo/Network/P2P/Payloads/Header.cs index 096707fd82..b1c73bb52b 100644 --- a/neo/Core/Header.cs +++ b/neo/Network/P2P/Payloads/Header.cs @@ -1,9 +1,8 @@ -using Neo.IO; -using Neo.VM; +using Neo.Ledger; using System; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public class Header : BlockBase, IEquatable
{ @@ -17,7 +16,7 @@ public override void Deserialize(BinaryReader reader) public bool Equals(Header other) { - if (ReferenceEquals(other, null)) return false; + if (other is null) return false; if (ReferenceEquals(other, this)) return true; return Hash.Equals(other.Hash); } @@ -27,18 +26,6 @@ public override bool Equals(object obj) return Equals(obj as Header); } - public static Header FromTrimmedData(byte[] data, int index) - { - Header header = new Header(); - using (MemoryStream ms = new MemoryStream(data, index, data.Length - index, false)) - using (BinaryReader reader = new BinaryReader(ms)) - { - ((IVerifiable)header).DeserializeUnsigned(reader); - reader.ReadByte(); header.Script = reader.ReadSerializable(); - } - return header; - } - public override int GetHashCode() { return Hash.GetHashCode(); @@ -49,5 +36,21 @@ public override void Serialize(BinaryWriter writer) base.Serialize(writer); writer.Write((byte)0); } + + public TrimmedBlock Trim() + { + return new TrimmedBlock + { + Version = Version, + PrevHash = PrevHash, + MerkleRoot = MerkleRoot, + Timestamp = Timestamp, + Index = Index, + ConsensusData = ConsensusData, + NextConsensus = NextConsensus, + Witness = Witness, + Hashes = new UInt256[0] + }; + } } } diff --git a/neo/Network/Payloads/HeadersPayload.cs b/neo/Network/P2P/Payloads/HeadersPayload.cs similarity index 77% rename from neo/Network/Payloads/HeadersPayload.cs rename to neo/Network/P2P/Payloads/HeadersPayload.cs index c4c03fe34e..54948a142d 100644 --- a/neo/Network/Payloads/HeadersPayload.cs +++ b/neo/Network/P2P/Payloads/HeadersPayload.cs @@ -1,13 +1,14 @@ -using Neo.Core; -using Neo.IO; +using Neo.IO; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class HeadersPayload : ISerializable { + public const int MaxHeadersCount = 2000; + public Header[] Headers; public int Size => Headers.GetVarSize(); @@ -22,7 +23,7 @@ public static HeadersPayload Create(IEnumerable
headers) void ISerializable.Deserialize(BinaryReader reader) { - Headers = reader.ReadSerializableArray
(2000); + Headers = reader.ReadSerializableArray
(MaxHeadersCount); } void ISerializable.Serialize(BinaryWriter writer) diff --git a/neo/Network/IInventory.cs b/neo/Network/P2P/Payloads/IInventory.cs similarity index 58% rename from neo/Network/IInventory.cs rename to neo/Network/P2P/Payloads/IInventory.cs index c4784cdc8c..dcf3114a8b 100644 --- a/neo/Network/IInventory.cs +++ b/neo/Network/P2P/Payloads/IInventory.cs @@ -1,6 +1,6 @@ -using Neo.Core; +using Neo.Persistence; -namespace Neo.Network +namespace Neo.Network.P2P.Payloads { public interface IInventory : IVerifiable { @@ -8,6 +8,6 @@ public interface IInventory : IVerifiable InventoryType InventoryType { get; } - bool Verify(); + bool Verify(Snapshot snapshot); } } diff --git a/neo/Network/P2P/Payloads/IVerifiable.cs b/neo/Network/P2P/Payloads/IVerifiable.cs new file mode 100644 index 0000000000..d70acb3e32 --- /dev/null +++ b/neo/Network/P2P/Payloads/IVerifiable.cs @@ -0,0 +1,18 @@ +using Neo.IO; +using Neo.Persistence; +using Neo.VM; +using System.IO; + +namespace Neo.Network.P2P.Payloads +{ + public interface IVerifiable : ISerializable, IScriptContainer + { + Witness[] Witnesses { get; set; } + + void DeserializeUnsigned(BinaryReader reader); + + UInt160[] GetScriptHashesForVerifying(Snapshot snapshot); + + void SerializeUnsigned(BinaryWriter writer); + } +} diff --git a/neo/Network/Payloads/InvPayload.cs b/neo/Network/P2P/Payloads/InvPayload.cs similarity index 59% rename from neo/Network/Payloads/InvPayload.cs rename to neo/Network/P2P/Payloads/InvPayload.cs index f862845c14..a3bc82a4d9 100644 --- a/neo/Network/Payloads/InvPayload.cs +++ b/neo/Network/P2P/Payloads/InvPayload.cs @@ -1,11 +1,15 @@ using Neo.IO; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class InvPayload : ISerializable { + public const int MaxHashesCount = 500; + public InventoryType Type; public UInt256[] Hashes; @@ -20,12 +24,22 @@ public static InvPayload Create(InventoryType type, params UInt256[] hashes) }; } + public static IEnumerable CreateGroup(InventoryType type, UInt256[] hashes) + { + for (int i = 0; i < hashes.Length; i += MaxHashesCount) + yield return new InvPayload + { + Type = type, + Hashes = hashes.Skip(i).Take(MaxHashesCount).ToArray() + }; + } + void ISerializable.Deserialize(BinaryReader reader) { Type = (InventoryType)reader.ReadByte(); if (!Enum.IsDefined(typeof(InventoryType), Type)) throw new FormatException(); - Hashes = reader.ReadSerializableArray(); + Hashes = reader.ReadSerializableArray(MaxHashesCount); } void ISerializable.Serialize(BinaryWriter writer) diff --git a/neo/Network/P2P/Payloads/InventoryType.cs b/neo/Network/P2P/Payloads/InventoryType.cs new file mode 100644 index 0000000000..58ad12471b --- /dev/null +++ b/neo/Network/P2P/Payloads/InventoryType.cs @@ -0,0 +1,9 @@ +namespace Neo.Network.P2P.Payloads +{ + public enum InventoryType : byte + { + TX = 0x01, + Block = 0x02, + Consensus = 0xe0 + } +} diff --git a/neo/Core/InvocationTransaction.cs b/neo/Network/P2P/Payloads/InvocationTransaction.cs similarity index 89% rename from neo/Core/InvocationTransaction.cs rename to neo/Network/P2P/Payloads/InvocationTransaction.cs index be1d63c27f..38880a4a58 100644 --- a/neo/Core/InvocationTransaction.cs +++ b/neo/Network/P2P/Payloads/InvocationTransaction.cs @@ -1,10 +1,11 @@ using Neo.IO; using Neo.IO.Json; +using Neo.Persistence; using System; using System.Collections.Generic; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public class InvocationTransaction : Transaction { @@ -58,10 +59,10 @@ public override JObject ToJson() return json; } - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { if (Gas.GetData() % 100000000 != 0) return false; - return base.Verify(mempool); + return base.Verify(snapshot, mempool); } } } diff --git a/neo/Core/IssueTransaction.cs b/neo/Network/P2P/Payloads/IssueTransaction.cs similarity index 68% rename from neo/Core/IssueTransaction.cs rename to neo/Network/P2P/Payloads/IssueTransaction.cs index 60e53c8f2e..841a50c5e5 100644 --- a/neo/Core/IssueTransaction.cs +++ b/neo/Network/P2P/Payloads/IssueTransaction.cs @@ -1,18 +1,14 @@ -using System; +using Neo.Ledger; +using Neo.Persistence; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 用于分发资产的特殊交易 - /// public class IssueTransaction : Transaction { - /// - /// 系统费用 - /// public override Fixed8 SystemFee { get @@ -34,34 +30,26 @@ protected override void DeserializeExclusiveData(BinaryReader reader) if (Version > 1) throw new FormatException(); } - /// - /// 获取需要校验的脚本散列值 - /// - /// 返回需要校验的脚本散列值 - public override UInt160[] GetScriptHashesForVerifying() + public override UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { - HashSet hashes = new HashSet(base.GetScriptHashesForVerifying()); + HashSet hashes = new HashSet(base.GetScriptHashesForVerifying(snapshot)); foreach (TransactionResult result in GetTransactionResults().Where(p => p.Amount < Fixed8.Zero)) { - AssetState asset = Blockchain.Default.GetAssetState(result.AssetId); + AssetState asset = snapshot.Assets.TryGet(result.AssetId); if (asset == null) throw new InvalidOperationException(); hashes.Add(asset.Issuer); } return hashes.OrderBy(p => p).ToArray(); } - /// - /// 验证交易 - /// - /// 返回验证后的结果 - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { - if (!base.Verify(mempool)) return false; + if (!base.Verify(snapshot, mempool)) return false; TransactionResult[] results = GetTransactionResults()?.Where(p => p.Amount < Fixed8.Zero).ToArray(); if (results == null) return false; foreach (TransactionResult r in results) { - AssetState asset = Blockchain.Default.GetAssetState(r.AssetId); + AssetState asset = snapshot.Assets.TryGet(r.AssetId); if (asset == null) return false; if (asset.Amount < Fixed8.Zero) continue; Fixed8 quantity_issued = asset.Available + mempool.OfType().Where(p => p != this).SelectMany(p => p.Outputs).Where(p => p.AssetId == r.AssetId).Sum(p => p.Value); diff --git a/neo/Network/Payloads/MerkleBlockPayload.cs b/neo/Network/P2P/Payloads/MerkleBlockPayload.cs similarity index 93% rename from neo/Network/Payloads/MerkleBlockPayload.cs rename to neo/Network/P2P/Payloads/MerkleBlockPayload.cs index cb5884f338..df1c9a17fa 100644 --- a/neo/Network/Payloads/MerkleBlockPayload.cs +++ b/neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -1,11 +1,10 @@ -using Neo.Core; -using Neo.Cryptography; +using Neo.Cryptography; using Neo.IO; using System.Collections; using System.IO; using System.Linq; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class MerkleBlockPayload : BlockBase { @@ -30,7 +29,7 @@ public static MerkleBlockPayload Create(Block block, BitArray flags) Index = block.Index, ConsensusData = block.ConsensusData, NextConsensus = block.NextConsensus, - Script = block.Script, + Witness = block.Witness, TxCount = block.Transactions.Length, Hashes = tree.ToHashArray(), Flags = buffer diff --git a/neo/Core/MinerTransaction.cs b/neo/Network/P2P/Payloads/MinerTransaction.cs similarity index 67% rename from neo/Core/MinerTransaction.cs rename to neo/Network/P2P/Payloads/MinerTransaction.cs index 78cdd8b4a4..e4fcc8a51e 100644 --- a/neo/Core/MinerTransaction.cs +++ b/neo/Network/P2P/Payloads/MinerTransaction.cs @@ -1,18 +1,13 @@ using Neo.IO.Json; +using Neo.Ledger; using System; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 用于分配字节费的特殊交易 - /// public class MinerTransaction : Transaction { - /// - /// 随机数 - /// public uint Nonce; public override Fixed8 NetworkFee => Fixed8.Zero; @@ -24,19 +19,12 @@ public MinerTransaction() { } - /// - /// 反序列化交易中的额外数据 - /// - /// 数据来源 protected override void DeserializeExclusiveData(BinaryReader reader) { if (Version != 0) throw new FormatException(); this.Nonce = reader.ReadUInt32(); } - /// - /// 反序列化进行完毕时触发 - /// protected override void OnDeserialized() { base.OnDeserialized(); @@ -46,10 +34,6 @@ protected override void OnDeserialized() throw new FormatException(); } - /// - /// 序列化交易中的额外数据 - /// - /// 存放序列化后的结果 protected override void SerializeExclusiveData(BinaryWriter writer) { writer.Write(Nonce); diff --git a/neo/Network/Payloads/NetworkAddressWithTime.cs b/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs similarity index 94% rename from neo/Network/Payloads/NetworkAddressWithTime.cs rename to neo/Network/P2P/Payloads/NetworkAddressWithTime.cs index bfc4f5958d..32b995cba6 100644 --- a/neo/Network/Payloads/NetworkAddressWithTime.cs +++ b/neo/Network/P2P/Payloads/NetworkAddressWithTime.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Net; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class NetworkAddressWithTime : ISerializable { @@ -32,7 +32,7 @@ void ISerializable.Deserialize(BinaryReader reader) Services = reader.ReadUInt64(); byte[] data = reader.ReadBytes(16); if (data.Length != 16) throw new FormatException(); - IPAddress address = new IPAddress(data); + IPAddress address = new IPAddress(data).Unmap(); data = reader.ReadBytes(2); if (data.Length != 2) throw new FormatException(); ushort port = data.Reverse().ToArray().ToUInt16(0); diff --git a/neo/Core/PublishTransaction.cs b/neo/Network/P2P/Payloads/PublishTransaction.cs similarity index 95% rename from neo/Core/PublishTransaction.cs rename to neo/Network/P2P/Payloads/PublishTransaction.cs index 3826cd3088..1a97f4c551 100644 --- a/neo/Core/PublishTransaction.cs +++ b/neo/Network/P2P/Payloads/PublishTransaction.cs @@ -1,12 +1,13 @@ using Neo.IO; using Neo.IO.Json; +using Neo.Persistence; using Neo.SmartContract; using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { [Obsolete] public class PublishTransaction : Transaction @@ -89,7 +90,7 @@ public override JObject ToJson() return json; } - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { return false; } diff --git a/neo/Core/RegisterTransaction.cs b/neo/Network/P2P/Payloads/RegisterTransaction.cs similarity index 63% rename from neo/Core/RegisterTransaction.cs rename to neo/Network/P2P/Payloads/RegisterTransaction.cs index 667df17b1f..5995210d0c 100644 --- a/neo/Core/RegisterTransaction.cs +++ b/neo/Network/P2P/Payloads/RegisterTransaction.cs @@ -1,6 +1,8 @@ using Neo.Cryptography.ECC; using Neo.IO; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; using Neo.SmartContract; using Neo.Wallets; using System; @@ -8,40 +10,20 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { [Obsolete] public class RegisterTransaction : Transaction { - /// - /// 资产类别 - /// public AssetType AssetType; - /// - /// 资产名称 - /// public string Name; - /// - /// 发行总量,共有2种模式: - /// 1. 限量模式:当Amount为正数时,表示当前资产的最大总量为Amount,且不可修改(股权在未来可能会支持扩股或增发,会考虑需要公司签名或一定比例的股东签名认可)。 - /// 2. 不限量模式:当Amount等于-1时,表示当前资产可以由创建者无限量发行。这种模式的自由度最大,但是公信力最低,不建议使用。 - /// public Fixed8 Amount; public byte Precision; - /// - /// 发行者的公钥 - /// public ECPoint Owner; - /// - /// 资产管理员的合约散列值 - /// public UInt160 Admin; public override int Size => base.Size + sizeof(AssetType) + Name.GetVarSize() + Amount.Size + sizeof(byte) + Owner.Size + Admin.Size; - /// - /// 系统费用 - /// public override Fixed8 SystemFee { get @@ -57,10 +39,6 @@ public RegisterTransaction() { } - /// - /// 反序列化交易中额外的数据 - /// - /// 数据来源 protected override void DeserializeExclusiveData(BinaryReader reader) { if (Version != 0) throw new FormatException(); @@ -74,14 +52,10 @@ protected override void DeserializeExclusiveData(BinaryReader reader) Admin = reader.ReadSerializable(); } - /// - /// 获取需要校验的脚本Hash值 - /// - /// 返回需要校验的脚本Hash值 - public override UInt160[] GetScriptHashesForVerifying() + public override UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { UInt160 owner = Contract.CreateSignatureRedeemScript(Owner).ToScriptHash(); - return base.GetScriptHashesForVerifying().Union(new[] { owner }).OrderBy(p => p).ToArray(); + return base.GetScriptHashesForVerifying(snapshot).Union(new[] { owner }).OrderBy(p => p).ToArray(); } protected override void OnDeserialized() @@ -93,10 +67,6 @@ protected override void OnDeserialized() throw new FormatException(); } - /// - /// 序列化交易中额外的数据 - /// - /// 存放序列化后的结果 protected override void SerializeExclusiveData(BinaryWriter writer) { writer.Write((byte)AssetType); @@ -107,10 +77,6 @@ protected override void SerializeExclusiveData(BinaryWriter writer) writer.Write(Admin); } - /// - /// 变成json对象 - /// - /// 返回json对象 public override JObject ToJson() { JObject json = base.ToJson(); @@ -127,11 +93,11 @@ public override JObject ToJson() json["asset"]["amount"] = Amount.ToString(); json["asset"]["precision"] = Precision; json["asset"]["owner"] = Owner.ToString(); - json["asset"]["admin"] = Wallet.ToAddress(Admin); + json["asset"]["admin"] = Admin.ToAddress(); return json; } - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { return false; } diff --git a/neo/Core/StateDescriptor.cs b/neo/Network/P2P/Payloads/StateDescriptor.cs similarity index 88% rename from neo/Core/StateDescriptor.cs rename to neo/Network/P2P/Payloads/StateDescriptor.cs index 70623423ef..7fbcb7a9ad 100644 --- a/neo/Core/StateDescriptor.cs +++ b/neo/Network/P2P/Payloads/StateDescriptor.cs @@ -1,13 +1,14 @@ using Neo.Cryptography.ECC; using Neo.IO; -using Neo.IO.Caching; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Persistence; using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public class StateDescriptor : ISerializable { @@ -95,12 +96,12 @@ public JObject ToJson() return json; } - internal bool Verify() + internal bool Verify(Snapshot snapshot) { switch (Type) { case StateType.Account: - return VerifyAccountState(); + return VerifyAccountState(snapshot); case StateType.Validator: return VerifyValidatorState(); default: @@ -108,12 +109,11 @@ internal bool Verify() } } - private bool VerifyAccountState() + private bool VerifyAccountState(Snapshot snapshot) { switch (Field) { case "Votes": - if (Blockchain.Default == null) return false; ECPoint[] pubkeys; try { @@ -124,15 +124,14 @@ private bool VerifyAccountState() return false; } UInt160 hash = new UInt160(Key); - AccountState account = Blockchain.Default.GetAccountState(hash); + AccountState account = snapshot.Accounts.TryGet(hash); if (account?.IsFrozen != false) return false; if (pubkeys.Length > 0) { if (account.GetBalance(Blockchain.GoverningToken.Hash).Equals(Fixed8.Zero)) return false; HashSet sv = new HashSet(Blockchain.StandbyValidators); - DataCache validators = Blockchain.Default.GetStates(); foreach (ECPoint pubkey in pubkeys) - if (!sv.Contains(pubkey) && validators.TryGet(pubkey)?.Registered != true) + if (!sv.Contains(pubkey) && snapshot.Validators.TryGet(pubkey)?.Registered != true) return false; } return true; diff --git a/neo/Core/StateTransaction.cs b/neo/Network/P2P/Payloads/StateTransaction.cs similarity index 88% rename from neo/Core/StateTransaction.cs rename to neo/Network/P2P/Payloads/StateTransaction.cs index 1fc996d351..aed1f06547 100644 --- a/neo/Core/StateTransaction.cs +++ b/neo/Network/P2P/Payloads/StateTransaction.cs @@ -1,13 +1,14 @@ using Neo.Cryptography.ECC; using Neo.IO; using Neo.IO.Json; +using Neo.Persistence; using Neo.SmartContract; using System; using System.Collections.Generic; using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public class StateTransaction : Transaction { @@ -26,9 +27,9 @@ protected override void DeserializeExclusiveData(BinaryReader reader) Descriptors = reader.ReadSerializableArray(16); } - public override UInt160[] GetScriptHashesForVerifying() + public override UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { - HashSet hashes = new HashSet(base.GetScriptHashesForVerifying()); + HashSet hashes = new HashSet(base.GetScriptHashesForVerifying(snapshot)); foreach (StateDescriptor descriptor in Descriptors) { switch (descriptor.Type) @@ -82,12 +83,12 @@ public override JObject ToJson() return json; } - public override bool Verify(IEnumerable mempool) + public override bool Verify(Snapshot snapshot, IEnumerable mempool) { foreach (StateDescriptor descriptor in Descriptors) - if (!descriptor.Verify()) + if (!descriptor.Verify(snapshot)) return false; - return base.Verify(mempool); + return base.Verify(snapshot, mempool); } } } diff --git a/neo/Core/StateType.cs b/neo/Network/P2P/Payloads/StateType.cs similarity index 72% rename from neo/Core/StateType.cs rename to neo/Network/P2P/Payloads/StateType.cs index e49cad623a..82874076b1 100644 --- a/neo/Core/StateType.cs +++ b/neo/Network/P2P/Payloads/StateType.cs @@ -1,4 +1,4 @@ -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public enum StateType : byte { diff --git a/neo/Core/Transaction.cs b/neo/Network/P2P/Payloads/Transaction.cs similarity index 72% rename from neo/Core/Transaction.cs rename to neo/Network/P2P/Payloads/Transaction.cs index 5fce0a3144..9c1e4d49b6 100644 --- a/neo/Core/Transaction.cs +++ b/neo/Network/P2P/Payloads/Transaction.cs @@ -2,7 +2,9 @@ using Neo.IO; using Neo.IO.Caching; using Neo.IO.Json; -using Neo.Network; +using Neo.Ledger; +using Neo.Persistence; +using Neo.SmartContract; using Neo.VM; using System; using System.Collections.Generic; @@ -10,11 +12,8 @@ using System.Linq; using System.Text; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 一切交易的基类 - /// public abstract class Transaction : IEquatable, IInventory { /// @@ -27,30 +26,12 @@ public abstract class Transaction : IEquatable, IInventory /// private static ReflectionCache ReflectionCache = ReflectionCache.CreateFromEnum(); - /// - /// 交易类型 - /// public readonly TransactionType Type; - /// - /// 版本 - /// public byte Version; - /// - /// 该交易所具备的额外特性 - /// public TransactionAttribute[] Attributes; - /// - /// 输入列表 - /// public CoinReference[] Inputs; - /// - /// 输出列表 - /// public TransactionOutput[] Outputs; - /// - /// 用于验证该交易的脚本列表 - /// - public Witness[] Scripts { get; set; } + public Witness[] Witnesses { get; set; } private UInt256 _hash = null; public UInt256 Hash @@ -65,9 +46,6 @@ public UInt256 Hash } } - /// - /// 清单类型 - /// InventoryType IInventory.InventoryType => InventoryType.TX; private Fixed8 _network_fee = -Fixed8.Satoshi; @@ -86,9 +64,6 @@ public virtual Fixed8 NetworkFee } private IReadOnlyDictionary _references; - /// - /// 每一个交易输入所引用的交易输出 - /// public IReadOnlyDictionary References { get @@ -98,7 +73,7 @@ public IReadOnlyDictionary References Dictionary dictionary = new Dictionary(); foreach (var group in Inputs.GroupBy(p => p.PrevHash)) { - Transaction tx = Blockchain.Default.GetTransaction(group.Key); + Transaction tx = Blockchain.Singleton.Store.GetTransaction(group.Key); if (tx == null) return null; foreach (var reference in group.Select(p => new { @@ -115,47 +90,26 @@ public IReadOnlyDictionary References } } - public virtual int Size => sizeof(TransactionType) + sizeof(byte) + Attributes.GetVarSize() + Inputs.GetVarSize() + Outputs.GetVarSize() + Scripts.GetVarSize(); + public virtual int Size => sizeof(TransactionType) + sizeof(byte) + Attributes.GetVarSize() + Inputs.GetVarSize() + Outputs.GetVarSize() + Witnesses.GetVarSize(); - /// - /// 系统费用 - /// public virtual Fixed8 SystemFee => Settings.Default.SystemFee.TryGetValue(Type, out Fixed8 fee) ? fee : Fixed8.Zero; - /// - /// 用指定的类型初始化Transaction对象 - /// - /// 交易类型 protected Transaction(TransactionType type) { this.Type = type; } - /// - /// 反序列化 - /// - /// 数据来源 void ISerializable.Deserialize(BinaryReader reader) { ((IVerifiable)this).DeserializeUnsigned(reader); - Scripts = reader.ReadSerializableArray(); + Witnesses = reader.ReadSerializableArray(); OnDeserialized(); } - /// - /// 反序列化交易中的额外数据 - /// - /// 数据来源 protected virtual void DeserializeExclusiveData(BinaryReader reader) { } - /// - /// 从指定的字节数组反序列化一笔交易 - /// - /// 字节数组 - /// 偏移量,反序列化从该偏移量处开始 - /// 返回反序列化后的结果 public static Transaction DeserializeFrom(byte[] value, int offset = 0) { using (MemoryStream ms = new MemoryStream(value, offset, value.Length - offset, false)) @@ -165,11 +119,6 @@ public static Transaction DeserializeFrom(byte[] value, int offset = 0) } } - /// - /// 反序列化 - /// - /// 数据来源 - /// 返回反序列化后的结果 internal static Transaction DeserializeFrom(BinaryReader reader) { // Looking for type in reflection cache @@ -177,7 +126,7 @@ internal static Transaction DeserializeFrom(BinaryReader reader) if (transaction == null) throw new FormatException(); transaction.DeserializeUnsignedWithoutType(reader); - transaction.Scripts = reader.ReadSerializableArray(); + transaction.Witnesses = reader.ReadSerializableArray(); transaction.OnDeserialized(); return transaction; } @@ -200,7 +149,7 @@ private void DeserializeUnsignedWithoutType(BinaryReader reader) public bool Equals(Transaction other) { - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; if (ReferenceEquals(this, other)) return true; return Hash.Equals(other.Hash); } @@ -220,18 +169,14 @@ byte[] IScriptContainer.GetMessage() return this.GetHashData(); } - /// - /// 获取需要校验的脚本散列值 - /// - /// 返回需要校验的脚本散列值 - public virtual UInt160[] GetScriptHashesForVerifying() + public virtual UInt160[] GetScriptHashesForVerifying(Snapshot snapshot) { if (References == null) throw new InvalidOperationException(); HashSet hashes = new HashSet(Inputs.Select(p => References[p].ScriptHash)); hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Script).Select(p => new UInt160(p.Data))); foreach (var group in Outputs.GroupBy(p => p.AssetId)) { - AssetState asset = Blockchain.Default.GetAssetState(group.Key); + AssetState asset = snapshot.Assets.TryGet(group.Key); if (asset == null) throw new InvalidOperationException(); if (asset.AssetType.HasFlag(AssetType.DutyFlag)) { @@ -241,20 +186,16 @@ public virtual UInt160[] GetScriptHashesForVerifying() return hashes.OrderBy(p => p).ToArray(); } - /// - /// 获取交易后各资产的变化量 - /// - /// 返回交易后各资产的变化量 public IEnumerable GetTransactionResults() { if (References == null) return null; return References.Values.Select(p => new { - AssetId = p.AssetId, - Value = p.Value + p.AssetId, + p.Value }).Concat(Outputs.Select(p => new { - AssetId = p.AssetId, + p.AssetId, Value = -p.Value })).GroupBy(p => p.AssetId, (k, g) => new TransactionResult { @@ -263,27 +204,16 @@ public IEnumerable GetTransactionResults() }).Where(p => p.Amount != Fixed8.Zero); } - /// - /// 通知子类反序列化完毕 - /// protected virtual void OnDeserialized() { } - /// - /// 序列化 - /// - /// 存放序列化后的结果 void ISerializable.Serialize(BinaryWriter writer) { ((IVerifiable)this).SerializeUnsigned(writer); - writer.Write(Scripts); + writer.Write(Witnesses); } - /// - /// 序列化交易中的额外数据 - /// - /// 存放序列化后的结果 protected virtual void SerializeExclusiveData(BinaryWriter writer) { } @@ -298,10 +228,6 @@ void IVerifiable.SerializeUnsigned(BinaryWriter writer) writer.Write(Outputs); } - /// - /// 变成json对象 - /// - /// 返回json对象 public virtual JObject ToJson() { JObject json = new JObject(); @@ -314,20 +240,16 @@ public virtual JObject ToJson() json["vout"] = Outputs.Select((p, i) => p.ToJson((ushort)i)).ToArray(); json["sys_fee"] = SystemFee.ToString(); json["net_fee"] = NetworkFee.ToString(); - json["scripts"] = Scripts.Select(p => p.ToJson()).ToArray(); + json["scripts"] = Witnesses.Select(p => p.ToJson()).ToArray(); return json; } - bool IInventory.Verify() + bool IInventory.Verify(Snapshot snapshot) { - return Verify(Enumerable.Empty()); + return Verify(snapshot, Enumerable.Empty()); } - /// - /// 验证交易 - /// - /// 返回验证的结果 - public virtual bool Verify(IEnumerable mempool) + public virtual bool Verify(Snapshot snapshot, IEnumerable mempool) { for (int i = 1; i < Inputs.Length; i++) for (int j = 0; j < i; j++) @@ -335,13 +257,13 @@ public virtual bool Verify(IEnumerable mempool) return false; if (mempool.Where(p => p != this).SelectMany(p => p.Inputs).Intersect(Inputs).Count() > 0) return false; - if (Blockchain.Default.IsDoubleSpend(this)) + if (snapshot.IsDoubleSpend(this)) return false; foreach (var group in Outputs.GroupBy(p => p.AssetId)) { - AssetState asset = Blockchain.Default.GetAssetState(group.Key); + AssetState asset = snapshot.Assets.TryGet(group.Key); if (asset == null) return false; - if (asset.Expiration <= Blockchain.Default.Height + 1 && asset.AssetType != AssetType.GoverningToken && asset.AssetType != AssetType.UtilityToken) + if (asset.Expiration <= snapshot.Height + 1 && asset.AssetType != AssetType.GoverningToken && asset.AssetType != AssetType.UtilityToken) return false; foreach (TransactionOutput output in group) if (output.Value.GetData() % (long)Math.Pow(10, 8 - asset.Precision) != 0) @@ -374,7 +296,34 @@ public virtual bool Verify(IEnumerable mempool) } if (Attributes.Count(p => p.Usage == TransactionAttributeUsage.ECDH02 || p.Usage == TransactionAttributeUsage.ECDH03) > 1) return false; - return this.VerifyScripts(); + if (!VerifyReceivingScripts()) return false; + return this.VerifyWitnesses(snapshot); + } + + private bool VerifyReceivingScripts() + { + //TODO: run ApplicationEngine + //foreach (UInt160 hash in Outputs.Select(p => p.ScriptHash).Distinct()) + //{ + // ContractState contract = Blockchain.Default.GetContract(hash); + // if (contract == null) continue; + // if (!contract.Payable) return false; + // using (StateReader service = new StateReader()) + // { + // ApplicationEngine engine = new ApplicationEngine(TriggerType.VerificationR, this, Blockchain.Default, service, Fixed8.Zero); + // engine.LoadScript(contract.Script, false); + // using (ScriptBuilder sb = new ScriptBuilder()) + // { + // sb.EmitPush(0); + // sb.Emit(OpCode.PACK); + // sb.EmitPush("receiving"); + // engine.LoadScript(sb.ToArray(), false); + // } + // if (!engine.Execute()) return false; + // if (engine.EvaluationStack.Count != 1 || !engine.EvaluationStack.Pop().GetBoolean()) return false; + // } + //} + return true; } } } diff --git a/neo/Core/TransactionAttribute.cs b/neo/Network/P2P/Payloads/TransactionAttribute.cs similarity index 89% rename from neo/Core/TransactionAttribute.cs rename to neo/Network/P2P/Payloads/TransactionAttribute.cs index 2cc04194d4..45cfcbd7f7 100644 --- a/neo/Core/TransactionAttribute.cs +++ b/neo/Network/P2P/Payloads/TransactionAttribute.cs @@ -5,20 +5,11 @@ using System.IO; using System.Linq; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 交易特性 - /// public class TransactionAttribute : IInteropInterface, ISerializable { - /// - /// 用途 - /// public TransactionAttributeUsage Usage; - /// - /// 特定于用途的外部数据 - /// public byte[] Data; public int Size @@ -66,10 +57,6 @@ void ISerializable.Serialize(BinaryWriter writer) writer.Write(Data); } - /// - /// 变成json对象 - /// - /// 返回json对象 public JObject ToJson() { JObject json = new JObject(); diff --git a/neo/Core/TransactionAttributeUsage.cs b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs similarity index 62% rename from neo/Core/TransactionAttributeUsage.cs rename to neo/Network/P2P/Payloads/TransactionAttributeUsage.cs index 74079f295d..74d7fb400a 100644 --- a/neo/Core/TransactionAttributeUsage.cs +++ b/neo/Network/P2P/Payloads/TransactionAttributeUsage.cs @@ -1,27 +1,12 @@ -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 表示交易特性的用途 - /// public enum TransactionAttributeUsage : byte { - /// - /// 外部合同的散列值 - /// ContractHash = 0x00, - /// - /// 用于ECDH密钥交换的公钥,该公钥的第一个字节为0x02 - /// ECDH02 = 0x02, - /// - /// 用于ECDH密钥交换的公钥,该公钥的第一个字节为0x03 - /// ECDH03 = 0x03, - /// - /// 用于对交易进行额外的验证 - /// Script = 0x20, Vote = 0x30, @@ -45,9 +30,6 @@ public enum TransactionAttributeUsage : byte Hash14 = 0xae, Hash15 = 0xaf, - /// - /// 备注 - /// Remark = 0xf0, Remark1 = 0xf1, Remark2 = 0xf2, diff --git a/neo/Core/TransactionOutput.cs b/neo/Network/P2P/Payloads/TransactionOutput.cs similarity index 67% rename from neo/Core/TransactionOutput.cs rename to neo/Network/P2P/Payloads/TransactionOutput.cs index ad69315fd9..8a48bb427c 100644 --- a/neo/Core/TransactionOutput.cs +++ b/neo/Network/P2P/Payloads/TransactionOutput.cs @@ -5,24 +5,12 @@ using System; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 交易输出 - /// public class TransactionOutput : IInteropInterface, ISerializable { - /// - /// 资产编号 - /// public UInt256 AssetId; - /// - /// 金额 - /// public Fixed8 Value; - /// - /// 收款地址 - /// public UInt160 ScriptHash; public int Size => AssetId.Size + Value.Size + ScriptHash.Size; @@ -42,18 +30,13 @@ void ISerializable.Serialize(BinaryWriter writer) writer.Write(ScriptHash); } - /// - /// 将交易输出转变为json对象 - /// - /// 该交易输出在交易中的索引 - /// 返回json对象 public JObject ToJson(ushort index) { JObject json = new JObject(); json["n"] = index; json["asset"] = AssetId.ToString(); json["value"] = Value.ToString(); - json["address"] = Wallet.ToAddress(ScriptHash); + json["address"] = ScriptHash.ToAddress(); return json; } } diff --git a/neo/Network/P2P/Payloads/TransactionResult.cs b/neo/Network/P2P/Payloads/TransactionResult.cs new file mode 100644 index 0000000000..f737ae0b67 --- /dev/null +++ b/neo/Network/P2P/Payloads/TransactionResult.cs @@ -0,0 +1,8 @@ +namespace Neo.Network.P2P.Payloads +{ + public class TransactionResult + { + public UInt256 AssetId; + public Fixed8 Amount; + } +} diff --git a/neo/Core/TransactionType.cs b/neo/Network/P2P/Payloads/TransactionType.cs similarity index 64% rename from neo/Core/TransactionType.cs rename to neo/Network/P2P/Payloads/TransactionType.cs index ea60017ef8..861500811b 100644 --- a/neo/Core/TransactionType.cs +++ b/neo/Network/P2P/Payloads/TransactionType.cs @@ -2,38 +2,20 @@ using Neo.IO.Caching; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { - /// - /// 交易类型 - /// public enum TransactionType : byte { - /// - /// 用于分配字节费的特殊交易 - /// [ReflectionCache(typeof(MinerTransaction))] MinerTransaction = 0x00, - /// - /// 用于分发资产的特殊交易 - /// [ReflectionCache(typeof(IssueTransaction))] IssueTransaction = 0x01, [ReflectionCache(typeof(ClaimTransaction))] ClaimTransaction = 0x02, - /// - /// 用于报名成为记账候选人的特殊交易 - /// [ReflectionCache(typeof(EnrollmentTransaction))] EnrollmentTransaction = 0x20, - /// - /// 用于资产登记的特殊交易 - /// [ReflectionCache(typeof(RegisterTransaction))] RegisterTransaction = 0x40, - /// - /// 合约交易,这是最常用的一种交易 - /// [ReflectionCache(typeof(ContractTransaction))] ContractTransaction = 0x80, [ReflectionCache(typeof(StateTransaction))] diff --git a/neo/Network/Payloads/VersionPayload.cs b/neo/Network/P2P/Payloads/VersionPayload.cs similarity index 92% rename from neo/Network/Payloads/VersionPayload.cs rename to neo/Network/P2P/Payloads/VersionPayload.cs index 7c33b64168..ddfdfc7d69 100644 --- a/neo/Network/Payloads/VersionPayload.cs +++ b/neo/Network/P2P/Payloads/VersionPayload.cs @@ -1,9 +1,8 @@ -using Neo.Core; -using Neo.IO; +using Neo.IO; using System; using System.IO; -namespace Neo.Network.Payloads +namespace Neo.Network.P2P.Payloads { public class VersionPayload : ISerializable { @@ -18,7 +17,7 @@ public class VersionPayload : ISerializable public int Size => sizeof(uint) + sizeof(ulong) + sizeof(uint) + sizeof(ushort) + sizeof(uint) + UserAgent.GetVarSize() + sizeof(uint) + sizeof(bool); - public static VersionPayload Create(int port, uint nonce, string userAgent) + public static VersionPayload Create(int port, uint nonce, string userAgent, uint startHeight) { return new VersionPayload { @@ -28,7 +27,7 @@ public static VersionPayload Create(int port, uint nonce, string userAgent) Port = (ushort)port, Nonce = nonce, UserAgent = userAgent, - StartHeight = Blockchain.Default?.Height ?? 0, + StartHeight = startHeight, Relay = true }; } diff --git a/neo/Core/Witness.cs b/neo/Network/P2P/Payloads/Witness.cs similarity index 89% rename from neo/Core/Witness.cs rename to neo/Network/P2P/Payloads/Witness.cs index b906fe6257..f0720c2897 100644 --- a/neo/Core/Witness.cs +++ b/neo/Network/P2P/Payloads/Witness.cs @@ -1,8 +1,9 @@ using Neo.IO; using Neo.IO.Json; +using Neo.SmartContract; using System.IO; -namespace Neo.Core +namespace Neo.Network.P2P.Payloads { public class Witness : ISerializable { @@ -36,10 +37,6 @@ void ISerializable.Serialize(BinaryWriter writer) writer.WriteVarBytes(VerificationScript); } - /// - /// 变成json对象 - /// - /// 返回json对象 public JObject ToJson() { JObject json = new JObject(); diff --git a/neo/Network/P2P/Peer.cs b/neo/Network/P2P/Peer.cs new file mode 100644 index 0000000000..1591be236d --- /dev/null +++ b/neo/Network/P2P/Peer.cs @@ -0,0 +1,219 @@ +using Akka.Actor; +using Akka.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Threading.Tasks; + +namespace Neo.Network.P2P +{ + public abstract class Peer : UntypedActor + { + public class Start { public int Port; public int WsPort; } + public class Peers { public IEnumerable EndPoints; } + public class Connect { public IPEndPoint EndPoint; } + private class Timer { } + private class WsConnected { public WebSocket Socket; public IPEndPoint Remote; public IPEndPoint Local; } + + private const int MaxConnectionsPerAddress = 3; + + private static readonly IActorRef tcp_manager = Context.System.Tcp(); + private IActorRef tcp_listener; + private IWebHost ws_host; + private ICancelable timer; + protected ActorSelection Connections => Context.ActorSelection("connection_*"); + + private static readonly HashSet localAddresses = new HashSet(); + private readonly Dictionary ConnectedAddresses = new Dictionary(); + protected readonly ConcurrentDictionary ConnectedPeers = new ConcurrentDictionary(); + protected ImmutableHashSet UnconnectedPeers = ImmutableHashSet.Empty; + + public int ListenerPort { get; private set; } + protected abstract int ConnectedMax { get; } + protected abstract int UnconnectedMax { get; } + + static Peer() + { + localAddresses.UnionWith(NetworkInterface.GetAllNetworkInterfaces().SelectMany(p => p.GetIPProperties().UnicastAddresses).Select(p => p.Address.Unmap())); + } + + protected void AddPeers(IEnumerable peers) + { + if (UnconnectedPeers.Count < UnconnectedMax) + { + peers = peers.Where(p => p.Port != ListenerPort || !localAddresses.Contains(p.Address)); + ImmutableInterlocked.Update(ref UnconnectedPeers, p => p.Union(peers)); + } + } + + protected void ConnectToPeer(IPEndPoint endPoint) + { + endPoint = endPoint.Unmap(); + if (endPoint.Port == ListenerPort && localAddresses.Contains(endPoint.Address)) return; + if (ConnectedAddresses.TryGetValue(endPoint.Address, out int count) && count >= MaxConnectionsPerAddress) + return; + if (ConnectedPeers.Values.Contains(endPoint)) return; + tcp_manager.Tell(new Tcp.Connect(endPoint)); + } + + private static bool IsIntranetAddress(IPAddress address) + { + byte[] data = address.MapToIPv4().GetAddressBytes(); + Array.Reverse(data); + uint value = data.ToUInt32(0); + return (value & 0xff000000) == 0x0a000000 || (value & 0xff000000) == 0x7f000000 || (value & 0xfff00000) == 0xac100000 || (value & 0xffff0000) == 0xc0a80000 || (value & 0xffff0000) == 0xa9fe0000; + } + + protected abstract void NeedMorePeers(int count); + + protected override void OnReceive(object message) + { + switch (message) + { + case Start start: + OnStart(start.Port, start.WsPort); + break; + case Timer _: + OnTimer(); + break; + case Peers peers: + AddPeers(peers.EndPoints); + break; + case Connect connect: + ConnectToPeer(connect.EndPoint); + break; + case WsConnected ws: + OnWsConnected(ws.Socket, ws.Remote, ws.Local); + break; + case Tcp.Connected connected: + OnTcpConnected(((IPEndPoint)connected.RemoteAddress).Unmap(), ((IPEndPoint)connected.LocalAddress).Unmap()); + break; + case Tcp.Bound _: + tcp_listener = Sender; + break; + case Tcp.CommandFailed _: + break; + case Terminated terminated: + OnTerminated(terminated.ActorRef); + break; + } + } + + private void OnStart(int port, int ws_port) + { + ListenerPort = port; + timer = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(0, 5000, Context.Self, new Timer(), ActorRefs.NoSender); + if ((port > 0 || ws_port > 0) + && localAddresses.All(p => !p.IsIPv4MappedToIPv6 || IsIntranetAddress(p)) + && UPnP.Discover()) + { + try + { + localAddresses.Add(UPnP.GetExternalIP()); + if (port > 0) + UPnP.ForwardPort(port, ProtocolType.Tcp, "NEO"); + if (ws_port > 0) + UPnP.ForwardPort(ws_port, ProtocolType.Tcp, "NEO WebSocket"); + } + catch { } + } + if (port > 0) + { + tcp_manager.Tell(new Tcp.Bind(Self, new IPEndPoint(IPAddress.Any, port), options: new[] { new Inet.SO.ReuseAddress(true) })); + } + if (ws_port > 0) + { + ws_host = new WebHostBuilder().UseKestrel().UseUrls($"http://*:{ws_port}").Configure(app => app.UseWebSockets().Run(ProcessWebSocketAsync)).Build(); + ws_host.Start(); + } + } + + private void OnTcpConnected(IPEndPoint remote, IPEndPoint local) + { + ConnectedAddresses.TryGetValue(remote.Address, out int count); + if (count >= MaxConnectionsPerAddress) + { + Sender.Tell(Tcp.Abort.Instance); + } + else + { + ConnectedAddresses[remote.Address] = count + 1; + IActorRef connection = Context.ActorOf(ProtocolProps(Sender, remote, local), $"connection_{Guid.NewGuid()}"); + Context.Watch(connection); + Sender.Tell(new Tcp.Register(connection)); + ConnectedPeers.TryAdd(connection, remote); + } + } + + private void OnTerminated(IActorRef actorRef) + { + if (ConnectedPeers.TryRemove(actorRef, out IPEndPoint endPoint)) + { + ConnectedAddresses.TryGetValue(endPoint.Address, out int count); + if (count > 0) count--; + if (count == 0) + ConnectedAddresses.Remove(endPoint.Address); + else + ConnectedAddresses[endPoint.Address] = count; + } + } + + private void OnTimer() + { + if (ConnectedPeers.Count >= ConnectedMax) return; + if (UnconnectedPeers.Count == 0) + NeedMorePeers(ConnectedMax - ConnectedPeers.Count); + IPEndPoint[] endpoints = UnconnectedPeers.Take(ConnectedMax - ConnectedPeers.Count).ToArray(); + ImmutableInterlocked.Update(ref UnconnectedPeers, p => p.Except(endpoints)); + foreach (IPEndPoint endpoint in endpoints) + { + ConnectToPeer(endpoint); + } + } + + private void OnWsConnected(WebSocket ws, IPEndPoint remote, IPEndPoint local) + { + ConnectedAddresses.TryGetValue(remote.Address, out int count); + if (count >= MaxConnectionsPerAddress) + { + ws.Abort(); + } + else + { + ConnectedAddresses[remote.Address] = count + 1; + Context.ActorOf(ProtocolProps(ws, remote, local), $"connection_{Guid.NewGuid()}"); + } + } + + protected override void PostStop() + { + timer.CancelIfNotNull(); + ws_host?.Dispose(); + tcp_listener?.Tell(Tcp.Unbind.Instance); + base.PostStop(); + } + + private async Task ProcessWebSocketAsync(HttpContext context) + { + if (!context.WebSockets.IsWebSocketRequest) return; + WebSocket ws = await context.WebSockets.AcceptWebSocketAsync(); + Self.Tell(new WsConnected + { + Socket = ws, + Remote = new IPEndPoint(context.Connection.RemoteIpAddress, context.Connection.RemotePort), + Local = new IPEndPoint(context.Connection.LocalIpAddress, context.Connection.LocalPort) + }); + } + + protected abstract Props ProtocolProps(object connection, IPEndPoint remote, IPEndPoint local); + } +} diff --git a/neo/Network/P2P/ProtocolHandler.cs b/neo/Network/P2P/ProtocolHandler.cs new file mode 100644 index 0000000000..b275aa42cd --- /dev/null +++ b/neo/Network/P2P/ProtocolHandler.cs @@ -0,0 +1,328 @@ +using Akka.Actor; +using Akka.Configuration; +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Actors; +using Neo.IO.Caching; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Neo.Network.P2P +{ + internal class ProtocolHandler : UntypedActor + { + public class SetVersion { public VersionPayload Version; } + public class SetVerack { } + public class SetFilter { public BloomFilter Filter; } + + private readonly NeoSystem system; + private readonly HashSet knownHashes = new HashSet(); + private readonly HashSet sentHashes = new HashSet(); + private VersionPayload version; + private bool verack = false; + private BloomFilter bloom_filter; + + public ProtocolHandler(NeoSystem system) + { + this.system = system; + } + + protected override void OnReceive(object message) + { + if (!(message is Message msg)) return; + if (version == null) + { + if (msg.Command != "version") + throw new ProtocolViolationException(); + OnVersionMessageReceived(msg.Payload.AsSerializable()); + return; + } + if (!verack) + { + if (msg.Command != "verack") + throw new ProtocolViolationException(); + OnVerackMessageReceived(); + return; + } + switch (msg.Command) + { + case "addr": + OnAddrMessageReceived(msg.Payload.AsSerializable()); + break; + case "block": + OnInventoryReceived(msg.Payload.AsSerializable()); + break; + case "consensus": + OnInventoryReceived(msg.Payload.AsSerializable()); + break; + case "filteradd": + OnFilterAddMessageReceived(msg.Payload.AsSerializable()); + break; + case "filterclear": + OnFilterClearMessageReceived(); + break; + case "filterload": + OnFilterLoadMessageReceived(msg.Payload.AsSerializable()); + break; + case "getaddr": + OnGetAddrMessageReceived(); + break; + case "getblocks": + OnGetBlocksMessageReceived(msg.Payload.AsSerializable()); + break; + case "getdata": + OnGetDataMessageReceived(msg.Payload.AsSerializable()); + break; + case "getheaders": + OnGetHeadersMessageReceived(msg.Payload.AsSerializable()); + break; + case "headers": + OnHeadersMessageReceived(msg.Payload.AsSerializable()); + break; + case "inv": + OnInvMessageReceived(msg.Payload.AsSerializable()); + break; + case "mempool": + OnMemPoolMessageReceived(); + break; + case "tx": + if (msg.Payload.Length <= 1024 * 1024) + OnInventoryReceived(Transaction.DeserializeFrom(msg.Payload)); + break; + case "verack": + case "version": + throw new ProtocolViolationException(); + case "alert": + case "merkleblock": + case "notfound": + case "ping": + case "pong": + case "reject": + default: + //暂时忽略 + break; + } + } + + private void OnAddrMessageReceived(AddrPayload payload) + { + system.LocalNode.Tell(new Peer.Peers + { + EndPoints = payload.AddressList.Select(p => p.EndPoint) + }); + } + + private void OnFilterAddMessageReceived(FilterAddPayload payload) + { + if (bloom_filter != null) + bloom_filter.Add(payload.Data); + } + + private void OnFilterClearMessageReceived() + { + bloom_filter = null; + Context.Parent.Tell(new SetFilter { Filter = null }); + } + + private void OnFilterLoadMessageReceived(FilterLoadPayload payload) + { + bloom_filter = new BloomFilter(payload.Filter.Length * 8, payload.K, payload.Tweak, payload.Filter); + Context.Parent.Tell(new SetFilter { Filter = bloom_filter }); + } + + private void OnGetAddrMessageReceived() + { + Random rand = new Random(); + IEnumerable peers = LocalNode.Singleton.RemoteNodes.Values + .Where(p => p.ListenerPort > 0) + .GroupBy(p => p.Remote.Address, (k, g) => g.First()) + .OrderBy(p => rand.Next()) + .Take(AddrPayload.MaxCountToSend); + NetworkAddressWithTime[] networkAddresses = peers.Select(p => NetworkAddressWithTime.Create(p.Listener, p.Version.Services, p.Version.Timestamp)).ToArray(); + if (networkAddresses.Length == 0) return; + Context.Parent.Tell(Message.Create("addr", AddrPayload.Create(networkAddresses))); + } + + private void OnGetBlocksMessageReceived(GetBlocksPayload payload) + { + UInt256 hash = payload.HashStart[0]; + if (hash == payload.HashStop) return; + BlockState state = Blockchain.Singleton.Store.GetBlocks().TryGet(hash); + if (state == null) return; + List hashes = new List(); + for (uint i = 1; i <= InvPayload.MaxHashesCount; i++) + { + uint index = state.TrimmedBlock.Index + i; + if (index > Blockchain.Singleton.Height) + break; + hash = Blockchain.Singleton.GetBlockHash(index); + if (hash == null) break; + hashes.Add(hash); + } + if (hashes.Count == 0) return; + Context.Parent.Tell(Message.Create("inv", InvPayload.Create(InventoryType.Block, hashes.ToArray()))); + } + + 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("tx", inventory)); + break; + case InventoryType.Block: + if (inventory == null) + inventory = Blockchain.Singleton.GetBlock(hash); + if (inventory is Block block) + { + if (bloom_filter == null) + { + Context.Parent.Tell(Message.Create("block", inventory)); + } + else + { + BitArray flags = new BitArray(block.Transactions.Select(p => bloom_filter.Test(p)).ToArray()); + Context.Parent.Tell(Message.Create("merkleblock", MerkleBlockPayload.Create(block, flags))); + } + } + break; + case InventoryType.Consensus: + if (inventory != null) + Context.Parent.Tell(Message.Create("consensus", inventory)); + break; + } + } + } + + private void OnGetHeadersMessageReceived(GetBlocksPayload payload) + { + UInt256 hash = payload.HashStart[0]; + if (hash == payload.HashStop) return; + DataCache cache = Blockchain.Singleton.Store.GetBlocks(); + BlockState state = cache.TryGet(hash); + if (state == null) return; + List
headers = new List
(); + for (uint i = 1; i <= HeadersPayload.MaxHeadersCount; i++) + { + uint index = state.TrimmedBlock.Index + i; + hash = Blockchain.Singleton.GetBlockHash(index); + if (hash == null) break; + Header header = cache.TryGet(hash)?.TrimmedBlock.Header; + if (header == null) break; + headers.Add(header); + } + if (headers.Count == 0) return; + Context.Parent.Tell(Message.Create("headers", HeadersPayload.Create(headers))); + } + + private void OnHeadersMessageReceived(HeadersPayload payload) + { + if (payload.Headers.Length == 0) return; + system.Blockchain.Tell(payload.Headers, Context.Parent); + } + + private void OnInventoryReceived(IInventory inventory) + { + system.TaskManager.Tell(new TaskManager.TaskCompleted { Hash = inventory.Hash }, Context.Parent); + if (inventory is MinerTransaction) return; + system.LocalNode.Tell(new LocalNode.Relay { Inventory = inventory }); + } + + private void OnInvMessageReceived(InvPayload payload) + { + UInt256[] hashes = payload.Hashes.Where(p => knownHashes.Add(p)).ToArray(); + if (hashes.Length == 0) return; + switch (payload.Type) + { + case InventoryType.Block: + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) + hashes = hashes.Where(p => !snapshot.ContainsBlock(p)).ToArray(); + break; + case InventoryType.TX: + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) + hashes = hashes.Where(p => !snapshot.ContainsTransaction(p)).ToArray(); + break; + } + if (hashes.Length == 0) return; + system.TaskManager.Tell(new TaskManager.NewTasks { Payload = InvPayload.Create(payload.Type, hashes) }, Context.Parent); + } + + private void OnMemPoolMessageReceived() + { + foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, Blockchain.Singleton.GetMemoryPool().Select(p => p.Hash).ToArray())) + Context.Parent.Tell(Message.Create("inv", payload)); + } + + private void OnVerackMessageReceived() + { + verack = true; + Context.Parent.Tell(new SetVerack()); + } + + private void OnVersionMessageReceived(VersionPayload payload) + { + version = payload; + Context.Parent.Tell(new SetVersion { Version = payload }); + } + + public static Props Props(NeoSystem system) + { + return Akka.Actor.Props.Create(() => new ProtocolHandler(system)).WithMailbox("protocol-handler-mailbox"); + } + } + + internal class ProtocolHandlerMailbox : PriorityMailbox + { + public ProtocolHandlerMailbox(Akka.Actor.Settings settings, Config config) + : base(settings, config) + { + } + + protected override bool IsHighPriority(object message) + { + if (!(message is Message msg)) return true; + switch (msg.Command) + { + case "consensus": + case "filteradd": + case "filterclear": + case "filterload": + case "verack": + case "version": + case "alert": + return true; + default: + return false; + } + } + + protected override bool ShallDrop(object message, IEnumerable queue) + { + if (!(message is Message msg)) return false; + switch (msg.Command) + { + case "getaddr": + case "getblocks": + case "getdata": + case "getheaders": + case "mempool": + return queue.OfType().Any(p => p.Command == msg.Command); + default: + return false; + } + } + } +} diff --git a/neo/Network/P2P/RemoteNode.cs b/neo/Network/P2P/RemoteNode.cs new file mode 100644 index 0000000000..9880339f73 --- /dev/null +++ b/neo/Network/P2P/RemoteNode.cs @@ -0,0 +1,243 @@ +using Akka.Actor; +using Akka.Configuration; +using Akka.IO; +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Actors; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Neo.Network.P2P +{ + public class RemoteNode : Connection + { + internal class Relay { public IInventory Inventory; } + + private readonly NeoSystem system; + private readonly IActorRef protocol; + private readonly Queue message_queue_high = new Queue(); + private readonly Queue message_queue_low = new Queue(); + private ByteString msg_buffer = ByteString.Empty; + private bool ack = true; + private BloomFilter bloom_filter; + private bool verack = false; + + public IPEndPoint Listener => new IPEndPoint(Remote.Address, ListenerPort); + public override int ListenerPort => Version?.Port ?? 0; + public VersionPayload Version { get; private set; } + + public RemoteNode(NeoSystem system, object connection, IPEndPoint remote, IPEndPoint local) + : base(connection, remote, local) + { + this.system = system; + this.protocol = Context.ActorOf(ProtocolHandler.Props(system)); + LocalNode.Singleton.RemoteNodes.TryAdd(Self, this); + SendMessage(Message.Create("version", VersionPayload.Create(LocalNode.Singleton.ListenerPort, LocalNode.Nonce, LocalNode.UserAgent, Blockchain.Singleton.Height))); + } + + private void CheckMessageQueue() + { + if (!verack || !ack) return; + Queue queue = message_queue_high; + if (queue.Count == 0) queue = message_queue_low; + if (queue.Count == 0) return; + SendMessage(queue.Dequeue()); + } + + private void EnqueueMessage(string command, ISerializable payload = null) + { + EnqueueMessage(Message.Create(command, payload)); + } + + private void EnqueueMessage(Message message) + { + bool is_single = false; + switch (message.Command) + { + case "addr": + case "getaddr": + case "getblocks": + case "getheaders": + case "mempool": + is_single = true; + break; + } + Queue message_queue; + switch (message.Command) + { + case "alert": + case "consensus": + case "filteradd": + case "filterclear": + case "filterload": + case "getaddr": + case "mempool": + message_queue = message_queue_high; + break; + default: + message_queue = message_queue_low; + break; + } + if (!is_single || message_queue.All(p => p.Command != message.Command)) + message_queue.Enqueue(message); + CheckMessageQueue(); + } + + protected override void OnAck() + { + ack = true; + CheckMessageQueue(); + } + + protected override void OnData(ByteString data) + { + msg_buffer = msg_buffer.Concat(data); + for (Message message = TryParseMessage(); message != null; message = TryParseMessage()) + protocol.Tell(message); + } + + protected override void OnReceive(object message) + { + base.OnReceive(message); + switch (message) + { + case Message msg: + EnqueueMessage(msg); + break; + case IInventory inventory: + OnSend(inventory); + break; + case Relay relay: + OnRelay(relay.Inventory); + break; + case ProtocolHandler.SetVersion setVersion: + OnSetVersion(setVersion.Version); + break; + case ProtocolHandler.SetVerack _: + OnSetVerack(); + break; + case ProtocolHandler.SetFilter setFilter: + OnSetFilter(setFilter.Filter); + break; + } + } + + private void OnRelay(IInventory inventory) + { + if (Version?.Relay != true) return; + if (inventory.InventoryType == InventoryType.TX) + { + if (bloom_filter != null && !bloom_filter.Test((Transaction)inventory)) + return; + } + EnqueueMessage("inv", InvPayload.Create(inventory.InventoryType, inventory.Hash)); + } + + private void OnSend(IInventory inventory) + { + if (Version?.Relay != true) return; + if (inventory.InventoryType == InventoryType.TX) + { + if (bloom_filter != null && !bloom_filter.Test((Transaction)inventory)) + return; + } + EnqueueMessage(inventory.InventoryType.ToString().ToLower(), inventory); + } + + private void OnSetFilter(BloomFilter filter) + { + bloom_filter = filter; + } + + private void OnSetVerack() + { + verack = true; + system.TaskManager.Tell(new TaskManager.Register { Version = Version }); + CheckMessageQueue(); + } + + private void OnSetVersion(VersionPayload version) + { + this.Version = version; + if (version.Nonce == LocalNode.Nonce) + { + Disconnect(true); + return; + } + if (LocalNode.Singleton.RemoteNodes.Values.Where(p => p != this).Any(p => p.Remote.Address.Equals(Remote.Address) && p.Version?.Nonce == version.Nonce)) + { + Disconnect(true); + return; + } + SendMessage(Message.Create("verack")); + } + + protected override void PostStop() + { + LocalNode.Singleton.RemoteNodes.TryRemove(Self, out _); + base.PostStop(); + } + + internal static Props Props(NeoSystem system, object connection, IPEndPoint remote, IPEndPoint local) + { + return Akka.Actor.Props.Create(() => new RemoteNode(system, connection, remote, local)).WithMailbox("remote-node-mailbox"); + } + + private void SendMessage(Message message) + { + ack = false; + SendData(ByteString.FromBytes(message.ToArray())); + } + + protected override SupervisorStrategy SupervisorStrategy() + { + return new OneForOneStrategy(ex => + { + Disconnect(true); + return Directive.Stop; + }, loggingEnabled: false); + } + + private Message TryParseMessage() + { + if (msg_buffer.Count < sizeof(uint)) return null; + uint magic = msg_buffer.Slice(0, sizeof(uint)).ToArray().ToUInt32(0); + if (magic != Message.Magic) + throw new FormatException(); + if (msg_buffer.Count < Message.HeaderSize) return null; + int length = msg_buffer.Slice(16, sizeof(int)).ToArray().ToInt32(0); + if (length > Message.PayloadMaxSize) + throw new FormatException(); + length += Message.HeaderSize; + if (msg_buffer.Count < length) return null; + Message message = msg_buffer.Slice(0, length).ToArray().AsSerializable(); + msg_buffer = msg_buffer.Slice(length).Compact(); + return message; + } + } + + internal class RemoteNodeMailbox : PriorityMailbox + { + public RemoteNodeMailbox(Akka.Actor.Settings settings, Config config) + : base(settings, config) + { + } + + protected override bool IsHighPriority(object message) + { + switch (message) + { + case Tcp.ConnectionClosed _: + case Connection.Timer _: + case Connection.Ack _: + return true; + default: + return false; + } + } + } +} diff --git a/neo/Network/P2P/TaskManager.cs b/neo/Network/P2P/TaskManager.cs new file mode 100644 index 0000000000..3f0e7e661c --- /dev/null +++ b/neo/Network/P2P/TaskManager.cs @@ -0,0 +1,225 @@ +using Akka.Actor; +using Akka.Configuration; +using Neo.IO.Actors; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Network.P2P +{ + internal class TaskManager : UntypedActor + { + public class Register { public VersionPayload Version; } + public class NewTasks { public InvPayload Payload; } + public class TaskCompleted { public UInt256 Hash; } + public class HeaderTaskCompleted { } + public class RestartTasks { public InvPayload Payload; } + private class Timer { } + + private static readonly TimeSpan TimerInterval = TimeSpan.FromSeconds(30); + private static readonly TimeSpan TaskTimeout = TimeSpan.FromMinutes(1); + + private readonly NeoSystem system; + private readonly HashSet knownHashes = new HashSet(); + private readonly HashSet globalTasks = new HashSet(); + private readonly Dictionary sessions = new Dictionary(); + private readonly ICancelable timer = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(TimerInterval, TimerInterval, Context.Self, new Timer(), ActorRefs.NoSender); + + private bool HeaderTask => sessions.Values.Any(p => p.HeaderTask); + + public TaskManager(NeoSystem system) + { + this.system = system; + } + + private void OnHeaderTaskCompleted() + { + if (!sessions.TryGetValue(Sender, out TaskSession session)) + return; + session.Tasks.Remove(UInt256.Zero); + RequestTasks(session); + } + + private void OnNewTasks(InvPayload payload) + { + if (!sessions.TryGetValue(Sender, out TaskSession session)) + return; + if (payload.Type == InventoryType.TX && Blockchain.Singleton.Height < Blockchain.Singleton.HeaderHeight) + { + RequestTasks(session); + return; + } + HashSet hashes = new HashSet(payload.Hashes); + hashes.ExceptWith(knownHashes); + if (payload.Type == InventoryType.Block) + session.AvailableTasks.UnionWith(hashes.Where(p => globalTasks.Contains(p))); + hashes.ExceptWith(globalTasks); + if (hashes.Count == 0) + { + RequestTasks(session); + return; + } + globalTasks.UnionWith(hashes); + foreach (UInt256 hash in hashes) + session.Tasks[hash] = DateTime.UtcNow; + foreach (InvPayload group in InvPayload.CreateGroup(payload.Type, hashes.ToArray())) + Sender.Tell(Message.Create("getdata", group)); + } + + protected override void OnReceive(object message) + { + switch (message) + { + case Register register: + OnRegister(register.Version); + break; + case NewTasks tasks: + OnNewTasks(tasks.Payload); + break; + case TaskCompleted completed: + OnTaskCompleted(completed.Hash); + break; + case HeaderTaskCompleted _: + OnHeaderTaskCompleted(); + break; + case RestartTasks restart: + OnRestartTasks(restart.Payload); + break; + case Timer _: + OnTimer(); + break; + case Terminated terminated: + OnTerminated(terminated.ActorRef); + break; + } + } + + private void OnRegister(VersionPayload version) + { + Context.Watch(Sender); + TaskSession session = new TaskSession(Sender, version); + sessions.Add(Sender, session); + RequestTasks(session); + } + + private void OnRestartTasks(InvPayload payload) + { + knownHashes.ExceptWith(payload.Hashes); + globalTasks.ExceptWith(payload.Hashes); + foreach (InvPayload group in InvPayload.CreateGroup(payload.Type, payload.Hashes)) + system.LocalNode.Tell(Message.Create("getdata", group)); + } + + private void OnTaskCompleted(UInt256 hash) + { + knownHashes.Add(hash); + globalTasks.Remove(hash); + foreach (TaskSession ms in sessions.Values) + ms.AvailableTasks.Remove(hash); + if (sessions.TryGetValue(Sender, out TaskSession session)) + { + session.Tasks.Remove(hash); + RequestTasks(session); + } + } + + private void OnTerminated(IActorRef actor) + { + if (!sessions.TryGetValue(actor, out TaskSession session)) + return; + sessions.Remove(actor); + globalTasks.ExceptWith(session.Tasks.Keys); + } + + private void OnTimer() + { + foreach (TaskSession session in sessions.Values) + foreach (var task in session.Tasks.ToArray()) + if (DateTime.UtcNow - task.Value > TaskTimeout) + { + globalTasks.Remove(task.Key); + session.Tasks.Remove(task.Key); + } + foreach (TaskSession session in sessions.Values) + RequestTasks(session); + } + + protected override void PostStop() + { + timer.CancelIfNotNull(); + base.PostStop(); + } + + public static Props Props(NeoSystem system) + { + return Akka.Actor.Props.Create(() => new TaskManager(system)).WithMailbox("task-manager-mailbox"); + } + + private void RequestTasks(TaskSession session) + { + if (session.HasTask) return; + if (session.AvailableTasks.Count > 0) + { + session.AvailableTasks.ExceptWith(knownHashes); + session.AvailableTasks.RemoveWhere(p => Blockchain.Singleton.ContainsBlock(p)); + HashSet hashes = new HashSet(session.AvailableTasks); + hashes.ExceptWith(globalTasks); + if (hashes.Count > 0) + { + session.AvailableTasks.ExceptWith(hashes); + globalTasks.UnionWith(hashes); + foreach (UInt256 hash in hashes) + session.Tasks[hash] = DateTime.UtcNow; + foreach (InvPayload group in InvPayload.CreateGroup(InventoryType.Block, hashes.ToArray())) + session.RemoteNode.Tell(Message.Create("getdata", group)); + return; + } + } + if (!HeaderTask && Blockchain.Singleton.HeaderHeight < session.Version.StartHeight) + { + session.Tasks[UInt256.Zero] = DateTime.UtcNow; + session.RemoteNode.Tell(Message.Create("getheaders", GetBlocksPayload.Create(Blockchain.Singleton.CurrentHeaderHash))); + } + else if (Blockchain.Singleton.Height < session.Version.StartHeight) + { + UInt256 hash = Blockchain.Singleton.CurrentBlockHash; + for (uint i = Blockchain.Singleton.Height + 1; i <= Blockchain.Singleton.HeaderHeight; i++) + { + hash = Blockchain.Singleton.GetBlockHash(i); + if (!globalTasks.Contains(hash)) + { + hash = Blockchain.Singleton.GetBlockHash(i - 1); + break; + } + } + session.RemoteNode.Tell(Message.Create("getblocks", GetBlocksPayload.Create(hash))); + } + } + } + + internal class TaskManagerMailbox : PriorityMailbox + { + public TaskManagerMailbox(Akka.Actor.Settings settings, Config config) + : base(settings, config) + { + } + + protected override bool IsHighPriority(object message) + { + switch (message) + { + case TaskManager.Register _: + case TaskManager.RestartTasks _: + return true; + case TaskManager.NewTasks tasks: + if (tasks.Payload.Type == InventoryType.Block || tasks.Payload.Type == InventoryType.Consensus) + return true; + return false; + default: + return false; + } + } + } +} diff --git a/neo/Network/P2P/TaskSession.cs b/neo/Network/P2P/TaskSession.cs new file mode 100644 index 0000000000..ebfdd8f9ec --- /dev/null +++ b/neo/Network/P2P/TaskSession.cs @@ -0,0 +1,24 @@ +using Akka.Actor; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Generic; + +namespace Neo.Network.P2P +{ + internal class TaskSession + { + public readonly IActorRef RemoteNode; + public readonly VersionPayload Version; + public readonly Dictionary Tasks = new Dictionary(); + public readonly HashSet AvailableTasks = new HashSet(); + + public bool HasTask => Tasks.Count > 0; + public bool HeaderTask => Tasks.ContainsKey(UInt256.Zero); + + public TaskSession(IActorRef node, VersionPayload version) + { + this.RemoteNode = node; + this.Version = version; + } + } +} diff --git a/neo/Network/RPC/RpcServer.cs b/neo/Network/RPC/RpcServer.cs index 0311d869da..1d8be381b0 100644 --- a/neo/Network/RPC/RpcServer.cs +++ b/neo/Network/RPC/RpcServer.cs @@ -1,16 +1,22 @@ -using Microsoft.AspNetCore.Builder; +using Akka.Actor; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.DependencyInjection; -using Neo.Core; using Neo.IO; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.Plugins; using Neo.SmartContract; using Neo.VM; using Neo.Wallets; +using Neo.Wallets.NEP6; using System; +using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; @@ -20,14 +26,16 @@ namespace Neo.Network.RPC { - public class RpcServer : IDisposable + public sealed class RpcServer : IDisposable { - protected readonly LocalNode LocalNode; + private readonly NeoSystem system; + private readonly Wallet wallet; private IWebHost host; - public RpcServer(LocalNode localNode) + public RpcServer(NeoSystem system, Wallet wallet = null) { - this.LocalNode = localNode; + this.system = system; + this.wallet = wallet; } private static JObject CreateErrorResponse(JObject id, int code, string message, JObject data = null) @@ -58,7 +66,7 @@ public void Dispose() } } - private static JObject GetInvokeResult(byte[] script) + private JObject GetInvokeResult(byte[] script) { ApplicationEngine engine = ApplicationEngine.Run(script); JObject json = new JObject(); @@ -73,39 +81,109 @@ private static JObject GetInvokeResult(byte[] script) { json["stack"] = "error: recursive reference"; } + if (wallet != null) + { + InvocationTransaction tx = new InvocationTransaction + { + Version = 1, + Script = json["script"].AsString().HexToBytes(), + Gas = Fixed8.Parse(json["gas_consumed"].AsString()) + }; + tx.Gas -= Fixed8.FromDecimal(10); + if (tx.Gas < Fixed8.Zero) tx.Gas = Fixed8.Zero; + tx.Gas = tx.Gas.Ceiling(); + tx = wallet.MakeTransaction(tx); + if (tx != null) + { + ContractParametersContext context = new ContractParametersContext(tx); + wallet.Sign(context); + if (context.Completed) + tx.Witnesses = context.GetWitnesses(); + else + tx = null; + } + json["tx"] = tx?.ToArray().ToHexString(); + } return json; } - protected virtual JObject Process(string method, JArray _params) + private static JObject GetRelayResult(RelayResultReason reason) + { + switch (reason) + { + case RelayResultReason.Succeed: + return true; + case RelayResultReason.AlreadyExists: + throw new RpcException(-501, "Block or transaction already exists and cannot be sent repeatedly."); + case RelayResultReason.OutOfMemory: + throw new RpcException(-502, "The memory pool is full and no more transactions can be sent."); + case RelayResultReason.UnableToVerify: + throw new RpcException(-503, "The block cannot be validated."); + case RelayResultReason.Invalid: + throw new RpcException(-504, "Block or transaction validation failed."); + default: + throw new RpcException(-500, "Unkown error."); + } + } + + private JObject Process(string method, JArray _params) { switch (method) { + case "dumpprivkey": + if (wallet == null) + throw new RpcException(-400, "Access denied"); + else + { + UInt160 scriptHash = _params[0].AsString().ToScriptHash(); + WalletAccount account = wallet.GetAccount(scriptHash); + return account.GetKey().Export(); + } case "getaccountstate": { - UInt160 script_hash = Wallet.ToScriptHash(_params[0].AsString()); - AccountState account = Blockchain.Default.GetAccountState(script_hash) ?? new AccountState(script_hash); + UInt160 script_hash = _params[0].AsString().ToScriptHash(); + AccountState account = Blockchain.Singleton.Store.GetAccounts().TryGet(script_hash) ?? new AccountState(script_hash); return account.ToJson(); } case "getassetstate": { UInt256 asset_id = UInt256.Parse(_params[0].AsString()); - AssetState asset = Blockchain.Default.GetAssetState(asset_id); + AssetState asset = Blockchain.Singleton.Store.GetAssets().TryGet(asset_id); return asset?.ToJson() ?? throw new RpcException(-100, "Unknown asset"); } + case "getbalance": + if (wallet == null) + throw new RpcException(-400, "Access denied."); + else + { + JObject json = new JObject(); + switch (UIntBase.Parse(_params[0].AsString())) + { + case UInt160 asset_id_160: //NEP-5 balance + json["balance"] = wallet.GetAvailable(asset_id_160).ToString(); + break; + case UInt256 asset_id_256: //Global Assets balance + IEnumerable coins = wallet.GetCoins().Where(p => !p.State.HasFlag(CoinState.Spent) && p.Output.AssetId.Equals(asset_id_256)); + json["balance"] = coins.Sum(p => p.Output.Value).ToString(); + json["confirmed"] = coins.Where(p => p.State.HasFlag(CoinState.Confirmed)).Sum(p => p.Output.Value).ToString(); + break; + } + return json; + } case "getbestblockhash": - return Blockchain.Default.CurrentBlockHash.ToString(); + return Blockchain.Singleton.CurrentBlockHash.ToString(); case "getblock": { Block block; if (_params[0] is JNumber) { uint index = (uint)_params[0].AsNumber(); - block = Blockchain.Default.GetBlock(index); + block = Blockchain.Singleton.Store.GetBlock(index); } else { UInt256 hash = UInt256.Parse(_params[0].AsString()); - block = Blockchain.Default.GetBlock(hash); + block = Blockchain.Singleton.Store.GetBlock(hash); } if (block == null) throw new RpcException(-100, "Unknown block"); @@ -113,29 +191,37 @@ protected virtual JObject Process(string method, JArray _params) if (verbose) { JObject json = block.ToJson(); - json["confirmations"] = Blockchain.Default.Height - block.Index + 1; - UInt256 hash = Blockchain.Default.GetNextBlockHash(block.Hash); + json["confirmations"] = Blockchain.Singleton.Height - block.Index + 1; + UInt256 hash = Blockchain.Singleton.Store.GetNextBlockHash(block.Hash); if (hash != null) json["nextblockhash"] = hash.ToString(); return json; } - return block.ToArray().ToHexString(); } case "getblockcount": - return Blockchain.Default.Height + 1; + return Blockchain.Singleton.Height + 1; + case "getblockhash": + { + uint height = (uint)_params[0].AsNumber(); + if (height <= Blockchain.Singleton.Height) + { + return Blockchain.Singleton.GetBlockHash(height).ToString(); + } + throw new RpcException(-100, "Invalid Height"); + } case "getblockheader": { Header header; if (_params[0] is JNumber) { uint height = (uint)_params[0].AsNumber(); - header = Blockchain.Default.GetHeader(height); + header = Blockchain.Singleton.Store.GetHeader(height); } else { UInt256 hash = UInt256.Parse(_params[0].AsString()); - header = Blockchain.Default.GetHeader(hash); + header = Blockchain.Singleton.Store.GetHeader(hash); } if (header == null) throw new RpcException(-100, "Unknown block"); @@ -144,8 +230,8 @@ protected virtual JObject Process(string method, JArray _params) if (verbose) { JObject json = header.ToJson(); - json["confirmations"] = Blockchain.Default.Height - header.Index + 1; - UInt256 hash = Blockchain.Default.GetNextBlockHash(header.Hash); + json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; + UInt256 hash = Blockchain.Singleton.Store.GetNextBlockHash(header.Hash); if (hash != null) json["nextblockhash"] = hash.ToString(); return json; @@ -153,66 +239,82 @@ protected virtual JObject Process(string method, JArray _params) return header.ToArray().ToHexString(); } - case "getblockhash": - { - uint height = (uint)_params[0].AsNumber(); - if (height <= Blockchain.Default.Height) - { - return Blockchain.Default.GetBlockHash(height).ToString(); - } - - throw new RpcException(-100, "Invalid Height"); - } case "getblocksysfee": { uint height = (uint)_params[0].AsNumber(); - if (height <= Blockchain.Default.Height) + if (height <= Blockchain.Singleton.Height) { - return Blockchain.Default.GetSysFeeAmount(height).ToString(); + return Blockchain.Singleton.Store.GetSysFeeAmount(height).ToString(); } - throw new RpcException(-100, "Invalid Height"); } case "getconnectioncount": - return LocalNode.RemoteNodeCount; + return LocalNode.Singleton.ConnectedCount; case "getcontractstate": { UInt160 script_hash = UInt160.Parse(_params[0].AsString()); - ContractState contract = Blockchain.Default.GetContract(script_hash); + ContractState contract = Blockchain.Singleton.Store.GetContracts().TryGet(script_hash); return contract?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); } + case "getnewaddress": + if (wallet == null) + throw new RpcException(-400, "Access denied"); + else + { + WalletAccount account = wallet.CreateAccount(); + if (wallet is NEP6Wallet nep6) + nep6.Save(); + return account.Address; + } + case "getpeers": + { + JObject json = new JObject(); + json["unconnected"] = new JArray(LocalNode.Singleton.GetUnconnectedPeers().Select(p => + { + JObject peerJson = new JObject(); + peerJson["address"] = p.Address.ToString(); + peerJson["port"] = p.Port; + return peerJson; + })); + json["bad"] = new JArray(); //badpeers has been removed + json["connected"] = new JArray(LocalNode.Singleton.GetRemoteNodes().Select(p => + { + JObject peerJson = new JObject(); + peerJson["address"] = p.Remote.Address.ToString(); + peerJson["port"] = p.ListenerPort; + return peerJson; + })); + return json; + } case "getrawmempool": - return new JArray(LocalNode.GetMemoryPool().Select(p => (JObject)p.Hash.ToString())); + return new JArray(Blockchain.Singleton.GetMemoryPool().Select(p => (JObject)p.Hash.ToString())); case "getrawtransaction": { UInt256 hash = UInt256.Parse(_params[0].AsString()); bool verbose = _params.Count >= 2 && _params[1].AsBooleanOrDefault(false); - int height = -1; - Transaction tx = LocalNode.GetTransaction(hash); - if (tx == null) - tx = Blockchain.Default.GetTransaction(hash, out height); + Transaction tx = Blockchain.Singleton.GetTransaction(hash); if (tx == null) throw new RpcException(-100, "Unknown transaction"); if (verbose) { JObject json = tx.ToJson(); - if (height >= 0) + uint? height = Blockchain.Singleton.Store.GetTransactions().TryGet(hash)?.BlockIndex; + if (height != null) { - Header header = Blockchain.Default.GetHeader((uint)height); + Header header = Blockchain.Singleton.Store.GetHeader((uint)height); json["blockhash"] = header.Hash.ToString(); - json["confirmations"] = Blockchain.Default.Height - header.Index + 1; + json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; json["blocktime"] = header.Timestamp; } return json; } - return tx.ToArray().ToHexString(); } case "getstorage": { UInt160 script_hash = UInt160.Parse(_params[0].AsString()); byte[] key = _params[1].AsString().HexToBytes(); - StorageItem item = Blockchain.Default.GetStorageItem(new StorageKey + StorageItem item = Blockchain.Singleton.Store.GetStorages().TryGet(new StorageKey { ScriptHash = script_hash, Key = key @@ -223,12 +325,13 @@ protected virtual JObject Process(string method, JArray _params) { UInt256 hash = UInt256.Parse(_params[0].AsString()); ushort index = (ushort)_params[1].AsNumber(); - return Blockchain.Default.GetUnspent(hash, index)?.ToJson(index); + return Blockchain.Singleton.Store.GetUnspent(hash, index)?.ToJson(index); } case "getvalidators": + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) { - var validators = Blockchain.Default.GetValidators(); - return Blockchain.Default.GetEnrollments().Select(p => + var validators = snapshot.GetValidators(); + return snapshot.GetEnrollments().Select(p => { JObject validator = new JObject(); validator["publickey"] = p.PublicKey.ToString(); @@ -237,6 +340,19 @@ protected virtual JObject Process(string method, JArray _params) return validator; }).ToArray(); } + case "getversion": + { + JObject json = new JObject(); + json["port"] = LocalNode.Singleton.ListenerPort; + json["nonce"] = LocalNode.Nonce; + json["useragent"] = LocalNode.UserAgent; + return json; + } + case "getwalletheight": + if (wallet == null) + throw new RpcException(-400, "Access denied."); + else + return (wallet.WalletHeight > 0) ? wallet.WalletHeight - 1 : 0; case "invoke": { UInt160 script_hash = UInt160.Parse(_params[0].AsString()); @@ -265,15 +381,154 @@ protected virtual JObject Process(string method, JArray _params) byte[] script = _params[0].AsString().HexToBytes(); return GetInvokeResult(script); } + case "listaddress": + if (wallet == null) + throw new RpcException(-400, "Access denied."); + else + return wallet.GetAccounts().Select(p => + { + JObject account = new JObject(); + account["address"] = p.Address; + account["haskey"] = p.HasKey; + account["label"] = p.Label; + account["watchonly"] = p.WatchOnly; + return account; + }).ToArray(); + case "sendfrom": + if (wallet == null) + throw new RpcException(-400, "Access denied"); + else + { + UIntBase assetId = UIntBase.Parse(_params[0].AsString()); + AssetDescriptor descriptor = new AssetDescriptor(assetId); + UInt160 from = _params[1].AsString().ToScriptHash(); + UInt160 to = _params[2].AsString().ToScriptHash(); + BigDecimal value = BigDecimal.Parse(_params[3].AsString(), descriptor.Decimals); + if (value.Sign <= 0) + throw new RpcException(-32602, "Invalid params"); + Fixed8 fee = _params.Count >= 5 ? Fixed8.Parse(_params[4].AsString()) : Fixed8.Zero; + if (fee < Fixed8.Zero) + throw new RpcException(-32602, "Invalid params"); + UInt160 change_address = _params.Count >= 6 ? _params[5].AsString().ToScriptHash() : null; + Transaction tx = wallet.MakeTransaction(null, new[] + { + new TransferOutput + { + AssetId = assetId, + Value = value, + ScriptHash = to + } + }, from: from, change_address: change_address, fee: fee); + if (tx == null) + throw new RpcException(-300, "Insufficient funds"); + ContractParametersContext context = new ContractParametersContext(tx); + wallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + wallet.ApplyTransaction(tx); + system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); + return tx.ToJson(); + } + else + { + return context.ToJson(); + } + } + case "sendmany": + if (wallet == null) + throw new RpcException(-400, "Access denied"); + else + { + JArray to = (JArray)_params[0]; + if (to.Count == 0) + throw new RpcException(-32602, "Invalid params"); + TransferOutput[] outputs = new TransferOutput[to.Count]; + for (int i = 0; i < to.Count; i++) + { + UIntBase asset_id = UIntBase.Parse(to[i]["asset"].AsString()); + AssetDescriptor descriptor = new AssetDescriptor(asset_id); + outputs[i] = new TransferOutput + { + AssetId = asset_id, + Value = BigDecimal.Parse(to[i]["value"].AsString(), descriptor.Decimals), + ScriptHash = to[i]["address"].AsString().ToScriptHash() + }; + if (outputs[i].Value.Sign <= 0) + throw new RpcException(-32602, "Invalid params"); + } + Fixed8 fee = _params.Count >= 2 ? Fixed8.Parse(_params[1].AsString()) : Fixed8.Zero; + if (fee < Fixed8.Zero) + throw new RpcException(-32602, "Invalid params"); + UInt160 change_address = _params.Count >= 3 ? _params[2].AsString().ToScriptHash() : null; + Transaction tx = wallet.MakeTransaction(null, outputs, change_address: change_address, fee: fee); + if (tx == null) + throw new RpcException(-300, "Insufficient funds"); + ContractParametersContext context = new ContractParametersContext(tx); + wallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + wallet.ApplyTransaction(tx); + system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); + return tx.ToJson(); + } + else + { + return context.ToJson(); + } + } case "sendrawtransaction": { Transaction tx = Transaction.DeserializeFrom(_params[0].AsString().HexToBytes()); - return LocalNode.Relay(tx); + RelayResultReason reason = system.Blockchain.Ask(tx).Result; + return GetRelayResult(reason); + } + case "sendtoaddress": + if (wallet == null) + throw new RpcException(-400, "Access denied"); + else + { + UIntBase assetId = UIntBase.Parse(_params[0].AsString()); + AssetDescriptor descriptor = new AssetDescriptor(assetId); + UInt160 scriptHash = _params[1].AsString().ToScriptHash(); + BigDecimal value = BigDecimal.Parse(_params[2].AsString(), descriptor.Decimals); + if (value.Sign <= 0) + throw new RpcException(-32602, "Invalid params"); + Fixed8 fee = _params.Count >= 4 ? Fixed8.Parse(_params[3].AsString()) : Fixed8.Zero; + if (fee < Fixed8.Zero) + throw new RpcException(-32602, "Invalid params"); + UInt160 change_address = _params.Count >= 5 ? _params[4].AsString().ToScriptHash() : null; + Transaction tx = wallet.MakeTransaction(null, new[] + { + new TransferOutput + { + AssetId = assetId, + Value = value, + ScriptHash = scriptHash + } + }, change_address: change_address, fee: fee); + if (tx == null) + throw new RpcException(-300, "Insufficient funds"); + ContractParametersContext context = new ContractParametersContext(tx); + wallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + wallet.ApplyTransaction(tx); + system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); + return tx.ToJson(); + } + else + { + return context.ToJson(); + } } case "submitblock": { Block block = _params[0].AsString().HexToBytes().AsSerializable(); - return LocalNode.Relay(block); + RelayResultReason reason = system.Blockchain.Ask(block).Result; + return GetRelayResult(reason); } case "validateaddress": { @@ -281,7 +536,7 @@ protected virtual JObject Process(string method, JArray _params) UInt160 scriptHash; try { - scriptHash = Wallet.ToScriptHash(_params[0].AsString()); + scriptHash = _params[0].AsString().ToScriptHash(); } catch { @@ -291,56 +546,6 @@ protected virtual JObject Process(string method, JArray _params) json["isvalid"] = scriptHash != null; return json; } - case "getpeers": - { - JObject json = new JObject(); - - { - JArray unconnectedPeers = new JArray(); - foreach (IPEndPoint peer in LocalNode.GetUnconnectedPeers()) - { - JObject peerJson = new JObject(); - peerJson["address"] = peer.Address.ToString(); - peerJson["port"] = peer.Port; - unconnectedPeers.Add(peerJson); - } - json["unconnected"] = unconnectedPeers; - } - - { - JArray badPeers = new JArray(); - foreach (IPEndPoint peer in LocalNode.GetBadPeers()) - { - JObject peerJson = new JObject(); - peerJson["address"] = peer.Address.ToString(); - peerJson["port"] = peer.Port; - badPeers.Add(peerJson); - } - json["bad"] = badPeers; - } - - { - JArray connectedPeers = new JArray(); - foreach (RemoteNode node in LocalNode.GetRemoteNodes()) - { - JObject peerJson = new JObject(); - peerJson["address"] = node.RemoteEndpoint.Address.ToString(); - peerJson["port"] = node.ListenerEndpoint?.Port ?? 0; - connectedPeers.Add(peerJson); - } - json["connected"] = connectedPeers; - } - - return json; - } - case "getversion": - { - JObject json = new JObject(); - json["port"] = LocalNode.Port; - json["nonce"] = LocalNode.Nonce; - json["useragent"] = LocalNode.UserAgent; - return json; - } default: throw new RpcException(-32601, "Method not found"); } @@ -423,7 +628,7 @@ private JObject ProcessRequest(HttpContext context, JObject request) { string method = request["method"].AsString(); JArray _params = (JArray)request["params"]; - foreach (RpcPlugin plugin in RpcPlugin.Instances) + foreach (IRpcPlugin plugin in Plugin.RpcPlugins) { result = plugin.OnProcess(context, method, _params); if (result != null) break; @@ -475,4 +680,4 @@ public void Start(int port, string sslCert = null, string password = null) host.Start(); } } -} +} \ No newline at end of file diff --git a/neo/Network/RemoteNode.cs b/neo/Network/RemoteNode.cs deleted file mode 100644 index 3896e6bbac..0000000000 --- a/neo/Network/RemoteNode.cs +++ /dev/null @@ -1,543 +0,0 @@ -using Neo.Core; -using Neo.Cryptography; -using Neo.IO; -using Neo.Network.Payloads; -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Neo.Network -{ - public abstract class RemoteNode : IDisposable - { - public event EventHandler Disconnected; - internal event EventHandler InventoryReceived; - internal event EventHandler PeersReceived; - - private static readonly TimeSpan HalfMinute = TimeSpan.FromSeconds(30); - private static readonly TimeSpan OneMinute = TimeSpan.FromMinutes(1); - private static readonly TimeSpan HalfHour = TimeSpan.FromMinutes(30); - private static readonly TimeSpan MissionExpiration = TimeSpan.FromMinutes(1); - - private Queue message_queue_high = new Queue(); - private Queue message_queue_low = new Queue(); - private static Dictionary missions_global = new Dictionary(); - private HashSet missions = new HashSet(); - private DateTime mission_start = DateTime.Now.AddYears(100); - - private LocalNode localNode; - private int disposed = 0; - private BloomFilter bloom_filter; - - public VersionPayload Version { get; private set; } - public IPEndPoint RemoteEndpoint { get; protected set; } - public IPEndPoint ListenerEndpoint { get; protected set; } - - protected RemoteNode(LocalNode localNode) - { - this.localNode = localNode; - } - - public virtual void Disconnect(bool error) - { - if (Interlocked.Exchange(ref disposed, 1) == 0) - { - Disconnected?.Invoke(this, error); - bool needSync = false; - lock (missions_global) - lock (missions) - if (missions.Count > 0) - { - foreach (UInt256 hash in missions) - missions_global.Remove(hash); - needSync = true; - } - if (needSync) - localNode.RequestGetBlocks(); - } - } - - public void Dispose() - { - Disconnect(false); - } - - public void EnqueueMessage(string command, ISerializable payload = null) - { - bool is_single = false; - switch (command) - { - case "addr": - case "getaddr": - case "getblocks": - case "getheaders": - case "mempool": - is_single = true; - break; - } - Queue message_queue; - switch (command) - { - case "alert": - case "consensus": - case "filteradd": - case "filterclear": - case "filterload": - case "getaddr": - case "mempool": - message_queue = message_queue_high; - break; - default: - message_queue = message_queue_low; - break; - } - lock (message_queue) - { - if (!is_single || message_queue.All(p => p.Command != command)) - { - message_queue.Enqueue(Message.Create(command, payload)); - } - } - } - - private void OnAddrMessageReceived(AddrPayload payload) - { - IPEndPoint[] peers = payload.AddressList.Select(p => p.EndPoint).Where(p => p.Port != localNode.Port || !LocalNode.LocalAddresses.Contains(p.Address)).ToArray(); - if (peers.Length > 0) PeersReceived?.Invoke(this, peers); - } - - private void OnFilterAddMessageReceived(FilterAddPayload payload) - { - if (bloom_filter != null) - bloom_filter.Add(payload.Data); - } - - private void OnFilterClearMessageReceived() - { - bloom_filter = null; - } - - private void OnFilterLoadMessageReceived(FilterLoadPayload payload) - { - bloom_filter = new BloomFilter(payload.Filter.Length * 8, payload.K, payload.Tweak, payload.Filter); - } - - private void OnGetAddrMessageReceived() - { - if (!localNode.ServiceEnabled) return; - AddrPayload payload; - lock (localNode.connectedPeers) - { - const int MaxCountToSend = 200; - IEnumerable peers = localNode.connectedPeers.Where(p => p.ListenerEndpoint != null && p.Version != null); - if (localNode.connectedPeers.Count > MaxCountToSend) - { - Random rand = new Random(); - peers = peers.OrderBy(p => rand.Next()); - } - peers = peers.Take(MaxCountToSend); - payload = AddrPayload.Create(peers.Select(p => NetworkAddressWithTime.Create(p.ListenerEndpoint, p.Version.Services, p.Version.Timestamp)).ToArray()); - } - EnqueueMessage("addr", payload); - } - - private void OnGetBlocksMessageReceived(GetBlocksPayload payload) - { - if (!localNode.ServiceEnabled) return; - if (Blockchain.Default == null) return; - UInt256 hash = payload.HashStart.Select(p => Blockchain.Default.GetHeader(p)).Where(p => p != null).OrderBy(p => p.Index).Select(p => p.Hash).FirstOrDefault(); - if (hash == null || hash == payload.HashStop) return; - List hashes = new List(); - do - { - hash = Blockchain.Default.GetNextBlockHash(hash); - if (hash == null) break; - hashes.Add(hash); - } while (hash != payload.HashStop && hashes.Count < 500); - if (hashes.Count > 0) - { - EnqueueMessage("inv", InvPayload.Create(InventoryType.Block, hashes.ToArray())); - } - } - - private void OnGetDataMessageReceived(InvPayload payload) - { - foreach (UInt256 hash in payload.Hashes.Distinct()) - { - IInventory inventory; - if (!localNode.RelayCache.TryGet(hash, out inventory) && !localNode.ServiceEnabled) - continue; - switch (payload.Type) - { - case InventoryType.TX: - if (inventory == null) - inventory = LocalNode.GetTransaction(hash); - if (inventory == null && Blockchain.Default != null) - inventory = Blockchain.Default.GetTransaction(hash); - if (inventory is Transaction) - EnqueueMessage("tx", inventory); - break; - case InventoryType.Block: - if (inventory == null && Blockchain.Default != null) - inventory = Blockchain.Default.GetBlock(hash); - if (inventory is Block block) - { - BloomFilter filter = bloom_filter; - if (filter == null) - { - EnqueueMessage("block", inventory); - } - else - { - BitArray flags = new BitArray(block.Transactions.Select(p => TestFilter(filter, p)).ToArray()); - EnqueueMessage("merkleblock", MerkleBlockPayload.Create(block, flags)); - } - } - break; - case InventoryType.Consensus: - if (inventory != null) - EnqueueMessage("consensus", inventory); - break; - } - } - } - - private void OnGetHeadersMessageReceived(GetBlocksPayload payload) - { - if (!localNode.ServiceEnabled) return; - if (Blockchain.Default == null) return; - UInt256 hash = payload.HashStart.Select(p => Blockchain.Default.GetHeader(p)).Where(p => p != null).OrderBy(p => p.Index).Select(p => p.Hash).FirstOrDefault(); - if (hash == null || hash == payload.HashStop) return; - List
headers = new List
(); - do - { - hash = Blockchain.Default.GetNextBlockHash(hash); - if (hash == null) break; - headers.Add(Blockchain.Default.GetHeader(hash)); - } while (hash != payload.HashStop && headers.Count < 2000); - EnqueueMessage("headers", HeadersPayload.Create(headers)); - } - - private void OnHeadersMessageReceived(HeadersPayload payload) - { - if (Blockchain.Default == null) return; - Blockchain.Default.AddHeaders(payload.Headers); - if (Blockchain.Default.HeaderHeight < Version.StartHeight) - { - EnqueueMessage("getheaders", GetBlocksPayload.Create(Blockchain.Default.CurrentHeaderHash)); - } - } - - private void OnInventoryReceived(IInventory inventory) - { - lock (missions_global) - { - lock (missions) - { - missions_global.Remove(inventory.Hash); - missions.Remove(inventory.Hash); - if (missions.Count == 0) - mission_start = DateTime.Now.AddYears(100); - else - mission_start = DateTime.Now; - } - } - if (inventory is MinerTransaction) return; - InventoryReceived?.Invoke(this, inventory); - } - - private void OnInvMessageReceived(InvPayload payload) - { - if (payload.Type != InventoryType.TX && payload.Type != InventoryType.Block && payload.Type != InventoryType.Consensus) - return; - HashSet hashes = new HashSet(payload.Hashes); - lock (LocalNode.KnownHashes) - { - hashes.RemoveWhere(p => LocalNode.KnownHashes.TryGetValue(p, out DateTime time) && time + LocalNode.HashesExpiration >= DateTime.UtcNow); - } - if (hashes.Count == 0) return; - lock (missions_global) - { - lock (missions) - { - if (localNode.GlobalMissionsEnabled) - hashes.RemoveWhere(p => missions_global.TryGetValue(p, out DateTime time) && time + MissionExpiration >= DateTime.UtcNow); - if (hashes.Count > 0) - { - if (missions.Count == 0) mission_start = DateTime.Now; - foreach (UInt256 hash in hashes) - if (!missions_global.ContainsKey(hash)) - missions_global.Add(hash, DateTime.UtcNow); - missions.UnionWith(hashes); - } - } - } - if (hashes.Count == 0) return; - EnqueueMessage("getdata", InvPayload.Create(payload.Type, hashes.ToArray())); - } - - private void OnMemPoolMessageReceived() - { - EnqueueMessage("inv", InvPayload.Create(InventoryType.TX, LocalNode.GetMemoryPool().Select(p => p.Hash).ToArray())); - } - - private void OnMessageReceived(Message message) - { - switch (message.Command) - { - case "addr": - OnAddrMessageReceived(message.Payload.AsSerializable()); - break; - case "block": - OnInventoryReceived(message.Payload.AsSerializable()); - break; - case "consensus": - OnInventoryReceived(message.Payload.AsSerializable()); - break; - case "filteradd": - OnFilterAddMessageReceived(message.Payload.AsSerializable()); - break; - case "filterclear": - OnFilterClearMessageReceived(); - break; - case "filterload": - OnFilterLoadMessageReceived(message.Payload.AsSerializable()); - break; - case "getaddr": - OnGetAddrMessageReceived(); - break; - case "getblocks": - OnGetBlocksMessageReceived(message.Payload.AsSerializable()); - break; - case "getdata": - OnGetDataMessageReceived(message.Payload.AsSerializable()); - break; - case "getheaders": - OnGetHeadersMessageReceived(message.Payload.AsSerializable()); - break; - case "headers": - OnHeadersMessageReceived(message.Payload.AsSerializable()); - break; - case "inv": - OnInvMessageReceived(message.Payload.AsSerializable()); - break; - case "mempool": - OnMemPoolMessageReceived(); - break; - case "tx": - if (message.Payload.Length <= 1024 * 1024) - OnInventoryReceived(Transaction.DeserializeFrom(message.Payload)); - break; - case "verack": - case "version": - Disconnect(true); - break; - case "alert": - case "merkleblock": - case "notfound": - case "ping": - case "pong": - case "reject": - default: - //暂时忽略 - break; - } - } - - protected abstract Task ReceiveMessageAsync(TimeSpan timeout); - - internal bool Relay(IInventory data) - { - if (Version?.Relay != true) return false; - if (data.InventoryType == InventoryType.TX) - { - BloomFilter filter = bloom_filter; - if (filter != null && !TestFilter(filter, (Transaction)data)) - return false; - } - EnqueueMessage("inv", InvPayload.Create(data.InventoryType, data.Hash)); - return true; - } - - internal void Relay(IEnumerable transactions) - { - if (Version?.Relay != true) return; - BloomFilter filter = bloom_filter; - if (filter != null) - transactions = transactions.Where(p => TestFilter(filter, p)); - UInt256[] hashes = transactions.Select(p => p.Hash).ToArray(); - if (hashes.Length == 0) return; - EnqueueMessage("inv", InvPayload.Create(InventoryType.TX, hashes)); - } - - internal void RequestMemoryPool() - { - EnqueueMessage("mempool", null); - } - - internal void RequestPeers() - { - EnqueueMessage("getaddr", null); - } - - protected abstract Task SendMessageAsync(Message message); - - internal async void StartProtocol() - { -#if !NET47 - //There is a bug in .NET Core 2.0 that blocks async method which returns void. - await Task.Yield(); -#endif - if (!await SendMessageAsync(Message.Create("version", VersionPayload.Create(localNode.Port, localNode.Nonce, localNode.UserAgent)))) - return; - Message message = await ReceiveMessageAsync(HalfMinute); - if (message == null) return; - if (message.Command != "version") - { - Disconnect(true); - return; - } - try - { - Version = message.Payload.AsSerializable(); - } - catch (EndOfStreamException) - { - Disconnect(false); - return; - } - catch (FormatException) - { - Disconnect(true); - return; - } - if (Version.Nonce == localNode.Nonce) - { - Disconnect(true); - return; - } - bool isSelf; - lock (localNode.connectedPeers) - { - isSelf = localNode.connectedPeers.Where(p => p != this).Any(p => p.RemoteEndpoint.Address.Equals(RemoteEndpoint.Address) && p.Version?.Nonce == Version.Nonce); - } - if (isSelf) - { - Disconnect(false); - return; - } - if (ListenerEndpoint == null && Version.Port > 0) - { - ListenerEndpoint = new IPEndPoint(RemoteEndpoint.Address, Version.Port); - } - if (!await SendMessageAsync(Message.Create("verack"))) return; - message = await ReceiveMessageAsync(HalfMinute); - if (message == null) return; - if (message.Command != "verack") - { - Disconnect(true); - return; - } - if (Blockchain.Default?.HeaderHeight < Version.StartHeight) - { - EnqueueMessage("getheaders", GetBlocksPayload.Create(Blockchain.Default.CurrentHeaderHash)); - } - StartSendLoop(); - while (disposed == 0) - { - if (Blockchain.Default != null) - { - if (missions.Count == 0 && Blockchain.Default.Height < Version.StartHeight) - { - EnqueueMessage("getblocks", GetBlocksPayload.Create(Blockchain.Default.CurrentBlockHash)); - } - } - TimeSpan timeout = missions.Count == 0 ? HalfHour : OneMinute; - message = await ReceiveMessageAsync(timeout); - if (message == null) break; - if (DateTime.Now - mission_start > OneMinute - && message.Command != "block" && message.Command != "consensus" && message.Command != "tx") - { - Disconnect(false); - break; - } - try - { - OnMessageReceived(message); - } - catch (EndOfStreamException) - { - Disconnect(false); - break; - } - catch (FormatException) - { - Disconnect(true); - break; - } - } - } - - private async void StartSendLoop() - { -#if !NET47 - //There is a bug in .NET Core 2.0 that blocks async method which returns void. - await Task.Yield(); -#endif - while (disposed == 0) - { - Message message = null; - lock (message_queue_high) - { - if (message_queue_high.Count > 0) - { - message = message_queue_high.Dequeue(); - } - } - if (message == null) - { - lock (message_queue_low) - { - if (message_queue_low.Count > 0) - { - message = message_queue_low.Dequeue(); - } - } - } - if (message == null) - { - for (int i = 0; i < 10 && disposed == 0; i++) - { - Thread.Sleep(100); - } - } - else - { - await SendMessageAsync(message); - } - } - } - - private bool TestFilter(BloomFilter filter, Transaction tx) - { - if (filter.Check(tx.Hash.ToArray())) return true; - if (tx.Outputs.Any(p => filter.Check(p.ScriptHash.ToArray()))) return true; - if (tx.Inputs.Any(p => filter.Check(p.ToArray()))) return true; - if (tx.Scripts.Any(p => filter.Check(p.ScriptHash.ToArray()))) - return true; - if (tx.Type == TransactionType.RegisterTransaction) - { -#pragma warning disable CS0612 - RegisterTransaction asset = (RegisterTransaction)tx; - if (filter.Check(asset.Admin.ToArray())) return true; -#pragma warning restore CS0612 - } - return false; - } - } -} diff --git a/neo/Network/TcpRemoteNode.cs b/neo/Network/TcpRemoteNode.cs deleted file mode 100644 index 62cd92ea4b..0000000000 --- a/neo/Network/TcpRemoteNode.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Neo.IO; -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -namespace Neo.Network -{ - internal class TcpRemoteNode : RemoteNode - { - private Socket socket; - private NetworkStream stream; - private bool connected = false; - private int disposed = 0; - - public TcpRemoteNode(LocalNode localNode, IPEndPoint remoteEndpoint) - : base(localNode) - { - this.socket = new Socket(remoteEndpoint.Address.IsIPv4MappedToIPv6 ? AddressFamily.InterNetwork : remoteEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - this.ListenerEndpoint = remoteEndpoint; - } - - public TcpRemoteNode(LocalNode localNode, Socket socket) - : base(localNode) - { - this.socket = socket; - OnConnected(); - } - - public async Task ConnectAsync() - { - IPAddress address = ListenerEndpoint.Address; - if (address.IsIPv4MappedToIPv6) - address = address.MapToIPv4(); - try - { - await socket.ConnectAsync(address, ListenerEndpoint.Port); - OnConnected(); - } - catch (SocketException) - { - Disconnect(false); - return false; - } - return true; - } - - public override void Disconnect(bool error) - { - if (Interlocked.Exchange(ref disposed, 1) == 0) - { - if (stream != null) stream.Dispose(); - socket.Dispose(); - base.Disconnect(error); - } - } - - private void OnConnected() - { - IPEndPoint remoteEndpoint = (IPEndPoint)socket.RemoteEndPoint; - RemoteEndpoint = new IPEndPoint(remoteEndpoint.Address.MapToIPv6(), remoteEndpoint.Port); - stream = new NetworkStream(socket); - connected = true; - } - - protected override async Task ReceiveMessageAsync(TimeSpan timeout) - { - CancellationTokenSource source = new CancellationTokenSource(timeout); - //Stream.ReadAsync doesn't support CancellationToken - //see: https://stackoverflow.com/questions/20131434/cancel-networkstream-readasync-using-tcplistener - source.Token.Register(() => Disconnect(false)); - try - { - return await Message.DeserializeFromAsync(stream, source.Token); - } - catch (ArgumentException) { } - catch (ObjectDisposedException) { } - catch (Exception ex) when (ex is FormatException || ex is IOException || ex is OperationCanceledException) - { - Disconnect(false); - } - finally - { - source.Dispose(); - } - return null; - } - - protected override async Task SendMessageAsync(Message message) - { - if (!connected) throw new InvalidOperationException(); - if (disposed > 0) return false; - byte[] buffer = message.ToArray(); - CancellationTokenSource source = new CancellationTokenSource(30000); - //Stream.WriteAsync doesn't support CancellationToken - //see: https://stackoverflow.com/questions/20131434/cancel-networkstream-readasync-using-tcplistener - source.Token.Register(() => Disconnect(false)); - try - { - await stream.WriteAsync(buffer, 0, buffer.Length, source.Token); - return true; - } - catch (ObjectDisposedException) { } - catch (Exception ex) when (ex is IOException || ex is OperationCanceledException) - { - Disconnect(false); - } - finally - { - source.Dispose(); - } - return false; - } - } -} diff --git a/neo/Network/UPnP.cs b/neo/Network/UPnP.cs index f5e5419c6d..7e4f123f93 100644 --- a/neo/Network/UPnP.cs +++ b/neo/Network/UPnP.cs @@ -4,7 +4,6 @@ using System.Net; using System.Net.Sockets; using System.Text; -using System.Threading.Tasks; using System.Xml; namespace Neo.Network @@ -15,7 +14,7 @@ public class UPnP public static TimeSpan TimeOut { get; set; } = TimeSpan.FromSeconds(3); - public static async Task DiscoverAsync() + public static bool Discover() { Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); s.ReceiveTimeout = (int)TimeOut.TotalMilliseconds; @@ -51,7 +50,7 @@ public static async Task DiscoverAsync() { resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9); resp = resp.Substring(0, resp.IndexOf("\r")).Trim(); - if (!string.IsNullOrEmpty(_serviceUrl = await GetServiceUrlAsync(resp))) + if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp))) { return true; } @@ -60,13 +59,13 @@ public static async Task DiscoverAsync() return false; } - private static async Task GetServiceUrlAsync(string resp) + private static string GetServiceUrl(string resp) { try { XmlDocument desc = new XmlDocument(); HttpWebRequest request = WebRequest.CreateHttp(resp); - WebResponse response = await request.GetResponseAsync(); + WebResponse response = request.GetResponse(); desc.Load(response.GetResponseStream()); XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable); nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); @@ -89,22 +88,22 @@ private static string CombineUrls(string resp, string p) return resp.Substring(0, n) + p; } - public static async Task ForwardPortAsync(int port, ProtocolType protocol, string description) + public static void ForwardPort(int port, ProtocolType protocol, string description) { if (string.IsNullOrEmpty(_serviceUrl)) throw new Exception("No UPnP service available or Discover() has not been called"); - XmlDocument xdoc = await SOAPRequestAsync(_serviceUrl, "" + + XmlDocument xdoc = SOAPRequest(_serviceUrl, "" + "" + port.ToString() + "" + protocol.ToString().ToUpper() + "" + - "" + port.ToString() + "" + (await Dns.GetHostAddressesAsync(Dns.GetHostName())).First(p => p.AddressFamily == AddressFamily.InterNetwork).ToString() + + "" + port.ToString() + "" + Dns.GetHostAddresses(Dns.GetHostName()).First(p => p.AddressFamily == AddressFamily.InterNetwork).ToString() + "1" + description + "0", "AddPortMapping"); } - public static async Task DeleteForwardingRuleAsync(int port, ProtocolType protocol) + public static void DeleteForwardingRule(int port, ProtocolType protocol) { if (string.IsNullOrEmpty(_serviceUrl)) throw new Exception("No UPnP service available or Discover() has not been called"); - XmlDocument xdoc = await SOAPRequestAsync(_serviceUrl, + XmlDocument xdoc = SOAPRequest(_serviceUrl, "" + "" + "" + @@ -113,11 +112,11 @@ public static async Task DeleteForwardingRuleAsync(int port, ProtocolType protoc "", "DeletePortMapping"); } - public static async Task GetExternalIPAsync() + public static IPAddress GetExternalIP() { if (string.IsNullOrEmpty(_serviceUrl)) throw new Exception("No UPnP service available or Discover() has not been called"); - XmlDocument xdoc = await SOAPRequestAsync(_serviceUrl, "" + + XmlDocument xdoc = SOAPRequest(_serviceUrl, "" + "", "GetExternalIPAddress"); XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable); nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); @@ -125,7 +124,7 @@ public static async Task GetExternalIPAsync() return IPAddress.Parse(IP); } - private static async Task SOAPRequestAsync(string url, string soap, string function) + private static XmlDocument SOAPRequest(string url, string soap, string function) { string req = "" + "" + @@ -138,10 +137,10 @@ private static async Task SOAPRequestAsync(string url, string soap, byte[] b = Encoding.UTF8.GetBytes(req); r.Headers["SOAPACTION"] = "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + function + "\""; r.ContentType = "text/xml; charset=\"utf-8\""; - Stream reqs = await r.GetRequestStreamAsync(); + Stream reqs = r.GetRequestStream(); reqs.Write(b, 0, b.Length); XmlDocument resp = new XmlDocument(); - WebResponse wres = await r.GetResponseAsync(); + WebResponse wres = r.GetResponse(); Stream ress = wres.GetResponseStream(); resp.Load(ress); return resp; diff --git a/neo/Network/WebSocketRemoteNode.cs b/neo/Network/WebSocketRemoteNode.cs deleted file mode 100644 index 6969aa2391..0000000000 --- a/neo/Network/WebSocketRemoteNode.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Neo.IO; -using System; -using System.IO; -using System.Net; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace Neo.Network -{ - internal class WebSocketRemoteNode : RemoteNode - { - private WebSocket socket; - private bool connected = false; - private int disposed = 0; - - public WebSocketRemoteNode(LocalNode localNode, WebSocket socket, IPEndPoint remoteEndpoint) - : base(localNode) - { - this.socket = socket; - this.RemoteEndpoint = new IPEndPoint(remoteEndpoint.Address.MapToIPv6(), remoteEndpoint.Port); - this.connected = true; - } - - public override void Disconnect(bool error) - { - if (Interlocked.Exchange(ref disposed, 1) == 0) - { - socket.Dispose(); - base.Disconnect(error); - } - } - - protected override async Task ReceiveMessageAsync(TimeSpan timeout) - { - CancellationTokenSource source = new CancellationTokenSource(timeout); - try - { - return await Message.DeserializeFromAsync(socket, source.Token); - } - catch (ArgumentException) { } - catch (ObjectDisposedException) { } - catch (Exception ex) when (ex is FormatException || ex is IOException || ex is WebSocketException || ex is OperationCanceledException) - { - Disconnect(false); - } - finally - { - source.Dispose(); - } - return null; - } - - protected override async Task SendMessageAsync(Message message) - { - if (!connected) throw new InvalidOperationException(); - if (disposed > 0) return false; - ArraySegment segment = new ArraySegment(message.ToArray()); - CancellationTokenSource source = new CancellationTokenSource(10000); - try - { - await socket.SendAsync(segment, WebSocketMessageType.Binary, true, source.Token); - return true; - } - catch (ObjectDisposedException) { } - catch (Exception ex) when (ex is WebSocketException || ex is OperationCanceledException) - { - Disconnect(false); - } - finally - { - source.Dispose(); - } - return false; - } - } -} diff --git a/neo/Persistence/CloneSnapshot.cs b/neo/Persistence/CloneSnapshot.cs new file mode 100644 index 0000000000..163856f21b --- /dev/null +++ b/neo/Persistence/CloneSnapshot.cs @@ -0,0 +1,42 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Caching; +using Neo.IO.Wrappers; +using Neo.Ledger; + +namespace Neo.Persistence +{ + internal class CloneSnapshot : Snapshot + { + public override DataCache Blocks { get; } + public override DataCache Transactions { get; } + public override DataCache Accounts { get; } + public override DataCache UnspentCoins { get; } + public override DataCache SpentCoins { get; } + public override DataCache Validators { get; } + public override DataCache Assets { get; } + public override DataCache Contracts { get; } + public override DataCache Storages { get; } + public override DataCache HeaderHashList { get; } + public override MetaDataCache ValidatorsCount { get; } + public override MetaDataCache BlockHashIndex { get; } + public override MetaDataCache HeaderHashIndex { get; } + + public CloneSnapshot(Snapshot snapshot) + { + this.PersistingBlock = snapshot.PersistingBlock; + this.Blocks = snapshot.Blocks.CreateSnapshot(); + this.Transactions = snapshot.Transactions.CreateSnapshot(); + this.Accounts = snapshot.Accounts.CreateSnapshot(); + this.UnspentCoins = snapshot.UnspentCoins.CreateSnapshot(); + this.SpentCoins = snapshot.SpentCoins.CreateSnapshot(); + this.Validators = snapshot.Validators.CreateSnapshot(); + this.Assets = snapshot.Assets.CreateSnapshot(); + this.Contracts = snapshot.Contracts.CreateSnapshot(); + this.Storages = snapshot.Storages.CreateSnapshot(); + this.HeaderHashList = snapshot.HeaderHashList.CreateSnapshot(); + this.ValidatorsCount = snapshot.ValidatorsCount.CreateSnapshot(); + this.BlockHashIndex = snapshot.BlockHashIndex.CreateSnapshot(); + this.HeaderHashIndex = snapshot.HeaderHashIndex.CreateSnapshot(); + } + } +} diff --git a/neo/Persistence/Helper.cs b/neo/Persistence/Helper.cs new file mode 100644 index 0000000000..9b31487a76 --- /dev/null +++ b/neo/Persistence/Helper.cs @@ -0,0 +1,117 @@ +using Neo.Cryptography.ECC; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Persistence +{ + public static class Helper + { + public static bool ContainsBlock(this IPersistence persistence, UInt256 hash) + { + BlockState state = persistence.Blocks.TryGet(hash); + if (state == null) return false; + return state.TrimmedBlock.IsBlock; + } + + public static bool ContainsTransaction(this IPersistence persistence, UInt256 hash) + { + TransactionState state = persistence.Transactions.TryGet(hash); + return state != null; + } + + public static Block GetBlock(this IPersistence persistence, uint index) + { + UInt256 hash = Blockchain.Singleton.GetBlockHash(index); + if (hash == null) return null; + return persistence.GetBlock(hash); + } + + public static Block GetBlock(this IPersistence persistence, UInt256 hash) + { + BlockState state = persistence.Blocks.TryGet(hash); + if (state == null) return null; + if (!state.TrimmedBlock.IsBlock) return null; + return state.TrimmedBlock.GetBlock(persistence.Transactions); + } + + public static IEnumerable GetEnrollments(this IPersistence persistence) + { + HashSet sv = new HashSet(Blockchain.StandbyValidators); + return persistence.Validators.Find().Select(p => p.Value).Where(p => p.Registered || sv.Contains(p.PublicKey)); + } + + public static Header GetHeader(this IPersistence persistence, uint index) + { + UInt256 hash = Blockchain.Singleton.GetBlockHash(index); + if (hash == null) return null; + return persistence.GetHeader(hash); + } + + public static Header GetHeader(this IPersistence persistence, UInt256 hash) + { + return persistence.Blocks.TryGet(hash)?.TrimmedBlock.Header; + } + + public static UInt256 GetNextBlockHash(this IPersistence persistence, UInt256 hash) + { + BlockState state = persistence.Blocks.TryGet(hash); + if (state == null) return null; + return Blockchain.Singleton.GetBlockHash(state.TrimmedBlock.Index + 1); + } + + public static long GetSysFeeAmount(this IPersistence persistence, uint height) + { + return persistence.GetSysFeeAmount(Blockchain.Singleton.GetBlockHash(height)); + } + + public static long GetSysFeeAmount(this IPersistence persistence, UInt256 hash) + { + BlockState block_state = persistence.Blocks.TryGet(hash); + if (block_state == null) return 0; + return block_state.SystemFeeAmount; + } + + public static Transaction GetTransaction(this IPersistence persistence, UInt256 hash) + { + return persistence.Transactions.TryGet(hash)?.Transaction; + } + + public static TransactionOutput GetUnspent(this IPersistence persistence, UInt256 hash, ushort index) + { + UnspentCoinState state = persistence.UnspentCoins.TryGet(hash); + if (state == null) return null; + if (index >= state.Items.Length) return null; + if (state.Items[index].HasFlag(CoinState.Spent)) return null; + return persistence.GetTransaction(hash).Outputs[index]; + } + + public static IEnumerable GetUnspent(this IPersistence persistence, UInt256 hash) + { + List outputs = new List(); + UnspentCoinState state = persistence.UnspentCoins.TryGet(hash); + if (state != null) + { + Transaction tx = persistence.GetTransaction(hash); + for (int i = 0; i < state.Items.Length; i++) + if (!state.Items[i].HasFlag(CoinState.Spent)) + outputs.Add(tx.Outputs[i]); + } + return outputs; + } + + public static bool IsDoubleSpend(this IPersistence persistence, Transaction tx) + { + if (tx.Inputs.Length == 0) return false; + foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) + { + UnspentCoinState state = persistence.UnspentCoins.TryGet(group.Key); + if (state == null) return true; + if (group.Any(p => p.PrevIndex >= state.Items.Length || state.Items[p.PrevIndex].HasFlag(CoinState.Spent))) + return true; + } + return false; + } + } +} diff --git a/neo/Persistence/IPersistence.cs b/neo/Persistence/IPersistence.cs new file mode 100644 index 0000000000..eb8f58db4f --- /dev/null +++ b/neo/Persistence/IPersistence.cs @@ -0,0 +1,24 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Caching; +using Neo.IO.Wrappers; +using Neo.Ledger; + +namespace Neo.Persistence +{ + public interface IPersistence + { + DataCache Blocks { get; } + DataCache Transactions { get; } + DataCache Accounts { get; } + DataCache UnspentCoins { get; } + DataCache SpentCoins { get; } + DataCache Validators { get; } + DataCache Assets { get; } + DataCache Contracts { get; } + DataCache Storages { get; } + DataCache HeaderHashList { get; } + MetaDataCache ValidatorsCount { get; } + MetaDataCache BlockHashIndex { get; } + MetaDataCache HeaderHashIndex { get; } + } +} diff --git a/neo/IO/Data/LevelDB/DbCache.cs b/neo/Persistence/LevelDB/DbCache.cs similarity index 55% rename from neo/IO/Data/LevelDB/DbCache.cs rename to neo/Persistence/LevelDB/DbCache.cs index f26fcbc308..b3d207d4de 100644 --- a/neo/IO/Data/LevelDB/DbCache.cs +++ b/neo/Persistence/LevelDB/DbCache.cs @@ -1,20 +1,24 @@ -using Neo.IO.Caching; +using Neo.IO; +using Neo.IO.Caching; +using Neo.IO.Data.LevelDB; using System; using System.Collections.Generic; -namespace Neo.IO.Data.LevelDB +namespace Neo.Persistence.LevelDB { - public class DbCache : DataCache + internal class DbCache : DataCache where TKey : IEquatable, ISerializable, new() where TValue : class, ICloneable, ISerializable, new() { - private DB db; - private WriteBatch batch; - private byte prefix; + private readonly DB db; + private readonly ReadOptions options; + private readonly WriteBatch batch; + private readonly byte prefix; - public DbCache(DB db, byte prefix, WriteBatch batch = null) + public DbCache(DB db, ReadOptions options, WriteBatch batch, byte prefix) { this.db = db; + this.options = options ?? ReadOptions.Default; this.batch = batch; this.prefix = prefix; } @@ -31,17 +35,17 @@ public override void DeleteInternal(TKey key) protected override IEnumerable> FindInternal(byte[] key_prefix) { - return db.Find(ReadOptions.Default, SliceBuilder.Begin(prefix).Add(key_prefix), (k, v) => new KeyValuePair(k.ToArray().AsSerializable(1), v.ToArray().AsSerializable())); + return db.Find(options, SliceBuilder.Begin(prefix).Add(key_prefix), (k, v) => new KeyValuePair(k.ToArray().AsSerializable(1), v.ToArray().AsSerializable())); } protected override TValue GetInternal(TKey key) { - return db.Get(ReadOptions.Default, prefix, key); + return db.Get(options, prefix, key); } protected override TValue TryGetInternal(TKey key) { - return db.TryGet(ReadOptions.Default, prefix, key); + return db.TryGet(options, prefix, key); } protected override void UpdateInternal(TKey key, TValue value) diff --git a/neo/Persistence/LevelDB/DbMetaDataCache.cs b/neo/Persistence/LevelDB/DbMetaDataCache.cs new file mode 100644 index 0000000000..e2c03ca2fd --- /dev/null +++ b/neo/Persistence/LevelDB/DbMetaDataCache.cs @@ -0,0 +1,42 @@ +using Neo.IO; +using Neo.IO.Caching; +using Neo.IO.Data.LevelDB; +using System; + +namespace Neo.Persistence.LevelDB +{ + internal class DbMetaDataCache : MetaDataCache + where T : class, ICloneable, ISerializable, new() + { + private readonly DB db; + private readonly ReadOptions options; + private readonly WriteBatch batch; + private readonly byte prefix; + + public DbMetaDataCache(DB db, ReadOptions options, WriteBatch batch, byte prefix, Func factory = null) + : base(factory) + { + this.db = db; + this.options = options ?? ReadOptions.Default; + this.batch = batch; + this.prefix = prefix; + } + + protected override void AddInternal(T item) + { + batch?.Put(prefix, item.ToArray()); + } + + protected override T TryGetInternal() + { + if (!db.TryGet(options, prefix, out Slice slice)) + return null; + return slice.ToArray().AsSerializable(); + } + + protected override void UpdateInternal(T item) + { + batch?.Put(prefix, item.ToArray()); + } + } +} diff --git a/neo/Persistence/LevelDB/DbSnapshot.cs b/neo/Persistence/LevelDB/DbSnapshot.cs new file mode 100644 index 0000000000..b8391315b8 --- /dev/null +++ b/neo/Persistence/LevelDB/DbSnapshot.cs @@ -0,0 +1,62 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Caching; +using Neo.IO.Data.LevelDB; +using Neo.IO.Wrappers; +using Neo.Ledger; +using LSnapshot = Neo.IO.Data.LevelDB.Snapshot; + +namespace Neo.Persistence.LevelDB +{ + internal class DbSnapshot : Snapshot + { + private readonly DB db; + private readonly LSnapshot snapshot; + private readonly WriteBatch batch; + + public override DataCache Blocks { get; } + public override DataCache Transactions { get; } + public override DataCache Accounts { get; } + public override DataCache UnspentCoins { get; } + public override DataCache SpentCoins { get; } + public override DataCache Validators { get; } + public override DataCache Assets { get; } + public override DataCache Contracts { get; } + public override DataCache Storages { get; } + public override DataCache HeaderHashList { get; } + public override MetaDataCache ValidatorsCount { get; } + public override MetaDataCache BlockHashIndex { get; } + public override MetaDataCache HeaderHashIndex { get; } + + public DbSnapshot(DB db) + { + this.db = db; + this.snapshot = db.GetSnapshot(); + this.batch = new WriteBatch(); + ReadOptions options = new ReadOptions { FillCache = false, Snapshot = snapshot }; + Blocks = new DbCache(db, options, batch, Prefixes.DATA_Block); + Transactions = new DbCache(db, options, batch, Prefixes.DATA_Transaction); + Accounts = new DbCache(db, options, batch, Prefixes.ST_Account); + UnspentCoins = new DbCache(db, options, batch, Prefixes.ST_Coin); + SpentCoins = new DbCache(db, options, batch, Prefixes.ST_SpentCoin); + Validators = new DbCache(db, options, batch, Prefixes.ST_Validator); + Assets = new DbCache(db, options, batch, Prefixes.ST_Asset); + Contracts = new DbCache(db, options, batch, Prefixes.ST_Contract); + Storages = new DbCache(db, options, batch, Prefixes.ST_Storage); + HeaderHashList = new DbCache(db, options, batch, Prefixes.IX_HeaderHashList); + ValidatorsCount = new DbMetaDataCache(db, options, batch, Prefixes.IX_ValidatorsCount); + BlockHashIndex = new DbMetaDataCache(db, options, batch, Prefixes.IX_CurrentBlock); + HeaderHashIndex = new DbMetaDataCache(db, options, batch, Prefixes.IX_CurrentHeader); + } + + public override void Commit() + { + base.Commit(); + db.Write(WriteOptions.Default, batch); + } + + public override void Dispose() + { + snapshot.Dispose(); + } + } +} diff --git a/neo/Persistence/LevelDB/LevelDBStore.cs b/neo/Persistence/LevelDB/LevelDBStore.cs new file mode 100644 index 0000000000..5aa1b586b8 --- /dev/null +++ b/neo/Persistence/LevelDB/LevelDBStore.cs @@ -0,0 +1,108 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Caching; +using Neo.IO.Data.LevelDB; +using Neo.IO.Wrappers; +using Neo.Ledger; +using System; +using System.Reflection; + +namespace Neo.Persistence.LevelDB +{ + public class LevelDBStore : Store, IDisposable + { + private readonly DB db; + + public LevelDBStore(string path) + { + this.db = DB.Open(path, new Options { CreateIfMissing = true }); + if (db.TryGet(ReadOptions.Default, SliceBuilder.Begin(Prefixes.SYS_Version), out Slice value) && Version.TryParse(value.ToString(), out Version version) && version >= Version.Parse("2.9.0")) + return; + WriteBatch batch = new WriteBatch(); + ReadOptions options = new ReadOptions { FillCache = false }; + using (Iterator it = db.NewIterator(options)) + { + for (it.SeekToFirst(); it.Valid(); it.Next()) + { + batch.Delete(it.Key()); + } + } + db.Put(WriteOptions.Default, SliceBuilder.Begin(Prefixes.SYS_Version), Assembly.GetExecutingAssembly().GetName().Version.ToString()); + db.Write(WriteOptions.Default, batch); + } + + public void Dispose() + { + db.Dispose(); + } + + public override DataCache GetAccounts() + { + return new DbCache(db, null, null, Prefixes.ST_Account); + } + + public override DataCache GetAssets() + { + return new DbCache(db, null, null, Prefixes.ST_Asset); + } + + public override DataCache GetBlocks() + { + return new DbCache(db, null, null, Prefixes.DATA_Block); + } + + public override DataCache GetContracts() + { + return new DbCache(db, null, null, Prefixes.ST_Contract); + } + + public override Snapshot GetSnapshot() + { + return new DbSnapshot(db); + } + + public override DataCache GetSpentCoins() + { + return new DbCache(db, null, null, Prefixes.ST_SpentCoin); + } + + public override DataCache GetStorages() + { + return new DbCache(db, null, null, Prefixes.ST_Storage); + } + + public override DataCache GetTransactions() + { + return new DbCache(db, null, null, Prefixes.DATA_Transaction); + } + + public override DataCache GetUnspentCoins() + { + return new DbCache(db, null, null, Prefixes.ST_Coin); + } + + public override DataCache GetValidators() + { + return new DbCache(db, null, null, Prefixes.ST_Validator); + } + + public override DataCache GetHeaderHashList() + { + return new DbCache(db, null, null, Prefixes.IX_HeaderHashList); + } + + public override MetaDataCache GetValidatorsCount() + { + return new DbMetaDataCache(db, null, null, Prefixes.IX_ValidatorsCount); + } + + public override MetaDataCache GetBlockHashIndex() + { + return new DbMetaDataCache(db, null, null, Prefixes.IX_CurrentBlock); + } + + public override MetaDataCache GetHeaderHashIndex() + { + return new DbMetaDataCache(db, null, null, Prefixes.IX_CurrentHeader); + } + } +} diff --git a/neo/Implementations/Blockchains/LevelDB/DataEntryPrefix.cs b/neo/Persistence/LevelDB/Prefixes.cs similarity index 74% rename from neo/Implementations/Blockchains/LevelDB/DataEntryPrefix.cs rename to neo/Persistence/LevelDB/Prefixes.cs index e667c2c8cf..a92363f38f 100644 --- a/neo/Implementations/Blockchains/LevelDB/DataEntryPrefix.cs +++ b/neo/Persistence/LevelDB/Prefixes.cs @@ -1,6 +1,6 @@ -namespace Neo.Implementations.Blockchains.LevelDB +namespace Neo.Persistence.LevelDB { - internal static class DataEntryPrefix + internal static class Prefixes { public const byte DATA_Block = 0x01; public const byte DATA_Transaction = 0x02; @@ -15,9 +15,9 @@ internal static class DataEntryPrefix public const byte IX_HeaderHashList = 0x80; public const byte IX_ValidatorsCount = 0x90; + public const byte IX_CurrentBlock = 0xc0; + public const byte IX_CurrentHeader = 0xc1; - public const byte SYS_CurrentBlock = 0xc0; - public const byte SYS_CurrentHeader = 0xc1; public const byte SYS_Version = 0xf0; } } diff --git a/neo/Persistence/Snapshot.cs b/neo/Persistence/Snapshot.cs new file mode 100644 index 0000000000..404d7a980a --- /dev/null +++ b/neo/Persistence/Snapshot.cs @@ -0,0 +1,273 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Caching; +using Neo.IO.Wrappers; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.VM; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Persistence +{ + public abstract class Snapshot : IDisposable, IPersistence, IScriptTable + { + public Block PersistingBlock { get; internal set; } + public abstract DataCache Blocks { get; } + public abstract DataCache Transactions { get; } + public abstract DataCache Accounts { get; } + public abstract DataCache UnspentCoins { get; } + public abstract DataCache SpentCoins { get; } + public abstract DataCache Validators { get; } + public abstract DataCache Assets { get; } + public abstract DataCache Contracts { get; } + public abstract DataCache Storages { get; } + public abstract DataCache HeaderHashList { get; } + public abstract MetaDataCache ValidatorsCount { get; } + public abstract MetaDataCache BlockHashIndex { get; } + public abstract MetaDataCache HeaderHashIndex { get; } + + public uint Height => BlockHashIndex.Get().Index; + public uint HeaderHeight => HeaderHashIndex.Get().Index; + public UInt256 CurrentBlockHash => BlockHashIndex.Get().Hash; + public UInt256 CurrentHeaderHash => HeaderHashIndex.Get().Hash; + + public Fixed8 CalculateBonus(IEnumerable inputs, bool ignoreClaimed = true) + { + List unclaimed = new List(); + foreach (var group in inputs.GroupBy(p => p.PrevHash)) + { + Dictionary claimable = GetUnclaimed(group.Key); + if (claimable == null || claimable.Count == 0) + if (ignoreClaimed) + continue; + else + throw new ArgumentException(); + foreach (CoinReference claim in group) + { + if (!claimable.TryGetValue(claim.PrevIndex, out SpentCoin claimed)) + if (ignoreClaimed) + continue; + else + throw new ArgumentException(); + unclaimed.Add(claimed); + } + } + return CalculateBonusInternal(unclaimed); + } + + public Fixed8 CalculateBonus(IEnumerable inputs, uint height_end) + { + List unclaimed = new List(); + foreach (var group in inputs.GroupBy(p => p.PrevHash)) + { + TransactionState tx_state = Transactions.TryGet(group.Key); + if (tx_state == null) throw new ArgumentException(); + if (tx_state.BlockIndex == height_end) continue; + foreach (CoinReference claim in group) + { + if (claim.PrevIndex >= tx_state.Transaction.Outputs.Length || !tx_state.Transaction.Outputs[claim.PrevIndex].AssetId.Equals(Blockchain.GoverningToken.Hash)) + throw new ArgumentException(); + unclaimed.Add(new SpentCoin + { + Output = tx_state.Transaction.Outputs[claim.PrevIndex], + StartHeight = tx_state.BlockIndex, + EndHeight = height_end + }); + } + } + return CalculateBonusInternal(unclaimed); + } + + private Fixed8 CalculateBonusInternal(IEnumerable unclaimed) + { + Fixed8 amount_claimed = Fixed8.Zero; + foreach (var group in unclaimed.GroupBy(p => new { p.StartHeight, p.EndHeight })) + { + uint amount = 0; + uint ustart = group.Key.StartHeight / Blockchain.DecrementInterval; + if (ustart < Blockchain.GenerationAmount.Length) + { + uint istart = group.Key.StartHeight % Blockchain.DecrementInterval; + uint uend = group.Key.EndHeight / Blockchain.DecrementInterval; + uint iend = group.Key.EndHeight % Blockchain.DecrementInterval; + if (uend >= Blockchain.GenerationAmount.Length) + { + uend = (uint)Blockchain.GenerationAmount.Length; + iend = 0; + } + if (iend == 0) + { + uend--; + iend = Blockchain.DecrementInterval; + } + while (ustart < uend) + { + amount += (Blockchain.DecrementInterval - istart) * Blockchain.GenerationAmount[ustart]; + ustart++; + istart = 0; + } + amount += (iend - istart) * Blockchain.GenerationAmount[ustart]; + } + amount += (uint)(this.GetSysFeeAmount(group.Key.EndHeight - 1) - (group.Key.StartHeight == 0 ? 0 : this.GetSysFeeAmount(group.Key.StartHeight - 1))); + amount_claimed += group.Sum(p => p.Value) / 100000000 * amount; + } + return amount_claimed; + } + + public Snapshot Clone() + { + return new CloneSnapshot(this); + } + + public virtual void Commit() + { + Accounts.DeleteWhere((k, v) => !v.IsFrozen && v.Votes.Length == 0 && v.Balances.All(p => p.Value <= Fixed8.Zero)); + UnspentCoins.DeleteWhere((k, v) => v.Items.All(p => p.HasFlag(CoinState.Spent))); + SpentCoins.DeleteWhere((k, v) => v.Items.Count == 0); + Blocks.Commit(); + Transactions.Commit(); + Accounts.Commit(); + UnspentCoins.Commit(); + SpentCoins.Commit(); + Validators.Commit(); + Assets.Commit(); + Contracts.Commit(); + Storages.Commit(); + HeaderHashList.Commit(); + ValidatorsCount.Commit(); + BlockHashIndex.Commit(); + HeaderHashIndex.Commit(); + } + + public virtual void Dispose() + { + } + + byte[] IScriptTable.GetScript(byte[] script_hash) + { + return Contracts[new UInt160(script_hash)].Script; + } + + public Dictionary GetUnclaimed(UInt256 hash) + { + TransactionState tx_state = Transactions.TryGet(hash); + if (tx_state == null) return null; + SpentCoinState coin_state = SpentCoins.TryGet(hash); + if (coin_state != null) + { + return coin_state.Items.ToDictionary(p => p.Key, p => new SpentCoin + { + Output = tx_state.Transaction.Outputs[p.Key], + StartHeight = tx_state.BlockIndex, + EndHeight = p.Value + }); + } + else + { + return new Dictionary(); + } + } + + private ECPoint[] _validators = null; + public ECPoint[] GetValidators() + { + if (_validators == null) + { + _validators = GetValidators(Enumerable.Empty()).ToArray(); + } + return _validators; + } + + public IEnumerable GetValidators(IEnumerable others) + { + Snapshot snapshot = Clone(); + foreach (Transaction tx in others) + { + foreach (TransactionOutput output in tx.Outputs) + { + AccountState account = snapshot.Accounts.GetAndChange(output.ScriptHash, () => new AccountState(output.ScriptHash)); + if (account.Balances.ContainsKey(output.AssetId)) + account.Balances[output.AssetId] += output.Value; + else + account.Balances[output.AssetId] = output.Value; + if (output.AssetId.Equals(Blockchain.GoverningToken.Hash) && account.Votes.Length > 0) + { + foreach (ECPoint pubkey in account.Votes) + snapshot.Validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += output.Value; + snapshot.ValidatorsCount.GetAndChange().Votes[account.Votes.Length - 1] += output.Value; + } + } + foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) + { + Transaction tx_prev = snapshot.GetTransaction(group.Key); + foreach (CoinReference input in group) + { + TransactionOutput out_prev = tx_prev.Outputs[input.PrevIndex]; + AccountState account = snapshot.Accounts.GetAndChange(out_prev.ScriptHash); + if (out_prev.AssetId.Equals(Blockchain.GoverningToken.Hash)) + { + if (account.Votes.Length > 0) + { + foreach (ECPoint pubkey in account.Votes) + { + ValidatorState validator = snapshot.Validators.GetAndChange(pubkey); + validator.Votes -= out_prev.Value; + if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) + snapshot.Validators.Delete(pubkey); + } + snapshot.ValidatorsCount.GetAndChange().Votes[account.Votes.Length - 1] -= out_prev.Value; + } + } + account.Balances[out_prev.AssetId] -= out_prev.Value; + } + } + switch (tx) + { +#pragma warning disable CS0612 + case EnrollmentTransaction tx_enrollment: + snapshot.Validators.GetAndChange(tx_enrollment.PublicKey, () => new ValidatorState(tx_enrollment.PublicKey)).Registered = true; + break; +#pragma warning restore CS0612 + case StateTransaction tx_state: + foreach (StateDescriptor descriptor in tx_state.Descriptors) + switch (descriptor.Type) + { + case StateType.Account: + Blockchain.ProcessAccountStateDescriptor(descriptor, snapshot); + break; + case StateType.Validator: + Blockchain.ProcessValidatorStateDescriptor(descriptor, snapshot); + break; + } + break; + } + } + int count = (int)snapshot.ValidatorsCount.Get().Votes.Select((p, i) => new + { + Count = i, + Votes = p + }).Where(p => p.Votes > Fixed8.Zero).ToArray().WeightedFilter(0.25, 0.75, p => p.Votes.GetData(), (p, w) => new + { + p.Count, + Weight = w + }).WeightedAverage(p => p.Count, p => p.Weight); + count = Math.Max(count, Blockchain.StandbyValidators.Length); + HashSet sv = new HashSet(Blockchain.StandbyValidators); + ECPoint[] pubkeys = snapshot.Validators.Find().Select(p => p.Value).Where(p => (p.Registered && p.Votes > Fixed8.Zero) || sv.Contains(p.PublicKey)).OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(count).ToArray(); + IEnumerable result; + if (pubkeys.Length == count) + { + result = pubkeys; + } + else + { + HashSet hashSet = new HashSet(pubkeys); + for (int i = 0; i < Blockchain.StandbyValidators.Length && hashSet.Count < count; i++) + hashSet.Add(Blockchain.StandbyValidators[i]); + result = hashSet; + } + return result.OrderBy(p => p); + } + } +} diff --git a/neo/Persistence/Store.cs b/neo/Persistence/Store.cs new file mode 100644 index 0000000000..5936fbcfbe --- /dev/null +++ b/neo/Persistence/Store.cs @@ -0,0 +1,40 @@ +using Neo.Cryptography.ECC; +using Neo.IO.Caching; +using Neo.IO.Wrappers; +using Neo.Ledger; + +namespace Neo.Persistence +{ + public abstract class Store : IPersistence + { + DataCache IPersistence.Blocks => GetBlocks(); + DataCache IPersistence.Transactions => GetTransactions(); + DataCache IPersistence.Accounts => GetAccounts(); + DataCache IPersistence.UnspentCoins => GetUnspentCoins(); + DataCache IPersistence.SpentCoins => GetSpentCoins(); + DataCache IPersistence.Validators => GetValidators(); + DataCache IPersistence.Assets => GetAssets(); + DataCache IPersistence.Contracts => GetContracts(); + DataCache IPersistence.Storages => GetStorages(); + DataCache IPersistence.HeaderHashList => GetHeaderHashList(); + MetaDataCache IPersistence.ValidatorsCount => GetValidatorsCount(); + MetaDataCache IPersistence.BlockHashIndex => GetBlockHashIndex(); + MetaDataCache IPersistence.HeaderHashIndex => GetHeaderHashIndex(); + + public abstract DataCache GetBlocks(); + public abstract DataCache GetTransactions(); + public abstract DataCache GetAccounts(); + public abstract DataCache GetUnspentCoins(); + public abstract DataCache GetSpentCoins(); + public abstract DataCache GetValidators(); + public abstract DataCache GetAssets(); + public abstract DataCache GetContracts(); + public abstract DataCache GetStorages(); + public abstract DataCache GetHeaderHashList(); + public abstract MetaDataCache GetValidatorsCount(); + public abstract MetaDataCache GetBlockHashIndex(); + public abstract MetaDataCache GetHeaderHashIndex(); + + public abstract Snapshot GetSnapshot(); + } +} diff --git a/neo/Plugins/Helper.cs b/neo/Plugins/Helper.cs new file mode 100644 index 0000000000..fcc4e515dd --- /dev/null +++ b/neo/Plugins/Helper.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Configuration; +using System.IO; +using System.Reflection; + +namespace Neo.Plugins +{ + public static class Helper + { + public static IConfigurationSection GetConfiguration(this Assembly assembly) + { + string path = Path.Combine("Plugins", assembly.GetName().Name, "config.json"); + return new ConfigurationBuilder().AddJsonFile(path).Build().GetSection("PluginConfiguration"); + } + } +} diff --git a/neo/Plugins/ILogPlugin.cs b/neo/Plugins/ILogPlugin.cs new file mode 100644 index 0000000000..ff1052be2e --- /dev/null +++ b/neo/Plugins/ILogPlugin.cs @@ -0,0 +1,7 @@ +namespace Neo.Plugins +{ + public interface ILogPlugin + { + void Log(string source, LogLevel level, string message); + } +} diff --git a/neo/Plugins/IPolicyPlugin.cs b/neo/Plugins/IPolicyPlugin.cs new file mode 100644 index 0000000000..956ed816f5 --- /dev/null +++ b/neo/Plugins/IPolicyPlugin.cs @@ -0,0 +1,11 @@ +using Neo.Network.P2P.Payloads; +using System.Collections.Generic; + +namespace Neo.Plugins +{ + public interface IPolicyPlugin + { + bool CheckPolicy(Transaction tx); + IEnumerable Filter(IEnumerable transactions); + } +} diff --git a/neo/Plugins/IRpcPlugin.cs b/neo/Plugins/IRpcPlugin.cs new file mode 100644 index 0000000000..ae8e6fc21f --- /dev/null +++ b/neo/Plugins/IRpcPlugin.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Http; +using Neo.IO.Json; + +namespace Neo.Plugins +{ + public interface IRpcPlugin + { + JObject OnProcess(HttpContext context, string method, JArray _params); + } +} diff --git a/neo/Plugins/LogLevel.cs b/neo/Plugins/LogLevel.cs new file mode 100644 index 0000000000..72158c8aad --- /dev/null +++ b/neo/Plugins/LogLevel.cs @@ -0,0 +1,11 @@ +namespace Neo.Plugins +{ + public enum LogLevel : byte + { + Fatal, + Error, + Warning, + Info, + Debug + } +} diff --git a/neo/Plugins/Plugin.cs b/neo/Plugins/Plugin.cs index 5c30406ee9..dddddb656d 100644 --- a/neo/Plugins/Plugin.cs +++ b/neo/Plugins/Plugin.cs @@ -7,20 +7,29 @@ namespace Neo.Plugins { public abstract class Plugin { - private static readonly List instances = new List(); + private static readonly List Plugins = new List(); + internal static readonly List Loggers = new List(); + internal static readonly List Policies = new List(); + internal static readonly List RpcPlugins = new List(); - public static IEnumerable Instances => instances; - public abstract string Name { get; } + protected static NeoSystem System { get; private set; } + public virtual string Name => GetType().Name; public virtual Version Version => GetType().Assembly.GetName().Version; + protected virtual bool OnMessage(object message) => false; + protected Plugin() { - instances.Add(this); + Plugins.Add(this); + if (this is ILogPlugin logger) Loggers.Add(logger); + if (this is IPolicyPlugin policy) Policies.Add(policy); + if (this is IRpcPlugin rpc) RpcPlugins.Add(rpc); } - static Plugin() + internal static void LoadPlugins(NeoSystem system) { - string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Plugins"); + System = system; + string path = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins"); if (!Directory.Exists(path)) return; foreach (string filename in Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly)) { @@ -36,8 +45,18 @@ static Plugin() } } - public static void LoadPlugins() + public static void Log(string source, LogLevel level, string message) + { + foreach (ILogPlugin plugin in Loggers) + plugin.Log(source, level, message); + } + + public static bool SendMessage(object message) { + foreach (Plugin plugin in Plugins) + if (plugin.OnMessage(message)) + return true; + return false; } } } diff --git a/neo/Plugins/PolicyPlugin.cs b/neo/Plugins/PolicyPlugin.cs deleted file mode 100644 index 82ba62c93a..0000000000 --- a/neo/Plugins/PolicyPlugin.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Neo.Core; -using System.Collections.Generic; - -namespace Neo.Plugins -{ - public abstract class PolicyPlugin : Plugin - { - private static readonly List instances = new List(); - - public new static IEnumerable Instances => instances; - - protected PolicyPlugin() - { - instances.Add(this); - } - - internal protected virtual bool CheckPolicy(Transaction tx) => true; - internal protected virtual IEnumerable Filter(IEnumerable transactions) => transactions; - } -} diff --git a/neo/Plugins/RpcPlugin.cs b/neo/Plugins/RpcPlugin.cs deleted file mode 100644 index 5d051c42e8..0000000000 --- a/neo/Plugins/RpcPlugin.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Neo.IO.Json; -using System.Collections.Generic; - -namespace Neo.Plugins -{ - public abstract class RpcPlugin : Plugin - { - private static readonly List instances = new List(); - - public new static IEnumerable Instances => instances; - - protected RpcPlugin() - { - instances.Add(this); - } - - internal protected virtual JObject OnProcess(HttpContext context, string method, JArray _params) => null; - } -} diff --git a/neo/Settings.cs b/neo/Settings.cs index e9bc5ae41f..b42c0cb614 100644 --- a/neo/Settings.cs +++ b/neo/Settings.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.Configuration; -using Neo.Core; +using Neo.Network.P2P.Payloads; using System; using System.Collections.Generic; using System.Linq; diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index 4f90c55602..4fb83525b6 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -1,5 +1,6 @@ -using Neo.Core; -using Neo.IO.Caching; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.VM; using Neo.VM.Types; using System.Collections; @@ -48,25 +49,20 @@ public class ApplicationEngine : ExecutionEngine private readonly long gas_amount; private long gas_consumed = 0; private readonly bool testMode; + private readonly Snapshot snapshot; private int stackitem_count = 0; private bool is_stackitem_count_strict = true; - private readonly CachedScriptTable script_table; - - public TriggerType Trigger { get; } public Fixed8 GasConsumed => new Fixed8(gas_consumed); + public new NeoService Service => (NeoService)base.Service; - public ApplicationEngine(TriggerType trigger, IScriptContainer container, IScriptTable table, InteropService service, Fixed8 gas, bool testMode = false) - : base(container, Cryptography.Crypto.Default, table, service) + public ApplicationEngine(TriggerType trigger, IScriptContainer container, Snapshot snapshot, Fixed8 gas, bool testMode = false) + : base(container, Cryptography.Crypto.Default, snapshot, new NeoService(trigger, snapshot)) { this.gas_amount = gas_free + gas.GetData(); this.testMode = testMode; - this.Trigger = trigger; - if (table is CachedScriptTable) - { - this.script_table = (CachedScriptTable)table; - } + this.snapshot = snapshot; } private bool CheckArraySize(OpCode nextInstruction) @@ -386,15 +382,21 @@ private bool CheckDynamicInvoke(OpCode nextInstruction) // if we get this far it is a dynamic call // now look at the current executing script // to determine if it can do dynamic calls - return script_table.GetContractState(CurrentContext.ScriptHash).HasDynamicInvoke; + return snapshot.Contracts[new UInt160(CurrentContext.ScriptHash)].HasDynamicInvoke; case OpCode.CALL_ED: case OpCode.CALL_EDT: - return script_table.GetContractState(CurrentContext.ScriptHash).HasDynamicInvoke; + return snapshot.Contracts[new UInt160(CurrentContext.ScriptHash)].HasDynamicInvoke; default: return true; } } + public override void Dispose() + { + base.Dispose(); + Service.Dispose(); + } + public new bool Execute() { try @@ -537,14 +539,9 @@ protected virtual long GetPriceForSysCall() return 100; case "Neo.Transaction.GetReferences": case "AntShares.Transaction.GetReferences": + return 200; case "Neo.Transaction.GetUnspentCoins": return 200; - case "Neo.Account.SetVotes": - case "AntShares.Account.SetVotes": - return 1000; - case "Neo.Validator.Register": - case "AntShares.Validator.Register": - return 1000L * 100000000L / ratio; case "Neo.Asset.Create": case "AntShares.Asset.Create": return 5000L * 100000000L / ratio; @@ -605,33 +602,27 @@ private bool PreStepInto(OpCode nextOpcode) return true; } - public static ApplicationEngine Run(byte[] script, IScriptContainer container = null, Block persisting_block = null) + public static ApplicationEngine Run(byte[] script, IScriptContainer container = null, Block persisting_block = null, bool testMode = false) { - if (persisting_block == null) - persisting_block = new Block + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) + { + snapshot.PersistingBlock = persisting_block ?? new Block { Version = 0, - PrevHash = Blockchain.Default.CurrentBlockHash, + PrevHash = snapshot.CurrentBlockHash, MerkleRoot = new UInt256(), - Timestamp = Blockchain.Default.GetHeader(Blockchain.Default.Height).Timestamp + Blockchain.SecondsPerBlock, - Index = Blockchain.Default.Height + 1, + Timestamp = snapshot.Blocks[snapshot.CurrentBlockHash].TrimmedBlock.Timestamp + Blockchain.SecondsPerBlock, + Index = snapshot.Height + 1, ConsensusData = 0, - NextConsensus = Blockchain.Default.GetHeader(Blockchain.Default.Height).NextConsensus, - Script = new Witness + NextConsensus = snapshot.Blocks[snapshot.CurrentBlockHash].TrimmedBlock.NextConsensus, + Witness = new Witness { InvocationScript = new byte[0], VerificationScript = new byte[0] }, Transactions = new Transaction[0] }; - DataCache accounts = Blockchain.Default.GetStates(); - DataCache assets = Blockchain.Default.GetStates(); - DataCache contracts = Blockchain.Default.GetStates(); - DataCache storages = Blockchain.Default.GetStates(); - CachedScriptTable script_table = new CachedScriptTable(contracts); - using (StateMachine service = new StateMachine(persisting_block, accounts, assets, contracts, storages)) - { - ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, script_table, service, Fixed8.Zero, true); + ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, snapshot, Fixed8.Zero, testMode); engine.LoadScript(script); engine.Execute(); return engine; diff --git a/neo/SmartContract/CachedScriptTable.cs b/neo/SmartContract/CachedScriptTable.cs deleted file mode 100644 index ae28cb8665..0000000000 --- a/neo/SmartContract/CachedScriptTable.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Neo.Core; -using Neo.IO.Caching; -using Neo.VM; - -namespace Neo.SmartContract -{ - internal class CachedScriptTable : IScriptTable - { - private DataCache contracts; - - public CachedScriptTable(DataCache contracts) - { - this.contracts = contracts; - } - - byte[] IScriptTable.GetScript(byte[] script_hash) - { - return contracts[new UInt160(script_hash)].Script; - } - - public ContractState GetContractState(byte[] script_hash) - { - return contracts[new UInt160(script_hash)]; - } - } -} diff --git a/neo/SmartContract/Contract.cs b/neo/SmartContract/Contract.cs index 8c3997e953..06ab833f94 100644 --- a/neo/SmartContract/Contract.cs +++ b/neo/SmartContract/Contract.cs @@ -1,5 +1,4 @@ -using Neo.Core; -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.VM; using Neo.Wallets; using System; @@ -13,16 +12,13 @@ public class Contract public ContractParameterType[] ParameterList; private string _address; - /// - /// 合约地址 - /// public string Address { get { if (_address == null) { - _address = Wallet.ToAddress(ScriptHash); + _address = ScriptHash.ToAddress(); } return _address; } @@ -142,7 +138,7 @@ public virtual bool IsMultiSigContract() ++i; break; case 2: - if (n != Script.ToUInt16(++i)) return false; + if (Script.Length < i + 3 || n != Script.ToUInt16(++i)) return false; i += 2; break; default: diff --git a/neo/SmartContract/ContractParameterType.cs b/neo/SmartContract/ContractParameterType.cs index 4e3e4221f0..4e743431cb 100644 --- a/neo/SmartContract/ContractParameterType.cs +++ b/neo/SmartContract/ContractParameterType.cs @@ -1,30 +1,12 @@ namespace Neo.SmartContract { - /// - /// 表示智能合约的参数类型 - /// public enum ContractParameterType : byte { - /// - /// 签名 - /// Signature = 0x00, Boolean = 0x01, - /// - /// 整数 - /// Integer = 0x02, - /// - /// 160位散列值 - /// Hash160 = 0x03, - /// - /// 256位散列值 - /// Hash256 = 0x04, - /// - /// 字节数组 - /// ByteArray = 0x05, PublicKey = 0x06, String = 0x07, diff --git a/neo/SmartContract/ContractParametersContext.cs b/neo/SmartContract/ContractParametersContext.cs index 350faf85a8..fcbf596cc3 100644 --- a/neo/SmartContract/ContractParametersContext.cs +++ b/neo/SmartContract/ContractParametersContext.cs @@ -1,6 +1,8 @@ -using Neo.Core; -using Neo.Cryptography.ECC; +using Neo.Cryptography.ECC; using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.VM; using System; using System.Collections.Generic; @@ -76,9 +78,10 @@ public IReadOnlyList ScriptHashes get { if (_ScriptHashes == null) - { - _ScriptHashes = Verifiable.GetScriptHashesForVerifying(); - } + using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot()) + { + _ScriptHashes = Verifiable.GetScriptHashesForVerifying(snapshot); + } return _ScriptHashes; } } @@ -157,7 +160,8 @@ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature) else index = i; - if(index == -1) { + if (index == -1) + { // unable to find ContractParameterType.Signature in contract.ParameterList // return now to prevent array index out of bounds exception return false; @@ -206,10 +210,10 @@ public IReadOnlyList GetParameters(UInt160 scriptHash) return item.Parameters; } - public Witness[] GetScripts() + public Witness[] GetWitnesses() { if (!Completed) throw new InvalidOperationException(); - Witness[] scripts = new Witness[ScriptHashes.Count]; + Witness[] witnesses = new Witness[ScriptHashes.Count]; for (int i = 0; i < ScriptHashes.Count; i++) { ContextItem item = ContextItems[ScriptHashes[i]]; @@ -219,14 +223,14 @@ public Witness[] GetScripts() { sb.EmitPush(parameter); } - scripts[i] = new Witness + witnesses[i] = new Witness { InvocationScript = sb.ToArray(), VerificationScript = item.Script ?? new byte[0] }; } } - return scripts; + return witnesses; } public static ContractParametersContext Parse(string value) diff --git a/neo/SmartContract/Helper.cs b/neo/SmartContract/Helper.cs new file mode 100644 index 0000000000..350a44982e --- /dev/null +++ b/neo/SmartContract/Helper.cs @@ -0,0 +1,54 @@ +using Neo.Cryptography; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.VM; +using System; + +namespace Neo.SmartContract +{ + public static class Helper + { + public static UInt160 ToScriptHash(this byte[] script) + { + return new UInt160(Crypto.Default.Hash160(script)); + } + + internal static bool VerifyWitnesses(this IVerifiable verifiable, Snapshot snapshot) + { + UInt160[] hashes; + try + { + hashes = verifiable.GetScriptHashesForVerifying(snapshot); + } + catch (InvalidOperationException) + { + return false; + } + if (hashes.Length != verifiable.Witnesses.Length) return false; + for (int i = 0; i < hashes.Length; i++) + { + byte[] verification = verifiable.Witnesses[i].VerificationScript; + if (verification.Length == 0) + { + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitAppCall(hashes[i].ToArray()); + verification = sb.ToArray(); + } + } + else + { + if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false; + } + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, Fixed8.Zero)) + { + engine.LoadScript(verification); + engine.LoadScript(verifiable.Witnesses[i].InvocationScript); + if (!engine.Execute()) return false; + if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; + } + } + return true; + } + } +} diff --git a/neo/SmartContract/Iterators/StorageIterator.cs b/neo/SmartContract/Iterators/StorageIterator.cs index e2bebeb536..2dd604d5d8 100644 --- a/neo/SmartContract/Iterators/StorageIterator.cs +++ b/neo/SmartContract/Iterators/StorageIterator.cs @@ -1,4 +1,4 @@ -using Neo.Core; +using Neo.Ledger; using Neo.VM; using System.Collections.Generic; diff --git a/neo/SmartContract/StateReader.cs b/neo/SmartContract/NeoService.cs similarity index 50% rename from neo/SmartContract/StateReader.cs rename to neo/SmartContract/NeoService.cs index 2f31b412b3..bb5d596603 100644 --- a/neo/SmartContract/StateReader.cs +++ b/neo/SmartContract/NeoService.cs @@ -1,113 +1,52 @@ -using Neo.Core; -using Neo.Cryptography.ECC; -using Neo.IO; -using Neo.IO.Caching; +using Neo.Cryptography.ECC; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.SmartContract.Enumerators; using Neo.SmartContract.Iterators; using Neo.VM; using Neo.VM.Types; using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Numerics; using System.Text; using VMArray = Neo.VM.Types.Array; -using VMBoolean = Neo.VM.Types.Boolean; namespace Neo.SmartContract { - public class StateReader : InteropService, IDisposable + public class NeoService : StandardService { - public static event EventHandler Notify; - public static event EventHandler Log; - - private readonly List notifications = new List(); - private readonly List disposables = new List(); - - public IReadOnlyList Notifications => notifications; - - private DataCache _accounts; - protected virtual DataCache Accounts - { - get - { - if (_accounts == null) - _accounts = Blockchain.Default.GetStates(); - return _accounts; - } - } - - private DataCache _assets; - protected virtual DataCache Assets - { - get - { - if (_assets == null) - _assets = Blockchain.Default.GetStates(); - return _assets; - } - } - - private DataCache _contracts; - protected virtual DataCache Contracts - { - get - { - if (_contracts == null) - _contracts = Blockchain.Default.GetStates(); - return _contracts; - } - } - - private DataCache _storages; - protected virtual DataCache Storages - { - get - { - if (_storages == null) - _storages = Blockchain.Default.GetStates(); - return _storages; - } - } - - public StateReader() + public NeoService(TriggerType trigger, Snapshot snapshot) + : base(trigger, snapshot) { - //Standard Library - Register("System.Runtime.GetTrigger", Runtime_GetTrigger); - Register("System.Runtime.CheckWitness", Runtime_CheckWitness); - Register("System.Runtime.Notify", Runtime_Notify); - Register("System.Runtime.Log", Runtime_Log); - Register("System.Runtime.GetTime", Runtime_GetTime); - Register("System.Runtime.Serialize", Runtime_Serialize); - Register("System.Runtime.Deserialize", Runtime_Deserialize); - Register("System.Blockchain.GetHeight", Blockchain_GetHeight); - Register("System.Blockchain.GetHeader", Blockchain_GetHeader); - Register("System.Blockchain.GetBlock", Blockchain_GetBlock); - Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction); - Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight); - Register("System.Blockchain.GetContract", Blockchain_GetContract); - Register("System.Header.GetIndex", Header_GetIndex); - Register("System.Header.GetHash", Header_GetHash); - Register("System.Header.GetPrevHash", Header_GetPrevHash); - Register("System.Header.GetTimestamp", Header_GetTimestamp); - Register("System.Block.GetTransactionCount", Block_GetTransactionCount); - Register("System.Block.GetTransactions", Block_GetTransactions); - Register("System.Block.GetTransaction", Block_GetTransaction); - Register("System.Transaction.GetHash", Transaction_GetHash); - Register("System.Storage.GetContext", Storage_GetContext); - Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext); - Register("System.Storage.Get", Storage_Get); - Register("System.StorageContext.AsReadOnly", StorageContext_AsReadOnly); - - //Neo Specified + Register("Neo.Runtime.GetTrigger", Runtime_GetTrigger); + Register("Neo.Runtime.CheckWitness", Runtime_CheckWitness); + Register("Neo.Runtime.Notify", Runtime_Notify); + Register("Neo.Runtime.Log", Runtime_Log); + Register("Neo.Runtime.GetTime", Runtime_GetTime); + Register("Neo.Runtime.Serialize", Runtime_Serialize); + Register("Neo.Runtime.Deserialize", Runtime_Deserialize); + Register("Neo.Blockchain.GetHeight", Blockchain_GetHeight); + Register("Neo.Blockchain.GetHeader", Blockchain_GetHeader); + Register("Neo.Blockchain.GetBlock", Blockchain_GetBlock); + Register("Neo.Blockchain.GetTransaction", Blockchain_GetTransaction); + Register("Neo.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight); Register("Neo.Blockchain.GetAccount", Blockchain_GetAccount); Register("Neo.Blockchain.GetValidators", Blockchain_GetValidators); Register("Neo.Blockchain.GetAsset", Blockchain_GetAsset); + Register("Neo.Blockchain.GetContract", Blockchain_GetContract); + Register("Neo.Header.GetHash", Header_GetHash); Register("Neo.Header.GetVersion", Header_GetVersion); + Register("Neo.Header.GetPrevHash", Header_GetPrevHash); Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot); + Register("Neo.Header.GetTimestamp", Header_GetTimestamp); + Register("Neo.Header.GetIndex", Header_GetIndex); Register("Neo.Header.GetConsensusData", Header_GetConsensusData); Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus); + Register("Neo.Block.GetTransactionCount", Block_GetTransactionCount); + Register("Neo.Block.GetTransactions", Block_GetTransactions); + Register("Neo.Block.GetTransaction", Block_GetTransaction); + Register("Neo.Transaction.GetHash", Transaction_GetHash); Register("Neo.Transaction.GetType", Transaction_GetType); Register("Neo.Transaction.GetAttributes", Transaction_GetAttributes); Register("Neo.Transaction.GetInputs", Transaction_GetInputs); @@ -125,6 +64,8 @@ public StateReader() Register("Neo.Account.GetScriptHash", Account_GetScriptHash); Register("Neo.Account.GetVotes", Account_GetVotes); Register("Neo.Account.GetBalance", Account_GetBalance); + Register("Neo.Asset.Create", Asset_Create); + Register("Neo.Asset.Renew", Asset_Renew); Register("Neo.Asset.GetAssetId", Asset_GetAssetId); Register("Neo.Asset.GetAssetType", Asset_GetAssetType); Register("Neo.Asset.GetAmount", Asset_GetAmount); @@ -133,9 +74,19 @@ public StateReader() Register("Neo.Asset.GetOwner", Asset_GetOwner); Register("Neo.Asset.GetAdmin", Asset_GetAdmin); Register("Neo.Asset.GetIssuer", Asset_GetIssuer); + Register("Neo.Contract.Create", Contract_Create); + Register("Neo.Contract.Migrate", Contract_Migrate); + Register("Neo.Contract.Destroy", Contract_Destroy); Register("Neo.Contract.GetScript", Contract_GetScript); Register("Neo.Contract.IsPayable", Contract_IsPayable); + Register("Neo.Contract.GetStorageContext", Contract_GetStorageContext); + Register("Neo.Storage.GetContext", Storage_GetContext); + Register("Neo.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext); + Register("Neo.Storage.Get", Storage_Get); + Register("Neo.Storage.Put", Storage_Put); + Register("Neo.Storage.Delete", Storage_Delete); Register("Neo.Storage.Find", Storage_Find); + Register("Neo.StorageContext.AsReadOnly", StorageContext_AsReadOnly); Register("Neo.Enumerator.Create", Enumerator_Create); Register("Neo.Enumerator.Next", Enumerator_Next); Register("Neo.Enumerator.Value", Enumerator_Value); @@ -151,48 +102,27 @@ public StateReader() #endregion #region Old APIs - Register("Neo.Runtime.GetTrigger", Runtime_GetTrigger); - Register("Neo.Runtime.CheckWitness", Runtime_CheckWitness); Register("AntShares.Runtime.CheckWitness", Runtime_CheckWitness); - Register("Neo.Runtime.Notify", Runtime_Notify); Register("AntShares.Runtime.Notify", Runtime_Notify); - Register("Neo.Runtime.Log", Runtime_Log); Register("AntShares.Runtime.Log", Runtime_Log); - Register("Neo.Runtime.GetTime", Runtime_GetTime); - Register("Neo.Runtime.Serialize", Runtime_Serialize); - Register("Neo.Runtime.Deserialize", Runtime_Deserialize); - Register("Neo.Blockchain.GetHeight", Blockchain_GetHeight); Register("AntShares.Blockchain.GetHeight", Blockchain_GetHeight); - Register("Neo.Blockchain.GetHeader", Blockchain_GetHeader); Register("AntShares.Blockchain.GetHeader", Blockchain_GetHeader); - Register("Neo.Blockchain.GetBlock", Blockchain_GetBlock); Register("AntShares.Blockchain.GetBlock", Blockchain_GetBlock); - Register("Neo.Blockchain.GetTransaction", Blockchain_GetTransaction); Register("AntShares.Blockchain.GetTransaction", Blockchain_GetTransaction); - Register("Neo.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight); Register("AntShares.Blockchain.GetAccount", Blockchain_GetAccount); Register("AntShares.Blockchain.GetValidators", Blockchain_GetValidators); Register("AntShares.Blockchain.GetAsset", Blockchain_GetAsset); - Register("Neo.Blockchain.GetContract", Blockchain_GetContract); Register("AntShares.Blockchain.GetContract", Blockchain_GetContract); - Register("Neo.Header.GetIndex", Header_GetIndex); - Register("Neo.Header.GetHash", Header_GetHash); Register("AntShares.Header.GetHash", Header_GetHash); Register("AntShares.Header.GetVersion", Header_GetVersion); - Register("Neo.Header.GetPrevHash", Header_GetPrevHash); Register("AntShares.Header.GetPrevHash", Header_GetPrevHash); Register("AntShares.Header.GetMerkleRoot", Header_GetMerkleRoot); - Register("Neo.Header.GetTimestamp", Header_GetTimestamp); Register("AntShares.Header.GetTimestamp", Header_GetTimestamp); Register("AntShares.Header.GetConsensusData", Header_GetConsensusData); Register("AntShares.Header.GetNextConsensus", Header_GetNextConsensus); - Register("Neo.Block.GetTransactionCount", Block_GetTransactionCount); Register("AntShares.Block.GetTransactionCount", Block_GetTransactionCount); - Register("Neo.Block.GetTransactions", Block_GetTransactions); Register("AntShares.Block.GetTransactions", Block_GetTransactions); - Register("Neo.Block.GetTransaction", Block_GetTransaction); Register("AntShares.Block.GetTransaction", Block_GetTransaction); - Register("Neo.Transaction.GetHash", Transaction_GetHash); Register("AntShares.Transaction.GetHash", Transaction_GetHash); Register("AntShares.Transaction.GetType", Transaction_GetType); Register("AntShares.Transaction.GetAttributes", Transaction_GetAttributes); @@ -209,6 +139,8 @@ public StateReader() Register("AntShares.Account.GetScriptHash", Account_GetScriptHash); Register("AntShares.Account.GetVotes", Account_GetVotes); Register("AntShares.Account.GetBalance", Account_GetBalance); + Register("AntShares.Asset.Create", Asset_Create); + Register("AntShares.Asset.Renew", Asset_Renew); Register("AntShares.Asset.GetAssetId", Asset_GetAssetId); Register("AntShares.Asset.GetAssetType", Asset_GetAssetType); Register("AntShares.Asset.GetAmount", Asset_GetAmount); @@ -217,420 +149,43 @@ public StateReader() Register("AntShares.Asset.GetOwner", Asset_GetOwner); Register("AntShares.Asset.GetAdmin", Asset_GetAdmin); Register("AntShares.Asset.GetIssuer", Asset_GetIssuer); + Register("AntShares.Contract.Create", Contract_Create); + Register("AntShares.Contract.Migrate", Contract_Migrate); + Register("AntShares.Contract.Destroy", Contract_Destroy); Register("AntShares.Contract.GetScript", Contract_GetScript); - Register("Neo.Storage.GetContext", Storage_GetContext); + Register("AntShares.Contract.GetStorageContext", Contract_GetStorageContext); Register("AntShares.Storage.GetContext", Storage_GetContext); - Register("Neo.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext); - Register("Neo.Storage.Get", Storage_Get); Register("AntShares.Storage.Get", Storage_Get); - Register("Neo.StorageContext.AsReadOnly", StorageContext_AsReadOnly); + Register("AntShares.Storage.Put", Storage_Put); + Register("AntShares.Storage.Delete", Storage_Delete); #endregion } - internal bool CheckStorageContext(StorageContext context) - { - ContractState contract = Contracts.TryGet(context.ScriptHash); - if (contract == null) return false; - if (!contract.HasStorage) return false; - return true; - } - - public void Dispose() - { - foreach (IDisposable disposable in disposables) - disposable.Dispose(); - disposables.Clear(); - } - - protected virtual bool Runtime_GetTrigger(ExecutionEngine engine) - { - ApplicationEngine app_engine = (ApplicationEngine)engine; - engine.CurrentContext.EvaluationStack.Push((int)app_engine.Trigger); - return true; - } - - protected bool CheckWitness(ExecutionEngine engine, UInt160 hash) - { - IVerifiable container = (IVerifiable)engine.ScriptContainer; - UInt160[] _hashes_for_verifying = container.GetScriptHashesForVerifying(); - return _hashes_for_verifying.Contains(hash); - } - - protected bool CheckWitness(ExecutionEngine engine, ECPoint pubkey) - { - return CheckWitness(engine, Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()); - } - - protected virtual bool Runtime_CheckWitness(ExecutionEngine engine) - { - byte[] hashOrPubkey = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - bool result; - if (hashOrPubkey.Length == 20) - result = CheckWitness(engine, new UInt160(hashOrPubkey)); - else if (hashOrPubkey.Length == 33) - result = CheckWitness(engine, ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)); - else - return false; - engine.CurrentContext.EvaluationStack.Push(result); - return true; - } - - protected virtual bool Runtime_Notify(ExecutionEngine engine) - { - StackItem state = engine.CurrentContext.EvaluationStack.Pop(); - NotifyEventArgs notification = new NotifyEventArgs(engine.ScriptContainer, new UInt160(engine.CurrentContext.ScriptHash), state); - Notify?.Invoke(this, notification); - notifications.Add(notification); - return true; - } - - protected virtual bool Runtime_Log(ExecutionEngine engine) - { - string message = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - Log?.Invoke(this, new LogEventArgs(engine.ScriptContainer, new UInt160(engine.CurrentContext.ScriptHash), message)); - return true; - } - - protected virtual bool Runtime_GetTime(ExecutionEngine engine) - { - BlockBase header = Blockchain.Default?.GetHeader(Blockchain.Default.Height); - if (header == null) header = Blockchain.GenesisBlock; - engine.CurrentContext.EvaluationStack.Push(header.Timestamp + Blockchain.SecondsPerBlock); - return true; - } - - private void SerializeStackItem(StackItem item, BinaryWriter writer) - { - List serialized = new List(); - Stack unserialized = new Stack(); - unserialized.Push(item); - while (unserialized.Count > 0) - { - item = unserialized.Pop(); - switch (item) - { - case ByteArray _: - writer.Write((byte)StackItemType.ByteArray); - writer.WriteVarBytes(item.GetByteArray()); - break; - case VMBoolean _: - writer.Write((byte)StackItemType.Boolean); - writer.Write(item.GetBoolean()); - break; - case Integer _: - writer.Write((byte)StackItemType.Integer); - writer.WriteVarBytes(item.GetByteArray()); - break; - case InteropInterface _: - throw new NotSupportedException(); - case VMArray array: - if (serialized.Any(p => ReferenceEquals(p, array))) - throw new NotSupportedException(); - serialized.Add(array); - if (array is Struct) - writer.Write((byte)StackItemType.Struct); - else - writer.Write((byte)StackItemType.Array); - writer.WriteVarInt(array.Count); - for (int i = array.Count - 1; i >= 0; i--) - unserialized.Push(array[i]); - break; - case Map map: - if (serialized.Any(p => ReferenceEquals(p, map))) - throw new NotSupportedException(); - serialized.Add(map); - writer.Write((byte)StackItemType.Map); - writer.WriteVarInt(map.Count); - foreach (var pair in map.Reverse()) - { - unserialized.Push(pair.Value); - unserialized.Push(pair.Key); - } - break; - } - } - } - - protected virtual bool Runtime_Serialize(ExecutionEngine engine) - { - using (MemoryStream ms = new MemoryStream()) - using (BinaryWriter writer = new BinaryWriter(ms)) - { - try - { - SerializeStackItem(engine.CurrentContext.EvaluationStack.Pop(), writer); - } - catch (NotSupportedException) - { - return false; - } - writer.Flush(); - if (ms.Length > ApplicationEngine.MaxItemSize) - return false; - engine.CurrentContext.EvaluationStack.Push(ms.ToArray()); - } - return true; - } - - private StackItem DeserializeStackItem(BinaryReader reader) - { - Stack deserialized = new Stack(); - int undeserialized = 1; - while (undeserialized-- > 0) - { - StackItemType type = (StackItemType)reader.ReadByte(); - switch (type) - { - case StackItemType.ByteArray: - deserialized.Push(new ByteArray(reader.ReadVarBytes())); - break; - case StackItemType.Boolean: - deserialized.Push(new VMBoolean(reader.ReadBoolean())); - break; - case StackItemType.Integer: - deserialized.Push(new Integer(new BigInteger(reader.ReadVarBytes()))); - break; - case StackItemType.Array: - case StackItemType.Struct: - { - int count = (int)reader.ReadVarInt(ApplicationEngine.MaxArraySize); - deserialized.Push(new ContainerPlaceholder - { - Type = type, - ElementCount = count - }); - undeserialized += count; - } - break; - case StackItemType.Map: - { - int count = (int)reader.ReadVarInt(ApplicationEngine.MaxArraySize); - deserialized.Push(new ContainerPlaceholder - { - Type = type, - ElementCount = count - }); - undeserialized += count * 2; - } - break; - default: - throw new FormatException(); - } - } - Stack stack_temp = new Stack(); - while (deserialized.Count > 0) - { - StackItem item = deserialized.Pop(); - if (item is ContainerPlaceholder placeholder) - { - switch (placeholder.Type) - { - case StackItemType.Array: - VMArray array = new VMArray(); - for (int i = 0; i < placeholder.ElementCount; i++) - array.Add(stack_temp.Pop()); - item = array; - break; - case StackItemType.Struct: - Struct @struct = new Struct(); - for (int i = 0; i < placeholder.ElementCount; i++) - @struct.Add(stack_temp.Pop()); - item = @struct; - break; - case StackItemType.Map: - Map map = new Map(); - for (int i = 0; i < placeholder.ElementCount; i++) - { - StackItem key = stack_temp.Pop(); - StackItem value = stack_temp.Pop(); - map.Add(key, value); - } - item = map; - break; - } - } - stack_temp.Push(item); - } - return stack_temp.Peek(); - } - - protected virtual bool Runtime_Deserialize(ExecutionEngine engine) - { - byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - using (MemoryStream ms = new MemoryStream(data, false)) - using (BinaryReader reader = new BinaryReader(ms)) - { - StackItem item; - try - { - item = DeserializeStackItem(reader); - } - catch (FormatException) - { - return false; - } - catch (IOException) - { - return false; - } - engine.CurrentContext.EvaluationStack.Push(item); - } - return true; - } - - protected virtual bool Blockchain_GetHeight(ExecutionEngine engine) - { - if (Blockchain.Default == null) - engine.CurrentContext.EvaluationStack.Push(0); - else - engine.CurrentContext.EvaluationStack.Push(Blockchain.Default.Height); - return true; - } - - protected virtual bool Blockchain_GetHeader(ExecutionEngine engine) - { - byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - Header header; - if (data.Length <= 5) - { - uint height = (uint)new BigInteger(data); - if (Blockchain.Default != null) - header = Blockchain.Default.GetHeader(height); - else if (height == 0) - header = Blockchain.GenesisBlock.Header; - else - header = null; - } - else if (data.Length == 32) - { - UInt256 hash = new UInt256(data); - if (Blockchain.Default != null) - header = Blockchain.Default.GetHeader(hash); - else if (hash == Blockchain.GenesisBlock.Hash) - header = Blockchain.GenesisBlock.Header; - else - header = null; - } - else - { - return false; - } - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(header)); - return true; - } - - protected virtual bool Blockchain_GetBlock(ExecutionEngine engine) - { - byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - Block block; - if (data.Length <= 5) - { - uint height = (uint)new BigInteger(data); - if (Blockchain.Default != null) - block = Blockchain.Default.GetBlock(height); - else if (height == 0) - block = Blockchain.GenesisBlock; - else - block = null; - } - else if (data.Length == 32) - { - UInt256 hash = new UInt256(data); - if (Blockchain.Default != null) - block = Blockchain.Default.GetBlock(hash); - else if (hash == Blockchain.GenesisBlock.Hash) - block = Blockchain.GenesisBlock; - else - block = null; - } - else - { - return false; - } - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(block)); - return true; - } - - protected virtual bool Blockchain_GetTransaction(ExecutionEngine engine) - { - byte[] hash = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - Transaction tx = Blockchain.Default?.GetTransaction(new UInt256(hash)); - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(tx)); - return true; - } - - protected virtual bool Blockchain_GetTransactionHeight(ExecutionEngine engine) - { - byte[] hash = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - int height; - if (Blockchain.Default == null) - height = -1; - else - Blockchain.Default.GetTransaction(new UInt256(hash), out height); - engine.CurrentContext.EvaluationStack.Push(height); - return true; - } - - protected virtual bool Blockchain_GetAccount(ExecutionEngine engine) + private bool Blockchain_GetAccount(ExecutionEngine engine) { UInt160 hash = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - AccountState account = Accounts.GetOrAdd(hash, () => new AccountState(hash)); + AccountState account = Snapshot.Accounts.GetOrAdd(hash, () => new AccountState(hash)); engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(account)); return true; } - protected virtual bool Blockchain_GetValidators(ExecutionEngine engine) + private bool Blockchain_GetValidators(ExecutionEngine engine) { - ECPoint[] validators = Blockchain.Default.GetValidators(); + ECPoint[] validators = Snapshot.GetValidators(); engine.CurrentContext.EvaluationStack.Push(validators.Select(p => (StackItem)p.EncodePoint(true)).ToArray()); return true; } - protected virtual bool Blockchain_GetAsset(ExecutionEngine engine) + private bool Blockchain_GetAsset(ExecutionEngine engine) { UInt256 hash = new UInt256(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - AssetState asset = Assets.TryGet(hash); + AssetState asset = Snapshot.Assets.TryGet(hash); if (asset == null) return false; engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(asset)); return true; } - protected virtual bool Blockchain_GetContract(ExecutionEngine engine) - { - UInt160 hash = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - ContractState contract = Contracts.TryGet(hash); - if (contract == null) - engine.CurrentContext.EvaluationStack.Push(new byte[0]); - else - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); - return true; - } - - protected virtual bool Header_GetIndex(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - BlockBase header = _interface.GetInterface(); - if (header == null) return false; - engine.CurrentContext.EvaluationStack.Push(header.Index); - return true; - } - return false; - } - - protected virtual bool Header_GetHash(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - BlockBase header = _interface.GetInterface(); - if (header == null) return false; - engine.CurrentContext.EvaluationStack.Push(header.Hash.ToArray()); - return true; - } - return false; - } - - protected virtual bool Header_GetVersion(ExecutionEngine engine) + private bool Header_GetVersion(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -642,19 +197,7 @@ protected virtual bool Header_GetVersion(ExecutionEngine engine) return false; } - protected virtual bool Header_GetPrevHash(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - BlockBase header = _interface.GetInterface(); - if (header == null) return false; - engine.CurrentContext.EvaluationStack.Push(header.PrevHash.ToArray()); - return true; - } - return false; - } - - protected virtual bool Header_GetMerkleRoot(ExecutionEngine engine) + private bool Header_GetMerkleRoot(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -666,19 +209,7 @@ protected virtual bool Header_GetMerkleRoot(ExecutionEngine engine) return false; } - protected virtual bool Header_GetTimestamp(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - BlockBase header = _interface.GetInterface(); - if (header == null) return false; - engine.CurrentContext.EvaluationStack.Push(header.Timestamp); - return true; - } - return false; - } - - protected virtual bool Header_GetConsensusData(ExecutionEngine engine) + private bool Header_GetConsensusData(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -690,7 +221,7 @@ protected virtual bool Header_GetConsensusData(ExecutionEngine engine) return false; } - protected virtual bool Header_GetNextConsensus(ExecutionEngine engine) + private bool Header_GetNextConsensus(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -702,60 +233,7 @@ protected virtual bool Header_GetNextConsensus(ExecutionEngine engine) return false; } - protected virtual bool Block_GetTransactionCount(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - Block block = _interface.GetInterface(); - if (block == null) return false; - engine.CurrentContext.EvaluationStack.Push(block.Transactions.Length); - return true; - } - return false; - } - - protected virtual bool Block_GetTransactions(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - Block block = _interface.GetInterface(); - if (block == null) return false; - if (block.Transactions.Length > ApplicationEngine.MaxArraySize) - return false; - engine.CurrentContext.EvaluationStack.Push(block.Transactions.Select(p => StackItem.FromInterface(p)).ToArray()); - return true; - } - return false; - } - - protected virtual bool Block_GetTransaction(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - Block block = _interface.GetInterface(); - int index = (int)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - if (block == null) return false; - if (index < 0 || index >= block.Transactions.Length) return false; - Transaction tx = block.Transactions[index]; - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(tx)); - return true; - } - return false; - } - - protected virtual bool Transaction_GetHash(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - Transaction tx = _interface.GetInterface(); - if (tx == null) return false; - engine.CurrentContext.EvaluationStack.Push(tx.Hash.ToArray()); - return true; - } - return false; - } - - protected virtual bool Transaction_GetType(ExecutionEngine engine) + private bool Transaction_GetType(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -767,7 +245,7 @@ protected virtual bool Transaction_GetType(ExecutionEngine engine) return false; } - protected virtual bool Transaction_GetAttributes(ExecutionEngine engine) + private bool Transaction_GetAttributes(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -781,7 +259,7 @@ protected virtual bool Transaction_GetAttributes(ExecutionEngine engine) return false; } - protected virtual bool Transaction_GetInputs(ExecutionEngine engine) + private bool Transaction_GetInputs(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -795,7 +273,7 @@ protected virtual bool Transaction_GetInputs(ExecutionEngine engine) return false; } - protected virtual bool Transaction_GetOutputs(ExecutionEngine engine) + private bool Transaction_GetOutputs(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -809,7 +287,7 @@ protected virtual bool Transaction_GetOutputs(ExecutionEngine engine) return false; } - protected virtual bool Transaction_GetReferences(ExecutionEngine engine) + private bool Transaction_GetReferences(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -823,13 +301,13 @@ protected virtual bool Transaction_GetReferences(ExecutionEngine engine) return false; } - protected virtual bool Transaction_GetUnspentCoins(ExecutionEngine engine) + private bool Transaction_GetUnspentCoins(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { Transaction tx = _interface.GetInterface(); if (tx == null) return false; - TransactionOutput[] outputs = Blockchain.Default.GetUnspent(tx.Hash).ToArray(); + TransactionOutput[] outputs = Snapshot.GetUnspent(tx.Hash).ToArray(); if (outputs.Length > ApplicationEngine.MaxArraySize) return false; engine.CurrentContext.EvaluationStack.Push(outputs.Select(p => StackItem.FromInterface(p)).ToArray()); @@ -838,7 +316,7 @@ protected virtual bool Transaction_GetUnspentCoins(ExecutionEngine engine) return false; } - protected virtual bool InvocationTransaction_GetScript(ExecutionEngine engine) + private bool InvocationTransaction_GetScript(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -850,7 +328,7 @@ protected virtual bool InvocationTransaction_GetScript(ExecutionEngine engine) return false; } - protected virtual bool Attribute_GetUsage(ExecutionEngine engine) + private bool Attribute_GetUsage(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -862,7 +340,7 @@ protected virtual bool Attribute_GetUsage(ExecutionEngine engine) return false; } - protected virtual bool Attribute_GetData(ExecutionEngine engine) + private bool Attribute_GetData(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -874,7 +352,7 @@ protected virtual bool Attribute_GetData(ExecutionEngine engine) return false; } - protected virtual bool Input_GetHash(ExecutionEngine engine) + private bool Input_GetHash(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -886,7 +364,7 @@ protected virtual bool Input_GetHash(ExecutionEngine engine) return false; } - protected virtual bool Input_GetIndex(ExecutionEngine engine) + private bool Input_GetIndex(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -898,7 +376,7 @@ protected virtual bool Input_GetIndex(ExecutionEngine engine) return false; } - protected virtual bool Output_GetAssetId(ExecutionEngine engine) + private bool Output_GetAssetId(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -910,7 +388,7 @@ protected virtual bool Output_GetAssetId(ExecutionEngine engine) return false; } - protected virtual bool Output_GetValue(ExecutionEngine engine) + private bool Output_GetValue(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -922,7 +400,7 @@ protected virtual bool Output_GetValue(ExecutionEngine engine) return false; } - protected virtual bool Output_GetScriptHash(ExecutionEngine engine) + private bool Output_GetScriptHash(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -934,7 +412,7 @@ protected virtual bool Output_GetScriptHash(ExecutionEngine engine) return false; } - protected virtual bool Account_GetScriptHash(ExecutionEngine engine) + private bool Account_GetScriptHash(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -946,7 +424,7 @@ protected virtual bool Account_GetScriptHash(ExecutionEngine engine) return false; } - protected virtual bool Account_GetVotes(ExecutionEngine engine) + private bool Account_GetVotes(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -958,7 +436,7 @@ protected virtual bool Account_GetVotes(ExecutionEngine engine) return false; } - protected virtual bool Account_GetBalance(ExecutionEngine engine) + private bool Account_GetBalance(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -972,7 +450,77 @@ protected virtual bool Account_GetBalance(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetAssetId(ExecutionEngine engine) + private bool Asset_Create(ExecutionEngine engine) + { + if (Trigger != TriggerType.Application) return false; + InvocationTransaction tx = (InvocationTransaction)engine.ScriptContainer; + AssetType asset_type = (AssetType)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + if (!Enum.IsDefined(typeof(AssetType), asset_type) || asset_type == AssetType.CreditFlag || asset_type == AssetType.DutyFlag || asset_type == AssetType.GoverningToken || asset_type == AssetType.UtilityToken) + return false; + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 1024) + return false; + string name = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + Fixed8 amount = new Fixed8((long)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger()); + if (amount == Fixed8.Zero || amount < -Fixed8.Satoshi) return false; + if (asset_type == AssetType.Invoice && amount != -Fixed8.Satoshi) + return false; + byte precision = (byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + if (precision > 8) return false; + if (asset_type == AssetType.Share && precision != 0) return false; + if (amount != -Fixed8.Satoshi && amount.GetData() % (long)Math.Pow(10, 8 - precision) != 0) + return false; + ECPoint owner = ECPoint.DecodePoint(engine.CurrentContext.EvaluationStack.Pop().GetByteArray(), ECCurve.Secp256r1); + if (owner.IsInfinity) return false; + if (!CheckWitness(engine, owner)) + return false; + UInt160 admin = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + UInt160 issuer = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + AssetState asset = Snapshot.Assets.GetOrAdd(tx.Hash, () => new AssetState + { + AssetId = tx.Hash, + AssetType = asset_type, + Name = name, + Amount = amount, + Available = Fixed8.Zero, + Precision = precision, + Fee = Fixed8.Zero, + FeeAddress = new UInt160(), + Owner = owner, + Admin = admin, + Issuer = issuer, + Expiration = Snapshot.Height + 1 + 2000000, + IsFrozen = false + }); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(asset)); + return true; + } + + private bool Asset_Renew(ExecutionEngine engine) + { + if (Trigger != TriggerType.Application) return false; + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + AssetState asset = _interface.GetInterface(); + if (asset == null) return false; + byte years = (byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + asset = Snapshot.Assets.GetAndChange(asset.AssetId); + if (asset.Expiration < Snapshot.Height + 1) + asset.Expiration = Snapshot.Height + 1; + try + { + asset.Expiration = checked(asset.Expiration + years * 2000000u); + } + catch (OverflowException) + { + asset.Expiration = uint.MaxValue; + } + engine.CurrentContext.EvaluationStack.Push(asset.Expiration); + return true; + } + return false; + } + + private bool Asset_GetAssetId(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -984,7 +532,7 @@ protected virtual bool Asset_GetAssetId(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetAssetType(ExecutionEngine engine) + private bool Asset_GetAssetType(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -996,7 +544,7 @@ protected virtual bool Asset_GetAssetType(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetAmount(ExecutionEngine engine) + private bool Asset_GetAmount(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1008,7 +556,7 @@ protected virtual bool Asset_GetAmount(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetAvailable(ExecutionEngine engine) + private bool Asset_GetAvailable(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1020,7 +568,7 @@ protected virtual bool Asset_GetAvailable(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetPrecision(ExecutionEngine engine) + private bool Asset_GetPrecision(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1032,7 +580,7 @@ protected virtual bool Asset_GetPrecision(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetOwner(ExecutionEngine engine) + private bool Asset_GetOwner(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1044,7 +592,7 @@ protected virtual bool Asset_GetOwner(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetAdmin(ExecutionEngine engine) + private bool Asset_GetAdmin(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1056,7 +604,7 @@ protected virtual bool Asset_GetAdmin(ExecutionEngine engine) return false; } - protected virtual bool Asset_GetIssuer(ExecutionEngine engine) + private bool Asset_GetIssuer(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1068,69 +616,129 @@ protected virtual bool Asset_GetIssuer(ExecutionEngine engine) return false; } - protected virtual bool Contract_GetScript(ExecutionEngine engine) + private bool Contract_Create(ExecutionEngine engine) { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + if (Trigger != TriggerType.Application) return false; + byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + if (script.Length > 1024 * 1024) return false; + ContractParameterType[] parameter_list = engine.CurrentContext.EvaluationStack.Pop().GetByteArray().Select(p => (ContractParameterType)p).ToArray(); + if (parameter_list.Length > 252) return false; + ContractParameterType return_type = (ContractParameterType)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string name = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string version = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string author = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string email = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 65536) return false; + string description = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + UInt160 hash = script.ToScriptHash(); + ContractState contract = Snapshot.Contracts.TryGet(hash); + if (contract == null) { - ContractState contract = _interface.GetInterface(); - if (contract == null) return false; - engine.CurrentContext.EvaluationStack.Push(contract.Script); - return true; + contract = new ContractState + { + Script = script, + ParameterList = parameter_list, + ReturnType = return_type, + ContractProperties = contract_properties, + Name = name, + CodeVersion = version, + Author = author, + Email = email, + Description = description + }; + Snapshot.Contracts.Add(hash, contract); + ContractsCreated.Add(hash, new UInt160(engine.CurrentContext.ScriptHash)); + } + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); + return true; + } + + private bool Contract_Migrate(ExecutionEngine engine) + { + if (Trigger != TriggerType.Application) return false; + byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + if (script.Length > 1024 * 1024) return false; + ContractParameterType[] parameter_list = engine.CurrentContext.EvaluationStack.Pop().GetByteArray().Select(p => (ContractParameterType)p).ToArray(); + if (parameter_list.Length > 252) return false; + ContractParameterType return_type = (ContractParameterType)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string name = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string version = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string author = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; + string email = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 65536) return false; + string description = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + UInt160 hash = script.ToScriptHash(); + ContractState contract = Snapshot.Contracts.TryGet(hash); + if (contract == null) + { + contract = new ContractState + { + Script = script, + ParameterList = parameter_list, + ReturnType = return_type, + ContractProperties = contract_properties, + Name = name, + CodeVersion = version, + Author = author, + Email = email, + Description = description + }; + Snapshot.Contracts.Add(hash, contract); + ContractsCreated.Add(hash, new UInt160(engine.CurrentContext.ScriptHash)); + if (contract.HasStorage) + { + foreach (var pair in Snapshot.Storages.Find(engine.CurrentContext.ScriptHash).ToArray()) + { + Snapshot.Storages.Add(new StorageKey + { + ScriptHash = hash, + Key = pair.Key.Key + }, new StorageItem + { + Value = pair.Value.Value + }); + } + } } - return false; + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); + return Contract_Destroy(engine); } - protected virtual bool Contract_IsPayable(ExecutionEngine engine) + private bool Contract_GetScript(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { ContractState contract = _interface.GetInterface(); if (contract == null) return false; - engine.CurrentContext.EvaluationStack.Push(contract.Payable); + engine.CurrentContext.EvaluationStack.Push(contract.Script); return true; } return false; } - protected virtual bool Storage_GetContext(ExecutionEngine engine) - { - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - { - ScriptHash = new UInt160(engine.CurrentContext.ScriptHash), - IsReadOnly = false - })); - return true; - } - - protected virtual bool Storage_GetReadOnlyContext(ExecutionEngine engine) - { - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - { - ScriptHash = new UInt160(engine.CurrentContext.ScriptHash), - IsReadOnly = true - })); - return true; - } - - protected virtual bool Storage_Get(ExecutionEngine engine) + private bool Contract_IsPayable(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { - StorageContext context = _interface.GetInterface(); - if (!CheckStorageContext(context)) return false; - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - StorageItem item = Storages.TryGet(new StorageKey - { - ScriptHash = context.ScriptHash, - Key = key - }); - engine.CurrentContext.EvaluationStack.Push(item?.Value ?? new byte[0]); + ContractState contract = _interface.GetInterface(); + if (contract == null) return false; + engine.CurrentContext.EvaluationStack.Push(contract.Payable); return true; } return false; } - protected virtual bool Storage_Find(ExecutionEngine engine) + private bool Storage_Find(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1153,32 +761,15 @@ protected virtual bool Storage_Find(ExecutionEngine engine) ms.Write(prefix, index, remain); prefix_key = context.ScriptHash.ToArray().Concat(ms.ToArray()).ToArray(); } - StorageIterator iterator = new StorageIterator(Storages.Find(prefix_key).Where(p => p.Key.Key.Take(prefix.Length).SequenceEqual(prefix)).GetEnumerator()); + StorageIterator iterator = new StorageIterator(Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.Take(prefix.Length).SequenceEqual(prefix)).GetEnumerator()); engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator)); - disposables.Add(iterator); - return true; - } - return false; - } - - protected virtual bool StorageContext_AsReadOnly(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - StorageContext context = _interface.GetInterface(); - if (!context.IsReadOnly) - context = new StorageContext - { - ScriptHash = context.ScriptHash, - IsReadOnly = true - }; - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); + Disposables.Add(iterator); return true; } return false; } - protected virtual bool Enumerator_Create(ExecutionEngine engine) + private bool Enumerator_Create(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is VMArray array) { @@ -1189,7 +780,7 @@ protected virtual bool Enumerator_Create(ExecutionEngine engine) return false; } - protected virtual bool Enumerator_Next(ExecutionEngine engine) + private bool Enumerator_Next(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1200,7 +791,7 @@ protected virtual bool Enumerator_Next(ExecutionEngine engine) return false; } - protected virtual bool Enumerator_Value(ExecutionEngine engine) + private bool Enumerator_Value(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1211,7 +802,7 @@ protected virtual bool Enumerator_Value(ExecutionEngine engine) return false; } - protected virtual bool Enumerator_Concat(ExecutionEngine engine) + private bool Enumerator_Concat(ExecutionEngine engine) { if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface1)) return false; if (!(engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface2)) return false; @@ -1222,7 +813,7 @@ protected virtual bool Enumerator_Concat(ExecutionEngine engine) return true; } - protected virtual bool Iterator_Create(ExecutionEngine engine) + private bool Iterator_Create(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is Map map) { @@ -1233,7 +824,7 @@ protected virtual bool Iterator_Create(ExecutionEngine engine) return false; } - protected virtual bool Iterator_Key(ExecutionEngine engine) + private bool Iterator_Key(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1244,7 +835,7 @@ protected virtual bool Iterator_Key(ExecutionEngine engine) return false; } - protected virtual bool Iterator_Keys(ExecutionEngine engine) + private bool Iterator_Keys(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { @@ -1255,7 +846,7 @@ protected virtual bool Iterator_Keys(ExecutionEngine engine) return false; } - protected virtual bool Iterator_Values(ExecutionEngine engine) + private bool Iterator_Values(ExecutionEngine engine) { if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) { diff --git a/neo/SmartContract/StandardService.cs b/neo/SmartContract/StandardService.cs new file mode 100644 index 0000000000..6677c11535 --- /dev/null +++ b/neo/SmartContract/StandardService.cs @@ -0,0 +1,633 @@ +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text; +using VMArray = Neo.VM.Types.Array; +using VMBoolean = Neo.VM.Types.Boolean; + +namespace Neo.SmartContract +{ + public class StandardService : InteropService, IDisposable + { + public static event EventHandler Notify; + public static event EventHandler Log; + + protected readonly TriggerType Trigger; + protected readonly Snapshot Snapshot; + protected readonly List Disposables = new List(); + protected readonly Dictionary ContractsCreated = new Dictionary(); + private readonly List notifications = new List(); + + public IReadOnlyList Notifications => notifications; + + public StandardService(TriggerType trigger, Snapshot snapshot) + { + this.Trigger = trigger; + this.Snapshot = snapshot; + Register("System.Runtime.GetTrigger", Runtime_GetTrigger); + Register("System.Runtime.CheckWitness", Runtime_CheckWitness); + Register("System.Runtime.Notify", Runtime_Notify); + Register("System.Runtime.Log", Runtime_Log); + Register("System.Runtime.GetTime", Runtime_GetTime); + Register("System.Runtime.Serialize", Runtime_Serialize); + Register("System.Runtime.Deserialize", Runtime_Deserialize); + Register("System.Blockchain.GetHeight", Blockchain_GetHeight); + Register("System.Blockchain.GetHeader", Blockchain_GetHeader); + Register("System.Blockchain.GetBlock", Blockchain_GetBlock); + Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction); + Register("System.Blockchain.GetTransactionHeight", Blockchain_GetTransactionHeight); + Register("System.Blockchain.GetContract", Blockchain_GetContract); + Register("System.Header.GetIndex", Header_GetIndex); + Register("System.Header.GetHash", Header_GetHash); + Register("System.Header.GetPrevHash", Header_GetPrevHash); + Register("System.Header.GetTimestamp", Header_GetTimestamp); + Register("System.Block.GetTransactionCount", Block_GetTransactionCount); + Register("System.Block.GetTransactions", Block_GetTransactions); + Register("System.Block.GetTransaction", Block_GetTransaction); + Register("System.Transaction.GetHash", Transaction_GetHash); + Register("System.Contract.Destroy", Contract_Destroy); + Register("System.Contract.GetStorageContext", Contract_GetStorageContext); + Register("System.Storage.GetContext", Storage_GetContext); + Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext); + Register("System.Storage.Get", Storage_Get); + Register("System.Storage.Put", Storage_Put); + Register("System.Storage.Delete", Storage_Delete); + Register("System.StorageContext.AsReadOnly", StorageContext_AsReadOnly); + } + + internal bool CheckStorageContext(StorageContext context) + { + ContractState contract = Snapshot.Contracts.TryGet(context.ScriptHash); + if (contract == null) return false; + if (!contract.HasStorage) return false; + return true; + } + + public void Commit() + { + Snapshot.Commit(); + } + + public void Dispose() + { + foreach (IDisposable disposable in Disposables) + disposable.Dispose(); + Disposables.Clear(); + } + + protected bool Runtime_GetTrigger(ExecutionEngine engine) + { + engine.CurrentContext.EvaluationStack.Push((int)Trigger); + return true; + } + + protected bool CheckWitness(ExecutionEngine engine, UInt160 hash) + { + IVerifiable container = (IVerifiable)engine.ScriptContainer; + UInt160[] _hashes_for_verifying = container.GetScriptHashesForVerifying(Snapshot); + return _hashes_for_verifying.Contains(hash); + } + + protected bool CheckWitness(ExecutionEngine engine, ECPoint pubkey) + { + return CheckWitness(engine, Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()); + } + + protected bool Runtime_CheckWitness(ExecutionEngine engine) + { + byte[] hashOrPubkey = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + bool result; + if (hashOrPubkey.Length == 20) + result = CheckWitness(engine, new UInt160(hashOrPubkey)); + else if (hashOrPubkey.Length == 33) + result = CheckWitness(engine, ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)); + else + return false; + engine.CurrentContext.EvaluationStack.Push(result); + return true; + } + + protected bool Runtime_Notify(ExecutionEngine engine) + { + StackItem state = engine.CurrentContext.EvaluationStack.Pop(); + NotifyEventArgs notification = new NotifyEventArgs(engine.ScriptContainer, new UInt160(engine.CurrentContext.ScriptHash), state); + Notify?.Invoke(this, notification); + notifications.Add(notification); + return true; + } + + protected bool Runtime_Log(ExecutionEngine engine) + { + string message = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + Log?.Invoke(this, new LogEventArgs(engine.ScriptContainer, new UInt160(engine.CurrentContext.ScriptHash), message)); + return true; + } + + protected bool Runtime_GetTime(ExecutionEngine engine) + { + if (Snapshot.PersistingBlock == null) + { + Header header = Snapshot.GetHeader(Snapshot.CurrentBlockHash); + engine.CurrentContext.EvaluationStack.Push(header.Timestamp + Blockchain.SecondsPerBlock); + } + else + { + engine.CurrentContext.EvaluationStack.Push(Snapshot.PersistingBlock.Timestamp); + } + return true; + } + + private void SerializeStackItem(StackItem item, BinaryWriter writer) + { + List serialized = new List(); + Stack unserialized = new Stack(); + unserialized.Push(item); + while (unserialized.Count > 0) + { + item = unserialized.Pop(); + switch (item) + { + case ByteArray _: + writer.Write((byte)StackItemType.ByteArray); + writer.WriteVarBytes(item.GetByteArray()); + break; + case VMBoolean _: + writer.Write((byte)StackItemType.Boolean); + writer.Write(item.GetBoolean()); + break; + case Integer _: + writer.Write((byte)StackItemType.Integer); + writer.WriteVarBytes(item.GetByteArray()); + break; + case InteropInterface _: + throw new NotSupportedException(); + case VMArray array: + if (serialized.Any(p => ReferenceEquals(p, array))) + throw new NotSupportedException(); + serialized.Add(array); + if (array is Struct) + writer.Write((byte)StackItemType.Struct); + else + writer.Write((byte)StackItemType.Array); + writer.WriteVarInt(array.Count); + for (int i = array.Count - 1; i >= 0; i--) + unserialized.Push(array[i]); + break; + case Map map: + if (serialized.Any(p => ReferenceEquals(p, map))) + throw new NotSupportedException(); + serialized.Add(map); + writer.Write((byte)StackItemType.Map); + writer.WriteVarInt(map.Count); + foreach (var pair in map.Reverse()) + { + unserialized.Push(pair.Value); + unserialized.Push(pair.Key); + } + break; + } + } + } + + protected bool Runtime_Serialize(ExecutionEngine engine) + { + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter writer = new BinaryWriter(ms)) + { + try + { + SerializeStackItem(engine.CurrentContext.EvaluationStack.Pop(), writer); + } + catch (NotSupportedException) + { + return false; + } + writer.Flush(); + if (ms.Length > ApplicationEngine.MaxItemSize) + return false; + engine.CurrentContext.EvaluationStack.Push(ms.ToArray()); + } + return true; + } + + private StackItem DeserializeStackItem(BinaryReader reader) + { + Stack deserialized = new Stack(); + int undeserialized = 1; + while (undeserialized-- > 0) + { + StackItemType type = (StackItemType)reader.ReadByte(); + switch (type) + { + case StackItemType.ByteArray: + deserialized.Push(new ByteArray(reader.ReadVarBytes())); + break; + case StackItemType.Boolean: + deserialized.Push(new VMBoolean(reader.ReadBoolean())); + break; + case StackItemType.Integer: + deserialized.Push(new Integer(new BigInteger(reader.ReadVarBytes()))); + break; + case StackItemType.Array: + case StackItemType.Struct: + { + int count = (int)reader.ReadVarInt(ApplicationEngine.MaxArraySize); + deserialized.Push(new ContainerPlaceholder + { + Type = type, + ElementCount = count + }); + undeserialized += count; + } + break; + case StackItemType.Map: + { + int count = (int)reader.ReadVarInt(ApplicationEngine.MaxArraySize); + deserialized.Push(new ContainerPlaceholder + { + Type = type, + ElementCount = count + }); + undeserialized += count * 2; + } + break; + default: + throw new FormatException(); + } + } + Stack stack_temp = new Stack(); + while (deserialized.Count > 0) + { + StackItem item = deserialized.Pop(); + if (item is ContainerPlaceholder placeholder) + { + switch (placeholder.Type) + { + case StackItemType.Array: + VMArray array = new VMArray(); + for (int i = 0; i < placeholder.ElementCount; i++) + array.Add(stack_temp.Pop()); + item = array; + break; + case StackItemType.Struct: + Struct @struct = new Struct(); + for (int i = 0; i < placeholder.ElementCount; i++) + @struct.Add(stack_temp.Pop()); + item = @struct; + break; + case StackItemType.Map: + Map map = new Map(); + for (int i = 0; i < placeholder.ElementCount; i++) + { + StackItem key = stack_temp.Pop(); + StackItem value = stack_temp.Pop(); + map.Add(key, value); + } + item = map; + break; + } + } + stack_temp.Push(item); + } + return stack_temp.Peek(); + } + + protected bool Runtime_Deserialize(ExecutionEngine engine) + { + byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + using (MemoryStream ms = new MemoryStream(data, false)) + using (BinaryReader reader = new BinaryReader(ms)) + { + StackItem item; + try + { + item = DeserializeStackItem(reader); + } + catch (FormatException) + { + return false; + } + catch (IOException) + { + return false; + } + engine.CurrentContext.EvaluationStack.Push(item); + } + return true; + } + + protected bool Blockchain_GetHeight(ExecutionEngine engine) + { + engine.CurrentContext.EvaluationStack.Push(Snapshot.Height); + return true; + } + + protected bool Blockchain_GetHeader(ExecutionEngine engine) + { + byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + UInt256 hash; + if (data.Length <= 5) + hash = Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); + else if (data.Length == 32) + hash = new UInt256(data); + else + return false; + if (hash == null) + { + engine.CurrentContext.EvaluationStack.Push(new byte[0]); + } + else + { + Header header = Snapshot.GetHeader(hash); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(header)); + } + return true; + } + + protected bool Blockchain_GetBlock(ExecutionEngine engine) + { + byte[] data = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + UInt256 hash; + if (data.Length <= 5) + hash = Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); + else if (data.Length == 32) + hash = new UInt256(data); + else + return false; + if (hash == null) + { + engine.CurrentContext.EvaluationStack.Push(new byte[0]); + } + else + { + Block block = Snapshot.GetBlock(hash); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(block)); + } + return true; + } + + protected bool Blockchain_GetTransaction(ExecutionEngine engine) + { + byte[] hash = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + Transaction tx = Snapshot.GetTransaction(new UInt256(hash)); + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(tx)); + return true; + } + + protected bool Blockchain_GetTransactionHeight(ExecutionEngine engine) + { + byte[] hash = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + int? height = (int?)Snapshot.Transactions.TryGet(new UInt256(hash))?.BlockIndex; + engine.CurrentContext.EvaluationStack.Push(height ?? -1); + return true; + } + + protected bool Blockchain_GetContract(ExecutionEngine engine) + { + UInt160 hash = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); + ContractState contract = Snapshot.Contracts.TryGet(hash); + if (contract == null) + engine.CurrentContext.EvaluationStack.Push(new byte[0]); + else + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); + return true; + } + + protected bool Header_GetIndex(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + BlockBase header = _interface.GetInterface(); + if (header == null) return false; + engine.CurrentContext.EvaluationStack.Push(header.Index); + return true; + } + return false; + } + + protected bool Header_GetHash(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + BlockBase header = _interface.GetInterface(); + if (header == null) return false; + engine.CurrentContext.EvaluationStack.Push(header.Hash.ToArray()); + return true; + } + return false; + } + + protected bool Header_GetPrevHash(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + BlockBase header = _interface.GetInterface(); + if (header == null) return false; + engine.CurrentContext.EvaluationStack.Push(header.PrevHash.ToArray()); + return true; + } + return false; + } + + protected bool Header_GetTimestamp(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + BlockBase header = _interface.GetInterface(); + if (header == null) return false; + engine.CurrentContext.EvaluationStack.Push(header.Timestamp); + return true; + } + return false; + } + + protected bool Block_GetTransactionCount(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + Block block = _interface.GetInterface(); + if (block == null) return false; + engine.CurrentContext.EvaluationStack.Push(block.Transactions.Length); + return true; + } + return false; + } + + protected bool Block_GetTransactions(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + Block block = _interface.GetInterface(); + if (block == null) return false; + if (block.Transactions.Length > ApplicationEngine.MaxArraySize) + return false; + engine.CurrentContext.EvaluationStack.Push(block.Transactions.Select(p => StackItem.FromInterface(p)).ToArray()); + return true; + } + return false; + } + + protected bool Block_GetTransaction(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + Block block = _interface.GetInterface(); + int index = (int)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + if (block == null) return false; + if (index < 0 || index >= block.Transactions.Length) return false; + Transaction tx = block.Transactions[index]; + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(tx)); + return true; + } + return false; + } + + protected bool Transaction_GetHash(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + Transaction tx = _interface.GetInterface(); + if (tx == null) return false; + engine.CurrentContext.EvaluationStack.Push(tx.Hash.ToArray()); + return true; + } + return false; + } + + protected bool Storage_GetContext(ExecutionEngine engine) + { + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + { + ScriptHash = new UInt160(engine.CurrentContext.ScriptHash), + IsReadOnly = false + })); + return true; + } + + protected bool Storage_GetReadOnlyContext(ExecutionEngine engine) + { + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + { + ScriptHash = new UInt160(engine.CurrentContext.ScriptHash), + IsReadOnly = true + })); + return true; + } + + protected bool Storage_Get(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (!CheckStorageContext(context)) return false; + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + StorageItem item = Snapshot.Storages.TryGet(new StorageKey + { + ScriptHash = context.ScriptHash, + Key = key + }); + engine.CurrentContext.EvaluationStack.Push(item?.Value ?? new byte[0]); + return true; + } + return false; + } + + protected bool StorageContext_AsReadOnly(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (!context.IsReadOnly) + context = new StorageContext + { + ScriptHash = context.ScriptHash, + IsReadOnly = true + }; + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context)); + return true; + } + return false; + } + + protected bool Contract_GetStorageContext(ExecutionEngine engine) + { + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + ContractState contract = _interface.GetInterface(); + if (!ContractsCreated.TryGetValue(contract.ScriptHash, out UInt160 created)) return false; + if (!created.Equals(new UInt160(engine.CurrentContext.ScriptHash))) return false; + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext + { + ScriptHash = contract.ScriptHash, + IsReadOnly = false + })); + return true; + } + return false; + } + + protected bool Contract_Destroy(ExecutionEngine engine) + { + if (Trigger != TriggerType.Application) return false; + UInt160 hash = new UInt160(engine.CurrentContext.ScriptHash); + ContractState contract = Snapshot.Contracts.TryGet(hash); + if (contract == null) return true; + Snapshot.Contracts.Delete(hash); + if (contract.HasStorage) + foreach (var pair in Snapshot.Storages.Find(hash.ToArray())) + Snapshot.Storages.Delete(pair.Key); + return true; + } + + protected bool Storage_Put(ExecutionEngine engine) + { + if (Trigger != TriggerType.Application && Trigger != TriggerType.ApplicationR) + return false; + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (context.IsReadOnly) return false; + if (!CheckStorageContext(context)) return false; + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + if (key.Length > 1024) return false; + byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + Snapshot.Storages.GetAndChange(new StorageKey + { + ScriptHash = context.ScriptHash, + Key = key + }, () => new StorageItem()).Value = value; + return true; + } + return false; + } + + protected bool Storage_Delete(ExecutionEngine engine) + { + if (Trigger != TriggerType.Application && Trigger != TriggerType.ApplicationR) + return false; + if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) + { + StorageContext context = _interface.GetInterface(); + if (context.IsReadOnly) return false; + if (!CheckStorageContext(context)) return false; + byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); + Snapshot.Storages.Delete(new StorageKey + { + ScriptHash = context.ScriptHash, + Key = key + }); + return true; + } + return false; + } + } +} diff --git a/neo/SmartContract/StateMachine.cs b/neo/SmartContract/StateMachine.cs deleted file mode 100644 index d122dd32d6..0000000000 --- a/neo/SmartContract/StateMachine.cs +++ /dev/null @@ -1,309 +0,0 @@ -using Neo.Core; -using Neo.Cryptography.ECC; -using Neo.IO.Caching; -using Neo.VM; -using Neo.VM.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Neo.SmartContract -{ - public class StateMachine : StateReader - { - private readonly Block persisting_block; - private readonly DataCache accounts; - private readonly DataCache assets; - private readonly DataCache contracts; - private readonly DataCache storages; - - private Dictionary contracts_created = new Dictionary(); - - protected override DataCache Accounts => accounts; - protected override DataCache Assets => assets; - protected override DataCache Contracts => contracts; - protected override DataCache Storages => storages; - - public StateMachine(Block persisting_block, DataCache accounts, DataCache assets, DataCache contracts, DataCache storages) - { - this.persisting_block = persisting_block; - this.accounts = accounts.CreateSnapshot(); - this.assets = assets.CreateSnapshot(); - this.contracts = contracts.CreateSnapshot(); - this.storages = storages.CreateSnapshot(); - - //Standard Library - Register("System.Contract.GetStorageContext", Contract_GetStorageContext); - Register("System.Contract.Destroy", Contract_Destroy); - Register("System.Storage.Put", Storage_Put); - Register("System.Storage.Delete", Storage_Delete); - - //Neo Specified - Register("Neo.Asset.Create", Asset_Create); - Register("Neo.Asset.Renew", Asset_Renew); - Register("Neo.Contract.Create", Contract_Create); - Register("Neo.Contract.Migrate", Contract_Migrate); - - #region Old APIs - Register("AntShares.Asset.Create", Asset_Create); - Register("AntShares.Asset.Renew", Asset_Renew); - Register("AntShares.Contract.Create", Contract_Create); - Register("AntShares.Contract.Migrate", Contract_Migrate); - Register("Neo.Contract.GetStorageContext", Contract_GetStorageContext); - Register("AntShares.Contract.GetStorageContext", Contract_GetStorageContext); - Register("Neo.Contract.Destroy", Contract_Destroy); - Register("AntShares.Contract.Destroy", Contract_Destroy); - Register("Neo.Storage.Put", Storage_Put); - Register("AntShares.Storage.Put", Storage_Put); - Register("Neo.Storage.Delete", Storage_Delete); - Register("AntShares.Storage.Delete", Storage_Delete); - #endregion - } - - public void Commit() - { - accounts.Commit(); - assets.Commit(); - contracts.Commit(); - storages.Commit(); - } - - protected override bool Runtime_GetTime(ExecutionEngine engine) - { - engine.CurrentContext.EvaluationStack.Push(persisting_block.Timestamp); - return true; - } - - private bool Asset_Create(ExecutionEngine engine) - { - InvocationTransaction tx = (InvocationTransaction)engine.ScriptContainer; - AssetType asset_type = (AssetType)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - if (!Enum.IsDefined(typeof(AssetType), asset_type) || asset_type == AssetType.CreditFlag || asset_type == AssetType.DutyFlag || asset_type == AssetType.GoverningToken || asset_type == AssetType.UtilityToken) - return false; - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 1024) - return false; - string name = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - Fixed8 amount = new Fixed8((long)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger()); - if (amount == Fixed8.Zero || amount < -Fixed8.Satoshi) return false; - if (asset_type == AssetType.Invoice && amount != -Fixed8.Satoshi) - return false; - byte precision = (byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - if (precision > 8) return false; - if (asset_type == AssetType.Share && precision != 0) return false; - if (amount != -Fixed8.Satoshi && amount.GetData() % (long)Math.Pow(10, 8 - precision) != 0) - return false; - ECPoint owner = ECPoint.DecodePoint(engine.CurrentContext.EvaluationStack.Pop().GetByteArray(), ECCurve.Secp256r1); - if (owner.IsInfinity) return false; - if (!CheckWitness(engine, owner)) - return false; - UInt160 admin = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - UInt160 issuer = new UInt160(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - AssetState asset = assets.GetOrAdd(tx.Hash, () => new AssetState - { - AssetId = tx.Hash, - AssetType = asset_type, - Name = name, - Amount = amount, - Available = Fixed8.Zero, - Precision = precision, - Fee = Fixed8.Zero, - FeeAddress = new UInt160(), - Owner = owner, - Admin = admin, - Issuer = issuer, - Expiration = Blockchain.Default.Height + 1 + 2000000, - IsFrozen = false - }); - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(asset)); - return true; - } - - private bool Asset_Renew(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - AssetState asset = _interface.GetInterface(); - if (asset == null) return false; - byte years = (byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - asset = assets.GetAndChange(asset.AssetId); - if (asset.Expiration < Blockchain.Default.Height + 1) - asset.Expiration = Blockchain.Default.Height + 1; - try - { - asset.Expiration = checked(asset.Expiration + years * 2000000u); - } - catch (OverflowException) - { - asset.Expiration = uint.MaxValue; - } - engine.CurrentContext.EvaluationStack.Push(asset.Expiration); - return true; - } - return false; - } - - private bool Contract_Create(ExecutionEngine engine) - { - byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - if (script.Length > 1024 * 1024) return false; - ContractParameterType[] parameter_list = engine.CurrentContext.EvaluationStack.Pop().GetByteArray().Select(p => (ContractParameterType)p).ToArray(); - if (parameter_list.Length > 252) return false; - ContractParameterType return_type = (ContractParameterType)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string name = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string version = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string author = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string email = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 65536) return false; - string description = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - UInt160 hash = script.ToScriptHash(); - ContractState contract = contracts.TryGet(hash); - if (contract == null) - { - contract = new ContractState - { - Script = script, - ParameterList = parameter_list, - ReturnType = return_type, - ContractProperties = contract_properties, - Name = name, - CodeVersion = version, - Author = author, - Email = email, - Description = description - }; - contracts.Add(hash, contract); - contracts_created.Add(hash, new UInt160(engine.CurrentContext.ScriptHash)); - } - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); - return true; - } - - private bool Contract_Migrate(ExecutionEngine engine) - { - byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - if (script.Length > 1024 * 1024) return false; - ContractParameterType[] parameter_list = engine.CurrentContext.EvaluationStack.Pop().GetByteArray().Select(p => (ContractParameterType)p).ToArray(); - if (parameter_list.Length > 252) return false; - ContractParameterType return_type = (ContractParameterType)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string name = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string version = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string author = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 252) return false; - string email = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - if (engine.CurrentContext.EvaluationStack.Peek().GetByteArray().Length > 65536) return false; - string description = Encoding.UTF8.GetString(engine.CurrentContext.EvaluationStack.Pop().GetByteArray()); - UInt160 hash = script.ToScriptHash(); - ContractState contract = contracts.TryGet(hash); - if (contract == null) - { - contract = new ContractState - { - Script = script, - ParameterList = parameter_list, - ReturnType = return_type, - ContractProperties = contract_properties, - Name = name, - CodeVersion = version, - Author = author, - Email = email, - Description = description - }; - contracts.Add(hash, contract); - contracts_created.Add(hash, new UInt160(engine.CurrentContext.ScriptHash)); - if (contract.HasStorage) - { - foreach (var pair in storages.Find(engine.CurrentContext.ScriptHash).ToArray()) - { - storages.Add(new StorageKey - { - ScriptHash = hash, - Key = pair.Key.Key - }, new StorageItem - { - Value = pair.Value.Value - }); - } - } - } - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract)); - return Contract_Destroy(engine); - } - - private bool Contract_GetStorageContext(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - ContractState contract = _interface.GetInterface(); - if (!contracts_created.TryGetValue(contract.ScriptHash, out UInt160 created)) return false; - if (!created.Equals(new UInt160(engine.CurrentContext.ScriptHash))) return false; - engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext - { - ScriptHash = contract.ScriptHash, - IsReadOnly = false - })); - return true; - } - return false; - } - - private bool Contract_Destroy(ExecutionEngine engine) - { - UInt160 hash = new UInt160(engine.CurrentContext.ScriptHash); - ContractState contract = contracts.TryGet(hash); - if (contract == null) return true; - contracts.Delete(hash); - if (contract.HasStorage) - foreach (var pair in storages.Find(hash.ToArray())) - storages.Delete(pair.Key); - return true; - } - - private bool Storage_Put(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - StorageContext context = _interface.GetInterface(); - if (context.IsReadOnly) return false; - if (!CheckStorageContext(context)) return false; - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - if (key.Length > 1024) return false; - byte[] value = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - storages.GetAndChange(new StorageKey - { - ScriptHash = context.ScriptHash, - Key = key - }, () => new StorageItem()).Value = value; - return true; - } - return false; - } - - private bool Storage_Delete(ExecutionEngine engine) - { - if (engine.CurrentContext.EvaluationStack.Pop() is InteropInterface _interface) - { - StorageContext context = _interface.GetInterface(); - if (context.IsReadOnly) return false; - if (!CheckStorageContext(context)) return false; - byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetByteArray(); - storages.Delete(new StorageKey - { - ScriptHash = context.ScriptHash, - Key = key - }); - return true; - } - return false; - } - } -} diff --git a/neo/SmartContract/TriggerType.cs b/neo/SmartContract/TriggerType.cs index e58b8675cf..be74afdb58 100644 --- a/neo/SmartContract/TriggerType.cs +++ b/neo/SmartContract/TriggerType.cs @@ -11,6 +11,16 @@ public enum TriggerType : byte /// Verification = 0x00, /// + /// The verificationR trigger indicates that the contract is being invoked as a verification function because it is specified as a target of an output of the transaction. + /// The verification function accepts no parameter, and should return a boolean value that indicates the validity of the transaction. + /// The entry point of the contract will be invoked if the contract is triggered by VerificationR: + /// main("receiving", new object[0]); + /// The receiving function should have the following signature: + /// public bool receiving() + /// The receiving function will be invoked automatically when a contract is receiving assets from a transfer. + /// + VerificationR = 0x01, + /// /// The application trigger indicates that the contract is being invoked as an application function. /// The application function can accept multiple parameters, change the states of the blockchain, and return any type of value. /// The contract can have any form of entry point, but we recommend that all contracts should have the following entry point: @@ -18,5 +28,15 @@ public enum TriggerType : byte /// The functions can be invoked by creating an InvocationTransaction. /// Application = 0x10, + /// + /// The ApplicationR trigger indicates that the default function received of the contract is being invoked because it is specified as a target of an output of the transaction. + /// The received function accepts no parameter, changes the states of the blockchain, and returns any type of value. + /// The entry point of the contract will be invoked if the contract is triggered by ApplicationR: + /// main("received", new object[0]); + /// The received function should have the following signature: + /// public byte[] received() + /// The received function will be invoked automatically when a contract is receiving assets from a transfer. + /// + ApplicationR = 0x11 } } diff --git a/neo/UIntBase.cs b/neo/UIntBase.cs index 1d92a64c37..7a94b241f4 100644 --- a/neo/UIntBase.cs +++ b/neo/UIntBase.cs @@ -73,10 +73,6 @@ public byte[] ToArray() return data_bytes; } - /// - /// 转为16进制字符串 - /// - /// 返回16进制字符串 public override string ToString() { return "0x" + data_bytes.Reverse().ToHexString(); diff --git a/neo/Wallets/AssetDescriptor.cs b/neo/Wallets/AssetDescriptor.cs index 5b2db76e8f..a1ca272f26 100644 --- a/neo/Wallets/AssetDescriptor.cs +++ b/neo/Wallets/AssetDescriptor.cs @@ -1,4 +1,4 @@ -using Neo.Core; +using Neo.Ledger; using Neo.SmartContract; using Neo.VM; using System; @@ -30,7 +30,7 @@ public AssetDescriptor(UIntBase asset_id) } else { - AssetState state = Blockchain.Default.GetAssetState((UInt256)asset_id); + AssetState state = Blockchain.Singleton.Store.GetAssets()[(UInt256)asset_id]; this.AssetId = state.AssetId; this.AssetName = state.GetName(); this.Decimals = state.Precision; diff --git a/neo/Wallets/BalanceEventArgs.cs b/neo/Wallets/BalanceEventArgs.cs index e728363b1a..492ab6322c 100644 --- a/neo/Wallets/BalanceEventArgs.cs +++ b/neo/Wallets/BalanceEventArgs.cs @@ -1,4 +1,4 @@ -using Neo.Core; +using Neo.Network.P2P.Payloads; using System; namespace Neo.Wallets diff --git a/neo/Wallets/Coin.cs b/neo/Wallets/Coin.cs index a313468a51..b993fd9c89 100644 --- a/neo/Wallets/Coin.cs +++ b/neo/Wallets/Coin.cs @@ -1,4 +1,5 @@ -using Neo.Core; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using System; namespace Neo.Wallets @@ -16,7 +17,7 @@ public string Address { if (_address == null) { - _address = Wallet.ToAddress(Output.ScriptHash); + _address = Output.ScriptHash.ToAddress(); } return _address; } @@ -25,7 +26,7 @@ public string Address public bool Equals(Coin other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; return Reference.Equals(other.Reference); } diff --git a/neo/Wallets/Helper.cs b/neo/Wallets/Helper.cs new file mode 100644 index 0000000000..78903b4637 --- /dev/null +++ b/neo/Wallets/Helper.cs @@ -0,0 +1,34 @@ +using Neo.Cryptography; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using System; +using System.Linq; + +namespace Neo.Wallets +{ + public static class Helper + { + public static byte[] Sign(this IVerifiable verifiable, KeyPair key) + { + return Crypto.Default.Sign(verifiable.GetHashData(), key.PrivateKey, key.PublicKey.EncodePoint(false).Skip(1).ToArray()); + } + + public static string ToAddress(this UInt160 scriptHash) + { + byte[] data = new byte[21]; + data[0] = Settings.Default.AddressVersion; + Buffer.BlockCopy(scriptHash.ToArray(), 0, data, 1, 20); + return data.Base58CheckEncode(); + } + + public static UInt160 ToScriptHash(this string address) + { + byte[] data = address.Base58CheckDecode(); + if (data.Length != 21) + throw new FormatException(); + if (data[0] != Settings.Default.AddressVersion) + throw new FormatException(); + return new UInt160(data.Skip(1).ToArray()); + } + } +} diff --git a/neo/Wallets/KeyPair.cs b/neo/Wallets/KeyPair.cs index 94b07116e1..0125d10f99 100644 --- a/neo/Wallets/KeyPair.cs +++ b/neo/Wallets/KeyPair.cs @@ -1,9 +1,7 @@ -using Neo.Core; -using Neo.Cryptography; +using Neo.Cryptography; using Neo.SmartContract; using System; using System.Linq; -using System.Security.Cryptography; using System.Text; namespace Neo.Wallets @@ -29,24 +27,12 @@ public KeyPair(byte[] privateKey) { this.PublicKey = Cryptography.ECC.ECPoint.FromBytes(privateKey, Cryptography.ECC.ECCurve.Secp256r1); } -#if NET47 - ProtectedMemory.Protect(PrivateKey, MemoryProtectionScope.SameProcess); -#endif - } - - public IDisposable Decrypt() - { -#if NET47 - return new ProtectedMemoryContext(PrivateKey, MemoryProtectionScope.SameProcess); -#else - return new System.IO.MemoryStream(0); -#endif } public bool Equals(KeyPair other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; return PublicKey.Equals(other.PublicKey); } @@ -57,37 +43,31 @@ public override bool Equals(object obj) public string Export() { - using (Decrypt()) - { - byte[] data = new byte[34]; - data[0] = 0x80; - Buffer.BlockCopy(PrivateKey, 0, data, 1, 32); - data[33] = 0x01; - string wif = data.Base58CheckEncode(); - Array.Clear(data, 0, data.Length); - return wif; - } + byte[] data = new byte[34]; + data[0] = 0x80; + Buffer.BlockCopy(PrivateKey, 0, data, 1, 32); + data[33] = 0x01; + string wif = data.Base58CheckEncode(); + Array.Clear(data, 0, data.Length); + return wif; } public string Export(string passphrase, int N = 16384, int r = 8, int p = 8) { - using (Decrypt()) - { - UInt160 script_hash = Contract.CreateSignatureRedeemScript(PublicKey).ToScriptHash(); - string address = Wallet.ToAddress(script_hash); - byte[] addresshash = Encoding.ASCII.GetBytes(address).Sha256().Sha256().Take(4).ToArray(); - byte[] derivedkey = SCrypt.DeriveKey(Encoding.UTF8.GetBytes(passphrase), addresshash, N, r, p, 64); - byte[] derivedhalf1 = derivedkey.Take(32).ToArray(); - byte[] derivedhalf2 = derivedkey.Skip(32).ToArray(); - byte[] encryptedkey = XOR(PrivateKey, derivedhalf1).AES256Encrypt(derivedhalf2); - byte[] buffer = new byte[39]; - buffer[0] = 0x01; - buffer[1] = 0x42; - buffer[2] = 0xe0; - Buffer.BlockCopy(addresshash, 0, buffer, 3, addresshash.Length); - Buffer.BlockCopy(encryptedkey, 0, buffer, 7, encryptedkey.Length); - return buffer.Base58CheckEncode(); - } + UInt160 script_hash = Contract.CreateSignatureRedeemScript(PublicKey).ToScriptHash(); + string address = script_hash.ToAddress(); + byte[] addresshash = Encoding.ASCII.GetBytes(address).Sha256().Sha256().Take(4).ToArray(); + byte[] derivedkey = SCrypt.DeriveKey(Encoding.UTF8.GetBytes(passphrase), addresshash, N, r, p, 64); + byte[] derivedhalf1 = derivedkey.Take(32).ToArray(); + byte[] derivedhalf2 = derivedkey.Skip(32).ToArray(); + byte[] encryptedkey = XOR(PrivateKey, derivedhalf1).AES256Encrypt(derivedhalf2); + byte[] buffer = new byte[39]; + buffer[0] = 0x01; + buffer[1] = 0x42; + buffer[2] = 0xe0; + Buffer.BlockCopy(addresshash, 0, buffer, 3, addresshash.Length); + Buffer.BlockCopy(encryptedkey, 0, buffer, 7, encryptedkey.Length); + return buffer.Base58CheckEncode(); } public override int GetHashCode() diff --git a/neo/Implementations/Wallets/NEP6/NEP6Account.cs b/neo/Wallets/NEP6/NEP6Account.cs similarity index 91% rename from neo/Implementations/Wallets/NEP6/NEP6Account.cs rename to neo/Wallets/NEP6/NEP6Account.cs index 201b2bf30b..ddeace2523 100644 --- a/neo/Implementations/Wallets/NEP6/NEP6Account.cs +++ b/neo/Wallets/NEP6/NEP6Account.cs @@ -1,8 +1,7 @@ using Neo.IO.Json; -using Neo.Wallets; using System; -namespace Neo.Implementations.Wallets.NEP6 +namespace Neo.Wallets.NEP6 { internal class NEP6Account : WalletAccount { @@ -29,7 +28,7 @@ public NEP6Account(NEP6Wallet wallet, UInt160 scriptHash, KeyPair key, string pa public static NEP6Account FromJson(JObject json, NEP6Wallet wallet) { - return new NEP6Account(wallet, Wallet.ToScriptHash(json["address"].AsString()), json["key"]?.AsString()) + return new NEP6Account(wallet, json["address"].AsString().ToScriptHash(), json["key"]?.AsString()) { Label = json["label"]?.AsString(), IsDefault = json["isDefault"].AsBoolean(), @@ -62,7 +61,7 @@ public KeyPair GetKey(string password) public JObject ToJson() { JObject account = new JObject(); - account["address"] = Wallet.ToAddress(ScriptHash); + account["address"] = ScriptHash.ToAddress(); account["label"] = Label; account["isDefault"] = IsDefault; account["lock"] = Lock; diff --git a/neo/Implementations/Wallets/NEP6/NEP6Contract.cs b/neo/Wallets/NEP6/NEP6Contract.cs similarity index 96% rename from neo/Implementations/Wallets/NEP6/NEP6Contract.cs rename to neo/Wallets/NEP6/NEP6Contract.cs index 2f2f096e1d..d934cf0326 100644 --- a/neo/Implementations/Wallets/NEP6/NEP6Contract.cs +++ b/neo/Wallets/NEP6/NEP6Contract.cs @@ -2,7 +2,7 @@ using Neo.SmartContract; using System.Linq; -namespace Neo.Implementations.Wallets.NEP6 +namespace Neo.Wallets.NEP6 { internal class NEP6Contract : Contract { diff --git a/neo/Implementations/Wallets/NEP6/NEP6Wallet.cs b/neo/Wallets/NEP6/NEP6Wallet.cs similarity index 97% rename from neo/Implementations/Wallets/NEP6/NEP6Wallet.cs rename to neo/Wallets/NEP6/NEP6Wallet.cs index 78cae1358e..76a96a9453 100644 --- a/neo/Implementations/Wallets/NEP6/NEP6Wallet.cs +++ b/neo/Wallets/NEP6/NEP6Wallet.cs @@ -1,18 +1,18 @@ -using Neo.Core; -using Neo.IO.Json; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; -using Neo.Wallets; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using UserWallet = Neo.Implementations.Wallets.EntityFramework.UserWallet; +using UserWallet = Neo.Wallets.SQLite.UserWallet; -namespace Neo.Implementations.Wallets.NEP6 +namespace Neo.Wallets.NEP6 { - public class NEP6Wallet : Wallet, IDisposable + public class NEP6Wallet : Wallet { public override event EventHandler BalanceChanged; @@ -86,7 +86,7 @@ private void AddAccount(NEP6Account account, bool is_import) } else { - indexer.RegisterAccounts(new[] { account.ScriptHash }, is_import ? 0 : Blockchain.Default?.Height ?? 0); + indexer.RegisterAccounts(new[] { account.ScriptHash }, is_import ? 0 : Blockchain.Singleton.Height); } accounts[account.ScriptHash] = account; } @@ -101,7 +101,7 @@ public override void ApplyTransaction(Transaction tx) BalanceChanged?.Invoke(this, new BalanceEventArgs { Transaction = tx, - RelatedAccounts = tx.Scripts.Select(p => p.ScriptHash).Union(tx.Outputs.Select(p => p.ScriptHash)).Where(p => Contains(p)).ToArray(), + RelatedAccounts = tx.Witnesses.Select(p => p.ScriptHash).Union(tx.Outputs.Select(p => p.ScriptHash)).Where(p => Contains(p)).ToArray(), Height = null, Time = DateTime.UtcNow.ToTimestamp() }); @@ -182,7 +182,7 @@ public override bool DeleteAccount(UInt160 scriptHash) return removed; } - public void Dispose() + public override void Dispose() { indexer.BalanceChanged -= WalletIndexer_BalanceChanged; } diff --git a/neo/Implementations/Wallets/NEP6/ScryptParameters.cs b/neo/Wallets/NEP6/ScryptParameters.cs similarity index 94% rename from neo/Implementations/Wallets/NEP6/ScryptParameters.cs rename to neo/Wallets/NEP6/ScryptParameters.cs index 1d53d34142..018f0bfd46 100644 --- a/neo/Implementations/Wallets/NEP6/ScryptParameters.cs +++ b/neo/Wallets/NEP6/ScryptParameters.cs @@ -1,6 +1,6 @@ using Neo.IO.Json; -namespace Neo.Implementations.Wallets.NEP6 +namespace Neo.Wallets.NEP6 { public class ScryptParameters { diff --git a/neo/Implementations/Wallets/NEP6/WalletLocker.cs b/neo/Wallets/NEP6/WalletLocker.cs similarity index 87% rename from neo/Implementations/Wallets/NEP6/WalletLocker.cs rename to neo/Wallets/NEP6/WalletLocker.cs index ce88f5bd6e..6f28779361 100644 --- a/neo/Implementations/Wallets/NEP6/WalletLocker.cs +++ b/neo/Wallets/NEP6/WalletLocker.cs @@ -1,6 +1,6 @@ using System; -namespace Neo.Implementations.Wallets.NEP6 +namespace Neo.Wallets.NEP6 { internal class WalletLocker : IDisposable { diff --git a/neo/Implementations/Wallets/EntityFramework/Account.cs b/neo/Wallets/SQLite/Account.cs similarity index 72% rename from neo/Implementations/Wallets/EntityFramework/Account.cs rename to neo/Wallets/SQLite/Account.cs index 009a6ed961..5c91556fb2 100644 --- a/neo/Implementations/Wallets/EntityFramework/Account.cs +++ b/neo/Wallets/SQLite/Account.cs @@ -1,4 +1,4 @@ -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { internal class Account { diff --git a/neo/Implementations/Wallets/EntityFramework/Address.cs b/neo/Wallets/SQLite/Address.cs similarity index 61% rename from neo/Implementations/Wallets/EntityFramework/Address.cs rename to neo/Wallets/SQLite/Address.cs index 63cf54cfaa..79ef9e1031 100644 --- a/neo/Implementations/Wallets/EntityFramework/Address.cs +++ b/neo/Wallets/SQLite/Address.cs @@ -1,4 +1,4 @@ -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { internal class Address { diff --git a/neo/Implementations/Wallets/EntityFramework/Contract.cs b/neo/Wallets/SQLite/Contract.cs similarity index 82% rename from neo/Implementations/Wallets/EntityFramework/Contract.cs rename to neo/Wallets/SQLite/Contract.cs index f236887921..a69c0f0968 100644 --- a/neo/Implementations/Wallets/EntityFramework/Contract.cs +++ b/neo/Wallets/SQLite/Contract.cs @@ -1,4 +1,4 @@ -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { internal class Contract { diff --git a/neo/Implementations/Wallets/EntityFramework/Key.cs b/neo/Wallets/SQLite/Key.cs similarity index 68% rename from neo/Implementations/Wallets/EntityFramework/Key.cs rename to neo/Wallets/SQLite/Key.cs index 21c83e68cd..97c063a3f2 100644 --- a/neo/Implementations/Wallets/EntityFramework/Key.cs +++ b/neo/Wallets/SQLite/Key.cs @@ -1,4 +1,4 @@ -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { internal class Key { diff --git a/neo/Implementations/Wallets/EntityFramework/UserWallet.cs b/neo/Wallets/SQLite/UserWallet.cs similarity index 91% rename from neo/Implementations/Wallets/EntityFramework/UserWallet.cs rename to neo/Wallets/SQLite/UserWallet.cs index c8642d11bd..d2b058052a 100644 --- a/neo/Implementations/Wallets/EntityFramework/UserWallet.cs +++ b/neo/Wallets/SQLite/UserWallet.cs @@ -1,9 +1,9 @@ using Microsoft.EntityFrameworkCore; -using Neo.Core; using Neo.Cryptography; using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; -using Neo.Wallets; using System; using System.Collections.Generic; using System.IO; @@ -12,9 +12,9 @@ using System.Security; using System.Security.Cryptography; -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { - public class UserWallet : Wallet, IDisposable + public class UserWallet : Wallet { public override event EventHandler BalanceChanged; @@ -34,7 +34,7 @@ public override Version Version get { byte[] buffer = LoadStoredData("Version"); - if (buffer == null) return new Version(0, 0); + if (buffer == null || buffer.Length < 16) return new Version(0, 0); int major = buffer.ToInt32(0); int minor = buffer.ToInt32(4); int build = buffer.ToInt32(8); @@ -63,9 +63,6 @@ private UserWallet(WalletIndexer indexer, string path, byte[] passwordKey, bool SaveStoredData("IV", iv); SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); SaveStoredData("Version", new[] { version.Major, version.Minor, version.Build, version.Revision }.Select(p => BitConverter.GetBytes(p)).SelectMany(p => p).ToArray()); -#if NET47 - ProtectedMemory.Protect(masterKey, MemoryProtectionScope.SameProcess); -#endif } else { @@ -74,9 +71,6 @@ private UserWallet(WalletIndexer indexer, string path, byte[] passwordKey, bool throw new CryptographicException(); this.iv = LoadStoredData("IV"); this.masterKey = LoadStoredData("MasterKey").AesDecrypt(passwordKey, iv); -#if NET47 - ProtectedMemory.Protect(masterKey, MemoryProtectionScope.SameProcess); -#endif this.accounts = LoadAccounts(); indexer.RegisterAccounts(accounts.Keys); } @@ -96,7 +90,7 @@ private void AddAccount(UserWalletAccount account, bool is_import) } else { - indexer.RegisterAccounts(new[] { account.ScriptHash }, is_import ? 0 : Blockchain.Default?.Height ?? 0); + indexer.RegisterAccounts(new[] { account.ScriptHash }, is_import ? 0 : Blockchain.Singleton.Height); } accounts[account.ScriptHash] = account; } @@ -107,10 +101,7 @@ private void AddAccount(UserWalletAccount account, bool is_import) { byte[] decryptedPrivateKey = new byte[96]; Buffer.BlockCopy(account.Key.PublicKey.EncodePoint(false), 1, decryptedPrivateKey, 0, 64); - using (account.Key.Decrypt()) - { - Buffer.BlockCopy(account.Key.PrivateKey, 0, decryptedPrivateKey, 64, 32); - } + Buffer.BlockCopy(account.Key.PrivateKey, 0, decryptedPrivateKey, 64, 32); byte[] encryptedPrivateKey = EncryptPrivateKey(decryptedPrivateKey); Array.Clear(decryptedPrivateKey, 0, decryptedPrivateKey.Length); Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash.SequenceEqual(account.Key.PublicKeyHash.ToArray())); @@ -168,7 +159,7 @@ public override void ApplyTransaction(Transaction tx) BalanceChanged?.Invoke(this, new BalanceEventArgs { Transaction = tx, - RelatedAccounts = tx.Scripts.Select(p => p.ScriptHash).Union(tx.Outputs.Select(p => p.ScriptHash)).Where(p => Contains(p)).ToArray(), + RelatedAccounts = tx.Witnesses.Select(p => p.ScriptHash).Union(tx.Outputs.Select(p => p.ScriptHash)).Where(p => Contains(p)).ToArray(), Height = null, Time = DateTime.UtcNow.ToTimestamp() }); @@ -187,20 +178,15 @@ public bool ChangePassword(string password_old, string password_new) { if (!VerifyPassword(password_old)) return false; byte[] passwordKey = password_new.ToAesKey(); -#if NET47 - using (new ProtectedMemoryContext(masterKey, MemoryProtectionScope.SameProcess)) -#endif + try { - try - { - SaveStoredData("PasswordHash", passwordKey.Sha256()); - SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); - return true; - } - finally - { - Array.Clear(passwordKey, 0, passwordKey.Length); - } + SaveStoredData("PasswordHash", passwordKey.Sha256()); + SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); + return true; + } + finally + { + Array.Clear(passwordKey, 0, passwordKey.Length); } } @@ -270,12 +256,7 @@ private byte[] DecryptPrivateKey(byte[] encryptedPrivateKey) { if (encryptedPrivateKey == null) throw new ArgumentNullException(nameof(encryptedPrivateKey)); if (encryptedPrivateKey.Length != 96) throw new ArgumentException(); -#if NET47 - using (new ProtectedMemoryContext(masterKey, MemoryProtectionScope.SameProcess)) -#endif - { - return encryptedPrivateKey.AesDecrypt(masterKey, iv); - } + return encryptedPrivateKey.AesDecrypt(masterKey, iv); } public override bool DeleteAccount(UInt160 scriptHash) @@ -314,19 +295,14 @@ public override bool DeleteAccount(UInt160 scriptHash) return false; } - public void Dispose() + public override void Dispose() { indexer.BalanceChanged -= WalletIndexer_BalanceChanged; } private byte[] EncryptPrivateKey(byte[] decryptedPrivateKey) { -#if NET47 - using (new ProtectedMemoryContext(masterKey, MemoryProtectionScope.SameProcess)) -#endif - { - return decryptedPrivateKey.AesEncrypt(masterKey, iv); - } + return decryptedPrivateKey.AesEncrypt(masterKey, iv); } public override Coin[] FindUnspentCoins(UInt256 asset_id, Fixed8 amount, UInt160[] from) diff --git a/neo/Implementations/Wallets/EntityFramework/UserWalletAccount.cs b/neo/Wallets/SQLite/UserWalletAccount.cs similarity index 81% rename from neo/Implementations/Wallets/EntityFramework/UserWalletAccount.cs rename to neo/Wallets/SQLite/UserWalletAccount.cs index 9b5609fc04..2d3ac74836 100644 --- a/neo/Implementations/Wallets/EntityFramework/UserWalletAccount.cs +++ b/neo/Wallets/SQLite/UserWalletAccount.cs @@ -1,6 +1,4 @@ -using Neo.Wallets; - -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { internal class UserWalletAccount : WalletAccount { diff --git a/neo/Implementations/Wallets/EntityFramework/VerificationContract.cs b/neo/Wallets/SQLite/VerificationContract.cs similarity index 58% rename from neo/Implementations/Wallets/EntityFramework/VerificationContract.cs rename to neo/Wallets/SQLite/VerificationContract.cs index c010d75eda..41f8d44055 100644 --- a/neo/Implementations/Wallets/EntityFramework/VerificationContract.cs +++ b/neo/Wallets/SQLite/VerificationContract.cs @@ -5,16 +5,12 @@ using System.IO; using System.Linq; -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { public class VerificationContract : SmartContract.Contract, IEquatable, ISerializable { public int Size => 20 + ParameterList.GetVarSize() + Script.GetVarSize(); - /// - /// 反序列化 - /// - /// 数据来源 public void Deserialize(BinaryReader reader) { reader.ReadSerializable(); @@ -22,41 +18,23 @@ public void Deserialize(BinaryReader reader) Script = reader.ReadVarBytes(); } - /// - /// 比较与另一个对象是否相等 - /// - /// 另一个对象 - /// 返回比较的结果 public bool Equals(VerificationContract other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; return ScriptHash.Equals(other.ScriptHash); } - /// - /// 比较与另一个对象是否相等 - /// - /// 另一个对象 - /// 返回比较的结果 public override bool Equals(object obj) { return Equals(obj as VerificationContract); } - /// - /// 获得HashCode - /// - /// 返回HashCode public override int GetHashCode() { return ScriptHash.GetHashCode(); } - /// - /// 序列化 - /// - /// 存放序列化后的结果 public void Serialize(BinaryWriter writer) { writer.Write(new UInt160()); diff --git a/neo/Implementations/Wallets/EntityFramework/WalletDataContext.cs b/neo/Wallets/SQLite/WalletDataContext.cs similarity index 96% rename from neo/Implementations/Wallets/EntityFramework/WalletDataContext.cs rename to neo/Wallets/SQLite/WalletDataContext.cs index 0befc66d3a..cba60cb00d 100644 --- a/neo/Implementations/Wallets/EntityFramework/WalletDataContext.cs +++ b/neo/Wallets/SQLite/WalletDataContext.cs @@ -1,7 +1,7 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -namespace Neo.Implementations.Wallets.EntityFramework +namespace Neo.Wallets.SQLite { internal class WalletDataContext : DbContext { @@ -20,8 +20,10 @@ public WalletDataContext(string filename) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - SqliteConnectionStringBuilder sb = new SqliteConnectionStringBuilder(); - sb.DataSource = filename; + SqliteConnectionStringBuilder sb = new SqliteConnectionStringBuilder + { + DataSource = filename + }; optionsBuilder.UseSqlite(sb.ToString()); } diff --git a/neo/Wallets/TransferOutput.cs b/neo/Wallets/TransferOutput.cs index 04200ecc85..1fc3d8ae19 100644 --- a/neo/Wallets/TransferOutput.cs +++ b/neo/Wallets/TransferOutput.cs @@ -1,4 +1,4 @@ -using Neo.Core; +using Neo.Network.P2P.Payloads; using System; namespace Neo.Wallets diff --git a/neo/Wallets/Wallet.cs b/neo/Wallets/Wallet.cs index 4ee1229633..82bd4a8791 100644 --- a/neo/Wallets/Wallet.cs +++ b/neo/Wallets/Wallet.cs @@ -1,5 +1,6 @@ -using Neo.Core; -using Neo.Cryptography; +using Neo.Cryptography; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; using System; @@ -14,7 +15,7 @@ namespace Neo.Wallets { - public abstract class Wallet + public abstract class Wallet : IDisposable { public abstract event EventHandler BalanceChanged; @@ -53,6 +54,10 @@ public WalletAccount CreateAccount(Contract contract, byte[] privateKey) return CreateAccount(contract, new KeyPair(privateKey)); } + public virtual void Dispose() + { + } + public IEnumerable FindUnspentCoins(params UInt160[] from) { IEnumerable accounts = from.Length > 0 ? from : GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash); @@ -104,6 +109,8 @@ public BigDecimal GetAvailable(UIntBase asset_id) script = sb.ToArray(); } ApplicationEngine engine = ApplicationEngine.Run(script); + if (engine.State.HasFlag(VMState.FAULT)) + return new BigDecimal(0, 0); byte decimals = (byte)engine.ResultStack.Pop().GetBigInteger(); BigInteger amount = ((VMArray)engine.ResultStack.Pop()).Aggregate(BigInteger.Zero, (x, y) => x + y.GetBigInteger()); return new BigDecimal(amount, decimals); @@ -154,7 +161,7 @@ public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, int N byte[] prikey = XOR(encryptedkey.AES256Decrypt(derivedhalf2), derivedhalf1); Cryptography.ECC.ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey; UInt160 script_hash = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(); - string address = ToAddress(script_hash); + string address = script_hash.ToAddress(); if (!Encoding.ASCII.GetBytes(address).Sha256().Sha256().Take(4).SequenceEqual(addresshash)) throw new FormatException(); return prikey; @@ -356,7 +363,7 @@ public virtual WalletAccount Import(string nep2, string passphrase) tx.Attributes = attributes.ToArray(); tx.Inputs = new CoinReference[0]; tx.Outputs = outputs.Where(p => p.IsGlobalAsset).Select(p => p.ToTxOutput()).ToArray(); - tx.Scripts = new Witness[0]; + tx.Witnesses = new Witness[0]; if (tx is InvocationTransaction itx) { ApplicationEngine engine = ApplicationEngine.Run(itx.Script, itx); @@ -389,24 +396,6 @@ public bool Sign(ContractParametersContext context) return fSuccess; } - public static string ToAddress(UInt160 scriptHash) - { - byte[] data = new byte[21]; - data[0] = Settings.Default.AddressVersion; - Buffer.BlockCopy(scriptHash.ToArray(), 0, data, 1, 20); - return data.Base58CheckEncode(); - } - - public static UInt160 ToScriptHash(string address) - { - byte[] data = address.Base58CheckDecode(); - if (data.Length != 21) - throw new FormatException(); - if (data[0] != Settings.Default.AddressVersion) - throw new FormatException(); - return new UInt160(data.Skip(1).ToArray()); - } - public abstract bool VerifyPassword(string password); private static byte[] XOR(byte[] x, byte[] y) diff --git a/neo/Wallets/WalletAccount.cs b/neo/Wallets/WalletAccount.cs index b81bde740d..a427a9e50b 100644 --- a/neo/Wallets/WalletAccount.cs +++ b/neo/Wallets/WalletAccount.cs @@ -10,7 +10,7 @@ public abstract class WalletAccount public bool Lock; public Contract Contract; - public string Address => Wallet.ToAddress(ScriptHash); + public string Address => ScriptHash.ToAddress(); public abstract bool HasKey { get; } public bool WatchOnly => Contract == null; diff --git a/neo/Wallets/WalletIndexer.cs b/neo/Wallets/WalletIndexer.cs index 63c88a7d2e..94ac19d82f 100644 --- a/neo/Wallets/WalletIndexer.cs +++ b/neo/Wallets/WalletIndexer.cs @@ -1,6 +1,8 @@ -using Neo.Core; -using Neo.IO; +using Neo.IO; using Neo.IO.Data.LevelDB; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using System; using System.Collections.Generic; using System.IO; @@ -118,14 +120,11 @@ private static byte[] GetGroupId() public IEnumerable GetTransactions(IEnumerable accounts) { ReadOptions options = new ReadOptions { FillCache = false }; - using (options.Snapshot = db.GetSnapshot()) - { - IEnumerable results = Enumerable.Empty(); - foreach (UInt160 account in accounts) - results = results.Union(db.Find(options, SliceBuilder.Begin(DataEntryPrefix.ST_Transaction).Add(account), (k, v) => new UInt256(k.ToArray().Skip(21).ToArray()))); - foreach (UInt256 hash in results) - yield return hash; - } + IEnumerable results = Enumerable.Empty(); + foreach (UInt160 account in accounts) + results = results.Union(db.Find(options, SliceBuilder.Begin(DataEntryPrefix.ST_Transaction).Add(account), (k, v) => new UInt256(k.ToArray().Skip(21).ToArray()))); + foreach (UInt256 hash in results) + yield return hash; } private void ProcessBlock(Block block, HashSet accounts, WriteBatch batch) @@ -209,45 +208,38 @@ private void ProcessBlock(Block block, HashSet accounts, WriteBatch bat private void ProcessBlocks() { - try + while (!disposed) { while (!disposed) - { - while (!disposed) - lock (SyncRoot) + lock (SyncRoot) + { + if (indexes.Count == 0) break; + uint height = indexes.Keys.Min(); + Block block = Blockchain.Singleton.Store.GetBlock(height); + if (block == null) break; + WriteBatch batch = new WriteBatch(); + HashSet accounts = indexes[height]; + ProcessBlock(block, accounts, batch); + ReadOptions options = ReadOptions.Default; + byte[] groupId = db.Get(options, SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height)).ToArray(); + indexes.Remove(height); + batch.Delete(SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height)); + height++; + if (indexes.TryGetValue(height, out HashSet accounts_next)) { - if (indexes.Count == 0) break; - uint height = indexes.Keys.Min(); - Block block = Blockchain.Default?.GetBlock(height); - if (block == null) break; - WriteBatch batch = new WriteBatch(); - HashSet accounts = indexes[height]; - ProcessBlock(block, accounts, batch); - ReadOptions options = ReadOptions.Default; - byte[] groupId = db.Get(options, SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height)).ToArray(); - indexes.Remove(height); - batch.Delete(SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height)); - height++; - if (indexes.TryGetValue(height, out HashSet accounts_next)) - { - accounts_next.UnionWith(accounts); - groupId = db.Get(options, SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height)).ToArray(); - batch.Put(SliceBuilder.Begin(DataEntryPrefix.IX_Accounts).Add(groupId), accounts_next.ToArray().ToByteArray()); - } - else - { - indexes.Add(height, accounts); - batch.Put(SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height), groupId); - } - db.Write(WriteOptions.Default, batch); + accounts_next.UnionWith(accounts); + groupId = db.Get(options, SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height)).ToArray(); + batch.Put(SliceBuilder.Begin(DataEntryPrefix.IX_Accounts).Add(groupId), accounts_next.ToArray().ToByteArray()); } - for (int i = 0; i < 20 && !disposed; i++) - Thread.Sleep(100); - } - } - catch when (Blockchain.Default == null || Blockchain.Default.IsDisposed) - { - return; + else + { + indexes.Add(height, accounts); + batch.Put(SliceBuilder.Begin(DataEntryPrefix.IX_Group).Add(height), groupId); + } + db.Write(WriteOptions.Default, batch); + } + for (int i = 0; i < 20 && !disposed; i++) + Thread.Sleep(100); } } diff --git a/neo/neo.csproj b/neo/neo.csproj index 6fc9e145c6..bb87926845 100644 --- a/neo/neo.csproj +++ b/neo/neo.csproj @@ -1,9 +1,9 @@  - 2015-2017 The Neo Project + 2015-2018 The Neo Project Neo - 2.8.0 + 2.9.0 The Neo Project netstandard2.0;net47 true @@ -32,17 +32,15 @@ - - - - - - + + + + + + + - - - - + diff --git a/neo/policy.json b/neo/policy.json deleted file mode 100644 index 1f20084c31..0000000000 --- a/neo/policy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "PolicyConfiguration": { - "PolicyLevel": "AllowAll", - "List": [] - } -}