Skip to content

Commit

Permalink
Merge pull request #498 from dahlia/action-eval-exception
Browse files Browse the repository at this point in the history
UnexpectedlyTerminatedActionException
  • Loading branch information
dahlia authored Sep 5, 2019
2 parents 7d66d36 + 934ead2 commit 21ac8d8
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 91 deletions.
53 changes: 35 additions & 18 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ To be released.

### Backward-incompatible interface changes

- Replaced `UnexpectedlyTerminatedTxRehearsalException` with
`UnexpectedlyTerminatedActionException`.
- The following methods became to throw
`UnexpectedlyTerminatedActionException` with having its `InnerException`
during actions being evaluated if any action of them throws an exception:
[[#498]]
- `Transaction<T>.EvaluateActions()`
- `Transaction<T>.EvaluateActionsGradually()`
- `Block<T>.EvaluateActionsPerTx()`
- `Block<T>.Evaluate()`
- `BlockChain<T>.GetStates(completeStates: true)`
- The concept of "namespaces" in `IStore` was replaced by "chain IDs"
to be consistent with `BlockChain<T>`. [[#483], [#486]]
- Renamed `IStore.ListNamespaces()` method to `ListChainIds()`.
Expand Down Expand Up @@ -53,12 +64,13 @@ To be released.
- Added `Swarm<T>.PrepareAsync()` method. The method should be called before
calling `Swarm<T>.BootstrapAsync()`, `Swarm<T>.PreloadAsync()` and
`Swarm<T>.StartAsync()`. [[#353]]
- Added `Swarm<T>.BootstrapAsync()` method to connect with seed peers. [[#353]]
- Added `Swarm<T>.BootstrapAsync()` method to connect with seed peers.
[[#353]]

### Behavioral changes

- `Swarm<T>` now broadcasts transactions as soon as new transactions are received.
[[#463], [#496]]
- `Swarm<T>` now broadcasts transactions as soon as new transactions are
received. [[#463], [#496]]
- `Swarm<T>` now ignores block hashes which already exists. [[#461], [#484]]
- `Swarm<T>.PreloadAsync()` method became to download precalculated states
of blocks from a likely branchpoint instead of a genesis block from
Expand All @@ -70,10 +82,10 @@ To be released.
instead of `HashDigest<SHA256>`. [[#465], [#481]]
- NetMQ instances are now initialized at `Swarm<T>.StartAsync()` instead of
`Swarm<T>()`. [[#353]]
- Peers now connected via [Kademlia protocol][Kademlia]. Peers are now selectively
connected to each peer. [[#353]]
- `TxId`s and `Block`s are now broadcasted to selected peers from routing table of
the host peer. [[#353]]
- Peers now connected via [Kademlia protocol][Kademlia]. Peers are now
selectively connected to each peer. [[#353]]
- `TxId`s and `Block`s are now broadcasted to selected peers from routing
table of the host peer. [[#353]]

### Bug fixes

Expand All @@ -90,6 +102,7 @@ To be released.
[#483]: https://github.com/planetarium/libplanet/issues/483
[#484]: https://github.com/planetarium/libplanet/pull/484
[#486]: https://github.com/planetarium/libplanet/pull/486
[#498]: https://github.com/planetarium/libplanet/pull/498
[#496]: https://github.com/planetarium/libplanet/pull/496
[Kademlia]: https://en.wikipedia.org/wiki/Kademlia

Expand Down Expand Up @@ -145,8 +158,8 @@ Released on August 22, 2019.

- Added `IStore.GetBlockIndex()` method. [[#385]]
- `StoreExtension.LookupStateReference<T>()` method became to return
`Tuple<HashDigest<SHA256>, long>` which is a nullable tuple of `Block<T>.Hash`
and `Block<T>.Index`. [[#350]]
`Tuple<HashDigest<SHA256>, long>` which is a nullable tuple of
`Block<T>.Hash` and `Block<T>.Index`. [[#350]]
- Added `IBlockPolicy<T>.BlockAction` property. [[#319], [#367]]
- Removed the type parameter of `ActionEvaluation`. [[#319], [#367]]
- `ActionEvaluation.Action` became to `IAction` type. [[#319], [#367]]
Expand All @@ -155,8 +168,9 @@ Released on August 22, 2019.
- `LiteDBStore()` constructor became to have a new option named `readOnly` and
turned off by default. [[#434]]
- `BaseIndex.ContainsKey()` method became `abstract`. [[#390]]
- `BlockDownloadState.TotalBlockCount` and `BlockDownloadState.ReceivedBlockCount`
became to `Int64` type. [[#396], [#399]]
- `BlockDownloadState.TotalBlockCount` and
`BlockDownloadState.ReceivedBlockCount` became to `Int64` type.
[[#396], [#399]]
- `IStore.IterateIndex()` method became to receive `offset` and `limit`
parameters. [[#425]]
- Added `IStore.GetCanonicalNamespace()` method. [[#426]]
Expand Down Expand Up @@ -187,7 +201,8 @@ Released on August 22, 2019.
`IComparable` interfaces. [[#363]]
- Added `BlockChain<T>.BlockHashes` property. [[#389]]
- `Swarm<T>.PreloadAsync(IProgress<PreloadState>, IImmutableSet<Address>,
CancellationToken)` became to report progress for all phases. [[#397], [#400]]
CancellationToken)` became to report progress for all phases.
[[#397], [#400]]
- Added `PreloadState`, `ActionExecutionState`, `StateReferenceDownloadState`,
and `BlockStateDownloadState` classes to cover all phases in the entire
preloading process. [[#397], [#400]]
Expand All @@ -214,7 +229,8 @@ Released on August 22, 2019.
instead of the window size of a chunk (i.e., 500). [[#396], [#399]]
- `Swarm<T>.PreloadAsync()` became to get the first parameter, `progress`,
which accepts `IProgress<PreloadState>`. [[#397], [#400]]
- `BlockHashes` messages became to contain one more higher hash. [[#408], [#445]]
- `BlockHashes` messages became to contain one more higher hash.
[[#408], [#445]]
- `Swarm<T>.PreloadAsync()` became safe from data corruption even
if a preloading process suddenly gets shutdown. [[#417]]
- `FileStore` and `LiteDBStore` became to guarantee atomicity of storing
Expand Down Expand Up @@ -387,8 +403,8 @@ Released on July 8, 2019.
- Improved performance of `Swarm<T>`'s response time to `GetBlockHashes`
request messages. [[#277]]
- Added IPv6 support to `Libplanet.Stun.StunAddress`. [[#267], [#271]]
- `IStore.GetBlockStates()` became able to return `null` to represent an absence
of states (i.e., incomplete states). [[#272], [#285]]
- `IStore.GetBlockStates()` became able to return `null` to represent
an absence of states (i.e., incomplete states). [[#272], [#285]]
- `Swarm<T>` became to broadcast staged `Transaction`s periodically
so that game apps no more need to maintain their own thread to
broadcast staged transactions. [[#274], [#297]]
Expand Down Expand Up @@ -504,7 +520,8 @@ Released on May 31, 2019.
[[#232]]
- Added `IStore.ForkStateReferences<T>(string, string, Block<T>,
IImmutableSet<Address>` method. [[#232]]
- Removed `Block<T>.Validate()` and `Block<T>.EvaluateActions()` method. [[#243]]
- Removed `Block<T>.Validate()` and `Block<T>.EvaluateActions()` method.
[[#243]]
- Added `Transaction<T>.Nonce` and `RawTransaction.Nonce` properties.
[[#246]]
- Added `IStore.GetTxNonce(string, Address)` method. [[#246]]
Expand Down Expand Up @@ -569,8 +586,8 @@ Released on May 31, 2019.
- `BlockChain<T>.Append()` method became to throw `InvalidTxNonceException`
when the `Transaction<T>.Nonce` does not correspond to its `Signer`'s
current nonce. [[#246]]
- `Swarm` became to enforce `ForceDotNet.Force()` in [AsyncIO] while it's running on
Mono runtime. [[#247]]
- `Swarm` became to enforce `ForceDotNet.Force()` in [AsyncIO] while
it's running on Mono runtime. [[#247]]

### Bug fixes

Expand Down
10 changes: 6 additions & 4 deletions Libplanet.Tests/Common/Action/ThrowException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ public ThrowException()
{
}

public bool Throw { get; set; } = false;
public bool ThrowOnRehearsal { get; set; } = false;

public bool ThrowOnExecution { get; set; } = false;

public IImmutableDictionary<string, object> PlainValue =>
new Dictionary<string, object>()
{
{ "throw", Throw },
{ "throw", ThrowOnRehearsal },
}.ToImmutableDictionary();

public void LoadPlainValue(IImmutableDictionary<string, object> plainValue)
{
Throw = (bool)plainValue["throw"];
ThrowOnRehearsal = (bool)plainValue["throw"];
}

public IAccountStateDelta Execute(IActionContext context)
{
if (Throw)
if (context.Rehearsal ? ThrowOnRehearsal : ThrowOnExecution)
{
throw new SomeException("An expected exception.");
}
Expand Down
56 changes: 46 additions & 10 deletions Libplanet.Tests/Tx/TransactionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Cryptography;
using Libplanet.Action;
using Libplanet.Crypto;
using Libplanet.Tests.Common.Action;
Expand Down Expand Up @@ -171,16 +172,22 @@ public void CreateWithMissingRequiredArguments()
[Fact]
public void CreateWithActionsThrowingException()
{
var action = new ThrowException { Throw = true };
Assert.Throws<UnexpectedlyTerminatedTxRehearsalException>(() =>
Transaction<ThrowException>.Create(
0,
_fx.PrivateKey1,
new[] { action },
ImmutableHashSet<Address>.Empty,
DateTimeOffset.UtcNow
)
);
var action = new ThrowException { ThrowOnRehearsal = true };
UnexpectedlyTerminatedActionException e =
Assert.Throws<UnexpectedlyTerminatedActionException>(() =>
Transaction<ThrowException>.Create(
0,
_fx.PrivateKey1,
new[] { action },
ImmutableHashSet<Address>.Empty,
DateTimeOffset.UtcNow
)
);
Assert.Null(e.BlockHash);
Assert.Null(e.BlockIndex);
Assert.Null(e.TxId);
Assert.Same(action, e.Action);
Assert.IsType<ThrowException.SomeException>(e.InnerException);
}

[Fact]
Expand Down Expand Up @@ -715,6 +722,35 @@ public void EvaluateActions()
}
}

[Fact]
public void EvaluateActionsThrowingException()
{
var action = new ThrowException { ThrowOnRehearsal = false, ThrowOnExecution = true };
Transaction<ThrowException> tx = Transaction<ThrowException>.Create(
0,
_fx.PrivateKey1,
new[] { action },
ImmutableHashSet<Address>.Empty,
DateTimeOffset.UtcNow
);
var hash = new HashDigest<SHA256>(GetRandomBytes(HashDigest<SHA256>.Size));
UnexpectedlyTerminatedActionException e =
Assert.Throws<UnexpectedlyTerminatedActionException>(() =>
tx.EvaluateActions(
blockHash: hash,
blockIndex: 123,
previousStates: new AccountStateDeltaImpl(_ => null),
minerAddress: GenesisMinerAddress,
rehearsal: false
)
);
Assert.Equal(hash, e.BlockHash);
Assert.Equal(123, e.BlockIndex);
Assert.Equal(tx.Id, e.TxId);
Assert.IsType<ThrowException>(e.Action);
Assert.IsType<ThrowException.SomeException>(e.InnerException);
}

[Fact]
public void Validate()
{
Expand Down
37 changes: 22 additions & 15 deletions Libplanet/Action/ActionEvaluation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using Libplanet.Blockchain.Policies;
using Libplanet.Blocks;
using Libplanet.Tx;

namespace Libplanet.Action
Expand Down Expand Up @@ -55,12 +57,13 @@ IAccountStateDelta outputStates
/// Executes the <paramref name="actions"/> step by step, and emits
/// <see cref="ActionEvaluation"/> for each step.
/// </summary>
/// <param name="blockHash">The <see cref="Libplanet.Blocks.Block{T}.Hash"/> of
/// <see cref="Libplanet.Blocks.Block{T}"/> that this <see cref="Transaction{T}"/> will
/// belong to.</param>
/// <param name="blockIndex">The <see cref="Libplanet.Blocks.Block{T}.Index"/> of
/// <see cref="Libplanet.Blocks.Block{T}"/> that this <see cref="Transaction{T}"/> will
/// belong to.</param>
/// <param name="blockHash">The <see cref="Block{T}.Hash"/> of <see cref="Block{T}"/> that
/// <paramref name="actions"/> belongs to.</param>
/// <param name="blockIndex">The <see cref="Block{T}.Index"/> of <see cref="Block{T}"/> that
/// <paramref name="actions"/> belongs to.</param>
/// <param name="txid">The <see cref="Transaction{T}.Id"/> of <see cref="Transaction{T}"/>
/// that <paramref name="actions"/> belongs to. This can be <c>null</c> on rehearsal mode
/// or if an action is a <see cref="IBlockPolicy{T}.BlockAction"/>.</param>
/// <param name="previousStates">The states immediately before <paramref name="actions"/>
/// being executed. Note that its <see cref="IAccountStateDelta.UpdatedAddresses"/> are
/// remained to the returned next states.</param>
Expand All @@ -77,17 +80,15 @@ IAccountStateDelta outputStates
/// Note that each <see cref="IActionContext.Random"/> object
/// has a unconsumed state.
/// </returns>
/// <exception cref="UnexpectedlyTerminatedTxRehearsalException">
/// Thrown when one of <paramref name="actions"/> throws some
/// exception during <paramref name="rehearsal"/> mode.
/// <exception cref="UnexpectedlyTerminatedActionException">
/// Thrown when one of <paramref name="actions"/> throws some exception.
/// The actual exception that an <see cref="IAction"/> threw
/// is stored in its <see cref="Exception.InnerException"/> property.
/// It is never thrown if the <paramref name="rehearsal"/> option is
/// <c>false</c>.
/// </exception>
internal static IEnumerable<ActionEvaluation> EvaluateActionsGradually(
HashDigest<SHA256> blockHash,
long blockIndex,
TxId? txid,
IAccountStateDelta previousStates,
Address minerAddress,
Address signer,
Expand Down Expand Up @@ -123,12 +124,18 @@ int randomSeed
}
catch (Exception e)
{
string msg;
if (!rehearsal)
{
throw;
msg = $"The action {action} (block #{blockIndex} {blockHash}, tx {txid}) " +
"threw an exception during execution. See also this exception's " +
"InnerException property.";
throw new UnexpectedlyTerminatedActionException(
blockHash, blockIndex, txid, action, msg, e
);
}

var msg =
msg =
$"The action {action} threw an exception during its " +
"rehearsal. It is probably because the logic of the " +
$"action {action} is not enough generic so that it " +
Expand All @@ -137,8 +144,8 @@ int randomSeed
"useful to make the action can deal with the case of " +
"rehearsal mode.\n" +
"See also this exception's InnerException property.";
throw new UnexpectedlyTerminatedTxRehearsalException(
action, msg, e
throw new UnexpectedlyTerminatedActionException(
null, null, null, action, msg, e
);
}

Expand Down
75 changes: 75 additions & 0 deletions Libplanet/Action/UnexpectedlyTerminatedActionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Security.Cryptography;
using Libplanet.Blockchain.Policies;
using Libplanet.Blocks;
using Libplanet.Tx;

namespace Libplanet.Action
{
/// <summary>
/// The exception that is thrown during an <see cref="IAction"/> is being evaluated.
/// <para>The actual exception that the <see cref="Action"/> threw
/// is stored in the <see cref="Exception.InnerException"/> property.</para>
/// </summary>
[Serializable]
public sealed class UnexpectedlyTerminatedActionException : Exception
{
/// <summary>
/// Creates a new <see cref="UnexpectedlyTerminatedActionException"/> object.
/// </summary>
/// <param name="blockHash">The <see cref="Block{T}.Hash"/> of the <see cref="Block{T}"/>
/// that <paramref name="action"/> belongs to. This can be <c>null</c> on rehearsal mode.
/// </param>
/// <param name="blockIndex">The <see cref="Block{T}.Index"/> of the <see cref="Block{T}"/>
/// that <paramref name="action"/> belongs to. This can be <c>null</c> on rehearsal mode.
/// </param>
/// <param name="txid">The <see cref="Transaction{T}.Id"/> of
/// the <see cref="Transaction{T}"/> that <paramref name="action"/> belongs to.
/// This can be <c>null</c> on rehearsal mode or if <paramref name="action"/> is
/// a <see cref="IBlockPolicy{T}.BlockAction"/>.
/// </param>
/// <param name="action">The <see cref="IAction"/> object which threw an exception.</param>
/// <param name="message">Specifies a <see cref="Exception.Message"/>.</param>
/// <param name="innerException">The actual exception that the <see cref="Action"/> threw.
/// </param>
public UnexpectedlyTerminatedActionException(
HashDigest<SHA256>? blockHash,
long? blockIndex,
TxId? txid,
IAction action,
string message,
Exception innerException
)
: base(message, innerException)
{
BlockHash = blockHash;
BlockIndex = blockIndex;
TxId = txid;
Action = action;
}

/// <summary>
/// The <see cref="Block{T}.Hash"/> of the <see cref="Block{T}"/> that <see cref="Action"/>
/// belongs to. This can be <c>null</c> on rehearsal mode.
/// </summary>
public HashDigest<SHA256>? BlockHash { get; }

/// <summary>
/// The <see cref="Block{T}.Index"/> of the <see cref="Block{T}"/> that <see cref="Action"/>
/// belongs to. This can be <c>null</c> on rehearsal mode.
/// </summary>
public long? BlockIndex { get; }

/// <summary>
/// The <see cref="Transaction{T}.Id"/> of the <see cref="Transaction{T}"/> that
/// <see cref="Action"/> belongs to. This can be <c>null</c> on rehearsal mode or
/// if <see cref="Action"/> is a <see cref="IBlockPolicy{T}.BlockAction"/>.
/// </summary>
public TxId? TxId { get; }

/// <summary>
/// The <see cref="IAction"/> object which threw an exception.
/// </summary>
public IAction Action { get; }
}
}
Loading

0 comments on commit 21ac8d8

Please sign in to comment.