diff --git a/CHANGES.md b/CHANGES.md index 1a2022c706..11c526a7fe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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.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()`. @@ -53,12 +64,13 @@ To be released. - Added `Swarm.PrepareAsync()` method. The method should be called before calling `Swarm.BootstrapAsync()`, `Swarm.PreloadAsync()` and `Swarm.StartAsync()`. [[#353]] - - Added `Swarm.BootstrapAsync()` method to connect with seed peers. [[#353]] + - Added `Swarm.BootstrapAsync()` method to connect with seed peers. + [[#353]] ### Behavioral changes - - `Swarm` now broadcasts transactions as soon as new transactions are received. - [[#463], [#496]] + - `Swarm` now broadcasts transactions as soon as new transactions are + received. [[#463], [#496]] - `Swarm` now ignores block hashes which already exists. [[#461], [#484]] - `Swarm.PreloadAsync()` method became to download precalculated states of blocks from a likely branchpoint instead of a genesis block from @@ -70,10 +82,10 @@ To be released. instead of `HashDigest`. [[#465], [#481]] - NetMQ instances are now initialized at `Swarm.StartAsync()` instead of `Swarm()`. [[#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 @@ -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 @@ -145,8 +158,8 @@ Released on August 22, 2019. - Added `IStore.GetBlockIndex()` method. [[#385]] - `StoreExtension.LookupStateReference()` method became to return - `Tuple, long>` which is a nullable tuple of `Block.Hash` - and `Block.Index`. [[#350]] + `Tuple, long>` which is a nullable tuple of + `Block.Hash` and `Block.Index`. [[#350]] - Added `IBlockPolicy.BlockAction` property. [[#319], [#367]] - Removed the type parameter of `ActionEvaluation`. [[#319], [#367]] - `ActionEvaluation.Action` became to `IAction` type. [[#319], [#367]] @@ -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]] @@ -187,7 +201,8 @@ Released on August 22, 2019. `IComparable` interfaces. [[#363]] - Added `BlockChain.BlockHashes` property. [[#389]] - `Swarm.PreloadAsync(IProgress, IImmutableSet
, - 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]] @@ -214,7 +229,8 @@ Released on August 22, 2019. instead of the window size of a chunk (i.e., 500). [[#396], [#399]] - `Swarm.PreloadAsync()` became to get the first parameter, `progress`, which accepts `IProgress`. [[#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.PreloadAsync()` became safe from data corruption even if a preloading process suddenly gets shutdown. [[#417]] - `FileStore` and `LiteDBStore` became to guarantee atomicity of storing @@ -387,8 +403,8 @@ Released on July 8, 2019. - Improved performance of `Swarm`'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` 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]] @@ -504,7 +520,8 @@ Released on May 31, 2019. [[#232]] - Added `IStore.ForkStateReferences(string, string, Block, IImmutableSet
` method. [[#232]] - - Removed `Block.Validate()` and `Block.EvaluateActions()` method. [[#243]] + - Removed `Block.Validate()` and `Block.EvaluateActions()` method. + [[#243]] - Added `Transaction.Nonce` and `RawTransaction.Nonce` properties. [[#246]] - Added `IStore.GetTxNonce(string, Address)` method. [[#246]] @@ -569,8 +586,8 @@ Released on May 31, 2019. - `BlockChain.Append()` method became to throw `InvalidTxNonceException` when the `Transaction.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 diff --git a/Libplanet.Tests/Common/Action/ThrowException.cs b/Libplanet.Tests/Common/Action/ThrowException.cs index 672adff589..204cccc73c 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 0179cfd822..ad507b3f71 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 33b7ae459b..e49648f62c 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 0000000000..5ab60de428 --- /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 6c7a914f02..6bec7e01f4 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -700,6 +700,7 @@ internal ActionEvaluation EvaluateBlockAction( return ActionEvaluation.EvaluateActionsGradually( block.Hash, block.Index, + null, lastStates, miner, miner, diff --git a/Libplanet/Tx/Transaction.cs b/Libplanet/Tx/Transaction.cs index 5eabe86b4a..f14dc63358 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 IEnumerable return ActionEvaluation.EvaluateActionsGradually( blockHash, blockIndex, + Id, previousStates, minerAddress, Signer, @@ -555,7 +556,7 @@ public IEnumerable /// 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 bacbbc913f..0000000000 --- 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; } - } -}