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

Add MaxBlockSystemFee #1689

Merged
merged 13 commits into from
Jun 13, 2020
20 changes: 17 additions & 3 deletions src/neo/Consensus/ConsensusContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ internal int GetExpectedBlockSize()
Transactions.Values.Sum(u => u.Size); // Sum Txs
}

/// <summary>
/// Return the expected block system fee
/// </summary>
internal long GetExpectedBlockSystemFee()
{
return Transactions.Values.Sum(u => u.SystemFee); // Sum Txs
}

/// <summary>
/// Return the expected block size without txs
/// </summary>
Expand Down Expand Up @@ -248,9 +256,10 @@ internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions)
/// Prevent that block exceed the max size
/// </summary>
/// <param name="txs">Ordered transactions</param>
internal void EnsureMaxBlockSize(IEnumerable<Transaction> txs)
internal void EnsureMaxBlockLimitation(IEnumerable<Transaction> txs)
{
uint maxBlockSize = NativeContract.Policy.GetMaxBlockSize(Snapshot);
long maxBlockSystemFee = NativeContract.Policy.GetMaxBlockSystemFee(Snapshot);
uint maxTransactionsPerBlock = NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot);

// Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool
Expand All @@ -261,14 +270,19 @@ internal void EnsureMaxBlockSize(IEnumerable<Transaction> txs)

// Expected block size
var blockSize = GetExpectedBlockSizeWithoutTransactions(txs.Count());
var blockSystemFee = 0L;

// Iterate transaction until reach the size
// Iterate transaction until reach the size or maximum system fee
foreach (Transaction tx in txs)
{
// Check if maximum block size has been already exceeded with the current selected set
blockSize += tx.Size;
if (blockSize > maxBlockSize) break;

// Check if maximum block system fee has been already exceeded with the current selected set
blockSystemFee += tx.SystemFee;
if (blockSystemFee > maxBlockSystemFee) break;

hashes.Add(tx.Hash);
Transactions.Add(tx.Hash, tx);
SendersFeeMonitor.AddSenderFee(tx);
Expand All @@ -283,7 +297,7 @@ public ConsensusPayload MakePrepareRequest()
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
random.NextBytes(buffer);
Block.ConsensusData.Nonce = BitConverter.ToUInt64(buffer);
EnsureMaxBlockSize(Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions());
EnsureMaxBlockLimitation(Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions());
Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1);

return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest
Expand Down
9 changes: 8 additions & 1 deletion src/neo/Consensus/ConsensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ private bool CheckPrepareResponse()
// Check maximum block size via Native Contract policy
if (context.GetExpectedBlockSize() > NativeContract.Policy.GetMaxBlockSize(context.Snapshot))
{
Log($"rejected block: {context.Block.Index}{Environment.NewLine} The size exceed the policy", LogLevel.Warning);
Log($"rejected block: {context.Block.Index} The size exceed the policy", LogLevel.Warning);
RequestChangeView(ChangeViewReason.BlockRejectedByPolicy);
return false;
}
// Check maximum block system fee via Native Contract policy
if (context.GetExpectedBlockSystemFee() > NativeContract.Policy.GetMaxBlockSystemFee(context.Snapshot))
{
Log($"rejected block: {context.Block.Index} The system fee exceed the policy", LogLevel.Warning);
RequestChangeView(ChangeViewReason.BlockRejectedByPolicy);
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions src/neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ public virtual VerifyResult VerifyForEachBlock(StoreView snapshot, BigInteger to
UInt160[] hashes = GetScriptHashesForVerifying(snapshot);
if (NativeContract.Policy.GetBlockedAccounts(snapshot).Intersect(hashes).Any())
return VerifyResult.PolicyFail;
if (NativeContract.Policy.GetMaxBlockSystemFee(snapshot) < SystemFee)
return VerifyResult.PolicyFail;
BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, Sender);
BigInteger fee = SystemFee + NetworkFee + totalSenderFeeFromPool;
if (balance < fee) return VerifyResult.InsufficientFunds;
Expand Down
23 changes: 22 additions & 1 deletion src/neo/SmartContract/Native/PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public sealed class PolicyContract : NativeContract
private const byte Prefix_MaxTransactionsPerBlock = 23;
private const byte Prefix_FeePerByte = 10;
private const byte Prefix_BlockedAccounts = 15;
private const byte Prefix_MaxBlockSize = 16;
private const byte Prefix_MaxBlockSize = 12;
private const byte Prefix_MaxBlockSystemFee = 17;

public PolicyContract()
{
Expand Down Expand Up @@ -50,6 +51,10 @@ internal override void Initialize(ApplicationEngine engine)
{
Value = BitConverter.GetBytes(512u)
});
engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_MaxBlockSystemFee), new StorageItem
{
Value = BitConverter.GetBytes(9000 * (long)GAS.Factor) // For the transfer method of NEP5, the maximum persisting time is about three seconds.
});
engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem
{
Value = BitConverter.GetBytes(1000L)
Expand All @@ -72,6 +77,12 @@ public uint GetMaxBlockSize(StoreView snapshot)
return BitConverter.ToUInt32(snapshot.Storages[CreateStorageKey(Prefix_MaxBlockSize)].Value, 0);
}

[ContractMethod(0_01000000, CallFlags.AllowStates)]
public long GetMaxBlockSystemFee(StoreView snapshot)
{
return BitConverter.ToInt64(snapshot.Storages[CreateStorageKey(Prefix_MaxBlockSystemFee)].Value, 0);
}

[ContractMethod(0_01000000, CallFlags.AllowStates)]
public long GetFeePerByte(StoreView snapshot)
{
Expand Down Expand Up @@ -103,6 +114,16 @@ private bool SetMaxTransactionsPerBlock(ApplicationEngine engine, uint value)
return true;
}

[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool SetMaxBlockSystemFee(ApplicationEngine engine, long value)
{
if (!CheckCommittees(engine)) return false;
if (value <= 4007600) return false;
StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxBlockSystemFee));
storage.Value = BitConverter.GetBytes(value);
return true;
}

[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool SetFeePerByte(ApplicationEngine engine, long value)
{
Expand Down
53 changes: 48 additions & 5 deletions tests/neo.UnitTests/Consensus/UT_ConsensusContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.SmartContract.Native;
using Neo.VM.Types;
using Neo.Wallets;
using System;
using System.Linq;
Expand Down Expand Up @@ -58,7 +59,7 @@ public void TestMaxBlockSize_Good()
// Only one tx, is included

var tx1 = CreateTransactionWithSize(200);
_context.EnsureMaxBlockSize(new Transaction[] { tx1 });
_context.EnsureMaxBlockLimitation(new Transaction[] { tx1 });
EnsureContext(_context, tx1);

// All txs included
Expand All @@ -68,7 +69,7 @@ public void TestMaxBlockSize_Good()

for (int x = 0; x < max; x++) txs[x] = CreateTransactionWithSize(100);

_context.EnsureMaxBlockSize(txs);
_context.EnsureMaxBlockLimitation(txs);
EnsureContext(_context, txs);
}

Expand All @@ -79,7 +80,7 @@ public void TestMaxBlockSize_Exceed()

var tx1 = CreateTransactionWithSize(200);
var tx2 = CreateTransactionWithSize(256 * 1024);
_context.EnsureMaxBlockSize(new Transaction[] { tx1, tx2 });
_context.EnsureMaxBlockLimitation(new Transaction[] { tx1, tx2 });
EnsureContext(_context, tx1);

// Exceed txs number, just MaxTransactionsPerBlock included
Expand All @@ -89,16 +90,41 @@ public void TestMaxBlockSize_Exceed()

for (int x = 0; x < max; x++) txs[x] = CreateTransactionWithSize(100);

_context.EnsureMaxBlockSize(txs);
_context.EnsureMaxBlockLimitation(txs);
EnsureContext(_context, txs.Take(max).ToArray());
}

[TestMethod]
public void TestMaxBlockSytemFee()
{
var tx1 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2);

// Less than MaxBlockSystemFee
_context.EnsureMaxBlockLimitation(new Transaction[] { tx1 });
EnsureContext(_context, new Transaction[] { tx1 });

// Equal MaxBlockSystemFee
tx1 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 + 1);
var tx2 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 - 1);

_context.EnsureMaxBlockLimitation(new Transaction[] { tx1, tx2 });
EnsureContext(_context, new Transaction[] { tx1, tx2 });

// Exceed MaxBlockSystemFee
tx1 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 + 3);
tx2 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 - 3);
var tx3 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 - 4);

_context.EnsureMaxBlockLimitation(new Transaction[] { tx1, tx2, tx3 });
EnsureContext(_context, new Transaction[] { tx1, tx2 });
}

private Transaction CreateTransactionWithSize(int v)
{
var r = new Random();
var tx = new Transaction()
{
Attributes = Array.Empty<TransactionAttribute>(),
Attributes = System.Array.Empty<TransactionAttribute>(),
NetworkFee = 0,
Nonce = (uint)Environment.TickCount,
Script = new byte[0],
Expand All @@ -114,6 +140,23 @@ private Transaction CreateTransactionWithSize(int v)
return tx;
}

private Transaction CreateTransactionWithSytemFee(long fee)
{
var tx = new Transaction()
{
Attributes = System.Array.Empty<TransactionAttribute>(),
NetworkFee = 0,
Nonce = (uint)Environment.TickCount,
Script = new byte[0],
Sender = UInt160.Zero,
SystemFee = fee,
ValidUntilBlock = int.MaxValue,
Version = 0,
Witnesses = new Witness[0],
};
return tx;
}

private Block SignBlock(ConsensusContext context)
{
context.Block.MerkleRoot = null;
Expand Down
55 changes: 54 additions & 1 deletion tests/neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Neo.Network.P2P.Payloads;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.SmartContract.Native.Tokens;
using Neo.UnitTests.Extensions;
using Neo.VM;
using System.Linq;
Expand All @@ -31,7 +32,7 @@ public void Check_Initialize()

NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0));

(keyCount + 4).Should().Be(snapshot.Storages.GetChangeSet().Count());
(keyCount + 5).Should().Be(snapshot.Storages.GetChangeSet().Count());

var ret = NativeContract.Policy.Call(snapshot, "getMaxTransactionsPerBlock");
ret.Should().BeOfType<VM.Types.Integer>();
Expand All @@ -41,6 +42,10 @@ public void Check_Initialize()
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1024 * 256);

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(9000 * 100000000L);

ret = NativeContract.Policy.Call(snapshot, "getFeePerByte");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1000);
Expand Down Expand Up @@ -98,6 +103,54 @@ public void Check_SetMaxBlockSize()
ret.GetBigInteger().Should().Be(1024);
}

[TestMethod]
public void Check_SetMaxBlockSystemFee()
{
var snapshot = Blockchain.Singleton.GetSnapshot();

// Fake blockchain

snapshot.PersistingBlock = new Block() { Index = 1000, PrevHash = UInt256.Zero };
snapshot.Blocks.Add(UInt256.Zero, new Neo.Ledger.TrimmedBlock() { NextConsensus = UInt160.Zero });

UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot);

NativeContract.Policy.Initialize(new ApplicationEngine(TriggerType.Application, null, snapshot, 0));

// Without signature

var ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(null),
"setMaxBlockSystemFee", new ContractParameter(ContractParameterType.Integer) { Value = 1024 * (long)NativeContract.GAS.Factor });
ret.Should().BeOfType<VM.Types.Boolean>();
ret.ToBoolean().Should().BeFalse();

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(9000 * (long)NativeContract.GAS.Factor);

// Less than expected

ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(committeeMultiSigAddr),
"setMaxBlockSystemFee", new ContractParameter(ContractParameterType.Integer) { Value = -1000 });
ret.Should().BeOfType<VM.Types.Boolean>();
ret.ToBoolean().Should().BeFalse();

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(9000 * (long)NativeContract.GAS.Factor);

// With signature

ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(committeeMultiSigAddr),
"setMaxBlockSystemFee", new ContractParameter(ContractParameterType.Integer) { Value = 1024 * (long)NativeContract.GAS.Factor });
ret.Should().BeOfType<VM.Types.Boolean>();
ret.ToBoolean().Should().BeTrue();

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSystemFee");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1024 * (long)NativeContract.GAS.Factor);
}

[TestMethod]
public void Check_SetMaxTransactionsPerBlock()
{
Expand Down