Skip to content

Commit

Permalink
Limit SYSCALLs in Verification (#856)
Browse files Browse the repository at this point in the history
* Limit SYSCALLs in Verification

* Allow deployed contract as verification script

* Simplify re-verification

* Clear mempool when policy is changed

* put _policyChanged in local scope

* Reverify transactions when policy changes
  • Loading branch information
erikzhang authored and vncoelho committed Jul 5, 2019
1 parent 950b5ff commit 1d71675
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 126 deletions.
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;
_feePerByte = newFeePerByte;
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);
_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)
{
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;
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;
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;
}
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;
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; }
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);
}
}
}
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);
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);
}

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

0 comments on commit 1d71675

Please sign in to comment.