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

UnexpectedlyTerminatedActionException #498

Merged
merged 2 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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