Skip to content

Commit

Permalink
Add MaxBlockSystemFee (#1689)
Browse files Browse the repository at this point in the history
* add max block system fee

* fix tx

* fix and add more ut

* format

* optimze code

* Reset maxblocksystem = 9000/GAS

* Update src/neo/SmartContract/Native/PolicyContract.cs

Co-authored-by: HaoqiangZhang <gripzhang@outlook.com>

* Remove new line in log

* Update ConsensusService.cs

* up Prefix_MaxBlockSize as skiptable.insert

Co-authored-by: Shargon <shargon@gmail.com>
Co-authored-by: HaoqiangZhang <gripzhang@outlook.com>
  • Loading branch information
3 people authored Jun 13, 2020
1 parent be28d77 commit 7cb2939
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 11 deletions.
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

0 comments on commit 7cb2939

Please sign in to comment.