Skip to content

Commit

Permalink
BlockChain<T>.Append() now reject too heavy blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Oct 26, 2020
1 parent a83893f commit 9e5da5d
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ To be released.
- Added `TurnClient.BindProxies()` method. [[#756], [#868]]
- Added `ActionEvaluation.Exception` property. [[#860], [[#875]]]
- Added `InvalidTxGenesisHashException` class. [[#796], [#878]]
- Added `InvalidBlockBytesLengthException` class. [[#201], [#1050]]
- Added `CurrencyPermissionException` class. [[#861], [#900]]
- Added `InsufficientBalanceException` class. [[#861], [#900], [#954]]
- Added `BlockChain<T>.GetBalance()` method. [[#861], [#900]]
Expand Down Expand Up @@ -257,6 +258,10 @@ To be released.
- Added `Transaction<T>.GenesisHash` property. [[#796], [#878]]
- Added `IAccountStateDelta.UpdatedAddresses` property contains
asset updates besides state updates. [[#861], [#900]]
- `BlockChain<T>.Append()` method became to throw
`InvalidBlockBytesLengthException` if the given block's serialized bytes
is longer than the limitation configured by
`IBlockPolicy.GetMaxBlockBytes()`. [[#201], [#1050]]
- `BlockChain<T>.MineBlock()` method became to cut off transactions to include
to fit into the limitation configured by `IBlockPolicy.GetMaxBlockBytes()`.
[[#201], [#1050]]
Expand Down
43 changes: 41 additions & 2 deletions Libplanet.Tests/Blockchain/BlockChainTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public BlockChainTest(ITestOutputHelper output)

_fx = new DefaultStoreFixture(memory: true);
_renderer = new ValidatingActionRenderer<DumbAction>();
_policy = new BlockPolicy<DumbAction>(new MinerReward(1));
_policy = new BlockPolicy<DumbAction>(new MinerReward(1), maxBlockBytes: 50 * 1024);
_blockChain = new BlockChain<DumbAction>(
_policy,
_fx.Store,
Expand Down Expand Up @@ -131,7 +131,7 @@ public async void MineBlock()
getMaxBlockBytes(block4.Index)
);
Assert.True(block4.BytesLength <= getMaxBlockBytes(block4.Index));
Assert.Equal(4, block4.Transactions.Count());
Assert.Equal(3, block4.Transactions.Count());
}

[Fact]
Expand Down Expand Up @@ -629,6 +629,45 @@ public void Append()
(Integer)blockRenders[1].NextStates.GetState(minerAddress));
}

[Fact]
public void AppendThrowsInvalidBlockBytesLengthException()
{
DumbAction[] manyActions =
Enumerable.Repeat(new DumbAction(default, "_"), 200).ToArray();
PrivateKey signer = null;
int nonce = 0;
var heavyTxs = new List<Transaction<DumbAction>>();
for (int i = 0; i < 100; i++)
{
if (i % 25 == 0)
{
nonce = 0;
signer = new PrivateKey();
}

Transaction<DumbAction> heavyTx = _fx.MakeTransaction(
manyActions,
nonce: nonce,
privateKey: signer);
heavyTxs.Add(heavyTx);
}

var block1 = TestUtils.MineNext(
_blockChain.Genesis,
heavyTxs,
difficulty: _blockChain.Policy.GetNextBlockDifficulty(_blockChain),
blockInterval: TimeSpan.FromSeconds(10)
);
int maxBytes = _blockChain.Policy.GetMaxBlockBytes(block1.Index);
Assert.True(block1.BytesLength > maxBytes);

var e = Assert.Throws<InvalidBlockBytesLengthException>(() =>
_blockChain.Append(block1)
);
Assert.Equal(maxBytes, e.MaxBlockBytesLength);
Assert.Equal(block1.BytesLength, e.BlockBytesLength);
}

[Fact]
public void AppendWithoutEvaluateActions()
{
Expand Down
28 changes: 28 additions & 0 deletions Libplanet.Tests/Blocks/InvalidBlockBytesLengthExceptionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Libplanet.Blocks;
using Xunit;

namespace Libplanet.Tests.Blocks
{
public class InvalidBlockBytesLengthExceptionTest
{
public void Serialization()
{
var e = new InvalidBlockBytesLengthException(10, 5, "A message.");
var f = new BinaryFormatter();
InvalidBlockBytesLengthException e2;

using (var s = new MemoryStream())
{
f.Serialize(s, e);
s.Seek(0, SeekOrigin.Begin);
e2 = (InvalidBlockBytesLengthException)f.Deserialize(s);
}

Assert.Equal(e.Message, e2.Message);
Assert.Equal(e.BlockBytesLength, e2.BlockBytesLength);
Assert.Equal(e.MaxBlockBytesLength, e2.MaxBlockBytesLength);
}
}
}
2 changes: 1 addition & 1 deletion Libplanet.Tests/Menees.Analyzers.Settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
<Menees.Analyzers.Settings>
<MaxLineColumns>100</MaxLineColumns>
<MaxMethodLines>300</MaxMethodLines>
<MaxFileLines>2700</MaxFileLines>
<MaxFileLines>2800</MaxFileLines>
</Menees.Analyzers.Settings>
12 changes: 12 additions & 0 deletions Libplanet/Blockchain/BlockChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,9 @@ public void Append(Block<T> block, StateCompleterSet<T>? stateCompleters = null)
/// are required for action execution and rendering.
/// <see cref="StateCompleterSet{T}.Recalculate"/> by default.
/// </param>
/// <exception cref="InvalidBlockBytesLengthException">Thrown when the block to mine is
/// too long (according to <see cref="IBlockPolicy{T}.GetMaxBlockBytes(long)"/>) in bytes.
/// </exception>
/// <exception cref="InvalidBlockException">Thrown when the given
/// <paramref name="block"/> is invalid, in itself or according to
/// the <see cref="Policy"/>.</exception>
Expand Down Expand Up @@ -947,6 +950,15 @@ internal void Append(

_logger.Debug("Trying to append block {blockIndex}: {block}", block?.Index, block);

if (block.BytesLength > Policy.GetMaxBlockBytes(block.Index))
{
throw new InvalidBlockBytesLengthException(
block.BytesLength,
Policy.GetMaxBlockBytes(block.Index),
"The block to append is too long in bytes."
);
}

IReadOnlyList<ActionEvaluation> evaluations = null;
_rwlock.EnterUpgradeableReadLock();
Block<T> prevTip = Tip;
Expand Down
65 changes: 65 additions & 0 deletions Libplanet/Blocks/InvalidBlockBytesLengthException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#nullable enable
using System;
using System.Runtime.Serialization;
using Libplanet.Blockchain.Policies;

namespace Libplanet.Blocks
{
/// <summary>
/// The exception that is thrown when a <see cref="Block{T}"/>'s
/// <see cref="Block{T}.BytesLength"/> (i.e., the length of bytes in its serialized format)
/// is too long.
/// </summary>
[Serializable]
public sealed class InvalidBlockBytesLengthException : InvalidBlockException, ISerializable
{
/// <summary>
/// Initializes a new instance of <see cref="InvalidBlockBytesLengthException"/> class.
/// </summary>
/// <param name="blockBytesLength">The invalid <see cref="Block{T}"/>'s
/// <see cref="Block{T}.BytesLength"/>. It is automatically included to the
/// <see cref="Exception.Message"/> string.</param>
/// <param name="maxBlockBytesLength">The maximum allowed length of bytes. It is
/// automatically included to the <see cref="Exception.Message"/> string.</param>
/// <param name="message">The message that describes the error.</param>
public InvalidBlockBytesLengthException(
int blockBytesLength,
int maxBlockBytesLength,
string message
)
: base(
$"{message}\n" +
$"Actual: #{blockBytesLength} bytes\n" +
$"Allowed (max): #{maxBlockBytesLength}")
{
BlockBytesLength = blockBytesLength;
MaxBlockBytesLength = maxBlockBytesLength;
}

private InvalidBlockBytesLengthException(SerializationInfo info, StreamingContext context)
: base(info.GetString(nameof(Message)) ?? string.Empty)
{
BlockBytesLength = info.GetInt32(nameof(BlockBytesLength));
MaxBlockBytesLength = info.GetInt32(nameof(MaxBlockBytesLength));
}

/// <summary>
/// The bytes length of the actual block.
/// </summary>
public int BlockBytesLength { get; set; }

/// <summary>
/// The maximum allowed length of a block in bytes.
/// </summary>
/// <seealso cref="IBlockPolicy{T}.GetMaxBlockBytes(long)"/>
public int MaxBlockBytesLength { get; set; }

public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(Message), Message);
info.AddValue(nameof(BlockBytesLength), BlockBytesLength);
info.AddValue(nameof(MaxBlockBytesLength), MaxBlockBytesLength);
}
}
}
6 changes: 6 additions & 0 deletions Libplanet/Blocks/InvalidBlockException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@

namespace Libplanet.Blocks
{
/// <summary>
/// Serves as the base class for exceptions related <see cref="Block{T}"/>s' integrity and
/// validity.
/// </summary>
public abstract class InvalidBlockException : Exception
{
/// <inheritdoc cref="Exception(string)"/>
protected InvalidBlockException(string message)
: base(message)
{
}

/// <inheritdoc cref="Exception(SerializationInfo, StreamingContext)"/>
protected InvalidBlockException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
Expand Down

0 comments on commit 9e5da5d

Please sign in to comment.