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

Set max block size #953

Merged
merged 54 commits into from
Aug 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
dfa6dc6
Draft
shargon Jul 24, 2019
d1616d8
Take into account p2p max payload
shargon Jul 24, 2019
89fe5b0
Move max allowed to policy
shargon Jul 25, 2019
0ddf1e9
Check block size on prepResponse
shargon Jul 25, 2019
5d97f71
Change reason
shargon Jul 25, 2019
f622e57
Prevent overflow
shargon Jul 25, 2019
618d2d9
Merge branch 'master' into max-block-size
shargon Jul 25, 2019
4a111bc
Optimization
shargon Jul 26, 2019
a013394
Reduce the length of the array
shargon Jul 26, 2019
94a29dc
Organizing consensus code
vncoelho Jul 30, 2019
dba9733
Revert "Organizing consensus code"
vncoelho Jul 30, 2019
b38c9a7
Remove Policy UT
shargon Jul 31, 2019
643af4d
Merge branch 'master' into max-block-size
shargon Jul 31, 2019
b05fc39
Resolve Policy conflicts
shargon Jul 31, 2019
2d54874
prepare unit test
shargon Jul 31, 2019
3c35703
Small unit test
shargon Jul 31, 2019
63d64c8
More ut
shargon Jul 31, 2019
9419e4a
Add one check
shargon Jul 31, 2019
138a342
Clean using
shargon Jul 31, 2019
5d4ebe8
Organizing consensus and comments
vncoelho Jul 31, 2019
aa6cbee
Split unit test
shargon Aug 2, 2019
bad503f
Merge branch 'master' into max-block-size
shargon Aug 2, 2019
a942fc5
UT check block size
shargon Aug 2, 2019
6821ee5
Clean
shargon Aug 2, 2019
f9dcbeb
Expected witness size
shargon Aug 2, 2019
5ad4500
optimize
erikzhang Aug 5, 2019
33b68c9
Merge branch 'master' into max-block-size
shargon Aug 5, 2019
7209503
Remove fakeWitness
shargon Aug 5, 2019
165fe96
Format comments
shargon Aug 5, 2019
94b8fc9
rename var
shargon Aug 5, 2019
7a26d2e
Add (..)
shargon Aug 5, 2019
2ef7c2f
Remove SetKey method
shargon Aug 6, 2019
1847366
Merge branch 'master' into max-block-size
shargon Aug 6, 2019
7a799db
Centralize expected block size
shargon Aug 6, 2019
13ccd2a
Merge remote-tracking branch 'shargon/max-block-size' into max-block-…
shargon Aug 6, 2019
1dca8d5
Optimize
shargon Aug 6, 2019
dbda619
Fix
shargon Aug 6, 2019
8d385a8
Add one test
shargon Aug 6, 2019
754541a
Merge branch 'master' into max-block-size
lock9 Aug 7, 2019
accbf72
Optimize `EnsureMaxBlockSize()`
erikzhang Aug 8, 2019
b8e80b0
Fix unit tests
shargon Aug 8, 2019
44de151
Rename
shargon Aug 8, 2019
fa5b743
Indent
shargon Aug 8, 2019
a5b1eb3
Merge branch 'master' into max-block-size
erikzhang Aug 9, 2019
f98a40a
Merge branch 'master' into max-block-size
shargon Aug 12, 2019
9aa49b4
Vitor suggestion
shargon Aug 12, 2019
dc9fd12
Merge with Scoped signatures
shargon Aug 12, 2019
e134881
Remove extra line
vncoelho Aug 12, 2019
a9282c0
Revert "Remove extra line"
vncoelho Aug 12, 2019
c7a063e
Remove extra line
vncoelho Aug 12, 2019
863611c
Merge branch 'master' into max-block-size
shargon Aug 12, 2019
396bd1a
Merge branch 'master' into max-block-size
igormcoelho Aug 16, 2019
7b5ebc1
Merge branch 'master' into max-block-size
vncoelho Aug 16, 2019
f42a7c9
Merge branch 'master' into max-block-size
shargon Aug 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions neo.UnitTests/Consensus/UT_ConsensusContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using Akka.TestKit.Xunit2;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Neo.Consensus;
using Neo.IO;
using Neo.Network.P2P.Payloads;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.Wallets;
using System;
using System.Linq;

namespace Neo.UnitTests.Consensus
{

[TestClass]
public class UT_ConsensusContext : TestKit
{
ConsensusContext _context;
KeyPair[] _validatorKeys;

[TestInitialize]
public void TestSetup()
{
TestBlockchain.InitializeMockNeoSystem();

var rand = new Random();
var mockWallet = new Mock<Wallet>();
mockWallet.Setup(p => p.GetAccount(It.IsAny<UInt160>())).Returns<UInt160>(p => new TestWalletAccount(p));

// Create dummy validators

_validatorKeys = new KeyPair[7];
for (int x = 0; x < _validatorKeys.Length; x++)
{
var pk = new byte[32];
rand.NextBytes(pk);

_validatorKeys[x] = new KeyPair(pk);
}

_context = new ConsensusContext(mockWallet.Object, TestBlockchain.GetStore())
{
Validators = _validatorKeys.Select(u => u.PublicKey).ToArray()
};
_context.Reset(0);
}

[TestCleanup]
public void Cleanup()
{
Shutdown();
}

[TestMethod]
public void TestMaxBlockSize_Good()
{
// Only one tx, is included

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

// All txs included

var max = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(_context.Snapshot);
var txs = new Transaction[max];

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

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

[TestMethod]
public void TestMaxBlockSize_Exceed()
{
// Two tx, the last one exceed the size rule, only the first will be included

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

// Exceed txs number, just MaxTransactionsPerBlock included

var max = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(_context.Snapshot);
var txs = new Transaction[max + 1];

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

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

private Transaction CreateTransactionWithSize(int v)
{
var r = new Random();
var tx = new Transaction()
{
Cosigners = new Cosigner[0],
Attributes = new TransactionAttribute[0],
NetworkFee = 0,
Nonce = (uint)Environment.TickCount,
Script = new byte[0],
Sender = UInt160.Zero,
SystemFee = 0,
ValidUntilBlock = (uint)r.Next(),
Version = 0,
Witnesses = new Witness[0],
};

// Could be higher (few bytes) if varSize grows
tx.Script = new byte[v - tx.Size];
return tx;
}

private Block SignBlock(ConsensusContext context)
{
context.Block.MerkleRoot = null;

for (int x = 0; x < _validatorKeys.Length; x++)
{
_context.MyIndex = x;

var com = _context.MakeCommit();
_context.CommitPayloads[_context.MyIndex] = com;
}

// Manual block sign

Contract contract = Contract.CreateMultiSigContract(context.M, context.Validators);
ContractParametersContext sc = new ContractParametersContext(context.Block);
for (int i = 0, j = 0; i < context.Validators.Length && j < context.M; i++)
{
if (context.CommitPayloads[i]?.ConsensusMessage.ViewNumber != context.ViewNumber) continue;
sc.AddSignature(contract, context.Validators[i], context.CommitPayloads[i].GetDeserializedMessage<Commit>().Signature);
j++;
}
context.Block.Witness = sc.GetWitnesses()[0];
context.Block.Transactions = context.TransactionHashes.Select(p => context.Transactions[p]).ToArray();
return context.Block;
}

private void EnsureContext(ConsensusContext context, params Transaction[] expected)
{
// Check all tx

Assert.AreEqual(expected.Length, context.Transactions.Count);
Assert.IsTrue(expected.All(tx => context.Transactions.ContainsKey(tx.Hash)));

Assert.AreEqual(expected.Length, context.TransactionHashes.Length);
Assert.IsTrue(expected.All(tx => context.TransactionHashes.Count(t => t == tx.Hash) == 1));

// Ensure length

var block = SignBlock(context);

Assert.AreEqual(context.GetExpectedBlockSize(), block.Size);
Assert.IsTrue(block.Size < NativeContract.Policy.GetMaxBlockSize(context.Snapshot));
}
}
}
52 changes: 51 additions & 1 deletion neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ public void Check_Initialize()

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

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

var ret = NativeContract.Policy.Call(snapshot, "getMaxTransactionsPerBlock");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(512);

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1024 * 256);

ret = NativeContract.Policy.Call(snapshot, "getFeePerByte");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1000);
Expand All @@ -47,6 +51,52 @@ public void Check_Initialize()
((VM.Types.Array)ret).Count.Should().Be(0);
}

[TestMethod]
public void Check_SetMaxBlockSize()
{
var snapshot = Store.GetSnapshot().Clone();

// Fake blockchain

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

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

// Without signature

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

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1024 * 256);

// More than expected

ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero),
"setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = Neo.Network.P2P.Message.PayloadMaxSize });
ret.Should().BeOfType<VM.Types.Boolean>();
ret.GetBoolean().Should().BeFalse();

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1024 * 256);

// With signature

ret = NativeContract.Policy.Call(snapshot, new Nep5NativeContractExtensions.ManualWitness(UInt160.Zero),
"setMaxBlockSize", new ContractParameter(ContractParameterType.Integer) { Value = 1024 });
ret.Should().BeOfType<VM.Types.Boolean>();
ret.GetBoolean().Should().BeTrue();

ret = NativeContract.Policy.Call(snapshot, "getMaxBlockSize");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetBigInteger().Should().Be(1024);
}

[TestMethod]
public void Check_SetMaxTransactionsPerBlock()
{
Expand Down
1 change: 1 addition & 0 deletions neo/Consensus/ChangeViewReason.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public enum ChangeViewReason : byte
TxNotFound = 0x2,
TxRejectedByPolicy = 0x3,
TxInvalid = 0x4,
BlockRejectedByPolicy = 0x5
shargon marked this conversation as resolved.
Show resolved Hide resolved
}
}
98 changes: 91 additions & 7 deletions neo/Consensus/ConsensusContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -38,10 +39,11 @@ internal class ConsensusContext : IDisposable, ISerializable

public Snapshot Snapshot { get; private set; }
private KeyPair keyPair;
private int _witnessSize;
shargon marked this conversation as resolved.
Show resolved Hide resolved
private readonly Wallet wallet;
private readonly Store store;
private readonly Random random = new Random();

public int F => (Validators.Length - 1) / 3;
public int M => Validators.Length - F;
public bool IsPrimary => MyIndex == Block.ConsensusData.PrimaryIndex;
Expand Down Expand Up @@ -203,19 +205,84 @@ private void SignPayload(ConsensusPayload payload)
return;
}
payload.Witness = sc.GetWitnesses()[0];
}

/// <summary>
/// Return the expected block size
/// </summary>
internal int GetExpectedBlockSize()
{
return GetExpectedBlockSizeWithoutTransactions(Transactions.Count) + // Base size
Transactions.Values.Sum(u => u.Size); // Sum Txs
}

/// <summary>
/// Return the expected block size without txs
/// </summary>
/// <param name="expectedTransactions">Expected transactions</param>
internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions)
{
var blockSize =
// BlockBase
sizeof(uint) + //Version
UInt256.Length + //PrevHash
UInt256.Length + //MerkleRoot
sizeof(ulong) + //Timestamp
sizeof(uint) + //Index
UInt160.Length + //NextConsensus
1 + //
_witnessSize; //Witness

blockSize +=
// Block
Block.ConsensusData.Size + //ConsensusData
IO.Helper.GetVarSize(expectedTransactions + 1); //Transactions count

return blockSize;
}

/// <summary>
/// Prevent that block exceed the max size
/// </summary>
/// <param name="txs">Ordered transactions</param>
internal void EnsureMaxBlockSize(IEnumerable<Transaction> txs)
{
shargon marked this conversation as resolved.
Show resolved Hide resolved
uint maxBlockSize = NativeContract.Policy.GetMaxBlockSize(Snapshot);
uint maxTransactionsPerBlock = NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot);

// Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool
txs = txs.Take((int)maxTransactionsPerBlock);
List<UInt256> hashes = new List<UInt256>();
Transactions = new Dictionary<UInt256, Transaction>();
shargon marked this conversation as resolved.
Show resolved Hide resolved
Block.Transactions = new Transaction[0];

// We need to know the expected block size

var blockSize = GetExpectedBlockSizeWithoutTransactions(txs.Count());

// Iterate transaction until reach the size

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;

hashes.Add(tx.Hash);
Transactions.Add(tx.Hash, tx);
}

TransactionHashes = hashes.ToArray();
}

public ConsensusPayload MakePrepareRequest()
{
byte[] buffer = new byte[sizeof(ulong)];
random.NextBytes(buffer);
List<Transaction> transactions = Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions()
.Take((int)NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot))
.ToList();
TransactionHashes = transactions.Select(p => p.Hash).ToArray();
Transactions = transactions.ToDictionary(p => p.Hash);
Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1);
Block.ConsensusData.Nonce = BitConverter.ToUInt64(buffer, 0);
EnsureMaxBlockSize(Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions());
igormcoelho marked this conversation as resolved.
Show resolved Hide resolved
Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1);

return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest
{
Timestamp = Block.Timestamp,
Expand Down Expand Up @@ -279,7 +346,24 @@ public void Reset(byte viewNumber)
NextConsensus = Blockchain.GetConsensusAddress(NativeContract.NEO.GetValidators(Snapshot).ToArray()),
ConsensusData = new ConsensusData()
};
var pv = Validators;
Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot);
if (_witnessSize == 0 || (pv != null && pv.Length != Validators.Length))
vncoelho marked this conversation as resolved.
Show resolved Hide resolved
{
// Compute the expected size of the witness
using (ScriptBuilder sb = new ScriptBuilder())
{
for (int x = 0; x < M; x++)
{
sb.EmitPush(new byte[64]);
}
_witnessSize = new Witness
Copy link
Contributor

@igormcoelho igormcoelho Aug 15, 2019

Choose a reason for hiding this comment

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

This is specially bad if we dont drop it, after cleaning next context, and eventually.number of nodes increases (but we keep same previous _witnessSize, and block may break). It's better if we attain to a simple const: Witness.GetMaxSize() or Witness.MaxSize (or even better, we just limit BlockHeader size + tx)

{
InvocationScript = sb.ToArray(),
VerificationScript = Contract.CreateMultiSigRedeemScript(M, Validators)
shargon marked this conversation as resolved.
Show resolved Hide resolved
}.Size;
}
}
MyIndex = -1;
ChangeViewPayloads = new ConsensusPayload[Validators.Length];
LastChangeViewPayloads = new ConsensusPayload[Validators.Length];
Expand Down
8 changes: 8 additions & 0 deletions neo/Consensus/ConsensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ private bool AddTransaction(Transaction tx, bool verify)
// previously sent prepare request, then we don't want to send a prepare response.
if (context.IsPrimary || context.WatchOnly) return true;

// 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);
RequestChangeView(ChangeViewReason.BlockRejectedByPolicy);
return false;
}

// Timeout extension due to prepare response sent
// around 2*15/M=30.0/5 ~ 40% block time (for M=5)
ExtendTimerByFactor(2);
Expand Down
Loading