Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit SYSCALLs in Verification #856

Merged
merged 10 commits into from
Jul 5, 2019
1 change: 1 addition & 0 deletions neo.UnitTests/UT_MemoryPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private Transaction CreateTransactionWithFee(long fee)
var randomBytes = new byte[16];
random.NextBytes(randomBytes);
Mock<Transaction> mock = new Mock<Transaction>();
mock.Setup(p => p.Reverify(It.IsAny<Snapshot>(), It.IsAny<IEnumerable<Transaction>>())).Returns(true);
mock.Setup(p => p.Verify(It.IsAny<Snapshot>(), It.IsAny<IEnumerable<Transaction>>())).Returns(true);
mock.Object.Script = randomBytes;
mock.Object.Sender = UInt160.Zero;
Expand Down
23 changes: 19 additions & 4 deletions neo/Ledger/MemoryPool.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Akka.Actor;
using Akka.Util.Internal;
using Neo.Network.P2P;
using Neo.Network.P2P.Payloads;
Expand Down Expand Up @@ -61,6 +62,7 @@ public class MemoryPool : IReadOnlyCollection<Transaction>
internal int UnverifiedSortedTxCount => _unverifiedSortedTransactions.Count;

private int _maxTxPerBlock;
private long _feePerByte;

/// <summary>
/// Total maximum capacity of transactions the pool can hold.
Expand Down Expand Up @@ -99,9 +101,13 @@ public MemoryPool(NeoSystem system, int capacity)
Capacity = capacity;
}

internal void LoadPolicy(Snapshot snapshot)
internal bool LoadPolicy(Snapshot snapshot)
{
_maxTxPerBlock = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(snapshot);
long newFeePerByte = NativeContract.Policy.GetFeePerByte(snapshot);
bool policyChanged = newFeePerByte > _feePerByte;
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
_feePerByte = newFeePerByte;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
return policyChanged;
}

/// <summary>
Expand Down Expand Up @@ -337,6 +343,8 @@ internal void InvalidateVerifiedTransactions()
// Note: this must only be called from a single thread (the Blockchain actor)
internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot)
{
bool policyChanged = LoadPolicy(snapshot);

_txRwLock.EnterWriteLock();
try
{
Expand All @@ -349,6 +357,14 @@ internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot)

// Add all the previously verified transactions back to the unverified transactions
InvalidateVerifiedTransactions();

if (policyChanged)
{
foreach (PoolItem item in _unverifiedSortedTransactions.Reverse())
_system.Blockchain.Tell(item.Tx, ActorRefs.NoSender);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance that, in the time you retell, and then you clear already retelled tx? 🤔

_unverifiedTransactions.Clear();
_unverifiedSortedTransactions.Clear();
}
}
finally
{
Expand All @@ -357,10 +373,9 @@ internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot)

// If we know about headers of future blocks, no point in verifying transactions from the unverified tx pool
// until we get caught up.
if (block.Index > 0 && block.Index < Blockchain.Singleton.HeaderHeight)
if (block.Index > 0 && block.Index < Blockchain.Singleton.HeaderHeight || policyChanged)
return;

LoadPolicy(snapshot);
ReverifyTransactions(_sortedTransactions, _unverifiedSortedTransactions,
_maxTxPerBlock, MaxSecondsToReverifyTx, snapshot);
}
Expand Down Expand Up @@ -388,7 +403,7 @@ internal void InvalidateAllTransactions()
// Since unverifiedSortedTxPool is ordered in an ascending manner, we take from the end.
foreach (PoolItem item in unverifiedSortedTxPool.Reverse().Take(count))
{
if (item.Tx.Verify(snapshot, _unsortedTransactions.Select(p => p.Value.Tx)))
if (item.Tx.Reverify(snapshot, _unsortedTransactions.Select(p => p.Value.Tx)))
reverifiedItems.Add(item);
else // Transaction no longer valid -- it will be removed from unverifiedTxPool.
invalidItems.Add(item);
Expand Down
30 changes: 21 additions & 9 deletions neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,26 @@ public UInt160[] GetScriptHashesForVerifying(Snapshot snapshot)
return hashes.OrderBy(p => p).ToArray();
}

public virtual bool Reverify(Snapshot snapshot, IEnumerable<Transaction> mempool)
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
{
if (ValidUntilBlock <= snapshot.Height || ValidUntilBlock > snapshot.Height + MaxValidUntilBlockIncrement)
return false;
if (NativeContract.Policy.GetBlockedAccounts(snapshot).Intersect(GetScriptHashesForVerifying(snapshot)).Count() > 0)
return false;
BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, Sender);
BigInteger fee = SystemFee + NetworkFee;
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
if (balance < fee) return false;
fee += mempool.Where(p => p != this && p.Sender.Equals(Sender)).Select(p => (BigInteger)(p.SystemFee + p.NetworkFee)).Sum();
vncoelho marked this conversation as resolved.
Show resolved Hide resolved
if (balance < fee) return false;
UInt160[] hashes = GetScriptHashesForVerifying(snapshot);
for (int i = 0; i < hashes.Length; i++)
{
if (Witnesses[i].VerificationScript.Length > 0) continue;
if (snapshot.Contracts.TryGet(hashes[i]) is null) return false;
}
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

void ISerializable.Serialize(BinaryWriter writer)
{
((IVerifiable)this).SerializeUnsigned(writer);
Expand Down Expand Up @@ -164,19 +184,11 @@ bool IInventory.Verify(Snapshot snapshot)

public virtual bool Verify(Snapshot snapshot, IEnumerable<Transaction> mempool)
{
if (ValidUntilBlock <= snapshot.Height || ValidUntilBlock > snapshot.Height + MaxValidUntilBlockIncrement)
return false;
if (!Reverify(snapshot, mempool)) return false;
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
int size = Size;
if (size > MaxTransactionSize) return false;
long net_fee = NetworkFee - size * NativeContract.Policy.GetFeePerByte(snapshot);
if (net_fee < 0) return false;
if (NativeContract.Policy.GetBlockedAccounts(snapshot).Intersect(GetScriptHashesForVerifying(snapshot)).Count() > 0)
return false;
BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, Sender);
BigInteger fee = SystemFee + NetworkFee;
if (balance < fee) return false;
fee += mempool.Where(p => p != this && p.Sender.Equals(Sender)).Select(p => (BigInteger)(p.SystemFee + p.NetworkFee)).Sum();
if (balance < fee) return false;
return this.VerifyWitnesses(snapshot, net_fee);
}
}
Expand Down
40 changes: 40 additions & 0 deletions neo/SmartContract/InteropDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Neo.VM;
using System;

namespace Neo.SmartContract
{
internal class InteropDescriptor
{
public string Method { get; }
public uint Hash { get; }
public Func<ApplicationEngine, bool> Handler { get; }
public long Price { get; }
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
public Func<RandomAccessStack<StackItem>, long> PriceCalculator { get; }
public TriggerType AllowedTriggers { get; }

public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers)
: this(method, handler, allowedTriggers)
{
this.Price = price;
}

public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, Func<RandomAccessStack<StackItem>, long> priceCalculator, TriggerType allowedTriggers)
: this(method, handler, allowedTriggers)
{
this.PriceCalculator = priceCalculator;
}

private InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, TriggerType allowedTriggers)
{
this.Method = method;
this.Hash = method.ToInteropMethodHash();
this.Handler = handler;
this.AllowedTriggers = allowedTriggers;
}

public long GetPrice(RandomAccessStack<StackItem> stack)
{
return PriceCalculator is null ? Price : PriceCalculator(stack);
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
58 changes: 27 additions & 31 deletions neo/SmartContract/InteropService.NEO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,37 @@ namespace Neo.SmartContract
{
static partial class InteropService
{
public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0);
public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 0_01000000);
public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig, GetCheckMultiSigPrice);
public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400);
public static readonly uint Neo_Header_GetMerkleRoot = Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot, 0_00000400);
public static readonly uint Neo_Header_GetNextConsensus = Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus, 0_00000400);
public static readonly uint Neo_Transaction_GetScript = Register("Neo.Transaction.GetScript", Transaction_GetScript, 0_00000400);
public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000);
public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400);
public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000);
public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice);
public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice);
public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 0_00000400);
public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 0_00000400);
public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 0_01000000);
public static readonly uint Neo_Enumerator_Create = Register("Neo.Enumerator.Create", Enumerator_Create, 0_00000400);
public static readonly uint Neo_Enumerator_Next = Register("Neo.Enumerator.Next", Enumerator_Next, 0_01000000);
public static readonly uint Neo_Enumerator_Value = Register("Neo.Enumerator.Value", Enumerator_Value, 0_00000400);
public static readonly uint Neo_Enumerator_Concat = Register("Neo.Enumerator.Concat", Enumerator_Concat, 0_00000400);
public static readonly uint Neo_Iterator_Create = Register("Neo.Iterator.Create", Iterator_Create, 0_00000400);
public static readonly uint Neo_Iterator_Key = Register("Neo.Iterator.Key", Iterator_Key, 0_00000400);
public static readonly uint Neo_Iterator_Keys = Register("Neo.Iterator.Keys", Iterator_Keys, 0_00000400);
public static readonly uint Neo_Iterator_Values = Register("Neo.Iterator.Values", Iterator_Values, 0_00000400);
public static readonly uint Neo_Iterator_Concat = Register("Neo.Iterator.Concat", Iterator_Concat, 0_00000400);
public static readonly uint Neo_Json_Serialize = Register("Neo.Json.Serialize", Json_Serialize, 0_00100000);
public static readonly uint Neo_Json_Deserialize = Register("Neo.Json.Deserialize", Json_Deserialize, 0_00500000);
public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application);
public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 0_01000000, TriggerType.All);
public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig, GetCheckMultiSigPrice, TriggerType.All);
public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Header_GetMerkleRoot = Register("Neo.Header.GetMerkleRoot", Header_GetMerkleRoot, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Header_GetNextConsensus = Register("Neo.Header.GetNextConsensus", Header_GetNextConsensus, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Transaction_GetScript = Register("Neo.Transaction.GetScript", Transaction_GetScript, 0_00000400, TriggerType.All);
public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000, TriggerType.All);
public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400, TriggerType.All);
public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000, TriggerType.All);
public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application);
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application);
public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application);
public static readonly uint Neo_Enumerator_Create = Register("Neo.Enumerator.Create", Enumerator_Create, 0_00000400, TriggerType.All);
public static readonly uint Neo_Enumerator_Next = Register("Neo.Enumerator.Next", Enumerator_Next, 0_01000000, TriggerType.All);
public static readonly uint Neo_Enumerator_Value = Register("Neo.Enumerator.Value", Enumerator_Value, 0_00000400, TriggerType.All);
public static readonly uint Neo_Enumerator_Concat = Register("Neo.Enumerator.Concat", Enumerator_Concat, 0_00000400, TriggerType.All);
public static readonly uint Neo_Iterator_Create = Register("Neo.Iterator.Create", Iterator_Create, 0_00000400, TriggerType.All);
public static readonly uint Neo_Iterator_Key = Register("Neo.Iterator.Key", Iterator_Key, 0_00000400, TriggerType.All);
public static readonly uint Neo_Iterator_Keys = Register("Neo.Iterator.Keys", Iterator_Keys, 0_00000400, TriggerType.All);
public static readonly uint Neo_Iterator_Values = Register("Neo.Iterator.Values", Iterator_Values, 0_00000400, TriggerType.All);
public static readonly uint Neo_Iterator_Concat = Register("Neo.Iterator.Concat", Iterator_Concat, 0_00000400, TriggerType.All);
public static readonly uint Neo_Json_Serialize = Register("Neo.Json.Serialize", Json_Serialize, 0_00100000, TriggerType.All);
public static readonly uint Neo_Json_Deserialize = Register("Neo.Json.Deserialize", Json_Deserialize, 0_00500000, TriggerType.All);

static InteropService()
{
foreach (NativeContract contract in NativeContract.Contracts)
Register(contract.ServiceName, contract.Invoke, contract.GetPrice);
Register(contract.ServiceName, contract.Invoke, contract.GetPrice, TriggerType.System | TriggerType.Application);
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
}

private static long GetCheckMultiSigPrice(RandomAccessStack<StackItem> stack)
Expand All @@ -70,7 +70,6 @@ private static long GetDeploymentPrice(RandomAccessStack<StackItem> stack)

private static bool Native_Deploy(ApplicationEngine engine)
{
if (engine.Trigger != TriggerType.Application) return false;
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
if (engine.Snapshot.PersistingBlock.Index != 0) return false;
foreach (NativeContract contract in NativeContract.Contracts)
{
Expand Down Expand Up @@ -244,7 +243,6 @@ private static bool Account_IsStandard(ApplicationEngine engine)

private static bool Contract_Create(ApplicationEngine engine)
{
if (engine.Trigger != TriggerType.Application) return false;
byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray();
if (script.Length > 1024 * 1024) return false;

Expand All @@ -269,8 +267,6 @@ private static bool Contract_Create(ApplicationEngine engine)

private static bool Contract_Update(ApplicationEngine engine)
{
if (engine.Trigger != TriggerType.Application) return false;

byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray();
if (script.Length > 1024 * 1024) return false;
var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString();
Expand Down
Loading