diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb71ca2890..05cc47a22f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,7 @@ on: pull_request: env: - DOTNET_VERSION: 6.0.x + DOTNET_VERSION: 7.0.x jobs: diff --git a/README.md b/README.md index 5818327a71..39d02a7fba 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,6 @@

- - Current TravisCI build status. - Current neo version. diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index a97f47a304..12b6e861e4 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 Neo enable false diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c107e19bfa..2e3fbc31b0 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,10 +2,10 @@ - 2015-2022 The Neo Project - 3.5.0 + 2015-2023 The Neo Project + 3.6.0 The Neo Project - net6.0 + net7.0 https://github.com/neo-project/neo MIT git diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index bd2c294229..5a9aef6b5c 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -12,6 +12,7 @@ namespace Neo { public enum Hardfork : byte { - HF_Aspidochelone + HF_Aspidochelone, + HF_Basilisk } } diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index 52b3c37ce5..8acec8c974 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -206,6 +206,8 @@ private void OnFillMemoryPool(IEnumerable transactions) { if (NativeContract.Ledger.ContainsTransaction(snapshot, tx.Hash)) continue; + if (NativeContract.Ledger.ContainsConflictHash(snapshot, tx.Hash, tx.Signers.Select(s => s.Account))) + continue; // First remove the tx if it is unverified in the pool. system.MemPool.TryRemoveUnVerified(tx.Hash, out _); // Add to the memory pool @@ -337,6 +339,7 @@ private VerifyResult OnNewExtensiblePayload(ExtensiblePayload payload) private VerifyResult OnNewTransaction(Transaction transaction) { if (system.ContainsTransaction(transaction.Hash)) return VerifyResult.AlreadyExists; + if (system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account))) return VerifyResult.HasConflicts; return system.MemPool.TryAdd(transaction, system.StoreView); } @@ -391,8 +394,9 @@ private void OnTransaction(Transaction tx) { if (system.ContainsTransaction(tx.Hash)) SendRelayResult(tx, VerifyResult.AlreadyExists); - else - system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true)); + else if (system.ContainsConflictHash(tx.Hash, tx.Signers.Select(s => s.Account))) + SendRelayResult(tx, VerifyResult.HasConflicts); + else system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true)); } private void Persist(Block block) diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs index 6ade372ee1..a966835fc3 100644 --- a/src/Neo/Ledger/MemoryPool.cs +++ b/src/Neo/Ledger/MemoryPool.cs @@ -55,6 +55,10 @@ public class MemoryPool : IReadOnlyCollection /// private readonly Dictionary _unsortedTransactions = new(); ///

+ /// Store transaction hashes that conflict with verified mempooled transactions. + /// + private readonly Dictionary> _conflicts = new(); + /// /// Stores the verified sorted transactions currently in the pool. /// private readonly SortedSet _sortedTransactions = new(); @@ -275,7 +279,8 @@ private PoolItem GetLowestFeeTransaction(out Dictionary unsor } } - // Note: this must only be called from a single thread (the Blockchain actor) + // Note: this must only be called from a single thread (the Blockchain actor) and + // doesn't take into account conflicting transactions. internal bool CanTransactionFitInPool(Transaction tx) { if (Count < Capacity) return true; @@ -293,15 +298,31 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) _txRwLock.EnterWriteLock(); try { - VerifyResult result = tx.VerifyStateDependent(_system.Settings, snapshot, VerificationContext); + if (!CheckConflicts(tx, out List conflictsToBeRemoved)) return VerifyResult.HasConflicts; + VerifyResult result = tx.VerifyStateDependent(_system.Settings, snapshot, VerificationContext, conflictsToBeRemoved.Select(c => c.Tx)); if (result != VerifyResult.Succeed) return result; _unsortedTransactions.Add(tx.Hash, poolItem); VerificationContext.AddTransaction(tx); _sortedTransactions.Add(poolItem); + foreach (var conflict in conflictsToBeRemoved) + { + if (TryRemoveVerified(conflict.Tx.Hash, out var _)) + VerificationContext.RemoveTransaction(conflict.Tx); + } + removedTransactions = conflictsToBeRemoved.Select(itm => itm.Tx).ToList(); + foreach (var attr in tx.GetAttributes()) + { + if (!_conflicts.TryGetValue(attr.Hash, out var pooled)) + { + pooled = new HashSet(); + } + pooled.Add(tx.Hash); + _conflicts.AddOrSet(attr.Hash, pooled); + } if (Count > Capacity) - removedTransactions = RemoveOverCapacity(); + removedTransactions.AddRange(RemoveOverCapacity()); } finally { @@ -309,7 +330,7 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) } TransactionAdded?.Invoke(this, poolItem.Tx); - if (removedTransactions != null) + if (removedTransactions.Count() > 0) TransactionRemoved?.Invoke(this, new() { Transactions = removedTransactions, @@ -320,6 +341,49 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) return VerifyResult.Succeed; } + /// + /// Checks whether there is no mismatch in Conflicts attributes between the current transaction + /// and mempooled unsorted transactions. If true, then these unsorted transactions will be added + /// into conflictsList. + /// + /// The current transaction needs to be checked. + /// The list of conflicting verified transactions that should be removed from the pool if tx fits the pool. + /// True if transaction fits the pool, otherwise false. + private bool CheckConflicts(Transaction tx, out List conflictsList) + { + conflictsList = new(); + long conflictsFeeSum = 0; + // Step 1: check if `tx` was in Conflicts attributes of unsorted transactions. + if (_conflicts.TryGetValue(tx.Hash, out var conflicting)) + { + foreach (var hash in conflicting) + { + var unsortedTx = _unsortedTransactions[hash]; + if (unsortedTx.Tx.Signers.Select(s => s.Account).Contains(tx.Sender)) + conflictsFeeSum += unsortedTx.Tx.NetworkFee; + conflictsList.Add(unsortedTx); + } + } + // Step 2: check if unsorted transactions were in `tx`'s Conflicts attributes. + foreach (var hash in tx.GetAttributes().Select(p => p.Hash)) + { + if (_unsortedTransactions.TryGetValue(hash, out PoolItem unsortedTx)) + { + if (!tx.Signers.Select(p => p.Account).Intersect(unsortedTx.Tx.Signers.Select(p => p.Account)).Any()) return false; + conflictsFeeSum += unsortedTx.Tx.NetworkFee; + conflictsList.Add(unsortedTx); + } + } + // Network fee of tx have to be larger than the sum of conflicting txs network fees. + if (conflictsFeeSum != 0 && conflictsFeeSum >= tx.NetworkFee) + return false; + + // Step 3: take into account sender's conflicting transactions while balance check, + // this will be done in VerifyStateDependant. + + return true; + } + private List RemoveOverCapacity() { List removedTransactions = new(); @@ -332,7 +396,10 @@ private List RemoveOverCapacity() removedTransactions.Add(minItem.Tx); if (ReferenceEquals(sortedPool, _sortedTransactions)) + { + RemoveConflictsOfVerified(minItem); VerificationContext.RemoveTransaction(minItem.Tx); + } } while (Count > Capacity); return removedTransactions; @@ -347,9 +414,27 @@ private bool TryRemoveVerified(UInt256 hash, out PoolItem item) _unsortedTransactions.Remove(hash); _sortedTransactions.Remove(item); + RemoveConflictsOfVerified(item); + return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RemoveConflictsOfVerified(PoolItem item) + { + foreach (var h in item.Tx.GetAttributes().Select(attr => attr.Hash)) + { + if (_conflicts.TryGetValue(h, out HashSet conflicts)) + { + conflicts.Remove(item.Tx.Hash); + if (conflicts.Count() == 0) + { + _conflicts.Remove(h); + } + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryRemoveUnVerified(UInt256 hash, out PoolItem item) { @@ -374,28 +459,67 @@ internal void InvalidateVerifiedTransactions() _unsortedTransactions.Clear(); VerificationContext = new TransactionVerificationContext(); _sortedTransactions.Clear(); + _conflicts.Clear(); } // Note: this must only be called from a single thread (the Blockchain actor) internal void UpdatePoolForBlockPersisted(Block block, DataCache snapshot) { + var conflictingItems = new List(); _txRwLock.EnterWriteLock(); try { + Dictionary> conflicts = new Dictionary>(); // First remove the transactions verified in the block. + // No need to modify VerificationContext as it will be reset afterwards. foreach (Transaction tx in block.Transactions) { - if (TryRemoveVerified(tx.Hash, out _)) continue; - TryRemoveUnVerified(tx.Hash, out _); + if (!TryRemoveVerified(tx.Hash, out _)) TryRemoveUnVerified(tx.Hash, out _); + var conflictingSigners = tx.Signers.Select(s => s.Account); + foreach (var h in tx.GetAttributes().Select(a => a.Hash)) + { + if (conflicts.TryGetValue(h, out var signersList)) + { + signersList.AddRange(conflictingSigners); + continue; + } + signersList = conflictingSigners.ToList(); + conflicts.Add(h, signersList); + } } - // Add all the previously verified transactions back to the unverified transactions + // Then remove the transactions conflicting with the accepted ones. + // No need to modify VerificationContext as it will be reset afterwards. + var persisted = block.Transactions.Select(t => t.Hash); + var stale = new List(); + foreach (var item in _sortedTransactions) + { + if ((conflicts.TryGetValue(item.Tx.Hash, out var signersList) && signersList.Intersect(item.Tx.Signers.Select(s => s.Account)).Any()) || item.Tx.GetAttributes().Select(a => a.Hash).Intersect(persisted).Any()) + { + stale.Add(item.Tx.Hash); + conflictingItems.Add(item.Tx); + } + } + foreach (var h in stale) + { + if (!TryRemoveVerified(h, out _)) TryRemoveUnVerified(h, out _); + } + + // Add all the previously verified transactions back to the unverified transactions and clear mempool conflicts list. InvalidateVerifiedTransactions(); } finally { _txRwLock.ExitWriteLock(); } + if (conflictingItems.Count() > 0) + { + TransactionRemoved?.Invoke(this, new() + { + Transactions = conflictingItems.ToArray(), + Reason = TransactionRemovalReason.Conflict, + }); + } // If we know about headers of future blocks, no point in verifying transactions from the unverified tx pool // until we get caught up. @@ -431,10 +555,31 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, // Since unverifiedSortedTxPool is ordered in an ascending manner, we take from the end. foreach (PoolItem item in unverifiedSortedTxPool.Reverse().Take(count)) { - if (item.Tx.VerifyStateDependent(_system.Settings, snapshot, VerificationContext) == VerifyResult.Succeed) + if (CheckConflicts(item.Tx, out List conflictsToBeRemoved) && + item.Tx.VerifyStateDependent(_system.Settings, snapshot, VerificationContext, conflictsToBeRemoved.Select(c => c.Tx)) == VerifyResult.Succeed) { reverifiedItems.Add(item); - VerificationContext.AddTransaction(item.Tx); + if (_unsortedTransactions.TryAdd(item.Tx.Hash, item)) + { + verifiedSortedTxPool.Add(item); + foreach (var attr in item.Tx.GetAttributes()) + { + if (!_conflicts.TryGetValue(attr.Hash, out var pooled)) + { + pooled = new HashSet(); + } + pooled.Add(item.Tx.Hash); + _conflicts.AddOrSet(attr.Hash, pooled); + } + VerificationContext.AddTransaction(item.Tx); + foreach (var conflict in conflictsToBeRemoved) + { + if (TryRemoveVerified(conflict.Tx.Hash, out var _)) + VerificationContext.RemoveTransaction(conflict.Tx); + invalidItems.Add(conflict); + } + + } } else // Transaction no longer valid -- it will be removed from unverifiedTxPool. invalidItems.Add(item); @@ -450,18 +595,14 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, var rebroadcastCutOffTime = TimeProvider.Current.UtcNow.AddMilliseconds(-_system.Settings.MillisecondsPerBlock * blocksTillRebroadcast); foreach (PoolItem item in reverifiedItems) { - if (_unsortedTransactions.TryAdd(item.Tx.Hash, item)) + if (_unsortedTransactions.ContainsKey(item.Tx.Hash)) { - verifiedSortedTxPool.Add(item); - if (item.LastBroadcastTimestamp < rebroadcastCutOffTime) { _system.LocalNode.Tell(new LocalNode.RelayDirectly { Inventory = item.Tx }, _system.Blockchain); item.LastBroadcastTimestamp = TimeProvider.Current.UtcNow; } } - else - VerificationContext.RemoveTransaction(item.Tx); _unverifiedTransactions.Remove(item.Tx.Hash); unverifiedSortedTxPool.Remove(item); diff --git a/src/Neo/Ledger/TransactionRemovalReason.cs b/src/Neo/Ledger/TransactionRemovalReason.cs index 9767fb919f..fa66b60981 100644 --- a/src/Neo/Ledger/TransactionRemovalReason.cs +++ b/src/Neo/Ledger/TransactionRemovalReason.cs @@ -16,13 +16,18 @@ namespace Neo.Ledger public enum TransactionRemovalReason : byte { /// - /// The transaction was ejected since it was the lowest priority transaction and the memory pool capacity was exceeded. + /// The transaction was rejected since it was the lowest priority transaction and the memory pool capacity was exceeded. /// CapacityExceeded, /// - /// The transaction was ejected due to failing re-validation after a block was persisted. + /// The transaction was rejected due to failing re-validation after a block was persisted. /// NoLongerValid, + + /// + /// The transaction was rejected due to conflict with higher priority transactions with Conflicts attribute. + /// + Conflict, } } diff --git a/src/Neo/Ledger/TransactionVerificationContext.cs b/src/Neo/Ledger/TransactionVerificationContext.cs index b77adfe9c7..5b6cc6cc9c 100644 --- a/src/Neo/Ledger/TransactionVerificationContext.cs +++ b/src/Neo/Ledger/TransactionVerificationContext.cs @@ -12,6 +12,7 @@ using Neo.Persistence; using Neo.SmartContract.Native; using System.Collections.Generic; +using System.Linq; using System.Numerics; namespace Neo.Ledger @@ -50,15 +51,18 @@ public void AddTransaction(Transaction tx) /// Determine whether the specified conflicts with other transactions. /// /// The specified . + /// The list of that conflicts with the specified one and are to be removed from the pool. /// The snapshot used to verify the . /// if the passes the check; otherwise, . - public bool CheckTransaction(Transaction tx, DataCache snapshot) + public bool CheckTransaction(Transaction tx, IEnumerable conflictingTxs, DataCache snapshot) { BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, tx.Sender); senderFee.TryGetValue(tx.Sender, out var totalSenderFeeFromPool); - BigInteger fee = tx.SystemFee + tx.NetworkFee + totalSenderFeeFromPool; - if (balance < fee) return false; + BigInteger expectedFee = tx.SystemFee + tx.NetworkFee + totalSenderFeeFromPool; + foreach (var conflictTx in conflictingTxs.Where(c => c.Sender.Equals(tx.Sender))) + expectedFee -= (conflictTx.NetworkFee + conflictTx.SystemFee); + if (balance < expectedFee) return false; var oracle = tx.GetAttribute(); if (oracle != null && oracleResponses.ContainsKey(oracle.Id)) diff --git a/src/Neo/Ledger/VerifyResult.cs b/src/Neo/Ledger/VerifyResult.cs index 7858903ad2..94c9e78bf8 100644 --- a/src/Neo/Ledger/VerifyResult.cs +++ b/src/Neo/Ledger/VerifyResult.cs @@ -77,6 +77,11 @@ public enum VerifyResult : byte /// PolicyFail, + /// + /// Indicates that the failed to verify because it conflicts with on-chain or mempooled transactions. + /// + HasConflicts, + /// /// Indicates that the failed to verify due to other reasons. /// diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index a671c484ad..c6919a3371 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -10,10 +10,11 @@ - - - - + + + + + diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs index 1007e0f40d..2f66389672 100644 --- a/src/Neo/NeoSystem.cs +++ b/src/Neo/NeoSystem.cs @@ -163,9 +163,10 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc public void Dispose() { + EnsureStopped(LocalNode); + EnsureStopped(Blockchain); foreach (var p in Plugin.Plugins) p.Dispose(); - EnsureStopped(LocalNode); // Dispose will call ActorSystem.Terminate() ActorSystem.Dispose(); ActorSystem.WhenTerminated.Wait(); @@ -277,5 +278,16 @@ public bool ContainsTransaction(UInt256 hash) if (MemPool.ContainsKey(hash)) return true; return NativeContract.Ledger.ContainsTransaction(StoreView, hash); } + + /// + /// Determines whether the specified transaction conflicts with some on-chain transaction. + /// + /// The hash of the transaction + /// The list of signer accounts of the transaction + /// if the transaction conflicts with on-chain transaction; otherwise, . + public bool ContainsConflictHash(UInt256 hash, IEnumerable signers) + { + return NativeContract.Ledger.ContainsConflictHash(StoreView, hash, signers); + } } } diff --git a/src/Neo/Network/P2P/Payloads/Conflicts.cs b/src/Neo/Network/P2P/Payloads/Conflicts.cs new file mode 100644 index 0000000000..db00b03259 --- /dev/null +++ b/src/Neo/Network/P2P/Payloads/Conflicts.cs @@ -0,0 +1,47 @@ +using Neo.IO; +using Neo.Json; +using Neo.Persistence; +using Neo.SmartContract.Native; +using System.IO; + +namespace Neo.Network.P2P.Payloads +{ + public class Conflicts : TransactionAttribute + { + /// + /// Indicates the conflict transaction hash. + /// + public UInt256 Hash; + + public override TransactionAttributeType Type => TransactionAttributeType.Conflicts; + + public override bool AllowMultiple => true; + + public override int Size => base.Size + Hash.Size; + + protected override void DeserializeWithoutType(ref MemoryReader reader) + { + Hash = reader.ReadSerializable(); + } + + protected override void SerializeWithoutType(BinaryWriter writer) + { + writer.Write(Hash); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["hash"] = Hash.ToString(); + return json; + } + + public override bool Verify(DataCache snapshot, Transaction tx) + { + // Only check if conflicting transaction is on chain. It's OK if the + // conflicting transaction was in the Conflicts attribute of some other + // on-chain transaction. + return !NativeContract.Ledger.ContainsTransaction(snapshot, Hash); + } + } +} diff --git a/src/Neo/Network/P2P/Payloads/NotValidBefore.cs b/src/Neo/Network/P2P/Payloads/NotValidBefore.cs new file mode 100644 index 0000000000..e77693813b --- /dev/null +++ b/src/Neo/Network/P2P/Payloads/NotValidBefore.cs @@ -0,0 +1,46 @@ +using Neo.IO; +using Neo.Json; +using Neo.Persistence; +using Neo.SmartContract.Native; +using System.IO; + +namespace Neo.Network.P2P.Payloads +{ + public class NotValidBefore : TransactionAttribute + { + /// + /// Indicates that the transaction is not valid before this height. + /// + public uint Height; + + public override TransactionAttributeType Type => TransactionAttributeType.NotValidBefore; + + public override bool AllowMultiple => false; + + public override int Size => base.Size + + sizeof(uint); // Height. + + protected override void DeserializeWithoutType(ref MemoryReader reader) + { + Height = reader.ReadUInt32(); + } + + protected override void SerializeWithoutType(BinaryWriter writer) + { + writer.Write(Height); + } + + public override JObject ToJson() + { + JObject json = base.ToJson(); + json["height"] = Height; + return json; + } + + public override bool Verify(DataCache snapshot, Transaction tx) + { + var block_height = NativeContract.Ledger.CurrentIndex(snapshot); + return block_height >= Height; + } + } +} diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index b99449ac48..24581e2b18 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -338,12 +338,13 @@ public JObject ToJson(ProtocolSettings settings) /// The used to verify the transaction. /// The snapshot used to verify the transaction. /// The used to verify the transaction. + /// The list of conflicting those fee should be excluded from sender's overall fee during -based verification in case of sender's match. /// The result of the verification. - public VerifyResult Verify(ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context) + public VerifyResult Verify(ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context, IEnumerable conflictsList) { VerifyResult result = VerifyStateIndependent(settings); if (result != VerifyResult.Succeed) return result; - return VerifyStateDependent(settings, snapshot, context); + return VerifyStateDependent(settings, snapshot, context, conflictsList); } /// @@ -352,8 +353,9 @@ public VerifyResult Verify(ProtocolSettings settings, DataCache snapshot, Transa /// The used to verify the transaction. /// The snapshot used to verify the transaction. /// The used to verify the transaction. + /// The list of conflicting those fee should be excluded from sender's overall fee during -based verification in case of sender's match. /// The result of the verification. - public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context) + public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context, IEnumerable conflictsList) { uint height = NativeContract.Ledger.CurrentIndex(snapshot); if (ValidUntilBlock <= height || ValidUntilBlock > height + settings.MaxValidUntilBlockIncrement) @@ -362,7 +364,7 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data foreach (UInt160 hash in hashes) if (NativeContract.Policy.IsBlocked(snapshot, hash)) return VerifyResult.PolicyFail; - if (!(context?.CheckTransaction(this, snapshot) ?? true)) return VerifyResult.InsufficientFunds; + if (!(context?.CheckTransaction(this, conflictsList, snapshot) ?? true)) return VerifyResult.InsufficientFunds; foreach (TransactionAttribute attribute in Attributes) if (!attribute.Verify(snapshot, this)) return VerifyResult.InvalidAttribute; diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs index bfda3f34ba..66dccb340b 100644 --- a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs +++ b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs @@ -27,6 +27,18 @@ public enum TransactionAttributeType : byte /// Indicates that the transaction is an oracle response. /// [ReflectionCache(typeof(OracleResponse))] - OracleResponse = 0x11 + OracleResponse = 0x11, + + /// + /// Indicates that the transaction is not valid before . + /// + [ReflectionCache(typeof(NotValidBefore))] + NotValidBefore = 0x20, + + /// + /// Indicates that the transaction conflicts with . + /// + [ReflectionCache(typeof(Conflicts))] + Conflicts = 0x21 } } diff --git a/src/Neo/Network/P2P/Payloads/WitnessScope.cs b/src/Neo/Network/P2P/Payloads/WitnessScope.cs index 19e7ea82dd..1ac2815af2 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessScope.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessScope.cs @@ -36,7 +36,7 @@ public enum WitnessScope : byte CustomContracts = 0x10, /// - /// Custom pubkey for group members. + /// Custom pubkey for group members. /// CustomGroups = 0x20, diff --git a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs index dfd21c63b8..ffbc53c1cb 100644 --- a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -318,7 +318,7 @@ private void OnInventoryReceived(IInventory inventory) switch (inventory) { case Transaction transaction: - if (!system.ContainsTransaction(transaction.Hash)) + if (!(system.ContainsTransaction(transaction.Hash) || system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account)))) system.TxRouter.Tell(new TransactionRouter.Preverify(transaction, true)); break; case Block block: diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index b387b2cde3..3c82c63eea 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -202,13 +202,42 @@ public void Delete(StorageKey key) /// Finds the entries starting with the specified prefix. /// /// The prefix of the key. + /// The search direction. /// The entries found with the desired prefix. - public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[] key_prefix = null) + public IEnumerable<(StorageKey Key, StorageItem Value)> Find(byte[] key_prefix = null, SeekDirection direction = SeekDirection.Forward) { - foreach (var (key, value) in Seek(key_prefix, SeekDirection.Forward)) + var seek_prefix = key_prefix; + if (direction == SeekDirection.Backward) + { + if (key_prefix == null || key_prefix.Length == 0) + { // Backwards seek for zero prefix is not supported for now. + throw new ArgumentException(); + } + seek_prefix = null; + for (int i = key_prefix.Length - 1; i >= 0; i--) + { + if (key_prefix[i] < 0xff) + { + seek_prefix = key_prefix.Take(i + 1).ToArray(); + // The next key after the key_prefix. + seek_prefix[i]++; + break; + } + } + if (seek_prefix == null) + { + throw new ArgumentException(); + } + } + return FindInternal(key_prefix, seek_prefix, direction); + } + + private IEnumerable<(StorageKey Key, StorageItem Value)> FindInternal(byte[] key_prefix, byte[] seek_prefix, SeekDirection direction) + { + foreach (var (key, value) in Seek(seek_prefix, direction)) if (key.ToArray().AsSpan().StartsWith(key_prefix)) yield return (key, value); - else + else if (direction == SeekDirection.Forward || !key.ToArray().SequenceEqual(seek_prefix)) yield break; } diff --git a/src/Neo/ProtocolSettings.cs b/src/Neo/ProtocolSettings.cs index 22df62185f..5373db8bf0 100644 --- a/src/Neo/ProtocolSettings.cs +++ b/src/Neo/ProtocolSettings.cs @@ -174,7 +174,9 @@ public static ProtocolSettings Load(string path, bool optional = true) { IConfigurationRoot config = new ConfigurationBuilder().AddJsonFile(path, optional).Build(); IConfigurationSection section = config.GetSection("ProtocolConfiguration"); - return Load(section); + var settings = Load(section); + CheckingHardfork(settings); + return settings; } /// @@ -208,5 +210,33 @@ public static ProtocolSettings Load(IConfigurationSection section) : Default.Hardforks }; } + + private static void CheckingHardfork(ProtocolSettings settings) + { + var allHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToList(); + // Check for continuity in configured hardforks + var sortedHardforks = settings.Hardforks.Keys + .OrderBy(h => allHardforks.IndexOf(h)) + .ToList(); + + for (int i = 0; i < sortedHardforks.Count - 1; i++) + { + int currentIndex = allHardforks.IndexOf(sortedHardforks[i]); + int nextIndex = allHardforks.IndexOf(sortedHardforks[i + 1]); + + // If they aren't consecutive, return false. + if (nextIndex - currentIndex > 1) + throw new Exception("Hardfork configuration is not continuous."); + } + // Check that block numbers are not higher in earlier hardforks than in later ones + for (int i = 0; i < sortedHardforks.Count - 1; i++) + { + if (settings.Hardforks[sortedHardforks[i]] > settings.Hardforks[sortedHardforks[i + 1]]) + { + // This means the block number for the current hardfork is greater than the next one, which should not be allowed. + throw new Exception($"The Hardfork configuration for {sortedHardforks[i]} is greater than for {sortedHardforks[i + 1]}"); + } + } + } } } diff --git a/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs b/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs index 68f9568155..5d8ea68016 100644 --- a/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs +++ b/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs @@ -75,7 +75,9 @@ partial class ApplicationEngine [OpCode.CALLA] = 1 << 9, [OpCode.CALLT] = 1 << 15, [OpCode.ABORT] = 0, + [OpCode.ABORTMSG] = 0, [OpCode.ASSERT] = 1 << 0, + [OpCode.ASSERTMSG] = 1 << 0, [OpCode.THROW] = 1 << 9, [OpCode.TRY] = 1 << 2, [OpCode.TRY_L] = 1 << 2, diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index 9fba368b20..88d07349f9 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -8,17 +8,17 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; using Array = Neo.VM.Types.Array; namespace Neo.SmartContract @@ -332,6 +332,35 @@ protected internal void RuntimeLog(byte[] state) /// The name of the event. /// The arguments of the event. protected internal void RuntimeNotify(byte[] eventName, Array state) + { + if (!IsHardforkEnabled(Hardfork.HF_Basilisk)) + { + RuntimeNotifyV1(eventName, state); + return; + } + if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); + string name = Utility.StrictUTF8.GetString(eventName); + ContractState contract = CurrentContext.GetState().Contract; + if (contract is null) + throw new InvalidOperationException("Notifications are not allowed in dynamic scripts."); + var @event = contract.Manifest.Abi.Events.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.Ordinal)); + if (@event is null) + throw new InvalidOperationException($"Event `{name}` does not exist."); + if (@event.Parameters.Length != state.Count) + throw new InvalidOperationException("The number of the arguments does not match the formal parameters of the event."); + for (int i = 0; i < @event.Parameters.Length; i++) + { + var p = @event.Parameters[i]; + if (!CheckItemType(state[i], p.Type)) + throw new InvalidOperationException($"The type of the argument `{p.Name}` does not match the formal parameter."); + } + using MemoryStream ms = new(MaxNotificationSize); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + BinarySerializer.Serialize(writer, state, MaxNotificationSize); + SendNotification(CurrentScriptHash, name, state); + } + + protected internal void RuntimeNotifyV1(byte[] eventName, Array state) { if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName)); if (CurrentContext.GetState().Contract is null) @@ -384,5 +413,59 @@ protected internal void BurnGas(long gas) throw new InvalidOperationException("GAS must be positive."); AddGas(gas); } + + private static bool CheckItemType(StackItem item, ContractParameterType type) + { + StackItemType aType = item.Type; + if (aType == StackItemType.Pointer) return false; + switch (type) + { + case ContractParameterType.Any: + return true; + case ContractParameterType.Boolean: + return aType == StackItemType.Boolean; + case ContractParameterType.Integer: + return aType == StackItemType.Integer; + case ContractParameterType.ByteArray: + return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer; + case ContractParameterType.String: + { + if (aType is StackItemType.ByteString or StackItemType.Buffer) + { + try + { + _ = Utility.StrictUTF8.GetString(item.GetSpan()); // Prevent any non-UTF8 string + return true; + } + catch { } + } + return false; + } + case ContractParameterType.Hash160: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == UInt160.Length; + case ContractParameterType.Hash256: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == UInt256.Length; + case ContractParameterType.PublicKey: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == 33; + case ContractParameterType.Signature: + if (aType == StackItemType.Any) return true; + if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false; + return item.GetSpan().Length == 64; + case ContractParameterType.Array: + return aType is StackItemType.Any or StackItemType.Array or StackItemType.Struct; + case ContractParameterType.Map: + return aType is StackItemType.Any or StackItemType.Map; + case ContractParameterType.InteropInterface: + return aType is StackItemType.Any or StackItemType.InteropInterface; + default: + return false; + } + } } } diff --git a/src/Neo/SmartContract/ApplicationEngine.Storage.cs b/src/Neo/SmartContract/ApplicationEngine.Storage.cs index 1ae615ce54..cfa323e227 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Storage.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Storage.cs @@ -8,6 +8,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Persistence; using Neo.SmartContract.Iterators; using Neo.SmartContract.Native; using System; @@ -152,7 +153,8 @@ protected internal IIterator Find(StorageContext context, byte[] prefix, FindOpt if ((options.HasFlag(FindOptions.PickField0) || options.HasFlag(FindOptions.PickField1)) && !options.HasFlag(FindOptions.DeserializeValues)) throw new ArgumentException(null, nameof(options)); byte[] prefix_key = StorageKey.CreateSearchPrefix(context.Id, prefix); - return new StorageIterator(Snapshot.Find(prefix_key).GetEnumerator(), prefix.Length, options); + SeekDirection direction = options.HasFlag(FindOptions.Backwards) ? SeekDirection.Backward : SeekDirection.Forward; + return new StorageIterator(Snapshot.Find(prefix_key, direction).GetEnumerator(), prefix.Length, options); } /// diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index 5889c3c11b..a67b622200 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -46,6 +46,7 @@ public partial class ApplicationEngine : ExecutionEngine /// public static event EventHandler Log; + private static readonly IList AllHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToArray(); private static Dictionary services; private readonly long gas_amount; private Dictionary states; @@ -220,7 +221,7 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe { ContractState currentContract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); if (currentContract?.CanCall(contract, method.Name) == false) - throw new InvalidOperationException($"Cannot Call Method {method} Of Contract {contract.Hash} From Contract {CurrentScriptHash}"); + throw new InvalidOperationException($"Cannot Call Method {method.Name} Of Contract {contract.Hash} From Contract {CurrentScriptHash}"); } if (invocationCounter.TryGetValue(contract.Hash, out var counter)) @@ -610,13 +611,30 @@ public void SetState(T state) states[typeof(T)] = state; } - private bool IsHardforkEnabled(Hardfork hardfork) + public bool IsHardforkEnabled(Hardfork hardfork) { - if (PersistingBlock is null) + // Return true if there's no specific configuration or PersistingBlock is null + if (PersistingBlock is null || ProtocolSettings.Hardforks.Count == 0) return true; - if (!ProtocolSettings.Hardforks.TryGetValue(hardfork, out uint height)) - return true; - return PersistingBlock.Index >= height; + + // If the hardfork isn't specified in the configuration, check if it's a new one. + if (!ProtocolSettings.Hardforks.ContainsKey(hardfork)) + { + int currentHardforkIndex = AllHardforks.IndexOf(hardfork); + int lastConfiguredHardforkIndex = AllHardforks.IndexOf(ProtocolSettings.Hardforks.Keys.Last()); + + // If it's a newer hardfork compared to the ones in the configuration, disable it. + if (currentHardforkIndex > lastConfiguredHardforkIndex) + return false; + } + + if (ProtocolSettings.Hardforks.TryGetValue(hardfork, out uint height)) + { + // If the hardfork has a specific height in the configuration, check the block height. + return PersistingBlock.Index >= height; + } + // If no specific conditions are met, return true. + return true; } } } diff --git a/src/Neo/SmartContract/FindOptions.cs b/src/Neo/SmartContract/FindOptions.cs index d1ae5e64ca..ab23d3bff5 100644 --- a/src/Neo/SmartContract/FindOptions.cs +++ b/src/Neo/SmartContract/FindOptions.cs @@ -53,9 +53,14 @@ public enum FindOptions : byte /// PickField1 = 1 << 5, + /// + /// Indicates that results should be returned in backwards (descending) order. + /// + Backwards = 1 << 7, + /// /// This value is only for internal use, and shouldn't be used in smart contracts. /// - All = KeysOnly | RemovePrefix | ValuesOnly | DeserializeValues | PickField0 | PickField1 + All = KeysOnly | RemovePrefix | ValuesOnly | DeserializeValues | PickField0 | PickField1 | Backwards } } diff --git a/src/Neo/SmartContract/JsonSerializer.cs b/src/Neo/SmartContract/JsonSerializer.cs index fed5354d76..06eb1d5c22 100644 --- a/src/Neo/SmartContract/JsonSerializer.cs +++ b/src/Neo/SmartContract/JsonSerializer.cs @@ -14,6 +14,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Numerics; @@ -158,17 +159,18 @@ public static byte[] SerializeToByteArray(StackItem item, uint maxSize) /// /// Deserializes a from . /// + /// The used. /// The to deserialize. /// The limits for the deserialization. /// The used by the . /// The deserialized . - public static StackItem Deserialize(JToken json, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ApplicationEngine engine, JToken json, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) { uint maxStackSize = limits.MaxStackSize; - return Deserialize(json, ref maxStackSize, referenceCounter); + return Deserialize(engine, json, ref maxStackSize, referenceCounter); } - private static StackItem Deserialize(JToken json, ref uint maxStackSize, ReferenceCounter referenceCounter) + private static StackItem Deserialize(ApplicationEngine engine, JToken json, ref uint maxStackSize, ReferenceCounter referenceCounter) { if (maxStackSize-- == 0) throw new FormatException(); switch (json) @@ -181,7 +183,7 @@ private static StackItem Deserialize(JToken json, ref uint maxStackSize, Referen { List list = new(array.Count); foreach (JToken obj in array) - list.Add(Deserialize(obj, ref maxStackSize, referenceCounter)); + list.Add(Deserialize(engine, obj, ref maxStackSize, referenceCounter)); return new Array(referenceCounter, list); } case JString str: @@ -191,7 +193,10 @@ private static StackItem Deserialize(JToken json, ref uint maxStackSize, Referen case JNumber num: { if ((num.Value % 1) != 0) throw new FormatException("Decimal value is not allowed"); - + if (engine.IsHardforkEnabled(Hardfork.HF_Basilisk)) + { + return BigInteger.Parse(num.Value.ToString(CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture); + } return (BigInteger)num.Value; } case JBoolean boolean: @@ -207,7 +212,7 @@ private static StackItem Deserialize(JToken json, ref uint maxStackSize, Referen if (maxStackSize-- == 0) throw new FormatException(); var key = entry.Key; - var value = Deserialize(entry.Value, ref maxStackSize, referenceCounter); + var value = Deserialize(engine, entry.Value, ref maxStackSize, referenceCounter); item[key] = value; } diff --git a/src/Neo/SmartContract/Manifest/ContractManifest.cs b/src/Neo/SmartContract/Manifest/ContractManifest.cs index 7a067409f2..e68c764800 100644 --- a/src/Neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/Neo/SmartContract/Manifest/ContractManifest.cs @@ -79,7 +79,8 @@ void IInteroperable.FromStackItem(StackItem stackItem) Permissions = ((Array)@struct[5]).Select(p => p.ToInteroperable()).ToArray(); Trusts = @struct[6] switch { - Null => WildcardContainer.CreateWildcard(), + Null _ => WildcardContainer.CreateWildcard(), + // Array array when array.Any(p => ((ByteString)p).Size == 0) => WildcardContainer.CreateWildcard(), Array array => WildcardContainer.Create(array.Select(p => new ContractPermissionDescriptor(p.GetSpan())).ToArray()), _ => throw new ArgumentException(null, nameof(stackItem)) }; @@ -96,7 +97,7 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) new Array(referenceCounter, SupportedStandards.Select(p => (StackItem)p)), Abi.ToStackItem(referenceCounter), new Array(referenceCounter, Permissions.Select(p => p.ToStackItem(referenceCounter))), - Trusts.IsWildcard ? StackItem.Null : new Array(referenceCounter, Trusts.Select(p => (StackItem)p.ToArray())), + Trusts.IsWildcard ? StackItem.Null : new Array(referenceCounter, Trusts.Select(p => p.ToArray()?? StackItem.Null)), Extra is null ? "null" : Extra.ToByteArray(false) }; } diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 47027e8286..f7ed44542d 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -116,6 +116,7 @@ internal override async ContractTask OnPersist(ApplicationEngine engine) Hash = contract.Hash, Manifest = contract.Manifest })); + engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); await contract.Initialize(engine); } } @@ -230,7 +231,7 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ NefFile nef = nefFile.AsSerializable(); ContractManifest parsedManifest = ContractManifest.Parse(manifest); - Helper.Check(nef.Script, parsedManifest.Abi); + Helper.Check(new VM.Script(nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), parsedManifest.Abi); UInt160 hash = Helper.GetContractHash(tx.Sender, nef.CheckSum, parsedManifest.Name); if (Policy.IsBlocked(engine.Snapshot, hash)) @@ -294,7 +295,7 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man throw new InvalidOperationException($"Invalid Manifest Hash: {contract.Hash}"); contract.Manifest = manifest_new; } - Helper.Check(contract.Nef.Script, contract.Manifest.Abi); + Helper.Check(new VM.Script(contract.Nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), contract.Manifest.Abi); contract.UpdateCounter++; // Increase update counter return OnDeploy(engine, contract, data, true); } diff --git a/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs b/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs new file mode 100644 index 0000000000..28e1c063b7 --- /dev/null +++ b/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs @@ -0,0 +1,134 @@ +using Neo.Cryptography.BLS12_381; +using Neo.VM.Types; +using System; + +namespace Neo.SmartContract.Native; + +partial class CryptoLib +{ + /// + /// Serialize a bls12381 point. + /// + /// The point to be serialized. + /// + [ContractMethod(CpuFee = 1 << 19)] + public static byte[] Bls12381Serialize(InteropInterface g) + { + return g.GetInterface() switch + { + G1Affine p => p.ToCompressed(), + G1Projective p => new G1Affine(p).ToCompressed(), + G2Affine p => p.ToCompressed(), + G2Projective p => new G2Affine(p).ToCompressed(), + Gt p => p.ToArray(), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:type mismatch") + }; + } + + /// + /// Deserialize a bls12381 point. + /// + /// The point as byte array. + /// + [ContractMethod(CpuFee = 1 << 19)] + public static InteropInterface Bls12381Deserialize(byte[] data) + { + return data.Length switch + { + 48 => new InteropInterface(G1Affine.FromCompressed(data)), + 96 => new InteropInterface(G2Affine.FromCompressed(data)), + 576 => new InteropInterface(Gt.FromBytes(data)), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:valid point length"), + }; + } + + /// + /// Determines whether the specified points are equal. + /// + /// The first point. + /// Teh second point. + /// true if the specified points are equal; otherwise, false. + [ContractMethod(CpuFee = 1 << 5)] + public static bool Bls12381Equal(InteropInterface x, InteropInterface y) + { + return (x.GetInterface(), y.GetInterface()) switch + { + (G1Affine p1, G1Affine p2) => p1.Equals(p2), + (G1Projective p1, G1Projective p2) => p1.Equals(p2), + (G2Affine p1, G2Affine p2) => p1.Equals(p2), + (G2Projective p1, G2Projective p2) => p1.Equals(p2), + (Gt p1, Gt p2) => p1.Equals(p2), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:type mismatch") + }; + } + + /// + /// Add operation of two points. + /// + /// The first point. + /// The second point. + /// + [ContractMethod(CpuFee = 1 << 19)] + public static InteropInterface Bls12381Add(InteropInterface x, InteropInterface y) + { + return (x.GetInterface(), y.GetInterface()) switch + { + (G1Affine p1, G1Affine p2) => new(new G1Projective(p1) + p2), + (G1Affine p1, G1Projective p2) => new(p1 + p2), + (G1Projective p1, G1Affine p2) => new(p1 + p2), + (G1Projective p1, G1Projective p2) => new(p1 + p2), + (G2Affine p1, G2Affine p2) => new(new G2Projective(p1) + p2), + (G2Affine p1, G2Projective p2) => new(p1 + p2), + (G2Projective p1, G2Affine p2) => new(p1 + p2), + (G2Projective p1, G2Projective p2) => new(p1 + p2), + (Gt p1, Gt p2) => new(p1 + p2), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:type mismatch") + }; + } + + /// + /// Mul operation of gt point and multiplier + /// + /// The point + /// Multiplier,32 bytes,little-endian + /// negative number + /// + [ContractMethod(CpuFee = 1 << 21)] + public static InteropInterface Bls12381Mul(InteropInterface x, byte[] mul, bool neg) + { + Scalar X = neg ? -Scalar.FromBytes(mul) : Scalar.FromBytes(mul); + return x.GetInterface() switch + { + G1Affine p => new(p * X), + G1Projective p => new(p * X), + G2Affine p => new(p * X), + G2Projective p => new(p * X), + Gt p => new(p * X), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:type mismatch") + }; + } + + /// + /// Pairing operation of g1 and g2 + /// + /// The g1 point. + /// The g2 point. + /// + [ContractMethod(CpuFee = 1 << 23)] + public static InteropInterface Bls12381Pairing(InteropInterface g1, InteropInterface g2) + { + G1Affine g1a = g1.GetInterface() switch + { + G1Affine g => g, + G1Projective g => new(g), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:type mismatch") + }; + G2Affine g2a = g2.GetInterface() switch + { + G2Affine g => g, + G2Projective g => new(g), + _ => throw new ArgumentException($"Bls12381 operation fault, type:format, error:type mismatch") + }; + return new(Bls12.Pairing(in g1a, in g2a)); + } +} diff --git a/src/Neo/SmartContract/Native/CryptoLib.cs b/src/Neo/SmartContract/Native/CryptoLib.cs index ba1f07659d..16ec960a27 100644 --- a/src/Neo/SmartContract/Native/CryptoLib.cs +++ b/src/Neo/SmartContract/Native/CryptoLib.cs @@ -18,7 +18,7 @@ namespace Neo.SmartContract.Native /// /// A native contract library that provides cryptographic algorithms. /// - public sealed class CryptoLib : NativeContract + public sealed partial class CryptoLib : NativeContract { private static readonly Dictionary curves = new() { diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index eec49da99a..cd405c098b 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -15,6 +15,7 @@ using Neo.Persistence; using Neo.VM; using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -47,6 +48,13 @@ internal override ContractTask OnPersist(ApplicationEngine engine) foreach (TransactionState tx in transactions) { engine.Snapshot.Add(CreateStorageKey(Prefix_Transaction).Add(tx.Transaction.Hash), new StorageItem(tx)); + var conflictingSigners = tx.Transaction.Signers.Select(s => s.Account); + foreach (var attr in tx.Transaction.GetAttributes()) + { + var conflictRecord = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash), + () => new StorageItem(new TransactionState { ConflictingSigners = Array.Empty() })).GetInteroperable(); + conflictRecord.ConflictingSigners = conflictRecord.ConflictingSigners.Concat(conflictingSigners).Distinct().ToArray(); + } } engine.SetState(transactions); return ContractTask.CompletedTask; @@ -126,7 +134,22 @@ public bool ContainsBlock(DataCache snapshot, UInt256 hash) /// if the blockchain contains the transaction; otherwise, . public bool ContainsTransaction(DataCache snapshot, UInt256 hash) { - return snapshot.Contains(CreateStorageKey(Prefix_Transaction).Add(hash)); + var txState = GetTransactionState(snapshot, hash); + return txState != null; + } + + /// + /// Determine whether the specified transaction hash is contained in the blockchain + /// as the hash of conflicting transaction. + /// + /// The snapshot used to read data. + /// The hash of the conflicting transaction. + /// The list of signer accounts of the conflicting transaction. + /// if the blockchain contains the hash of the conflicting transaction; otherwise, . + public bool ContainsConflictHash(DataCache snapshot, UInt256 hash, IEnumerable signers) + { + var state = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); + return state is not null && state.Transaction is null && (signers is null || state.ConflictingSigners.Intersect(signers).Any()); } /// @@ -220,7 +243,9 @@ public Header GetHeader(DataCache snapshot, uint index) /// The with the specified hash. public TransactionState GetTransactionState(DataCache snapshot, UInt256 hash) { - return snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); + var state = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); + if (state?.Transaction is null) return null; + return state; } /// diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index b24573ad84..74fe70db37 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -10,6 +10,11 @@ #pragma warning disable IDE0051 +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Persistence; @@ -17,11 +22,6 @@ using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Neo.SmartContract.Native { @@ -149,9 +149,14 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, // PersistingBlock is null when running under the debugger if (engine.PersistingBlock is null) return null; - BigInteger gas = CalculateBonus(engine.Snapshot, state.VoteTo, state.Balance, state.BalanceHeight, engine.PersistingBlock.Index); + BigInteger gas = CalculateBonus(engine.Snapshot, state, engine.PersistingBlock.Index); state.BalanceHeight = engine.PersistingBlock.Index; - + if (state.VoteTo is not null) + { + var keyLastest = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(state.VoteTo); + var latestGasPerVote = engine.Snapshot.TryGet(keyLastest) ?? BigInteger.Zero; + state.LastGasPerVote = latestGasPerVote; + } if (gas == 0) return null; return new GasDistribution { @@ -160,24 +165,22 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, }; } - private BigInteger CalculateBonus(DataCache snapshot, ECPoint vote, BigInteger value, uint start, uint end) + private BigInteger CalculateBonus(DataCache snapshot, NeoAccountState state, uint end) { - if (value.IsZero || start >= end) return BigInteger.Zero; - if (value.Sign < 0) throw new ArgumentOutOfRangeException(nameof(value)); - - BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, value, start, end); - if (vote is null) return neoHolderReward; + if (state.Balance.IsZero) return BigInteger.Zero; + if (state.Balance.Sign < 0) throw new ArgumentOutOfRangeException(nameof(state.Balance)); - byte[] border = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).ToArray(); - byte[] keyStart = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(start).ToArray(); - (_, var item) = snapshot.FindRange(keyStart, border, SeekDirection.Backward).FirstOrDefault(); - BigInteger startRewardPerNeo = item ?? BigInteger.Zero; + var expectEnd = Ledger.CurrentIndex(snapshot) + 1; + if (expectEnd != end) throw new ArgumentOutOfRangeException(nameof(end)); + if (state.BalanceHeight >= end) return BigInteger.Zero; + BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, state.Balance, state.BalanceHeight, end); + if (state.VoteTo is null) return neoHolderReward; - byte[] keyEnd = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(end).ToArray(); - (_, item) = snapshot.FindRange(keyEnd, border, SeekDirection.Backward).FirstOrDefault(); - BigInteger endRewardPerNeo = item ?? BigInteger.Zero; + var keyLastest = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(state.VoteTo); + var latestGasPerVote = snapshot.TryGet(keyLastest) ?? BigInteger.Zero; + var voteReward = state.Balance * (latestGasPerVote - state.LastGasPerVote) / 100000000L; - return neoHolderReward + value * (endRewardPerNeo - startRewardPerNeo) / 100000000L; + return neoHolderReward + voteReward; } private BigInteger CalculateNeoHolderReward(DataCache snapshot, BigInteger value, uint start, uint end) @@ -203,8 +206,7 @@ private void CheckCandidate(DataCache snapshot, ECPoint pubkey, CandidateState c { if (!candidate.Registered && candidate.Votes.IsZero) { - foreach (var (rewardKey, _) in snapshot.Find(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(pubkey).ToArray()).ToArray()) - snapshot.Delete(rewardKey); + snapshot.Delete(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(pubkey)); snapshot.Delete(CreateStorageKey(Prefix_Candidate).Add(pubkey)); } } @@ -260,16 +262,14 @@ internal override async ContractTask PostPersist(ApplicationEngine engine) BigInteger voterRewardOfEachCommittee = gasPerBlock * VoterRewardRatio * 100000000L * m / (m + n) / 100; // Zoom in 100000000 times, and the final calculation should be divided 100000000L for (index = 0; index < committee.Count; index++) { - var member = committee[index]; + var (PublicKey, Votes) = committee[index]; var factor = index < n ? 2 : 1; // The `voter` rewards of validator will double than other committee's - if (member.Votes > 0) + if (Votes > 0) { - BigInteger voterSumRewardPerNEO = factor * voterRewardOfEachCommittee / member.Votes; - StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(member.PublicKey).AddBigEndian(engine.PersistingBlock.Index + 1); - byte[] border = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(member.PublicKey).ToArray(); - (_, var item) = engine.Snapshot.FindRange(voterRewardKey.ToArray(), border, SeekDirection.Backward).FirstOrDefault(); - voterSumRewardPerNEO += (item ?? BigInteger.Zero); - engine.Snapshot.Add(voterRewardKey, new StorageItem(voterSumRewardPerNEO)); + BigInteger voterSumRewardPerNEO = factor * voterRewardOfEachCommittee / Votes; + StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(PublicKey); + StorageItem lastRewardPerNeo = engine.Snapshot.GetAndChange(voterRewardKey, () => new StorageItem(BigInteger.Zero)); + lastRewardPerNeo.Add(voterSumRewardPerNEO); } } } @@ -339,7 +339,7 @@ public BigInteger UnclaimedGas(DataCache snapshot, UInt160 account, uint end) StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(account)); if (storage is null) return BigInteger.Zero; NeoAccountState state = storage.GetInteroperable(); - return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end); + return CalculateBonus(snapshot, state, end); } [ContractMethod(RequiredCallFlags = CallFlags.States)] @@ -406,8 +406,15 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, state_validator.Votes -= state_account.Balance; CheckCandidate(engine.Snapshot, state_account.VoteTo, state_validator); } + if (voteTo != null && voteTo != state_account.VoteTo) + { + StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteTo); + var latestGasPerVote = engine.Snapshot.TryGet(voterRewardKey) ?? BigInteger.Zero; + state_account.LastGasPerVote = latestGasPerVote; + } ECPoint from = state_account.VoteTo; state_account.VoteTo = voteTo; + if (validator_new != null) { validator_new.Votes += state_account.Balance; @@ -572,12 +579,15 @@ public class NeoAccountState : AccountState /// public ECPoint VoteTo; + public BigInteger LastGasPerVote; + public override void FromStackItem(StackItem stackItem) { base.FromStackItem(stackItem); Struct @struct = (Struct)stackItem; BalanceHeight = (uint)@struct[1].GetInteger(); VoteTo = @struct[2].IsNull ? null : ECPoint.DecodePoint(@struct[2].GetSpan(), ECCurve.Secp256r1); + LastGasPerVote = @struct[3].GetInteger(); } public override StackItem ToStackItem(ReferenceCounter referenceCounter) @@ -585,6 +595,7 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add(BalanceHeight); @struct.Add(VoteTo?.ToArray() ?? StackItem.Null); + @struct.Add(LastGasPerVote); return @struct; } } diff --git a/src/Neo/SmartContract/Native/StdLib.cs b/src/Neo/SmartContract/Native/StdLib.cs index 37e5ed7a99..709a85724f 100644 --- a/src/Neo/SmartContract/Native/StdLib.cs +++ b/src/Neo/SmartContract/Native/StdLib.cs @@ -49,7 +49,7 @@ private static byte[] JsonSerialize(ApplicationEngine engine, StackItem item) [ContractMethod(CpuFee = 1 << 14)] private static StackItem JsonDeserialize(ApplicationEngine engine, byte[] json) { - return JsonSerializer.Deserialize(JToken.Parse(json, 10), engine.Limits, engine.ReferenceCounter); + return JsonSerializer.Deserialize(engine, JToken.Parse(json, 10), engine.Limits, engine.ReferenceCounter); } /// diff --git a/src/Neo/SmartContract/Native/TransactionState.cs b/src/Neo/SmartContract/Native/TransactionState.cs index 5c7380f1b8..d3c7f644be 100644 --- a/src/Neo/SmartContract/Native/TransactionState.cs +++ b/src/Neo/SmartContract/Native/TransactionState.cs @@ -13,6 +13,7 @@ using Neo.VM; using Neo.VM.Types; using System; +using System.Linq; namespace Neo.SmartContract.Native { @@ -27,10 +28,12 @@ public class TransactionState : IInteroperable public uint BlockIndex; /// - /// The transaction. + /// The transaction, if the transaction is trimmed this value will be null /// public Transaction Transaction; + public UInt160[] ConflictingSigners; + /// /// The execution state /// @@ -44,6 +47,7 @@ IInteroperable IInteroperable.Clone() { BlockIndex = BlockIndex, Transaction = Transaction, + ConflictingSigners = ConflictingSigners, State = State, _rawTransaction = _rawTransaction }; @@ -54,6 +58,7 @@ void IInteroperable.FromReplica(IInteroperable replica) TransactionState from = (TransactionState)replica; BlockIndex = from.BlockIndex; Transaction = from.Transaction; + ConflictingSigners = from.ConflictingSigners; State = from.State; if (_rawTransaction.IsEmpty) _rawTransaction = from._rawTransaction; @@ -62,6 +67,11 @@ void IInteroperable.FromReplica(IInteroperable replica) void IInteroperable.FromStackItem(StackItem stackItem) { Struct @struct = (Struct)stackItem; + if (@struct.Count == 1) + { + ConflictingSigners = ((VM.Types.Array)@struct[0]).Select(u => new UInt160(u.GetSpan())).ToArray(); + return; + } BlockIndex = (uint)@struct[0].GetInteger(); _rawTransaction = ((ByteString)@struct[1]).Memory; Transaction = _rawTransaction.AsSerializable(); @@ -70,6 +80,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) { + if (Transaction is null) return new Struct(referenceCounter) { new VM.Types.Array(referenceCounter, ConflictingSigners.Select(u => new ByteString(u.ToArray())).ToArray()) }; if (_rawTransaction.IsEmpty) _rawTransaction = Transaction.ToArray(); return new Struct(referenceCounter) { BlockIndex, _rawTransaction, (byte)State }; diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 734a0f50c6..875c10b42b 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -2,16 +2,16 @@ - net6.0 + net7.0 false - - - - - + + + + + diff --git a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs index 3df0f9cd42..32d6fcd408 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs @@ -152,11 +152,52 @@ public void TestFind() store.Put(key3.ToArray(), value3.ToArray()); store.Put(key4.ToArray(), value4.ToArray()); - var items = myDataCache.Find(key1.ToArray()); + var k1 = key1.ToArray(); + var items = myDataCache.Find(k1); key1.Should().Be(items.ElementAt(0).Key); value1.Should().Be(items.ElementAt(0).Value); items.Count().Should().Be(1); + // null and empty with the forward direction -> finds everything. + items = myDataCache.Find(null); + items.Count().Should().Be(4); + items = myDataCache.Find(new byte[] { }); + items.Count().Should().Be(4); + + // null and empty with the backwards direction -> miserably fails. + Action action = () => myDataCache.Find(null, SeekDirection.Backward); + action.Should().Throw(); + action = () => myDataCache.Find(new byte[] { }, SeekDirection.Backward); + action.Should().Throw(); + + items = myDataCache.Find(k1, SeekDirection.Backward); + key1.Should().Be(items.ElementAt(0).Key); + value1.Should().Be(items.ElementAt(0).Value); + items.Count().Should().Be(1); + + var prefix = k1.Take(k1.Count() - 1).ToArray(); // Just the "key" part to match everything. + items = myDataCache.Find(prefix); + items.Count().Should().Be(4); + key1.Should().Be(items.ElementAt(0).Key); + value1.Should().Be(items.ElementAt(0).Value); + key2.Should().Be(items.ElementAt(1).Key); + value2.Should().Be(items.ElementAt(1).Value); + key3.Should().Be(items.ElementAt(2).Key); + value3.EqualsTo(items.ElementAt(2).Value).Should().BeTrue(); + key4.Should().Be(items.ElementAt(3).Key); + value4.EqualsTo(items.ElementAt(3).Value).Should().BeTrue(); + + items = myDataCache.Find(prefix, SeekDirection.Backward); + items.Count().Should().Be(4); + key4.Should().Be(items.ElementAt(0).Key); + value4.EqualsTo(items.ElementAt(0).Value).Should().BeTrue(); + key3.Should().Be(items.ElementAt(1).Key); + value3.EqualsTo(items.ElementAt(1).Value).Should().BeTrue(); + key2.Should().Be(items.ElementAt(2).Key); + value2.Should().Be(items.ElementAt(2).Value); + key1.Should().Be(items.ElementAt(3).Key); + value1.Should().Be(items.ElementAt(3).Value); + items = myDataCache.Find(key5.ToArray()); items.Count().Should().Be(0); } diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs index 130df54e60..1cd8c42360 100644 --- a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs +++ b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs @@ -6,6 +6,7 @@ using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; +using Neo.VM; using Neo.Wallets; using Neo.Wallets.NEP6; using System; @@ -97,5 +98,75 @@ private static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, tx.Witnesses = data.GetWitnesses(); return tx; } + + [TestMethod] + public void TestMaliciousOnChainConflict() + { + var snapshot = TestBlockchain.TheNeoSystem.GetSnapshot(); + var walletA = TestUtils.GenerateTestWallet("123"); + var accA = walletA.CreateAccount(); + var walletB = TestUtils.GenerateTestWallet("456"); + var accB = walletB.CreateAccount(); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + engine.LoadScript(Array.Empty()); + + // Fake balance for accounts A and B. + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(accA.ScriptHash); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); + + key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(accB.ScriptHash); + entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); + + // Create transactions: + // tx1 conflicts with tx2 and has the same sender (thus, it's a valid conflict and must prevent tx2 from entering the chain); + // tx2 conflicts with tx3 and has different sender (thus, this conflict is invalid and must not prevent tx3 from entering the chain). + var tx1 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); + var tx2 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); + var tx3 = CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); + + tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx2.Hash }, new Conflicts() { Hash = tx3.Hash } }; + + // Persist tx1. + var block = new Block + { + Header = new Header() + { + Index = 10000, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = new Transaction[] { tx1 }, + }; + byte[] onPersistScript; + using (ScriptBuilder sb = new()) + { + sb.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); + onPersistScript = sb.ToArray(); + } + TransactionState[] transactionStates; + using (ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, block, TestBlockchain.TheNeoSystem.Settings, 0)) + { + engine2.LoadScript(onPersistScript); + if (engine2.Execute() != VMState.HALT) throw new InvalidOperationException(); + Blockchain.ApplicationExecuted application_executed = new(engine2); + transactionStates = engine2.GetState(); + engine2.Snapshot.Commit(); + } + snapshot.Commit(); + + // Add tx2: must fail because valid conflict is alredy on chain (tx1). + senderProbe.Send(TestBlockchain.TheNeoSystem.Blockchain, tx2); + senderProbe.ExpectMsg(p => p.Result == VerifyResult.HasConflicts); + + // Add tx3: must succeed because on-chain conflict is invalid (doesn't have proper signer). + senderProbe.Send(TestBlockchain.TheNeoSystem.Blockchain, tx3); + senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed); + } } } diff --git a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs index 98f7b009fd..de5bff6ba0 100644 --- a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -1,19 +1,21 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Neo.Cryptography; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; +using Neo.VM; namespace Neo.UnitTests.Ledger { @@ -68,7 +70,7 @@ private Transaction CreateTransactionWithFee(long fee) var randomBytes = new byte[16]; random.NextBytes(randomBytes); Mock mock = new(); - mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns(VerifyResult.Succeed); mock.Setup(p => p.VerifyStateIndependent(It.IsAny())).Returns(VerifyResult.Succeed); mock.Object.Script = randomBytes; mock.Object.NetworkFee = fee; @@ -92,7 +94,7 @@ private Transaction CreateTransactionWithFeeAndBalanceVerify(long fee) random.NextBytes(randomBytes); Mock mock = new(); UInt160 sender = senderAccount; - mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny())).Returns((ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context) => context.CheckTransaction(mock.Object, snapshot) ? VerifyResult.Succeed : VerifyResult.InsufficientFunds); + mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns((ProtocolSettings settings, DataCache snapshot, TransactionVerificationContext context, IEnumerable conflictsList) => context.CheckTransaction(mock.Object, conflictsList, snapshot) ? VerifyResult.Succeed : VerifyResult.InsufficientFunds); mock.Setup(p => p.VerifyStateIndependent(It.IsAny())).Returns(VerifyResult.Succeed); mock.Object.Script = randomBytes; mock.Object.NetworkFee = fee; @@ -241,6 +243,215 @@ public async Task BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered( _ = NativeContract.GAS.Mint(applicationEngine, sender, balance, true); } + [TestMethod] + public async Task UpdatePoolForBlockPersisted_RemoveBlockConflicts() + { + // Arrange: prepare mempooled and in-bock txs conflicting with each other. + long txFee = 1; + var snapshot = GetSnapshot(); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, senderAccount); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + engine.LoadScript(Array.Empty()); + await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 7, true); // balance enough for 7 mempooled txs + + var mp1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp1 doesn't conflict with anyone + _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.Succeed); + var tx1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // but in-block tx1 conflicts with mempooled mp1 => mp1 should be removed from pool after persist + tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; + + var mp2 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp1 and mp2 don't conflict with anyone + _unit.TryAdd(mp2, engine.Snapshot); + var mp3 = CreateTransactionWithFeeAndBalanceVerify(txFee); + _unit.TryAdd(mp3, engine.Snapshot); + var tx2 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx2 conflicts with mempooled mp2 and mp3 => mp2 and mp3 should be removed from pool after persist + tx2.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp2.Hash }, new Conflicts() { Hash = mp3.Hash } }; + + var tx3 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx3 doesn't conflict with anyone + var mp4 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp4 conflicts with in-block tx3 => mp4 should be removed from pool after persist + mp4.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx3.Hash } }; + _unit.TryAdd(mp4, engine.Snapshot); + + var tx4 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx4 and tx5 don't conflict with anyone + var tx5 = CreateTransactionWithFeeAndBalanceVerify(txFee); + var mp5 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp5 conflicts with in-block tx4 and tx5 => mp5 should be removed from pool after persist + mp5.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx4.Hash }, new Conflicts() { Hash = tx5.Hash } }; + _unit.TryAdd(mp5, engine.Snapshot); + + var mp6 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp6 doesn't conflict with anyone and noone conflicts with mp6 => mp6 should be left in the pool after persist + _unit.TryAdd(mp6, engine.Snapshot); + + _unit.SortedTxCount.Should().Be(6); + _unit.UnverifiedSortedTxCount.Should().Be(0); + + var mp7 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp7 doesn't conflict with anyone + var tx6 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx6 conflicts with mp7, but doesn't include sender of mp7 into signers list => even if tx6 is included into block, mp7 shouldn't be removed from the pool + tx6.Signers = new Signer[] { new Signer() { Account = new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })) }, new Signer() { Account = new UInt160(Crypto.Hash160(new byte[] { 4, 5, 6 })) } }; + tx6.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp7.Hash } }; + _unit.TryAdd(mp7, engine.Snapshot); + + // Act: persist block and reverify all mempooled txs. + var block = new Block + { + Header = new Header(), + Transactions = new Transaction[] { tx1, tx2, tx3, tx4, tx5, tx6 }, + }; + _unit.UpdatePoolForBlockPersisted(block, engine.Snapshot); + + // Assert: conflicting txs should be removed from the pool; the only mp6 that doesn't conflict with anyone should be left. + _unit.SortedTxCount.Should().Be(2); + _unit.GetSortedVerifiedTransactions().Select(tx => tx.Hash).Should().Contain(mp6.Hash); + _unit.GetSortedVerifiedTransactions().Select(tx => tx.Hash).Should().Contain(mp7.Hash); + _unit.UnverifiedSortedTxCount.Should().Be(0); + + // Cleanup: revert the balance. + await NativeContract.GAS.Burn(engine, UInt160.Zero, txFee * 7); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, balance, true); + } + + [TestMethod] + public async Task TryAdd_AddRangeOfConflictingTransactions() + { + // Arrange: prepare mempooled txs that have conflicts. + long txFee = 1; + var maliciousSender = new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })); + var snapshot = GetSnapshot(); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, senderAccount); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + engine.LoadScript(Array.Empty()); + await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 100, true); // balance enough for all mempooled txs + _ = NativeContract.GAS.Mint(engine, maliciousSender, 100, true); // balance enough for all mempooled txs + + var mp1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp1 doesn't conflict with anyone and not in the pool yet + + var mp2_1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp2_1 conflicts with mp1 and has the same network fee + mp2_1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; + _unit.TryAdd(mp2_1, engine.Snapshot); + var mp2_2 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp2_2 also conflicts with mp1 and has the same network fee as mp1 and mp2_1 + mp2_2.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; + _unit.TryAdd(mp2_2, engine.Snapshot); + + var mp3 = CreateTransactionWithFeeAndBalanceVerify(2 * txFee); // mp3 conflicts with mp1 and has larger network fee + mp3.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; + _unit.TryAdd(mp3, engine.Snapshot); + + var mp4 = CreateTransactionWithFeeAndBalanceVerify(3 * txFee); // mp4 conflicts with mp3 and has larger network fee + mp4.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp3.Hash } }; + + var malicious = CreateTransactionWithFeeAndBalanceVerify(3 * txFee); // malicious conflicts with mp3 and has larger network fee, but different sender + malicious.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp3.Hash } }; + malicious.Signers = new Signer[] { new Signer() { Account = new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })), Scopes = WitnessScope.None } }; + + var mp5 = CreateTransactionWithFeeAndBalanceVerify(2 * txFee); // mp5 conflicts with mp4 and has smaller network fee + mp5.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp4.Hash } }; + + var mp6 = CreateTransactionWithFeeAndBalanceVerify(mp2_1.NetworkFee + mp2_2.NetworkFee + 1); // mp6 conflicts with mp2_1 and mp2_2 and has larger network fee. + mp6.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp2_1.Hash }, new Conflicts() { Hash = mp2_2.Hash } }; + + var mp7 = CreateTransactionWithFeeAndBalanceVerify(txFee * 2 + 1); // mp7 doesn't conflicts with anyone, but mp8, mp9 and mp10malicious has smaller sum network fee and conflict with mp7. + var mp8 = CreateTransactionWithFeeAndBalanceVerify(txFee); + mp8.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp7.Hash } }; + var mp9 = CreateTransactionWithFeeAndBalanceVerify(txFee); + mp9.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp7.Hash } }; + var mp10malicious = CreateTransactionWithFeeAndBalanceVerify(txFee); + mp10malicious.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp7.Hash } }; + mp10malicious.Signers = new Signer[] { new Signer() { Account = maliciousSender, Scopes = WitnessScope.None } }; + + _unit.SortedTxCount.Should().Be(3); + _unit.UnverifiedSortedTxCount.Should().Be(0); + + // Act & Assert: try to add conlflicting transactions to the pool. + _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2_1, mp2_2 and mp3 but has lower network fee than mp3 => mp1 fails to be added + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp3 }); + + _unit.TryAdd(malicious, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // malicious conflicts with mp3, has larger network fee but malicious (different) sender => mp3 shoould be left in pool + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp3 }); + + _unit.TryAdd(mp4, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp4 conflicts with mp3 and has larger network fee => mp3 shoould be removed from pool + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp4 }); + + _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2_1 and mp2_2 and has same network fee => mp2_1 and mp2_2 should be left in pool. + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp4 }); + + _unit.TryAdd(mp6, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp6 conflicts with mp2_1 and mp2_2 and has larger network fee than the sum of mp2_1 and mp2_2 fees => mp6 should be added. + _unit.SortedTxCount.Should().Be(2); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp6, mp4 }); + + _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp1 conflicts with mp2_1 and mp2_2, but they are not in the pool now => mp1 should be added. + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4 }); + + _unit.TryAdd(mp2_1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp2_1 conflicts with mp1 and has same network fee => mp2_1 shouldn't be added to the pool. + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4 }); + + _unit.TryAdd(mp5, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp5 conflicts with mp4 and has smaller network fee => mp5 fails to be added. + _unit.SortedTxCount.Should().Be(3); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4 }); + + _unit.TryAdd(mp8, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp8, mp9 and mp10malicious conflict with mp7, but mo7 is not in the pool yet. + _unit.TryAdd(mp9, engine.Snapshot).Should().Be(VerifyResult.Succeed); + _unit.TryAdd(mp10malicious, engine.Snapshot).Should().Be(VerifyResult.Succeed); + _unit.SortedTxCount.Should().Be(6); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4, mp8, mp9, mp10malicious }); + _unit.TryAdd(mp7, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp7 has larger network fee than the sum of mp8 and mp9 fees => should be added to the pool. + _unit.SortedTxCount.Should().Be(4); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4, mp7 }); + + // Cleanup: revert the balance. + await NativeContract.GAS.Burn(engine, UInt160.Zero, 100); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, balance, true); + await NativeContract.GAS.Burn(engine, maliciousSender, 100); + _ = NativeContract.GAS.Mint(engine, maliciousSender, balance, true); + } + + [TestMethod] + public async Task TryRemoveVerified_RemoveVerifiedTxWithConflicts() + { + // Arrange: prepare mempooled txs that have conflicts. + long txFee = 1; + var snapshot = GetSnapshot(); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, senderAccount); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + engine.LoadScript(Array.Empty()); + await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 100, true); // balance enough for all mempooled txs + + var mp1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp1 doesn't conflict with anyone and not in the pool yet + + var mp2 = CreateTransactionWithFeeAndBalanceVerify(2 * txFee); // mp2 conflicts with mp1 and has larger same network fee + mp2.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; + _unit.TryAdd(mp2, engine.Snapshot); + + _unit.SortedTxCount.Should().Be(1); + _unit.UnverifiedSortedTxCount.Should().Be(0); + + _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2 but has lower network fee + _unit.SortedTxCount.Should().Be(1); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2 }); + + // Act & Assert: try to invalidate verified transactions and push conflicting one. + _unit.InvalidateVerifiedTransactions(); + _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp1 conflicts with mp2 but mp2 is not verified anymore + _unit.SortedTxCount.Should().Be(1); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1 }); + + var tx1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx1 doesn't conflict with anyone and is aimed to trigger reverification + var block = new Block + { + Header = new Header(), + Transactions = new Transaction[] { tx1 }, + }; + _unit.UpdatePoolForBlockPersisted(block, engine.Snapshot); + _unit.SortedTxCount.Should().Be(1); + _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2 }); // after reverificaion mp2 should be back at verified list; mp1 should be completely kicked off + } + private static void VerifyTransactionsSortedDescending(IEnumerable transactions) { Transaction lastTransaction = null; @@ -525,6 +736,74 @@ public void TestUpdatePoolForBlockPersisted() _unit.VerifiedCount.Should().Be(0); } + + [TestMethod] + public async Task Malicious_OnChain_Conflict() + { + // Arrange: prepare mempooled txs that have conflicts. + long txFee = 1; + var snapshot = GetSnapshot(); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, senderAccount); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + engine.LoadScript(Array.Empty()); + + var tx1 = CreateTransactionWithFeeAndBalanceVerify(txFee); + var tx2 = CreateTransactionWithFeeAndBalanceVerify(txFee); + + tx1.Signers[0].Account = UInt160.Parse("0x0001020304050607080900010203040506070809"); // Different sender + + await NativeContract.GAS.Mint(engine, tx1.Sender, 100000, true); // balance enough for all mempooled txs + await NativeContract.GAS.Mint(engine, tx2.Sender, 100000, true); // balance enough for all mempooled txs + + tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx2.Hash } }; + + Assert.AreEqual(_unit.TryAdd(tx1, engine.Snapshot), VerifyResult.Succeed); + + var block = new Block + { + Header = new Header() + { + Index = 10000, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = new Transaction[] { tx1 }, + }; + _unit.UpdatePoolForBlockPersisted(block, engine.Snapshot); + + _unit.SortedTxCount.Should().Be(0); + + // Simulate persist tx1 + + Assert.AreEqual(_unit.TryAdd(tx2, engine.Snapshot), VerifyResult.Succeed); + + byte[] onPersistScript; + using (ScriptBuilder sb = new()) + { + sb.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); + onPersistScript = sb.ToArray(); + } + + TransactionState[] transactionStates; + using (ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.OnPersist, null, engine.Snapshot, block, TestBlockchain.TheNeoSystem.Settings, 0)) + { + engine2.LoadScript(onPersistScript); + if (engine2.Execute() != VMState.HALT) throw new InvalidOperationException(); + Blockchain.ApplicationExecuted application_executed = new(engine2); + transactionStates = engine2.GetState(); + } + + // Test tx2 arrive + + snapshot.Commit(); + + var senderProbe = CreateTestProbe(); + senderProbe.Send(TestBlockchain.TheNeoSystem.Blockchain, tx2); + senderProbe.ExpectMsg(p => p.Result == VerifyResult.InsufficientFunds); // should be Succedded. + } + public static StorageKey CreateStorageKey(int id, byte prefix, byte[] key = null) { byte[] buffer = GC.AllocateUninitializedArray(sizeof(byte) + (key?.Length ?? 0)); diff --git a/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs b/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs index db023d3eb8..0270d97891 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -14,6 +15,8 @@ public class UT_TransactionState { TransactionState origin; + TransactionState originTrimmed; + [TestInitialize] public void Initialize() { @@ -31,6 +34,14 @@ public void Initialize() } } } }; + originTrimmed = new TransactionState + { + ConflictingSigners = new UInt160[] + { + new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })), + new UInt160(Crypto.Hash160(new byte[] { 4, 5, 6 })) + } + }; } [TestMethod] @@ -39,11 +50,27 @@ public void TestDeserialize() var data = BinarySerializer.Serialize(((IInteroperable)origin).ToStackItem(null), 1024); var reader = new MemoryReader(data); - TransactionState dest = new TransactionState(); + TransactionState dest = new(); ((IInteroperable)dest).FromStackItem(BinarySerializer.Deserialize(ref reader, ExecutionEngineLimits.Default, null)); dest.BlockIndex.Should().Be(origin.BlockIndex); dest.Transaction.Hash.Should().Be(origin.Transaction.Hash); + dest.Transaction.Should().NotBeNull(); + } + + [TestMethod] + public void TestDeserializeTrimmed() + { + var data = BinarySerializer.Serialize(((IInteroperable)originTrimmed).ToStackItem(null), 1024); + var reader = new MemoryReader(data); + + TransactionState dest = new(); + ((IInteroperable)dest).FromStackItem(BinarySerializer.Deserialize(ref reader, ExecutionEngineLimits.Default, null)); + + dest.BlockIndex.Should().Be(0); + dest.Transaction.Should().Be(null); + dest.Transaction.Should().BeNull(); + CollectionAssert.AreEqual(originTrimmed.ConflictingSigners, dest.ConflictingSigners); } } } diff --git a/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs b/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs index 27d584dca9..522b964ff9 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs @@ -7,6 +7,7 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using System; +using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; @@ -27,7 +28,7 @@ private Transaction CreateTransactionWithFee(long networkFee, long systemFee) var randomBytes = new byte[16]; random.NextBytes(randomBytes); Mock mock = new(); - mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny())).Returns(VerifyResult.Succeed); + mock.Setup(p => p.VerifyStateDependent(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns(VerifyResult.Succeed); mock.Setup(p => p.VerifyStateIndependent(It.IsAny())).Returns(VerifyResult.Succeed); mock.Object.Script = randomBytes; mock.Object.NetworkFee = networkFee; @@ -60,12 +61,13 @@ public async Task TestDuplicateOracle() TransactionVerificationContext verificationContext = new(); var tx = CreateTransactionWithFee(1, 2); tx.Attributes = new TransactionAttribute[] { new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = 1, Result = Array.Empty() } }; - verificationContext.CheckTransaction(tx, snapshot).Should().BeTrue(); + var conflicts = new List(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); verificationContext.AddTransaction(tx); tx = CreateTransactionWithFee(2, 1); tx.Attributes = new TransactionAttribute[] { new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = 1, Result = Array.Empty() } }; - verificationContext.CheckTransaction(tx, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); } [TestMethod] @@ -79,15 +81,40 @@ public async Task TestTransactionSenderFee() TransactionVerificationContext verificationContext = new(); var tx = CreateTransactionWithFee(1, 2); - verificationContext.CheckTransaction(tx, snapshot).Should().BeTrue(); + var conflicts = new List(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); verificationContext.RemoveTransaction(tx); - verificationContext.CheckTransaction(tx, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); + } + + [TestMethod] + public async Task TestTransactionSenderFeeWithConflicts() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); + await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); + _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 3 + 3 + 1, true); // balance is enough for 2 transactions and 1 GAS is left. + + TransactionVerificationContext verificationContext = new(); + var tx = CreateTransactionWithFee(1, 2); + var conflictingTx = CreateTransactionWithFee(1, 1); // costs 2 GAS + + var conflicts = new List(); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.AddTransaction(tx); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.AddTransaction(tx); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); + + conflicts.Add(conflictingTx); + verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); // 1 GAS is left on the balance + 2 GAS is free after conflicts removal => enough for one more trasnaction. } } } diff --git a/tests/Neo.UnitTests/Neo.UnitTests.csproj b/tests/Neo.UnitTests/Neo.UnitTests.csproj index b415956da2..1aef941090 100644 --- a/tests/Neo.UnitTests/Neo.UnitTests.csproj +++ b/tests/Neo.UnitTests/Neo.UnitTests.csproj @@ -1,13 +1,13 @@ - + true - - - + + + diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs new file mode 100644 index 0000000000..bbf46920aa --- /dev/null +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs @@ -0,0 +1,89 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using Neo.SmartContract; +using Neo.VM; +using System; + +namespace Neo.UnitTests.Network.P2P.Payloads +{ + [TestClass] + public class UT_Conflicts + { + private const byte Prefix_Transaction = 11; + private static UInt256 _u = new UInt256(new byte[32] { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01 + }); + + [TestMethod] + public void Size_Get() + { + var test = new Conflicts() { Hash = _u }; + test.Size.Should().Be(1 + 32); + } + + [TestMethod] + public void ToJson() + { + var test = new Conflicts() { Hash = _u }; + var json = test.ToJson().ToString(); + Assert.AreEqual(@"{""type"":""Conflicts"",""hash"":""0x0101010101010101010101010101010101010101010101010101010101010101""}", json); + } + + [TestMethod] + public void DeserializeAndSerialize() + { + var test = new Conflicts() { Hash = _u }; + + var clone = test.ToArray().AsSerializable(); + Assert.AreEqual(clone.Type, test.Type); + + // As transactionAttribute + byte[] buffer = test.ToArray(); + var reader = new MemoryReader(buffer); + clone = TransactionAttribute.DeserializeFrom(ref reader) as Conflicts; + Assert.AreEqual(clone.Type, test.Type); + + // Wrong type + buffer[0] = 0xff; + Assert.ThrowsException(() => + { + var reader = new MemoryReader(buffer); + TransactionAttribute.DeserializeFrom(ref reader); + }); + } + + [TestMethod] + public void Verify() + { + var test = new Conflicts() { Hash = _u }; + var snapshot = TestBlockchain.GetTestSnapshot(); + var key = Ledger.UT_MemoryPool.CreateStorageKey(NativeContract.Ledger.Id, Prefix_Transaction, _u.ToArray()); + + // Conflicting transaction is in the Conflicts attribute of some other on-chain transaction. + var conflict = new TransactionState(); + snapshot.Add(key, new StorageItem(conflict)); + Assert.IsTrue(test.Verify(snapshot, new Transaction())); + + // Conflicting transaction is on-chain. + snapshot.Delete(key); + conflict = new TransactionState + { + BlockIndex = 123, + Transaction = new Transaction(), + State = VMState.NONE + }; + snapshot.Add(key, new StorageItem(conflict)); + Assert.IsFalse(test.Verify(snapshot, new Transaction())); + + // There's no conflicting transaction at all. + snapshot.Delete(key); + Assert.IsTrue(test.Verify(snapshot, new Transaction())); + } + } +} diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs new file mode 100644 index 0000000000..e47159d9ff --- /dev/null +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs @@ -0,0 +1,75 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using System; + +namespace Neo.UnitTests.Network.P2P.Payloads +{ + [TestClass] + public class UT_NotValidBefore + { + [TestMethod] + public void Size_Get() + { + var test = new NotValidBefore(); + test.Size.Should().Be(5); + } + + [TestMethod] + public void ToJson() + { + var test = new NotValidBefore(); + test.Height = 42; + var json = test.ToJson().ToString(); + Assert.AreEqual(@"{""type"":""NotValidBefore"",""height"":42}", json); + } + + [TestMethod] + public void DeserializeAndSerialize() + { + var test = new NotValidBefore(); + + var clone = test.ToArray().AsSerializable(); + Assert.AreEqual(clone.Type, test.Type); + + // As transactionAttribute + + byte[] buffer = test.ToArray(); + var reader = new MemoryReader(buffer); + clone = TransactionAttribute.DeserializeFrom(ref reader) as NotValidBefore; + Assert.AreEqual(clone.Type, test.Type); + + // Wrong type + + buffer[0] = 0xff; + reader = new MemoryReader(buffer); + try + { + TransactionAttribute.DeserializeFrom(ref reader); + Assert.Fail(); + } + catch (FormatException) { } + reader = new MemoryReader(buffer); + try + { + new NotValidBefore().Deserialize(ref reader); + Assert.Fail(); + } + catch (FormatException) { } + } + + [TestMethod] + public void Verify() + { + var test = new NotValidBefore(); + var snapshot = TestBlockchain.GetTestSnapshot(); + test.Height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + + Assert.IsFalse(test.Verify(snapshot, new Transaction())); + test.Height--; + Assert.IsTrue(test.Verify(snapshot, new Transaction())); + } + } +} diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs index 98c49f31c4..1df1340012 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs @@ -10,6 +10,7 @@ using Neo.VM; using Neo.Wallets; using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -773,7 +774,7 @@ public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() }; UInt160[] hashes = txSimple.GetScriptHashesForVerifying(snapshot); Assert.AreEqual(1, hashes.Length); - Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext())); + Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List())); } [TestMethod] @@ -1204,11 +1205,12 @@ public void Test_VerifyStateDependent() var key = NativeContract.GAS.CreateStorageKey(20, tx.Sender); var balance = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); balance.GetInteroperable().Balance = tx.NetworkFee; + var conflicts = new List(); - tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Invalid); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), conflicts).Should().Be(VerifyResult.Invalid); balance.GetInteroperable().Balance = 0; tx.SystemFee = 10; - tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.InsufficientFunds); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), conflicts).Should().Be(VerifyResult.InsufficientFunds); var walletA = TestUtils.GenerateTestWallet("123"); var walletB = TestUtils.GenerateTestWallet("123"); @@ -1254,7 +1256,7 @@ public void Test_VerifyStateDependent() Assert.IsTrue(data.Completed); tx.Witnesses = data.GetWitnesses(); - tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext()).Should().Be(VerifyResult.Succeed); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List()).Should().Be(VerifyResult.Succeed); } [TestMethod] diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index 7d895a6e3b..986716e694 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -1,8 +1,11 @@ +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.IO; using Neo.Json; using Neo.SmartContract; using Neo.SmartContract.Manifest; +using Neo.VM; namespace Neo.UnitTests.SmartContract.Manifest { @@ -53,12 +56,12 @@ public void ParseFromJson_SafeMethods() [TestMethod] public void ParseFromJson_Trust() { - var json = @"{""name"":""testManifest"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testMethod"",""parameters"":[],""returntype"":""Void"",""offset"":0,""safe"":true}],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""extra"":null}"; + ReferenceCounter referenceCounter = new ReferenceCounter(); + var json = @"{""name"":""testManifest"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testMethod"",""parameters"":[],""returntype"":""Void"",""offset"":0,""safe"":true}],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001"",""*""],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToJson().ToString(), json); - var check = TestUtils.CreateDefaultManifest(); - check.Trusts = WildcardContainer.Create(ContractPermissionDescriptor.Create(UInt160.Parse("0x0000000000000000000000000000000000000001"))); + check.Trusts = WildcardContainer.Create(ContractPermissionDescriptor.Create(UInt160.Parse("0x0000000000000000000000000000000000000001")), ContractPermissionDescriptor.CreateWildcard()); Assert.AreEqual(manifest.ToJson().ToString(), check.ToJson().ToString()); } @@ -103,6 +106,21 @@ public void TestDeserializeAndSerialize() Assert.AreEqual(expected.ToString(), clone.ToString()); } + [TestMethod] + public void TestSerializeTrusts() + { + var check = TestUtils.CreateDefaultManifest(); + check.Trusts = WildcardContainer.Create(ContractPermissionDescriptor.Create(UInt160.Parse("0x0000000000000000000000000000000000000001")), ContractPermissionDescriptor.CreateWildcard()); + var si = check.ToStackItem(null); + + var actualTrusts = ((VM.Types.Array)si)[6]; + + Assert.AreEqual(((VM.Types.Array)actualTrusts).Count, 2); + Assert.AreEqual(((VM.Types.Array)actualTrusts)[0], new VM.Types.ByteString(UInt160.Parse("0x0000000000000000000000000000000000000001").ToArray())); + // Wildcard trust should be represented as Null stackitem (not as zero-length ByteString): + Assert.AreEqual(((VM.Types.Array)actualTrusts)[1], VM.Types.StackItem.Null); + } + [TestMethod] public void TestGenerator() { diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs new file mode 100644 index 0000000000..f6e142500a --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs @@ -0,0 +1,328 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.BLS12_381; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using System.Security.Cryptography; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_CryptoLib + { + private readonly byte[] g1 = "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb".ToLower().HexToBytes(); + private readonly byte[] g2 = "93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8".ToLower().HexToBytes(); + private readonly byte[] gt = "0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b67663104c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef03350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a211b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba5706fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1a19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89d018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b601b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934f1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170f1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6".ToLower().HexToBytes(); + + + private readonly byte[] not_g1 = + "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".ToLower().HexToBytes(); + private readonly byte[] not_g2 = + "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .ToLower().HexToBytes(); + + [TestMethod] + public void TestG1() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetInterface().ToCompressed().ToHexString().Should().Be("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"); + } + + [TestMethod] + public void TestG2() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g2); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetInterface().ToCompressed().ToHexString().Should().Be("93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"); + } + + [TestMethod] + public void TestNotG1() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", not_g1); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.FAULT, engine.Execute()); + } + + [TestMethod] + public void TestNotG2() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", not_g2); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.FAULT, engine.Execute()); + } + [TestMethod] + public void TestBls12381Add() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt); + script.EmitPush(2); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("bls12381Add"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetInterface().ToArray().ToHexString().Should().Be("079AB7B345EB23C944C957A36A6B74C37537163D4CBF73BAD9751DE1DD9C68EF72CB21447E259880F72A871C3EDA1B0C017F1C95CF79B22B459599EA57E613E00CB75E35DE1F837814A93B443C54241015AC9761F8FB20A44512FF5CFC04AC7F0F6B8B52B2B5D0661CBF232820A257B8C5594309C01C2A45E64C6A7142301E4FB36E6E16B5A85BD2E437599D103C3ACE06D8046C6B3424C4CD2D72CE98D279F2290A28A87E8664CB0040580D0C485F34DF45267F8C215DCBCD862787AB555C7E113286DEE21C9C63A458898BEB35914DC8DAAAC453441E7114B21AF7B5F47D559879D477CF2A9CBD5B40C86BECD071280900410BB2751D0A6AF0FE175DCF9D864ECAAC463C6218745B543F9E06289922434EE446030923A3E4C4473B4E3B1914081ABD33A78D31EB8D4C1BB3BAAB0529BB7BAF1103D848B4CEAD1A8E0AA7A7B260FBE79C67DBE41CA4D65BA8A54A72B61692A61CE5F4D7A093B2C46AA4BCA6C4A66CF873D405EBC9C35D8AA639763720177B23BEFFAF522D5E41D3C5310EA3331409CEBEF9EF393AA00F2AC64673675521E8FC8FDDAF90976E607E62A740AC59C3DDDF95A6DE4FBA15BEB30C43D4E3F803A3734DBEB064BF4BC4A03F945A4921E49D04AB8D45FD753A28B8FA082616B4B17BBCB685E455FF3BF8F60C3BD32A0C185EF728CF41A1B7B700B7E445F0B372BC29E370BC227D443C70AE9DBCF73FEE8ACEDBD317A286A53266562D817269C004FB0F149DD925D2C590A960936763E519C2B62E14C7759F96672CD852194325904197B0B19C6B528AB33566946AF39B".ToLower()); + } + + [TestMethod] + public void TestBls12381Mul() + { + var data = new byte[32]; + data[0] = 0x03; + var snapshot = TestBlockchain.GetTestSnapshot(); + using (ScriptBuilder script = new()) + { + script.EmitPush(false); + script.EmitPush(data); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt); + script.EmitPush(3); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("bls12381Mul"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetInterface().ToArray().ToHexString().Should().Be("18B2DB6B3286BAEA116CCAD8F5554D170A69B329A6DE5B24C50B8834965242001A1C58089FD872B211ACD3263897FA660B117248D69D8AC745283A3E6A4CCEC607F6CF7CEDEE919575D4B7C8AE14C36001F76BE5FCA50ADC296EF8DF4926FA7F0B55A75F255FE61FC2DA7CFFE56ADC8775AAAB54C50D0C4952AD919D90FB0EB221C41ABB9F2352A11BE2D7F176ABE41E0E30AFB34FC2CE16136DE66900D92068F30011E9882C0A56E7E7B30F08442BE9E58D093E1888151136259D059FB539210D635BC491D5244A16CA28FDCF10546EC0F7104D3A419DDC081BA30ECB0CD2289010C2D385946229B7A9735ADC82736914FE61AD26C6C38B787775DE3B939105DE055F8D7004358272A0823F6F1787A7ABB6C3C59C8C9CBD1674AC900512632818CDD273F0D38833C07467EAF77743B70C924D43975D3821D47110A358757F926FCF970660FBDD74EF15D93B81E3AA290C78F59CBC6ED0C1E0DCBADFD11A73EB7137850D29EFEB6FA321330D0CF70F5C7F6B004BCF86AC99125F8FECF83157930BEC2AF89F8B378C6D7F63B0A07B3651F5207A84F62CEE929D574DA154EBE795D519B661086F069C9F061BA3B53DC4910EA1614C87B114E2F9EF328AC94E93D00440B412D5AE5A3C396D52D26C0CDF2156EBD3D3F60EA500C42120A7CE1F7EF80F15323118956B17C09E80E96ED4E1572461D604CDE2533330C684F86680406B1D3EE830CBAFE6D29C9A0A2F41E03E26095B713EB7E782144DB1EC6B53047FCB606B7B665B3DD1F52E95FCF2AE59C4AB159C3F98468C0A43C36C022B548189B6".ToLower()); + } + using (ScriptBuilder script = new()) + { + script.EmitPush(true); + script.EmitPush(data); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt); + script.EmitPush(3); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("bls12381Mul"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetInterface().ToArray().ToHexString().Should().Be("014E367F06F92BB039AEDCDD4DF65FC05A0D985B4CA6B79AA2254A6C605EB424048FA7F6117B8D4DA8522CD9C767B0450EEF9FA162E25BD305F36D77D8FEDE115C807C0805968129F15C1AD8489C32C41CB49418B4AEF52390900720B6D8B02C0EAB6A8B1420007A88412AB65DE0D04FEECCA0302E7806761483410365B5E771FCE7E5431230AD5E9E1C280E8953C68D0BD06236E9BD188437ADC14D42728C6E7177399B6B5908687F491F91EE6CCA3A391EF6C098CBEAEE83D962FA604A718A0C9DB625A7AAC25034517EB8743B5868A3803B37B94374E35F152F922BA423FB8E9B3D2B2BBF9DD602558CA5237D37420502B03D12B9230ED2A431D807B81BD18671EBF78380DD3CF490506187996E7C72F53C3914C76342A38A536FFAED478318CDD273F0D38833C07467EAF77743B70C924D43975D3821D47110A358757F926FCF970660FBDD74EF15D93B81E3AA290C78F59CBC6ED0C1E0DCBADFD11A73EB7137850D29EFEB6FA321330D0CF70F5C7F6B004BCF86AC99125F8FECF83157930BEC2AF89F8B378C6D7F63B0A07B3651F5207A84F62CEE929D574DA154EBE795D519B661086F069C9F061BA3B53DC4910EA1614C87B114E2F9EF328AC94E93D00440B412D5AE5A3C396D52D26C0CDF2156EBD3D3F60EA500C42120A7CE1F7EF80F15323118956B17C09E80E96ED4E1572461D604CDE2533330C684F86680406B1D3EE830CBAFE6D29C9A0A2F41E03E26095B713EB7E782144DB1EC6B53047FCB606B7B665B3DD1F52E95FCF2AE59C4AB159C3F98468C0A43C36C022B548189B6".ToLower()); + } + } + + [TestMethod] + public void TestBls12381Pairing() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g2); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); + script.EmitPush(2); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("bls12381Pairing"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetInterface().ToArray().ToHexString().Should().Be("0F41E58663BF08CF068672CBD01A7EC73BACA4D72CA93544DEFF686BFD6DF543D48EAA24AFE47E1EFDE449383B67663104C581234D086A9902249B64728FFD21A189E87935A954051C7CDBA7B3872629A4FAFC05066245CB9108F0242D0FE3EF03350F55A7AEFCD3C31B4FCB6CE5771CC6A0E9786AB5973320C806AD360829107BA810C5A09FFDD9BE2291A0C25A99A211B8B424CD48BF38FCEF68083B0B0EC5C81A93B330EE1A677D0D15FF7B984E8978EF48881E32FAC91B93B47333E2BA5706FBA23EB7C5AF0D9F80940CA771B6FFD5857BAAF222EB95A7D2809D61BFE02E1BFD1B68FF02F0B8102AE1C2D5D5AB1A19F26337D205FB469CD6BD15C3D5A04DC88784FBB3D0B2DBDEA54D43B2B73F2CBB12D58386A8703E0F948226E47EE89D018107154F25A764BD3C79937A45B84546DA634B8F6BE14A8061E55CCEBA478B23F7DACAA35C8CA78BEAE9624045B4B601B2F522473D171391125BA84DC4007CFBF2F8DA752F7C74185203FCCA589AC719C34DFFBBAAD8431DAD1C1FB597AAA5193502B86EDB8857C273FA075A50512937E0794E1E65A7617C90D8BD66065B1FFFE51D7A579973B1315021EC3C19934F1368BB445C7C2D209703F239689CE34C0378A68E72A6B3B216DA0E22A5031B54DDFF57309396B38C881C4C849EC23E87089A1C5B46E5110B86750EC6A532348868A84045483C92B7AF5AF689452EAFABF1A8943E50439F1D59882A98EAA0170F1250EBD871FC0A92A7B2D83168D0D727272D441BEFA15C503DD8E90CE98DB3E7B6D194F60839C508A84305AACA1789B6".ToLower()); + } + + [TestMethod] + public void Bls12381Equal() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using ScriptBuilder script = new(); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); + script.EmitPush(2); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("bls12381Equal"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + result.GetBoolean().Should().BeTrue(); + } + + private enum BLS12381PointType : byte + { + G1Proj, + G2Proj, + GT + } + + private void CheckBls12381ScalarMul_Compat(string point, string mul, bool negative, string expected, BLS12381PointType expectedType) + { + var data = new byte[32]; + data[0] = 0x03; + var snapshot = TestBlockchain.GetTestSnapshot(); + using (ScriptBuilder script = new()) + { + script.EmitPush(negative); + script.EmitPush(mul.ToLower().HexToBytes()); + script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", point.ToLower().HexToBytes()); + script.EmitPush(3); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("bls12381Mul"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + var result = engine.ResultStack.Pop(); + switch (expectedType) + { + case BLS12381PointType.G1Proj: + { + new G1Affine(result.GetInterface()).ToCompressed().ToHexString().Should().Be(expected); + break; + } + case BLS12381PointType.G2Proj: + { + new G2Affine(result.GetInterface()).ToCompressed().ToHexString().Should().Be(expected); + break; + } + case BLS12381PointType.GT: + { + result.GetInterface().ToArray().ToHexString().Should().Be(expected); + break; + } + default: + Assert.Fail("Unknown result point type."); + break; + } + } + } + + [TestMethod] + public void TestBls12381ScalarMul_Compat() + { + // GT mul by positive scalar. + CheckBls12381ScalarMul_Compat( + "14fd52fe9bfd08bbe23fcdf1d3bc5390c62e75a8786a72f8a343123a30a7c5f8d18508a21a2bf902f4db2c068913bc1c130e7ce13260d601c89ee717acfd3d4e1d80f409dd2a5c38b176f0b64d3d0a224c502717270dfecf2b825ac24608215c0d7fcfdf3c1552ada42b7e0521bc2e7389436660c352ecbf2eedf30b77b6b501df302399e6240473af47abe56fc974780c214542fcc0cf10e3001fa5e82d398f6ba1ddd1ccdf133bfd75e033eae50aec66bd5e884b8c74d4c1c6ac7c01278ac5164a54600cb2e24fec168f82542fbf98234dbb9ddf06503dc3c497da88b73db584ba19e685b1b398b51f40160e6c8f0917b4a68dedcc04674e5f5739cf0d845ba801263f712ed4ddda59c1d9909148e3f28124ae770682c9b19233bf0bcfa00d05bfe708d381b066b83a883ba8251ce2ea6772cbde51e1322d82b2c8a026a2153f4822e20cb69b8b05003ee74e09cb481728d688caa8a671f90b55488e272f48c7c5ae32526d3635a5343eb02640358d9ac445c76a5d8f52f653bbaee04ba5ce03c68b88c25be6fd3611cc21c9968e4f87e541beeccc5170b8696a439bb666ad8a6608ab30ebc7dfe56eaf0dd9ab8439171a6e4e0d608e6e6c8ac5ddcf8d6d2a950d06051e6b6c4d3feb6dc8dac2acadd345cadfb890454a2101a112f7471f0e001701f60f3d4352c4d388c0f198854908c0e939719709c1b3f82d2a25cc7156a3838bc141e041c259849326fbd0839f15cea6a78b89349dcd1c03695a74e72d3657af4ee2cf267337bc96363ef4a1c5d5d7a673cc3a3c1a1350043f99537d62", + "8463159bd9a1d1e1fd815172177ec24c0c291353ed88b3d1838fd9d63b1efd0b", + false, + "03dc980ce0c037634816f9fc1edb2e1807e38a51f838e3a684f195d6c52c41d6a8a5b64d57d3fda507bebe3bd4b661af0e4f7c46754b373c955982b4d64a24838cbc010d04b6ceb499bf411d114dab77eaf70f96ab66c2868dcd63706b602b07010c487fc16c90b61e1c2ad33c31c8f3fc86d114a59b127ac584640f149f3597102c55dd1ed8a305a10c052c0a724e570fc079e410123735a6144ccd88d9e4e91d7b889f80b18a1741eacd6f244fce3cf57795e619b6648b9238053b4b8e4ed6115c905fbcb61525370667ff43144e12b700662a7344ac1af97f11d09779ca6865973f95ff318b42ff00df7c6eb958160947a0ab6cb25534af51ce1f0b076907c6eb5ce0760bd7670cab8814cc3308766eb6e52b5427dbf85d6424990fd3354515ab880358bc55075a08f36b855694c02ee0bd63adefe235ba4ee41dc600a1cae950c1dc760bf7b1edd8712e9e90eebb19de705e29f4feb870129441bd4b9e91c3d37e60c12fa79a5b1e4132ba9498044e6fbf2de37e4dd88b4e9095b46f122019e73a561ba3967b32813c3ec74b8e1b6ab619eeab698e6638114cb29ca9c3d353192db3d392fee2b4dfdfd36b13db440534dd754417cffcd470f4d4cfdcb6d7896181c27b8b30622d7a4ca0a05a7ea67ca011cab07738235b115bbd330239691487d2de5d679a8cad2fe5c7fff16b0b0f3f929619c8005289c3d7ffe5bcd5ea19651bfc9366682a2790cab45ee9a98815bb7e58dc666e2209cd9d700546cf181ceb43fe719243930984b696b0d18d4cd1f5d960e149a2b753b1396e4f8f3b16", + BLS12381PointType.GT + ); + // GT mul by positive scalar. + CheckBls12381ScalarMul_Compat( + "0e0c651ff4a57adebab1fa41aa8d1e53d1cf6a6cc554282a24bb460ea0dc169d3ede8b5a93a331698f3926d273a729aa18788543413f43ada55a6a7505e3514f0db7e14d58311c3211962a350bcf908b3af90fbae31ff536fe542328ad25cd3e044a796200c8a8ead7edbc3a8a37209c5d37433ca7d8b0e644d7aac9726b524c41fef1cf0d546c252d795dffc445ddee07041f57c4c9a673bd314294e280ab61390731c09ad904bdd7b8c087d0ce857ea86e78f2d98e75d9b5e377e5751d67cf1717cbce31bc7ea6df95132549bf6d284a68005c53228127671afa54ecfd4c5c4debc437c4c6d9b9aeeee8b4159a5691128c6dc68b309fd822b14f3ce8ff390bd6834d30147e8ab2edc59d0d7b14cc13c79e6eed5fd6cae1795ba3760345d59c0c585f79c900902515e3e95938d9929ad8310e71fc7fd54be9c7529f244af40dadaca0b3bd8afd911f24b261079de48b161dd8f340d42bd84e717275193a0375d9e10fbe048bbea30abd64d3fe085c15b9be192f7baaa0b3a9658bcbb4292a0c0149beb30e54b065a75df45e5da77583f4471e3454cea90a00b5a9a224c15e2ebe01f0ab8aa86591c1012c618d41fdce07ecfcaddc8dc408b7176b79d8711a4161a56f41a5be6714cbcaa70e53387ab049826ac9e636640bc6da919e52f86f3209572b62d9bfd48bd2b5ef217932237b90a70d40167623d0f25a73b753e3214310bc5b6e017aebc1a9ca0c8067a97da6162c70cc754f1b2ac3b05ba834712758c8de4641ef09237edf588989182ab3047ee42da2b840fd3633fa0f34d46ad961", + "06c93a0ebbc8b5cd3af798b8f72442a67aa885b395452a08e48ec80b4e9f1b3f", + false, + "0d6d91f120ab61e14a3163601ce584f053f1de9dc0a548b6fbf37a776ec7b6ce6b866e8c8b0fc0ac8d32a9a9747c98bf0e6aee5bddd058313958bfc3ac1ed75284628f92bb9b99fee101e1bee9d74bad7812287ea76bdbe07f20ff9998d6e9f016689be1cfc4337433644a679945d5c34a6d4dd984c56d6c28428438268b385cb1d86f69b0377b18f9b084e1d0b6596213233d559a1b5caaba38be853f667fc3b1f9f2c4c9020584502ff5f370b0aba7768a1a4ca4328bc3c7be2bc9c3949f5e16fd3bfc16b11da41b7393e56e777640b000db15b6e6192e5c59dfece90c6fc0b6071fdeef7061974b5e967c5b88b1db09f7c92077c16f56aff9e9627f5e09928e965daee17d05ef3fdc0c502b649db473b5b2bba867d829b04d32cfeab7387614190b265382378f75e4e085a5537d4f200fe56b74b7c52c5546b30d51862e1ac1f60eba157880090a42ea9b0295529f134c1fc90f19a4c20dc0be105b07e0c67218b2f5619a66d8d770d539658eb74c255743e5847bc437fef3077d0a6c4f17198d63cf17e6957f2ad9449269af009635697e92254a3f67be9b8760fd9f974826a1829fedb4cf66968b7c63b0c88c510da12e6d52255256757afa03ad29b5c1624292ef7eb463eb4bc81ac7426f36db3fe1513bdd31bc138bfe903bbb0c5207001335f708c16cea15ef6b77c3215326a779e927b8c2081b15adffe71ba75164e376665533c5bb59373b27dbe93a0a0e1796d821a1b9ff01846446c5ad53064cb9b941f97aa870285395e1a44c9f6e5144ea5a0cf57b9fdd962a5ec3ff1f72fe", + BLS12381PointType.GT + ); + // GT mul by positive scalar. + CheckBls12381ScalarMul_Compat( + "0e0c651ff4a57adebab1fa41aa8d1e53d1cf6a6cc554282a24bb460ea0dc169d3ede8b5a93a331698f3926d273a729aa18788543413f43ada55a6a7505e3514f0db7e14d58311c3211962a350bcf908b3af90fbae31ff536fe542328ad25cd3e044a796200c8a8ead7edbc3a8a37209c5d37433ca7d8b0e644d7aac9726b524c41fef1cf0d546c252d795dffc445ddee07041f57c4c9a673bd314294e280ab61390731c09ad904bdd7b8c087d0ce857ea86e78f2d98e75d9b5e377e5751d67cf1717cbce31bc7ea6df95132549bf6d284a68005c53228127671afa54ecfd4c5c4debc437c4c6d9b9aeeee8b4159a5691128c6dc68b309fd822b14f3ce8ff390bd6834d30147e8ab2edc59d0d7b14cc13c79e6eed5fd6cae1795ba3760345d59c0c585f79c900902515e3e95938d9929ad8310e71fc7fd54be9c7529f244af40dadaca0b3bd8afd911f24b261079de48b161dd8f340d42bd84e717275193a0375d9e10fbe048bbea30abd64d3fe085c15b9be192f7baaa0b3a9658bcbb4292a0c0149beb30e54b065a75df45e5da77583f4471e3454cea90a00b5a9a224c15e2ebe01f0ab8aa86591c1012c618d41fdce07ecfcaddc8dc408b7176b79d8711a4161a56f41a5be6714cbcaa70e53387ab049826ac9e636640bc6da919e52f86f3209572b62d9bfd48bd2b5ef217932237b90a70d40167623d0f25a73b753e3214310bc5b6e017aebc1a9ca0c8067a97da6162c70cc754f1b2ac3b05ba834712758c8de4641ef09237edf588989182ab3047ee42da2b840fd3633fa0f34d46ad961", + "b0010000000000005e0000000000000071f30400000000006d9189c813000000", + false, + "0919ad29cdbe0b6bbd636fbe3c8930a1b959e5aa37294a6cc7d018e2776580768bb98bf91ce1bc97f2e6fa647e7dad7b15db564645d2e4868129ed414b7e369e831b8ff93997a22b6ca0e2ba288783f535aed4b44cf3e952897db1536da18a120a70da2b9dd901bd12a5a7047d3b6346ba1aea53b642b7355a91f957687fccd840ef24af100d0ada6b49e35183456ec30b505098526b975477b6ca0273d3a841c85e4a8319b950e76ec217a4f939844baa6b875a4046a30c618636fe9b25c620030f31044f883789945c2bcb75d7d4099b2bc97665e75c1bee27bc3864e7e5e2ccb57a9da0b57be1a6aca217a6cfda090c4fd222f7b8cfdc32969da4fe8828a59ee1314546efdf99ef7ede1a42df6e7a126fe83b4c41b5e70a56bd9ab499f7e80e27a08884be05f1d2a527417fc6e30448333c0724463bf92d722ef5fd6f06949e294e6f941976d24c856038b55a2ec200d14d958a688f23b572993bd0f18cbbc20defe88e423b262c552dcc4d9f63ad78e85efbcea9449f81f39e1a887eb79b07056bb5a672444e240660617ba7a40985a622c687c1d05c12cee7b086abfc5f39a83a5ad7638ee559f710013b772d4207924687cb30100bcd4e8c83c9fa19dce7785bf3ae7681a0968fd9661c990e2dace05902dceeed65aacf51a04e72f0fd04858ea70fb72f2a3807dc1839a385d85b536abfd3ec76d4931b3bc5ec4d90e2ebc0342567c9507abdfafa602fc6983f13f20eb26b4169dc3908109fe3c1887db4be8f30edad989dc8caa234f9818ac488b110ad30a30f769277168650b6910e", + BLS12381PointType.GT + ); + // GT mul by negative scalar. + CheckBls12381ScalarMul_Compat( + "0bdbfc3b68e7067630a1908de2ce15e1890d57b855ffc2ee0fe765293581c304d0507254fd9921d8ff4bff3185b1e8ae017091a6b9e243c3108b4302f30e2f4cb452c4574d23d06942cf915fb0b64c3546aa0bfbba5182dc42b63ebd09cd950f06ebf85ff360032e63d5422fed5969b80ed4abaf58d29317d9cf8e5a55744993ffc0ccc586a187c63f9c47d4b41870aa0fd73e13a4f7d3b072407a3bfa6539f8d56856542b17326ab77833df274e61a41c237a6dbf20a333698a675fded6ab1a114891795eabbedcb81590ff9bfb4b23b66c8b8376a69cf58511c80f3ac83d52c0c950be8c30d01108479f232d8e4e8919d869dc85db0b9d6ccf40eb8f8ab08e43a910c341737a55e751fa4a097ee82c5ac83d38c543d957bd9850af16039d1a00c96575d2ee24e9990b3401153446aa6593d3afb6ce7ca57d6432b8dda31aaa1a08834ad38deae5a807d11663adc5c20ae7227a2cbb7917d1489175b89ed1ba415e4fc55b7d0a286caf2f5f40b0dd39cdd8fc8c271d8a7ae952fe6ece5f7c1019bfab0167af86314a73bfa37fd16bc6edff6d9ee75610a4eec1818c668ef9f509b1cdd54542e73dc0e343a4fd6e3bb618540c1d060b60b63b645a895105425eb813b08b6ac91be3145da04040f2a45ffcf06e96b685519fca93b0f15238dc0e030c2199127ba82fa8a193f5f01ae24270e9669923653db38cae711d68169aa25df51a8915f3f8219892f4f5e67d550b00910011685017dcc1777a9d48689ce590d57c1fc942d49cfad0ed7efc0169a95d7e7378af26bafb90d1619bcdab64cd", + "688e58217305c1fd2fe0637cbd8e7414d4d0a2113314eb05592f97930d23b34d", + true, + "056fdc84f044148950c0b7c4c0613f5710fcaeb1b023b9d8f814dc39d48702db70ce41aa276566960e37237f22b086b017b9ed0e264e2b7872c8a7affb8b9f847a528d092a038dab4ac58d3a33d30e2e5078b5e39ebb7441c56ae7556b63ecd6139ed9be1c5eb9f987cc704c913c1e23d44d2e04377347f6c471edc40cdb2cd4e32c396194363cd21ceff9bedbd164a41050e701012f0456383210f8054e76c0906e3f37e10d4a3d6342e79e39d566ea785b385bb692cddbd6c16456dfabf19f0f84c27ec4bce096af0369ac070747cd89d97bc287afe5ed5e495ed2d743adbd8eec47df6c3a69628e803e23d824845800e44a8d874756a7541128892e55e9df1d1fe0583ef967db6740617a9ff50766866c0fa631aed8639cd0c13d3d6f6f210b340ee315caec4cc31c916d651db5e002e259fca081fb605258ccf692d786bd5bb45a054c4d8498ac2a7fa241870df60ba0fd8a2b063740af11e7530db1e758a8e2858a443104b8337e18c083035768a0e93126f116bb9c50c8cebe30e0ceaa0c0b53eb2b6a1f96b34b6cc36f3417edda184e19ae1790d255337f14315323e1d2d7382b344bdc0b6b2cfab5837c24c916640ca351539d5459389a9c7f9b0d79e04e4a8392e0c2495dcecf7d48b10c7043825b7c6709108d81856ebf98385f0d099e6521714c48b8eb5d2e97665375175f47c57d427d35a9dc44064a99d1c079028e36d34540baba947333ab3c8976b801ea48578159f041e740ea5bf73c1de3c1043a6e03311d0f2463b72694249ccc5d603e4a93cfd8a6713fb0470383c23f", + BLS12381PointType.GT + ); + // GT mul by zero scalar. + CheckBls12381ScalarMul_Compat( + "176ec726aa447f1791e69fc70a71103c84b17385094ef06a9a0235ac7241f6635377f55ad486c216c8701d61ea2ace3e05ca1605f238dc8f29f868b795e45645c6f7ff8d9d8ffd77b5e149b0325c2a8f24dde40e80a3381ae72a9a1104ef02d70af7cf8f2fe6ff38961b352b0fde6f8536424fc9aa5805b8e12313bdfc01d5c1db1c0a37654c307fbd252c265dcbfc040ee5605ffd6ac20aab15b0343e47831f4157a20ecedd7350d2cf070c0c7d423786fd97aa7236b99f4462fb23e173528815bf2cf3ccbfc38303fa8154d70ee5e1e3158cbb14d5c87a773cbe948a5cfec2763c5e7129940906920aed344453b0f801760fd3eac8e254ce8e0ae4edd30c914bea9e2935acd4a6a9d42d185a9a6e786c8e462b769b2112423f6591b093347718897438ba918b9e4525888194b20ee17709f7dea319cfd053bb1c222783340326953fd3763eb6feaaa4d1458ee6ca001818ad88222a97e43a71dca8d2abaef70657b9ff7b94ca422d0c50ddb4265fa35514ed534217ce2f0219c6985ec2827a0ee1dc17940926551072d693d89e36e6d14162f414b52587e5612ed4a562c9ac15df9d5fa68ccf61d52fea64b2f5d7a600e0a8fa735105bc9a2ecb69b6d9161e55a4ccdc2285164c6846fa5bdc106d1e0693ebd5fe86432e5e88c55f0159ec3217332c8492332dfbd93970f002a6a05f23484e081f38815785e766779c843765d58b2444295a87939ad7f8fa4c11e8530a62426063c9a57cf3481a00372e443dc014fd6ef4723dd4636105d7ce7b96c4b2b3b641c3a2b6e0fa9be6187e5bfaf9", + "0000000000000000000000000000000000000000000000000000000000000000", + false, + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + BLS12381PointType.GT + ); + // G1Affine mul by positive scalar. + CheckBls12381ScalarMul_Compat( + "a1f9855f7670a63e4c80d64dfe6ddedc2ed2bfaebae27e4da82d71ba474987a39808e8921d3df97df6e5d4b979234de8", + "8463159bd9a1d1e1fd815172177ec24c0c291353ed88b3d1838fd9d63b1efd0b", + false, + "ae85e3e2d677c9e3424ed79b5a7554262c3d6849202b84d2e7024e4b1f2e9dd3f7cf20b807a9f2a67d87e47e9e94d361", + BLS12381PointType.G1Proj + ); + // G1Affine mul by negative scalar. + CheckBls12381ScalarMul_Compat( + "a1f9855f7670a63e4c80d64dfe6ddedc2ed2bfaebae27e4da82d71ba474987a39808e8921d3df97df6e5d4b979234de8", + "8463159bd9a1d1e1fd815172177ec24c0c291353ed88b3d1838fd9d63b1efd0b", + true, + "8e85e3e2d677c9e3424ed79b5a7554262c3d6849202b84d2e7024e4b1f2e9dd3f7cf20b807a9f2a67d87e47e9e94d361", + BLS12381PointType.G1Proj + ); + // G1Affine mul by zero scalar. + CheckBls12381ScalarMul_Compat( + "a1f9855f7670a63e4c80d64dfe6ddedc2ed2bfaebae27e4da82d71ba474987a39808e8921d3df97df6e5d4b979234de8", + "0000000000000000000000000000000000000000000000000000000000000000", + false, + "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + BLS12381PointType.G1Proj + ); + // G2Affine mul by positive scalar. + CheckBls12381ScalarMul_Compat( + "a41e586fdd58d39616fea921a855e65417a5732809afc35e28466e3acaeed3d53dd4b97ca398b2f29bf6bbcaca026a6609a42bdeaaeef42813ae225e35c23c61c293e6ecb6759048fb76ac648ba3bc49f0fcf62f73fca38cdc5e7fa5bf511365", + "cbfffe3e37e53e31306addde1a1725641fbe88cd047ee7477966c44a3f764b47", + false, + "88ae9bba988e854877c66dfb7ff84aa5e107861aa51d1a2a8dac2414d716a7e219bc4b0239e4b12d2182f57b5eea82830639f2e6713098ae8d4b4c3942f366614bac35c91c83ecb57fa90fe03094aca1ecd3555a7a6fdfa2417b5bb06917732e", + BLS12381PointType.G2Proj + ); + // G2Affine mul by negative scalar. + CheckBls12381ScalarMul_Compat( + "a41e586fdd58d39616fea921a855e65417a5732809afc35e28466e3acaeed3d53dd4b97ca398b2f29bf6bbcaca026a6609a42bdeaaeef42813ae225e35c23c61c293e6ecb6759048fb76ac648ba3bc49f0fcf62f73fca38cdc5e7fa5bf511365", + "cbfffe3e37e53e31306addde1a1725641fbe88cd047ee7477966c44a3f764b47", + true, + "a8ae9bba988e854877c66dfb7ff84aa5e107861aa51d1a2a8dac2414d716a7e219bc4b0239e4b12d2182f57b5eea82830639f2e6713098ae8d4b4c3942f366614bac35c91c83ecb57fa90fe03094aca1ecd3555a7a6fdfa2417b5bb06917732e", + BLS12381PointType.G2Proj + ); + // G2Affine mul by negative scalar. + CheckBls12381ScalarMul_Compat( + "a41e586fdd58d39616fea921a855e65417a5732809afc35e28466e3acaeed3d53dd4b97ca398b2f29bf6bbcaca026a6609a42bdeaaeef42813ae225e35c23c61c293e6ecb6759048fb76ac648ba3bc49f0fcf62f73fca38cdc5e7fa5bf511365", + "0000000000000000000000000000000000000000000000000000000000000000", + false, + "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + BLS12381PointType.G2Proj + ); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs index 8e110680ca..997c4ce335 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -1,3 +1,7 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -6,10 +10,6 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; -using System; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; namespace Neo.UnitTests.SmartContract.Native { @@ -42,10 +42,12 @@ public async Task Check_BalanceOfTransferAndBurn() var persistingBlock = new Block { Header = new Header { Index = 1000 } }; byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; - var keyCount = snapshot.GetChangeSet().Count(); var supply = NativeContract.GAS.TotalSupply(snapshot); supply.Should().Be(5200000050000000); // 3000000000000000 + 50000000 (neo holder reward) + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + var keyCount = snapshot.GetChangeSet().Count(); // Check unclaim var unclaim = UT_NeoToken.Check_UnclaimedGas(snapshot, from, persistingBlock); @@ -111,10 +113,9 @@ await Assert.ThrowsExceptionAsync(async () => engine.Snapshot.GetChangeSet().Count().Should().Be(2); // Burn all - await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(5200049999999999)); - (keyCount - 1).Should().Be(engine.Snapshot.GetChangeSet().Count()); + (keyCount - 2).Should().Be(engine.Snapshot.GetChangeSet().Count()); // Bad inputs diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs index 76f68b5674..ee1ee4753e 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; +using System.Numerics; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -9,9 +12,6 @@ using Neo.UnitTests.Extensions; using Neo.VM; using Neo.Wallets; -using System; -using System.Linq; -using System.Numerics; using static Neo.SmartContract.Native.NeoToken; namespace Neo.UnitTests.SmartContract.Native @@ -48,6 +48,9 @@ public void Check_Vote() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); // No signature @@ -103,6 +106,9 @@ public void Check_Vote_Sameaccounts() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.Balance = 100; @@ -132,6 +138,8 @@ public void Check_Vote_ChangeVote() { var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); //from vote to G byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); @@ -165,7 +173,8 @@ public void Check_Vote_VoteToNull() { var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); @@ -199,6 +208,9 @@ public void Check_UnclaimedGas() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); @@ -242,7 +254,7 @@ public void Check_RegisterValidator() public void Check_UnregisterCandidate() { var snapshot = _snapshot.CreateSnapshot(); - + _persistingBlock.Header.Index = 1; var keyCount = snapshot.GetChangeSet().Count(); var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); @@ -306,7 +318,7 @@ public void Check_GetCommittee() var keyCount = snapshot.GetChangeSet().Count(); var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); var persistingBlock = _persistingBlock; - + persistingBlock.Header.Index = 1; //register with votes with 20000000 var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); @@ -368,6 +380,8 @@ public void Check_Transfer() byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); var keyCount = snapshot.GetChangeSet().Count(); // Check unclaim @@ -508,6 +522,11 @@ public void TestCalculateBonus() { Balance = 100 })); + + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + var item = snapshot.GetAndChange(storageKey).GetInteroperable(); + item.Index = 99; + NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); snapshot.Delete(key); @@ -591,7 +610,7 @@ public void TestCheckCandidate() var point = committee[0].EncodePoint(true); // Prepare Prefix_VoterRewardPerCommittee - var storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]).AddBigEndian(20); + var storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]); snapshot.Add(storageKey, new StorageItem(new BigInteger(1000))); // Prepare Candidate @@ -728,7 +747,7 @@ public void TestEconomicParameter() NeoAccountState state = storage.GetInteroperable(); state.Balance = 1000; state.BalanceHeight = 0; - height.Index = 0; // Fake Height=0 + height.Index = persistingBlock.Index + 1; NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, persistingBlock.Index + 2).Should().Be(6500); } @@ -776,7 +795,7 @@ public void TestClaimGas() var accountB = committee[ProtocolSettings.Default.CommitteeMembersCount - 1]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); - StorageItem storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA).AddBigEndian(1)); + StorageItem storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA)); ((BigInteger)storageItem).Should().Be(30000000000); snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountB).AddBigEndian(uint.MaxValue - 1)).Should().BeNull(); @@ -799,7 +818,7 @@ public void TestClaimGas() NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash).Should().Be(0); - storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[1]).AddBigEndian(1)); + storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[1])); ((BigInteger)storageItem).Should().Be(30000000000); // Next block @@ -821,7 +840,7 @@ public void TestClaimGas() accountA = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray()[2]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); - storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2]).AddBigEndian(22)); + storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2])); ((BigInteger)storageItem).Should().Be(30000000000 * 2); // Claim GAS @@ -831,9 +850,12 @@ public void TestClaimGas() { BalanceHeight = 3, Balance = 200 * 10000 - 2 * 100, - VoteTo = committee[2] + VoteTo = committee[2], + LastGasPerVote = 30000000000, })); NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(1999800); + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.GetAndChange(storageKey).GetInteroperable().Index = 29 + 2; BigInteger value = NativeContract.NEO.UnclaimedGas(snapshot, account, 29 + 3); value.Should().Be(1999800 * 30000000000 / 100000000L + (1999800L * 10 * 5 * 29 / 100)); } @@ -854,6 +876,7 @@ public void TestVote() UInt160 account = UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"); StorageKey keyAccount = CreateStorageKey(20, account.ToArray()); StorageKey keyValidator = CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()); + _persistingBlock.Header.Index = 1; var ret = Check_Vote(snapshot, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), false, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); @@ -888,7 +911,8 @@ public void TestVote() internal (bool State, bool Result) Transfer4TesingOnBalanceChanging(BigInteger amount, bool addVotes) { var snapshot = _snapshot.CreateSnapshot(); - var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings); + _persistingBlock.Header.Index = 1; + var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, _persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); ScriptBuilder sb = new(); var tmp = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); UInt160 from = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot)[0]; @@ -911,7 +935,8 @@ public void TestVote() sb.EmitDynamicCall(NativeContract.NEO.Hash, "transfer", from, UInt160.Zero, amount, null); engine.LoadScript(sb.ToArray()); - engine.Execute(); + var state = engine.Execute(); + Console.WriteLine($"{state} {engine.FaultException}"); var result = engine.ResultStack.Peek(); result.GetType().Should().Be(typeof(VM.Types.Boolean)); return (true, result.GetBoolean()); @@ -984,6 +1009,7 @@ internal static (bool State, bool Result) Check_Vote(DataCache snapshot, byte[] if (engine.Execute() == VMState.FAULT) { + Console.WriteLine(engine.FaultException); return (false, false); } @@ -1039,6 +1065,7 @@ internal static (BigInteger Value, bool State) Check_UnclaimedGas(DataCache snap if (engine.Execute() == VMState.FAULT) { + Console.WriteLine(engine.FaultException); return (BigInteger.Zero, false); } diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs index 61bef60e2c..7d4faee5b5 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs @@ -2,8 +2,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; using Neo.SmartContract; +using Neo.SmartContract.Manifest; using System; using System.Numerics; +using System.Text; namespace Neo.UnitTests.SmartContract { @@ -24,21 +26,44 @@ public void TestNotSupportedNotification() { using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); engine.LoadScript(Array.Empty()); - engine.CurrentContext.GetState().Contract = new(); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() + { + Events = new[] + { + new ContractEventDescriptor + { + Name = "e1", + Parameters = new[] + { + new ContractParameterDefinition + { + Type = ContractParameterType.Array + } + } + } + } + } + } + }; // circular VM.Types.Array array = new(); array.Add(array); - Assert.ThrowsException(() => engine.RuntimeNotify(new byte[] { 0x01 }, array)); + Assert.ThrowsException(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array)); // Buffer array.Clear(); array.Add(new VM.Types.Buffer(1)); + engine.CurrentContext.GetState().Contract.Manifest.Abi.Events[0].Parameters[0].Type = ContractParameterType.ByteArray; - engine.RuntimeNotify(new byte[] { 0x01 }, array); + engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array); engine.Notifications[0].State[0].Type.Should().Be(VM.Types.StackItemType.ByteString); // Pointer @@ -46,14 +71,15 @@ public void TestNotSupportedNotification() array.Clear(); array.Add(new VM.Types.Pointer(Array.Empty(), 1)); - Assert.ThrowsException(() => engine.RuntimeNotify(new byte[] { 0x01 }, array)); + Assert.ThrowsException(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array)); // InteropInterface array.Clear(); array.Add(new VM.Types.InteropInterface(new object())); + engine.CurrentContext.GetState().Contract.Manifest.Abi.Events[0].Parameters[0].Type = ContractParameterType.InteropInterface; - Assert.ThrowsException(() => engine.RuntimeNotify(new byte[] { 0x01 }, array)); + Assert.ThrowsException(() => engine.RuntimeNotify(Encoding.ASCII.GetBytes("e1"), array)); } [TestMethod] diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index 5a47a87485..3687f0605e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -1,7 +1,10 @@ +using System; +using System.Collections.Immutable; +using System.Linq; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; -using Neo.VM.Types; +using Array = Neo.VM.Types.Array; namespace Neo.UnitTests.SmartContract { @@ -56,5 +59,38 @@ public void TestCreateDummyBlock() engine.PersistingBlock.PrevHash.Should().Be(TestBlockchain.TheNeoSystem.GenesisBlock.Hash); engine.PersistingBlock.MerkleRoot.Should().Be(new UInt256()); } + + [TestMethod] + public void TestCheckingHardfork() + { + var allHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToList(); + + var builder = ImmutableDictionary.CreateBuilder(); + builder.Add(Hardfork.HF_Aspidochelone, 0); + builder.Add(Hardfork.HF_Basilisk, 1); + + var setting = builder.ToImmutable(); + + // Check for continuity in configured hardforks + var sortedHardforks = setting.Keys + .OrderBy(h => allHardforks.IndexOf(h)) + .ToList(); + + for (int i = 0; i < sortedHardforks.Count - 1; i++) + { + int currentIndex = allHardforks.IndexOf(sortedHardforks[i]); + int nextIndex = allHardforks.IndexOf(sortedHardforks[i + 1]); + + // If they aren't consecutive, return false. + var inc = nextIndex - currentIndex; + inc.Should().Be(1); + } + + // Check that block numbers are not higher in earlier hardforks than in later ones + for (int i = 0; i < sortedHardforks.Count - 1; i++) + { + (setting[sortedHardforks[i]] > setting[sortedHardforks[i + 1]]).Should().Be(false); + } + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 0494ab22e0..081fb87661 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -45,7 +45,22 @@ public void Runtime_GetNotifications_Test() scriptHash2 = script.ToArray().ToScriptHash(); snapshot.DeleteContract(scriptHash2); - snapshot.AddContract(scriptHash2, TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer))); + ContractState contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer)); + contract.Manifest.Abi.Events = new[] + { + new ContractEventDescriptor + { + Name = "testEvent2", + Parameters = new[] + { + new ContractParameterDefinition + { + Type = ContractParameterType.Any + } + } + } + }; + snapshot.AddContract(scriptHash2, contract); } // Wrong length @@ -93,7 +108,23 @@ public void Runtime_GetNotifications_Test() // Execute engine.LoadScript(script.ToArray()); - engine.CurrentContext.GetState().Contract = new(); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() + { + Events = new[] + { + new ContractEventDescriptor + { + Name = "testEvent1", + Parameters = System.Array.Empty() + } + } + } + } + }; var currentScriptHash = engine.EntryScriptHash; Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -146,7 +177,23 @@ public void Runtime_GetNotifications_Test() // Execute engine.LoadScript(script.ToArray()); - engine.CurrentContext.GetState().Contract = new(); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() + { + Events = new[] + { + new ContractEventDescriptor + { + Name = "testEvent1", + Parameters = System.Array.Empty() + } + } + } + } + }; var currentScriptHash = engine.EntryScriptHash; Assert.AreEqual(VMState.HALT, engine.Execute()); diff --git a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs index 4b5e1618d8..bf608bc57e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs @@ -80,10 +80,10 @@ public void JsonTest_Numbers() Assert.AreEqual("[1,-2,3.5]", parsed.ToString()); - json = "[200.500000E+005,200.500000e+5,-1.1234e-100]"; + json = "[200.500000E+005,200.500000e+5,-1.1234e-100,9.05E+28]"; parsed = JObject.Parse(json); - Assert.AreEqual("[20050000,20050000,-1.1234E-100]", parsed.ToString()); + Assert.AreEqual("[20050000,20050000,-1.1234E-100,9.05E+28]", parsed.ToString()); json = "[-]"; Assert.ThrowsException(() => JObject.Parse(json)); @@ -182,7 +182,8 @@ public void JsonTest_Object() [TestMethod] public void Deserialize_WrongJson() { - Assert.ThrowsException(() => JsonSerializer.Deserialize(JObject.Parse("x"), ExecutionEngineLimits.Default)); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + Assert.ThrowsException(() => JsonSerializer.Deserialize(engine, JObject.Parse("x"), ExecutionEngineLimits.Default)); } [TestMethod] @@ -216,7 +217,8 @@ public void Serialize_Null() [TestMethod] public void Deserialize_EmptyObject() { - var items = JsonSerializer.Deserialize(JObject.Parse("{}"), ExecutionEngineLimits.Default); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + var items = JsonSerializer.Deserialize(engine, JObject.Parse("{}"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(Map)); Assert.AreEqual(((Map)items).Count, 0); @@ -234,7 +236,8 @@ public void Serialize_EmptyArray() [TestMethod] public void Deserialize_EmptyArray() { - var items = JsonSerializer.Deserialize(JObject.Parse("[]"), ExecutionEngineLimits.Default); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + var items = JsonSerializer.Deserialize(engine, JObject.Parse("[]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); Assert.AreEqual(((VM.Types.Array)items).Count, 0); @@ -258,7 +261,8 @@ public void Serialize_Map_Test() [TestMethod] public void Deserialize_Map_Test() { - var items = JsonSerializer.Deserialize(JObject.Parse("{\"test1\":123,\"test2\":321}"), ExecutionEngineLimits.Default); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + var items = JsonSerializer.Deserialize(engine, JObject.Parse("{\"test1\":123,\"test2\":321}"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(Map)); Assert.AreEqual(((Map)items).Count, 2); @@ -287,16 +291,18 @@ public void Serialize_Array_Bool_Str_Num() [TestMethod] public void Deserialize_Array_Bool_Str_Num() { - var items = JsonSerializer.Deserialize(JObject.Parse("[true,\"test\",123]"), ExecutionEngineLimits.Default); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + var items = JsonSerializer.Deserialize(engine, JObject.Parse("[true,\"test\",123,9.05E+28]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); - Assert.AreEqual(((VM.Types.Array)items).Count, 3); + Assert.AreEqual(((VM.Types.Array)items).Count, 4); var array = (VM.Types.Array)items; Assert.IsTrue(array[0].GetBoolean()); Assert.AreEqual(array[1].GetString(), "test"); Assert.AreEqual(array[2].GetInteger(), 123); + Assert.AreEqual(array[3].GetInteger(), BigInteger.Parse("90500000000000000000000000000")); } [TestMethod] @@ -316,7 +322,8 @@ public void Serialize_Array_OfArray() [TestMethod] public void Deserialize_Array_OfArray() { - var items = JsonSerializer.Deserialize(JObject.Parse("[[true,\"test1\",123],[true,\"test2\",321]]"), ExecutionEngineLimits.Default); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + var items = JsonSerializer.Deserialize(engine, JObject.Parse("[[true,\"test1\",123],[true,\"test2\",321]]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); Assert.AreEqual(((VM.Types.Array)items).Count, 2); diff --git a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs index 39c2018f25..d03a411b4d 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs @@ -6,9 +6,9 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_KeyBuilder { - private struct a + private struct TestKey { - public int x; + public int Value; } [TestMethod] @@ -28,11 +28,11 @@ public void Test() Assert.AreEqual("010000000203040000000000000000000000000000000000000000", key.ToArray().ToHexString()); key = new KeyBuilder(1, 2); - key = key.Add(new a() { x = 123 }); + key = key.Add(new TestKey { Value = 123 }); Assert.AreEqual("01000000027b000000", key.ToArray().ToHexString()); key = new KeyBuilder(1, 0); - key = key.AddBigEndian(new a() { x = 1 }); + key = key.AddBigEndian(new TestKey { Value = 1 }); Assert.AreEqual("010000000000000001", key.ToArray().ToHexString()); } } diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs index bb58b4c9c7..bd8e087aa7 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs @@ -70,7 +70,6 @@ public void TestCleanUp() [TestMethod] public void TestCreateAccount() { - var uut = new NEP6Wallet(wPath, "123", ProtocolSettings.Default); var acc = uut.CreateAccount("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632549".HexToBytes()); var tx = new Transaction() { @@ -79,7 +78,7 @@ public void TestCreateAccount() Signers = new Signer[] { new Signer() { Account = acc.ScriptHash } }, }; var ctx = new ContractParametersContext(TestBlockchain.GetTestSnapshot(), tx, ProtocolSettings.Default.Network); - var sig = uut.Sign(ctx); + Assert.IsTrue(uut.Sign(ctx)); tx.Witnesses = ctx.GetWitnesses(); Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, TestBlockchain.GetTestSnapshot(), long.MaxValue)); Assert.ThrowsException(() => uut.CreateAccount((byte[])null)); diff --git a/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs b/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs index e14bb83fd7..5e5996ff0a 100644 --- a/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs +++ b/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs @@ -98,8 +98,9 @@ public void TestGetHashCode() { byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - KeyPair keyPair = new KeyPair(privateKey); - keyPair.GetHashCode().Should().Be(1544360595); + KeyPair keyPair1 = new KeyPair(privateKey); + KeyPair keyPair2 = new KeyPair(privateKey); + keyPair1.GetHashCode().Should().Be(keyPair2.GetHashCode()); } [TestMethod]