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 @@
-
-
-
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