From 11132c69f4cc653a0376306d53c9ec947b068768 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Wed, 4 Sep 2019 22:31:09 +0900 Subject: [PATCH] UnexpectedlyTerminatedActionException --- CHANGES.md | 10 +++ .../Common/Action/ThrowException.cs | 10 ++- Libplanet.Tests/Tx/TransactionTest.cs | 56 +++++++++++--- Libplanet/Action/ActionEvaluation.cs | 37 +++++---- .../UnexpectedlyTerminatedActionException.cs | 75 +++++++++++++++++++ Libplanet/Blockchain/BlockChain.cs | 1 + Libplanet/Tx/Transaction.cs | 7 +- ...xpectedlyTerminatedTxRehearsalException.cs | 41 ---------- 8 files changed, 164 insertions(+), 73 deletions(-) create mode 100644 Libplanet/Action/UnexpectedlyTerminatedActionException.cs delete mode 100644 Libplanet/Tx/UnexpectedlyTerminatedTxRehearsalException.cs diff --git a/CHANGES.md b/CHANGES.md index 437e52355a4..f1d053228d2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,16 @@ 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: + - `Transaction.EvaluateActions()` + - `Transaction.EvaluateActionsGradually()` + - `Block.EvaluateActionsPerTx()` + - `Block.Evaluate()` + - `BlockChain.GetStates(completeStates: true)` - The concept of "namespaces" in `IStore` was replaced by "chain IDs" to be consistent with `BlockChain`. [[#483], [#486]] - Renamed `IStore.ListNamespaces()` method to `ListChainIds()`. diff --git a/Libplanet.Tests/Common/Action/ThrowException.cs b/Libplanet.Tests/Common/Action/ThrowException.cs index 672adff589a..204cccc73cd 100644 --- a/Libplanet.Tests/Common/Action/ThrowException.cs +++ b/Libplanet.Tests/Common/Action/ThrowException.cs @@ -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 PlainValue => new Dictionary() { - { "throw", Throw }, + { "throw", ThrowOnRehearsal }, }.ToImmutableDictionary(); public void LoadPlainValue(IImmutableDictionary 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."); } diff --git a/Libplanet.Tests/Tx/TransactionTest.cs b/Libplanet.Tests/Tx/TransactionTest.cs index 0179cfd822d..ad507b3f714 100644 --- a/Libplanet.Tests/Tx/TransactionTest.cs +++ b/Libplanet.Tests/Tx/TransactionTest.cs @@ -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; @@ -171,16 +172,22 @@ public void CreateWithMissingRequiredArguments() [Fact] public void CreateWithActionsThrowingException() { - var action = new ThrowException { Throw = true }; - Assert.Throws(() => - Transaction.Create( - 0, - _fx.PrivateKey1, - new[] { action }, - ImmutableHashSet
.Empty, - DateTimeOffset.UtcNow - ) - ); + var action = new ThrowException { ThrowOnRehearsal = true }; + UnexpectedlyTerminatedActionException e = + Assert.Throws(() => + Transaction.Create( + 0, + _fx.PrivateKey1, + new[] { action }, + ImmutableHashSet
.Empty, + DateTimeOffset.UtcNow + ) + ); + Assert.Null(e.BlockHash); + Assert.Null(e.BlockIndex); + Assert.Null(e.TxId); + Assert.Same(action, e.Action); + Assert.IsType(e.InnerException); } [Fact] @@ -715,6 +722,35 @@ public void EvaluateActions() } } + [Fact] + public void EvaluateActionsThrowingException() + { + var action = new ThrowException { ThrowOnRehearsal = false, ThrowOnExecution = true }; + Transaction tx = Transaction.Create( + 0, + _fx.PrivateKey1, + new[] { action }, + ImmutableHashSet
.Empty, + DateTimeOffset.UtcNow + ); + var hash = new HashDigest(GetRandomBytes(HashDigest.Size)); + UnexpectedlyTerminatedActionException e = + Assert.Throws(() => + 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(e.Action); + Assert.IsType(e.InnerException); + } + [Fact] public void Validate() { diff --git a/Libplanet/Action/ActionEvaluation.cs b/Libplanet/Action/ActionEvaluation.cs index 33b7ae459be..e49648f62cc 100644 --- a/Libplanet/Action/ActionEvaluation.cs +++ b/Libplanet/Action/ActionEvaluation.cs @@ -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 @@ -55,12 +57,13 @@ IAccountStateDelta outputStates /// Executes the step by step, and emits /// for each step. /// - /// The of - /// that this will - /// belong to. - /// The of - /// that this will - /// belong to. + /// The of that + /// belongs to. + /// The of that + /// belongs to. + /// The of + /// that belongs to. This can be null on rehearsal mode + /// or if an action is a . /// The states immediately before /// being executed. Note that its are /// remained to the returned next states. @@ -77,17 +80,15 @@ IAccountStateDelta outputStates /// Note that each object /// has a unconsumed state. /// - /// - /// Thrown when one of throws some - /// exception during mode. + /// + /// Thrown when one of throws some exception. /// The actual exception that an threw /// is stored in its property. - /// It is never thrown if the option is - /// false. /// internal static IEnumerable EvaluateActionsGradually( HashDigest blockHash, long blockIndex, + TxId? txid, IAccountStateDelta previousStates, Address minerAddress, Address signer, @@ -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 " + @@ -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 ); } diff --git a/Libplanet/Action/UnexpectedlyTerminatedActionException.cs b/Libplanet/Action/UnexpectedlyTerminatedActionException.cs new file mode 100644 index 00000000000..5ab60de4288 --- /dev/null +++ b/Libplanet/Action/UnexpectedlyTerminatedActionException.cs @@ -0,0 +1,75 @@ +using System; +using System.Security.Cryptography; +using Libplanet.Blockchain.Policies; +using Libplanet.Blocks; +using Libplanet.Tx; + +namespace Libplanet.Action +{ + /// + /// The exception that is thrown during an is being evaluated. + /// The actual exception that the threw + /// is stored in the property. + /// + [Serializable] + public sealed class UnexpectedlyTerminatedActionException : Exception + { + /// + /// Creates a new object. + /// + /// The of the + /// that belongs to. This can be null on rehearsal mode. + /// + /// The of the + /// that belongs to. This can be null on rehearsal mode. + /// + /// The of + /// the that belongs to. + /// This can be null on rehearsal mode or if is + /// a . + /// + /// The object which threw an exception. + /// Specifies a . + /// The actual exception that the threw. + /// + public UnexpectedlyTerminatedActionException( + HashDigest? blockHash, + long? blockIndex, + TxId? txid, + IAction action, + string message, + Exception innerException + ) + : base(message, innerException) + { + BlockHash = blockHash; + BlockIndex = blockIndex; + TxId = txid; + Action = action; + } + + /// + /// The of the that + /// belongs to. This can be null on rehearsal mode. + /// + public HashDigest? BlockHash { get; } + + /// + /// The of the that + /// belongs to. This can be null on rehearsal mode. + /// + public long? BlockIndex { get; } + + /// + /// The of the that + /// belongs to. This can be null on rehearsal mode or + /// if is a . + /// + public TxId? TxId { get; } + + /// + /// The object which threw an exception. + /// + public IAction Action { get; } + } +} diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 6c7a914f027..6bec7e01f4b 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -700,6 +700,7 @@ IReadOnlyList EvaluateActions() return ActionEvaluation.EvaluateActionsGradually( block.Hash, block.Index, + null, lastStates, miner, miner, diff --git a/Libplanet/Tx/Transaction.cs b/Libplanet/Tx/Transaction.cs index 5eabe86b4af..f14dc633589 100644 --- a/Libplanet/Tx/Transaction.cs +++ b/Libplanet/Tx/Transaction.cs @@ -353,7 +353,7 @@ public static Transaction FromBencodex(byte[] bytes) /// is passed to or /// or . /// - /// + /// /// Thrown when one of throws some /// exception during their rehearsal. /// This exception is thrown probably because the logic of some of @@ -503,7 +503,7 @@ public byte[] ToBencodex(bool sign) /// Note that each object has /// a unconsumed state. /// - /// + /// /// Thrown when one of throws some /// exception during mode. /// The actual exception that an threw @@ -524,6 +524,7 @@ public byte[] ToBencodex(bool sign) return ActionEvaluation.EvaluateActionsGradually( blockHash, blockIndex, + Id, previousStates, minerAddress, Signer, @@ -555,7 +556,7 @@ public byte[] ToBencodex(bool sign) /// being executed. Note that it maintains /// of the given /// as well. - /// + /// /// Thrown when one of throws some /// exception during mode. /// The actual exception that an threw diff --git a/Libplanet/Tx/UnexpectedlyTerminatedTxRehearsalException.cs b/Libplanet/Tx/UnexpectedlyTerminatedTxRehearsalException.cs deleted file mode 100644 index bacbbc913fa..00000000000 --- a/Libplanet/Tx/UnexpectedlyTerminatedTxRehearsalException.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Libplanet.Action; - -namespace Libplanet.Tx -{ - /// - /// The exception that is thrown during a - /// is being created when one of - /// throws some exception during rehearsal mode. - /// The actual exception that the threw - /// is stored in the property. - /// - [Serializable] - public sealed class UnexpectedlyTerminatedTxRehearsalException : Exception - { - /// - /// Creates a new object with an . - /// - /// The object which threw - /// an exception. - /// Specifies a . - /// - /// The actual exception that - /// the threw. - public UnexpectedlyTerminatedTxRehearsalException( - IAction action, - string message, - Exception innerException - ) - : base(message, innerException) - { - Action = action; - } - - /// - /// The object which threw an exception. - /// - public IAction Action { get; } - } -}