diff --git a/CHANGES.md b/CHANGES.md index 57246a65923..ec4fd2caaeb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Libplanet changelog =================== -Version 3.4.0 +Version 3.7.0 ------------- To be released. @@ -18,12 +18,149 @@ To be released. ### Behavioral changes + - Slightly optimized `BlockMarshaler`. [[#3454]] + ### Bug fixes ### Dependencies ### CLI tools +[#3454]: https://github.com/planetarium/libplanet/pull/3454 + + +Version 3.6.0 +------------- + +Released on October 6, 2023. + +### Backward-incompatible API changes + + - Changed `IActionEvaluator.Evaluate()`'s return type to + `IReadOnlyList` from + `IReadOnlyList`. [[#3445]] + - Changed `BlockChain.DetermineStateRootHash(IActionEvaluator, + IPreEvaluationBlock, out IReadOnlyList)` to + `BlockChain.DetermineStateRootHash(IActionEvaluator, + IPreEvaluationBlock, out IReadOnlyList)`. + [[#3445]] + - Changed `BlockChain.EvaluateGenesis()`'s return type to + `IReadOnlyList` from + `IReadOnlyList`. [[#3445]] + - Changed `BlockChain.EvaluateBlock()`'s return type to + `IReadOnlyList` from + `IReadOnlyList`. [[#3445]] + - Removed `StateStoreExtensions` class. [[#3323], [#3450]] + +### Added APIs + + - (Libplanet.Explorer) Added `TxResult.InputState` of type + `HashDigest?`. [[#3446], [#3447]] + - (Libplanet.Explorer) Added `TxResult.OutputState` of type + `HashDigest?`. [[#3446], [#3447]] + - (Libplanet.Explorer) Added `offsetStateRootHash` of type + `HashDigest?` argument for `StateQuery.states` field. + [[#3448], [#3449]] + - (Libplanet.Explorer) Added `offsetStateRootHash` of type + `HashDigest?` argument for `StateQuery.balance` field. + [[#3448], [#3449]] + - (Libplanet.Explorer) Added `offsetStateRootHash` of type + `HashDigest?` argument for `StateQuery.totalSupply` field. + [[#3448], [#3449]] + - (Libplanet.Explorer) Added `offsetStateRootHash` of type + `HashDigest?` argument for `StateQuery.validators` field. + [[#3448], [#3449]] + +### Behavioral changes + + - `IActionEvaluator.Evaluate()`, `BlockChain.EvaluateGenesis()`, + and `BlockChain.EvaluateBlock()` have a side-effect of storing + data to `IStateStore` when called. [[#3445]] + +[#3323]: https://github.com/planetarium/libplanet/issues/3323 +[#3445]: https://github.com/planetarium/libplanet/pull/3445 +[#3446]: https://github.com/planetarium/libplanet/issues/3446 +[#3447]: https://github.com/planetarium/libplanet/pull/3447 +[#3448]: https://github.com/planetarium/libplanet/issues/3448 +[#3449]: https://github.com/planetarium/libplanet/pull/3449 +[#3450]: https://github.com/planetarium/libplanet/pull/3450 + + +Version 3.5.0 +------------- + +Released on October 4, 2023. + +### Backward-incompatible API changes + + - Removed `IActionContext.Random` property. Use `IActionContext.GetRandom()` + instead. [[#3437]] + - Added `IActionContext.RandomSeed` property. [[#3437]] + - Added `IActionContext.GetRandom()` method. [[#3437]] + - Changed `IActionEvaluator.Evaluate(IPreEvaluationBlock)` to + `IActionEvaluator.Evaluate(IPreEvaluationBlock, HashDigest)`. + [[#3438]] + - Changed `ActionEvaluator` to accept `IStateStore` instead of + `IBlockChainStates` [[#3439]] + +[#3437]: https://github.com/planetarium/libplanet/pull/3437 +[#3438]: https://github.com/planetarium/libplanet/pull/3438 +[#3439]: https://github.com/planetarium/libplanet/pull/3439 + + +Version 3.4.0 +------------- + +Released on September 25, 2023. + +### Backward-incompatible API changes + + - Added `IBlockChainStates.GetAccountState(HashDigest?)` + interface method. [[#3425]] + - Removed `TxFailure.ExceptionMetadata` property. [[#3428]] + - Removed `ISerializable` interface from `TxExecution`, `TxSuccess`, + and `TxFailure`. [[#3428]] + - Removed `TxSuccess` and `TxFailure` class. [[#3429]] + - Changed `TxExecution` class as `sealed` from `abstract.` [[#3429]] + - All properties of `TxExecution` except `BlockHash` and `TxId` were + overhauled. [[#3429]] + - (Libplanet.Store) Removed `IStore.PutTxExecution(TxSuccess)` and + `IStore.PutTxExecution(TxFailure)`; + added `IStore.PutTxExecution(TxExecution)`. [[#3429]] + - (Libplanet.Explorer) Removed `TxResult.ExceptionName` of type `string?` + and added `TxResult.ExceptionNames` of type `List?`. [[#3429]] + - (Libplanet.Explorer) Removed `TxResult.UpdatedStates` and + `TxResult.UpdatedFungibleAssets`. [[#3429]] + - Changed `IActionRenderer.RenderAction(IValue, IActionContext, IAccount)` + to `IActionRenderer.RenderAction(IValue, ICommittedActionContext, + HashDigest)`. [[#3431]] + - Changed `IActionRenderer.RenderActionError(IValue, IActionContext, + Exception)` to `IActionRenderer.RenderActionError(IValue, + ICommittedActionContext, Exception)`. [[#3431]] + +### Added APIs + + - Added `AccountDiff` class. [[#3424]] + - Added `ICommittedActionContext` interface. [[#3431]] + - Added `ICommittedActionEvaluation` interface. [[#3431]] + +[#3424]: https://github.com/planetarium/libplanet/pull/3424 +[#3425]: https://github.com/planetarium/libplanet/pull/3425 +[#3428]: https://github.com/planetarium/libplanet/pull/3428 +[#3429]: https://github.com/planetarium/libplanet/pull/3429 +[#3431]: https://github.com/planetarium/libplanet/pull/3431 + + +Version 3.3.1 +------------- + +Released on September 8, 2023. + +- (Libplanet.Store) Fixed a bug where `ITrie.Get()` could wrongly retrieve + an `IValue` from a non-existent path. [[#3420]] + +[#3420]: https://github.com/planetarium/libplanet/pull/3420 + Version 3.3.0 ------------- diff --git a/Libplanet.Action.Tests/ActionContextTest.cs b/Libplanet.Action.Tests/ActionContextTest.cs index 493d31c470a..4044de57780 100644 --- a/Libplanet.Action.Tests/ActionContextTest.cs +++ b/Libplanet.Action.Tests/ActionContextTest.cs @@ -40,7 +40,7 @@ public void RandomShouldBeDeterministic() randomSeed: seed, gasLimit: 0 ); - IRandom random = context.Random; + IRandom random = context.GetRandom(); Assert.Equal(expected, random.Next()); } } @@ -93,11 +93,14 @@ public void GuidShouldBeDeterministic() ), }; + var rand1 = context1.GetRandom(); + var rand2 = context2.GetRandom(); + var rand3 = context3.GetRandom(); foreach (var (expected, diff) in testCases) { - Assert.Equal(expected, context1.Random.GenerateRandomGuid()); - Assert.Equal(expected, context2.Random.GenerateRandomGuid()); - Assert.Equal(diff, context3.Random.GenerateRandomGuid()); + Assert.Equal(expected, rand1.GenerateRandomGuid()); + Assert.Equal(expected, rand2.GenerateRandomGuid()); + Assert.Equal(diff, rand3.GenerateRandomGuid()); } } @@ -116,39 +119,11 @@ public void GuidVersionAndVariant() randomSeed: i, gasLimit: 0 ); - var guid = context.Random.GenerateRandomGuid().ToString(); + var guid = context.GetRandom().GenerateRandomGuid().ToString(); Assert.Equal('4', guid[14]); Assert.True(guid[19] >= '8' && guid[19] <= 'b'); } } - - [Fact] - public void GetUnconsumedContext() - { - var original = new ActionContext( - signer: _address, - txid: _txid, - miner: _address, - blockIndex: 1, - blockProtocolVersion: Block.CurrentProtocolVersion, - previousState: new Account(MockAccountState.Empty), - randomSeed: _random.Next(), - gasLimit: 0); - - // Consume original's random state... - int[] values = - { - original.Random.Next(), - original.Random.Next(), - original.Random.Next(), - }; - - IActionContext clone = original.GetUnconsumedContext(); - Assert.Equal( - values, - new[] { clone.Random.Next(), clone.Random.Next(), clone.Random.Next() } - ); - } } } diff --git a/Libplanet.Action.Tests/Common/DumbAction.cs b/Libplanet.Action.Tests/Common/DumbAction.cs index 066ce6fbc67..cb2f6a1c439 100644 --- a/Libplanet.Action.Tests/Common/DumbAction.cs +++ b/Libplanet.Action.Tests/Common/DumbAction.cs @@ -178,7 +178,7 @@ public IAccount Execute(IActionContext context) { states = states.SetState( RandomRecordsAddress, - (Integer)context.Random.Next() + (Integer)context.GetRandom().Next() ); } diff --git a/Libplanet.Action.Tests/Common/RandomAction.cs b/Libplanet.Action.Tests/Common/RandomAction.cs index e5f7439a92b..6c71de6b1e2 100644 --- a/Libplanet.Action.Tests/Common/RandomAction.cs +++ b/Libplanet.Action.Tests/Common/RandomAction.cs @@ -36,7 +36,7 @@ public IAccount Execute(IActionContext context) return states.SetState(Address, Null.Value); } - return states.SetState(Address, (Integer)context.Random.Next()); + return states.SetState(Address, (Integer)context.GetRandom().Next()); } } } diff --git a/Libplanet.Action.Tests/Sys/InitializeTest.cs b/Libplanet.Action.Tests/Sys/InitializeTest.cs index ff4243e9336..5c6bea6328c 100644 --- a/Libplanet.Action.Tests/Sys/InitializeTest.cs +++ b/Libplanet.Action.Tests/Sys/InitializeTest.cs @@ -59,10 +59,10 @@ public void Execute() validatorSet: _validatorSet ); - var nextStates = initialize.Execute(context); + var nextState = initialize.Execute(context); - Assert.Equal(_validatorSet, nextStates.GetValidatorSet()); - Assert.Equal(_states[default], nextStates.GetState(default)); + Assert.Equal(_validatorSet, nextState.GetValidatorSet()); + Assert.Equal(_states[default], nextState.GetState(default)); } [Fact] diff --git a/Libplanet.Action/ActionContext.cs b/Libplanet.Action/ActionContext.cs index 4c0b3bfce21..179c42bcf30 100644 --- a/Libplanet.Action/ActionContext.cs +++ b/Libplanet.Action/ActionContext.cs @@ -11,7 +11,6 @@ internal class ActionContext : IActionContext { public static readonly AsyncLocal GetGasMeter = new AsyncLocal(); - private readonly int _randomSeed; private readonly long _gasLimit; public ActionContext( @@ -32,8 +31,7 @@ public ActionContext( BlockProtocolVersion = blockProtocolVersion; Rehearsal = rehearsal; PreviousState = previousState; - Random = new Random(randomSeed); - _randomSeed = randomSeed; + RandomSeed = randomSeed; _gasLimit = gasLimit; GetGasMeter.Value = new GasMeter(_gasLimit); @@ -60,8 +58,8 @@ public ActionContext( /// public IAccount PreviousState { get; } - /// - public IRandom Random { get; } + /// + public int RandomSeed { get; } /// public bool BlockAction => TxId is null; @@ -69,18 +67,8 @@ public ActionContext( /// public void UseGas(long gas) => GetGasMeter.Value?.UseGas(gas); - [Pure] - public IActionContext GetUnconsumedContext() => - new ActionContext( - Signer, - TxId, - Miner, - BlockIndex, - BlockProtocolVersion, - PreviousState, - _randomSeed, - _gasLimit, - Rehearsal); + /// + public IRandom GetRandom() => new Random(RandomSeed); /// /// Returns the elapsed gas of the current action. diff --git a/Libplanet.Action/ActionEvaluator.cs b/Libplanet.Action/ActionEvaluator.cs index 094fda3a82a..ffdfbae2e91 100644 --- a/Libplanet.Action/ActionEvaluator.cs +++ b/Libplanet.Action/ActionEvaluator.cs @@ -10,7 +10,8 @@ using Libplanet.Action.Loader; using Libplanet.Action.State; using Libplanet.Common; -using Libplanet.Crypto; +using Libplanet.Store; +using Libplanet.Store.Trie; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using Serilog; @@ -24,7 +25,7 @@ public class ActionEvaluator : IActionEvaluator { private readonly ILogger _logger; private readonly PolicyBlockActionGetter _policyBlockActionGetter; - private readonly IBlockChainStates _blockChainStates; + private readonly IStateStore _stateStore; private readonly IActionLoader _actionLoader; /// @@ -32,19 +33,19 @@ public class ActionEvaluator : IActionEvaluator /// /// A delegator to get policy block action to evaluate /// at the end for each that gets evaluated. - /// The to use to retrieve - /// the states for a provided . + /// The to use to retrieve + /// the states for a provided . /// A implementation using /// action type lookup. public ActionEvaluator( PolicyBlockActionGetter policyBlockActionGetter, - IBlockChainStates blockChainStates, + IStateStore stateStore, IActionLoader actionTypeLoader) { _logger = Log.ForContext() .ForContext("Source", nameof(ActionEvaluator)); _policyBlockActionGetter = policyBlockActionGetter; - _blockChainStates = blockChainStates; + _stateStore = stateStore; _actionLoader = actionTypeLoader; } @@ -76,7 +77,9 @@ public static int GenerateRandomSeed( /// [Pure] - public IReadOnlyList Evaluate(IPreEvaluationBlock block) + public IReadOnlyList Evaluate( + IPreEvaluationBlock block, + HashDigest? baseStateRootHash) { _logger.Information( "Evaluating actions in the block #{BlockIndex} " + @@ -88,24 +91,20 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) stopwatch.Start(); try { - IAccount previousState = PrepareInitialDelta(block); + IAccount previousState = PrepareInitialDelta(baseStateRootHash); ImmutableList evaluations = EvaluateBlock(block, previousState).ToImmutableList(); var policyBlockAction = _policyBlockActionGetter(block); - if (policyBlockAction is null) - { - return evaluations; - } - else + if (policyBlockAction is { } blockAction) { previousState = evaluations.Count > 0 ? evaluations.Last().OutputState : previousState; - return evaluations.Add( - EvaluatePolicyBlockAction(block, previousState) - ); + evaluations = evaluations.Add(EvaluatePolicyBlockAction(block, previousState)); } + + return ToCommittedEvaluation(block, evaluations, baseStateRootHash); } finally { @@ -188,7 +187,7 @@ IActionContext CreateActionContext( long gasLimit = tx?.GasLimit ?? long.MaxValue; - byte[] signature = tx?.Signature ?? new byte[0]; + byte[] signature = tx?.Signature ?? Array.Empty(); byte[] hashedSignature; using (var hasher = SHA1.Create()) { @@ -228,8 +227,7 @@ internal static (ActionEvaluation Evaluation, long NextGasLimit) EvaluateAction( IAction action, ILogger? logger = null) { - // Make a copy since ActionContext is stateful. - IActionContext inputContext = context.GetUnconsumedContext(); + IActionContext inputContext = context; IAccount state = inputContext.PreviousState; Exception? exc = null; IFeeCollector feeCollector = new FeeCollector(context, tx?.MaxGasPrice); @@ -243,7 +241,7 @@ IActionContext CreateActionContext(IAccount newPrevState) blockIndex: inputContext.BlockIndex, blockProtocolVersion: inputContext.BlockProtocolVersion, previousState: newPrevState, - randomSeed: inputContext.Random.Seed, + randomSeed: inputContext.RandomSeed, gasLimit: inputContext.GasLimit()); } @@ -473,17 +471,67 @@ internal ActionEvaluation EvaluatePolicyBlockAction( actions: new[] { policyBlockAction }.ToImmutableList()).Single(); } - /// - /// Prepares the initial to for evaluating - /// . - /// - /// The to evaluate.. - /// The initial to be used - /// for evaluating . - /// - internal IAccount PrepareInitialDelta(IPreEvaluationBlock block) + internal IAccount PrepareInitialDelta(HashDigest? stateRootHash) + { + return new Account(new AccountState(_stateStore.GetStateRoot(stateRootHash))); + } + + internal IReadOnlyList + ToCommittedEvaluation( + IPreEvaluationBlock block, + IReadOnlyList evaluations, + HashDigest? baseStateRootHash) { - return new Account(_blockChainStates.GetAccountState(block.PreviousHash)); + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + ITrie trie = _stateStore.GetStateRoot(baseStateRootHash); + var committedEvaluations = new List(); + + int setCount = 0; + foreach (var evaluation in evaluations) + { + ITrie nextTrie = trie; + foreach (var kv in evaluation.OutputState.Delta.ToRawDelta()) + { + nextTrie = nextTrie.Set(kv.Key, kv.Value); + setCount++; + } + + nextTrie = _stateStore.Commit(nextTrie); + var committedEvaluation = new CommittedActionEvaluation( + action: evaluation.Action, + inputContext: new CommittedActionContext( + signer: evaluation.InputContext.Signer, + txId: evaluation.InputContext.TxId, + miner: evaluation.InputContext.Miner, + blockIndex: evaluation.InputContext.BlockIndex, + blockProtocolVersion: evaluation.InputContext.BlockProtocolVersion, + rehearsal: evaluation.InputContext.Rehearsal, + previousState: trie.Hash, + randomSeed: evaluation.InputContext.RandomSeed, + blockAction: evaluation.InputContext.BlockAction), + outputState: nextTrie.Hash, + exception: evaluation.Exception); + committedEvaluations.Add(committedEvaluation); + + trie = nextTrie; + } + + _logger + .ForContext("Tag", "Metric") + .ForContext("Subtag", "StateUpdateDuration") + .Information( + "Took {DurationMs} ms to update the states with {Count} key changes " + + "and resulting in state root hash {StateRootHash} for " + + "block #{BlockIndex} pre-evaluation hash {PreEvaluationHash}", + stopwatch.ElapsedMilliseconds, + setCount, + trie.Hash, + block.Index, + block.PreEvaluationHash); + + return committedEvaluations; } [Pure] diff --git a/Libplanet.Action/CommittedActionContext.cs b/Libplanet.Action/CommittedActionContext.cs new file mode 100644 index 00000000000..ea819d0790e --- /dev/null +++ b/Libplanet.Action/CommittedActionContext.cs @@ -0,0 +1,86 @@ +using System.Diagnostics.Contracts; +using System.Security.Cryptography; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Tx; + +namespace Libplanet.Action +{ + public class CommittedActionContext : ICommittedActionContext + { + public CommittedActionContext(IActionContext context) + : this( + signer: context.Signer, + txId: context.TxId, + miner: context.Miner, + blockIndex: context.BlockIndex, + blockProtocolVersion: context.BlockProtocolVersion, + rehearsal: context.Rehearsal, + previousState: context.PreviousState.Trie.Hash, + randomSeed: context.RandomSeed, + blockAction: context.BlockAction) + { + } + + public CommittedActionContext( + Address signer, + TxId? txId, + Address miner, + long blockIndex, + int blockProtocolVersion, + bool rehearsal, + HashDigest previousState, + int randomSeed, + bool blockAction) + { + Signer = signer; + TxId = txId; + Miner = miner; + BlockIndex = blockIndex; + BlockProtocolVersion = blockProtocolVersion; + Rehearsal = rehearsal; + PreviousState = previousState; + RandomSeed = randomSeed; + BlockAction = blockAction; + } + + /// + [Pure] + public Address Signer { get; } + + /// + [Pure] + public TxId? TxId { get; } + + /// + [Pure] + public Address Miner { get; } + + /// + [Pure] + public long BlockIndex { get; } + + /// + [Pure] + public int BlockProtocolVersion { get; } + + /// + [Pure] + public bool Rehearsal { get; } + + /// + [Pure] + public HashDigest PreviousState { get; } + + /// + public int RandomSeed { get; } + + /// + [Pure] + public bool BlockAction { get; } + + /// + [Pure] + public IRandom GetRandom() => new Random(RandomSeed); + } +} diff --git a/Libplanet.Action/CommittedActionEvaluation.cs b/Libplanet.Action/CommittedActionEvaluation.cs new file mode 100644 index 00000000000..6f7e04ff10d --- /dev/null +++ b/Libplanet.Action/CommittedActionEvaluation.cs @@ -0,0 +1,60 @@ +using System; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Common; + +namespace Libplanet.Action +{ + /// + /// A record type to represent an evaluation plan and result of + /// a single action. + /// + public class CommittedActionEvaluation : ICommittedActionEvaluation + { + /// + /// Creates an instance + /// with filling properties. + /// + /// An action to evaluate. + /// An input to + /// evaluate . + /// The result states that + /// makes. + /// An exception that has risen during evaluating a given + /// . + public CommittedActionEvaluation( + IValue action, + ICommittedActionContext inputContext, + HashDigest outputState, + Exception? exception = null) + { + Action = action; + InputContext = inputContext; + OutputState = outputState; + Exception = exception; + } + + /// + /// An action to evaluate. + /// + public IValue Action { get; } + + /// + /// An input to evaluate + /// . + /// + /// Its property + /// is not consumed yet. + public ICommittedActionContext InputContext { get; } + + /// + /// The result states that makes. + /// + public HashDigest OutputState { get; } + + /// + /// An exception that had risen during evaluation. + /// + public Exception? Exception { get; } + } +} diff --git a/Libplanet.Action/IActionContext.cs b/Libplanet.Action/IActionContext.cs index f3996bc678c..793ea3c2d78 100644 --- a/Libplanet.Action/IActionContext.cs +++ b/Libplanet.Action/IActionContext.cs @@ -73,15 +73,15 @@ public interface IActionContext IAccount PreviousState { get; } /// - /// An initialized pseudorandom number generator. Its seed (state) - /// is determined by a block and a transaction, which is + /// The random seed to use for pseudorandom number generator. This value + /// is determined by various block properties, the signature of the transaction + /// containing the action to execute, and index of the action to execute, which is /// deterministic so that every node can replay the same action and /// then reproduce the same result, while neither a single block miner /// nor a single transaction signer can predict the result and cheat. /// - /// A random object that shares interface mostly equivalent - /// to . - IRandom Random { get; } + /// + int RandomSeed { get; } /// /// Whether this action is executed as a block action. @@ -99,15 +99,13 @@ public interface IActionContext void UseGas(long gas); /// - /// Returns a clone of this context, except that its has the unconsumed - /// state (with the same seed). The clone and its original are a distinct instance - /// each other, in other words, one's state transfer must not affect the other one - /// (i.e., consuming source should be local to a context instance). + /// Returns a newly initialized using + /// as its seed value. /// - /// A clone instance, which is distinct from its original. Its internal state - /// is entirely equivalent to the original's unconsumed initial state. + /// A newly initialized using + /// as its seed value. [Pure] - IActionContext GetUnconsumedContext(); + IRandom GetRandom(); /// /// Returns the total gas used by the current action. diff --git a/Libplanet.Action/IActionEvaluator.cs b/Libplanet.Action/IActionEvaluator.cs index d72d0e7864f..39c802e78a5 100644 --- a/Libplanet.Action/IActionEvaluator.cs +++ b/Libplanet.Action/IActionEvaluator.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Security.Cryptography; using Libplanet.Action.Loader; +using Libplanet.Common; +using Libplanet.Store; using Libplanet.Types.Blocks; namespace Libplanet.Action @@ -18,17 +21,25 @@ public interface IActionEvaluator /// The main entry point for evaluating a . /// /// The block to evaluate. + /// The base state to use when evaluating + /// . /// The result of evaluating every related to /// as an of - /// s. + /// s. /// - /// Publicly exposed for benchmarking. - /// First evaluates all s in + /// + /// This has a side-effect of writing data to internally held . + /// + /// + /// First evaluates all s in /// of and appends the /// evaluation of the held by the instance at - /// the end. + /// the end. + /// /// [Pure] - IReadOnlyList Evaluate(IPreEvaluationBlock block); + IReadOnlyList Evaluate( + IPreEvaluationBlock block, + HashDigest? baseStateRootHash); } } diff --git a/Libplanet.Action/ICommittedActionContext.cs b/Libplanet.Action/ICommittedActionContext.cs new file mode 100644 index 00000000000..d4e2440c92d --- /dev/null +++ b/Libplanet.Action/ICommittedActionContext.cs @@ -0,0 +1,95 @@ +using System.Diagnostics.Contracts; +using System.Security.Cryptography; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Blocks; +using Libplanet.Types.Tx; + +namespace Libplanet.Action +{ + /// + /// Contextual data determined by a transaction and a block for rendering. + /// + public interface ICommittedActionContext + { + /// + /// The of the that contains + /// the to be executed. If the is + /// not part of a , e.g. , + /// this is set to instead. + /// + [Pure] + Address Signer { get; } + + /// + /// The of the that contains + /// the . If the is not part of + /// a , e.g. , + /// this is set to . + /// + [Pure] + TxId? TxId { get; } + + /// + /// The of the that contains + /// the . + /// + [Pure] + Address Miner { get; } + + /// + /// The of the that contains + /// the . + /// + [Pure] + long BlockIndex { get; } + + /// + /// The of the that contains + /// the . + /// + [Pure] + int BlockProtocolVersion { get; } + + /// + /// Whether an is being executed during + /// “rehearsal mode”, that there is nothing + /// in . + /// + [Pure] + bool Rehearsal { get; } + + /// + /// The state root hash of the previous state. + /// + [Pure] + HashDigest PreviousState { get; } + + /// + /// The random seed to use for pseudorandom number generator. This value + /// is determined by various block properties, the signature of the transaction + /// containing the action to execute, and index of the action to execute, which is + /// deterministic so that every node can replay the same action and + /// then reproduce the same result, while neither a single block miner + /// nor a single transaction signer can predict the result and cheat. + /// + /// + int RandomSeed { get; } + + /// + /// Whether this action is executed as a block action. + /// if it belongs to a transaction. + /// + [Pure] + bool BlockAction { get; } + + /// + /// Returns a newly initialized using + /// as its seed value. + /// + /// A newly initialized using + /// as its seed value. + [Pure] + IRandom GetRandom(); + } +} diff --git a/Libplanet.Action/ICommittedActionEvaluation.cs b/Libplanet.Action/ICommittedActionEvaluation.cs new file mode 100644 index 00000000000..3fea4f8d9d8 --- /dev/null +++ b/Libplanet.Action/ICommittedActionEvaluation.cs @@ -0,0 +1,35 @@ +using System; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Common; + +namespace Libplanet.Action +{ + public interface ICommittedActionEvaluation + { + /// + /// An action data to evaluate. When the + /// . is true, + /// use instead of trying deserialization. + /// + public IValue Action { get; } + + /// + /// An input to evaluate + /// . + /// + /// Its property + /// is not consumed yet. + public ICommittedActionContext InputContext { get; } + + /// + /// The result states that makes. + /// + public HashDigest OutputState { get; } + + /// + /// An exception that had risen during evaluation. + /// + public Exception? Exception { get; } + } +} diff --git a/Libplanet.Action/Random.cs b/Libplanet.Action/Random.cs index 294611db661..a5f974322b7 100644 --- a/Libplanet.Action/Random.cs +++ b/Libplanet.Action/Random.cs @@ -1,6 +1,3 @@ -#nullable disable -using System; - namespace Libplanet.Action { internal class Random : System.Random, IRandom diff --git a/Libplanet.Action/State/AccountDiff.cs b/Libplanet.Action/State/AccountDiff.cs new file mode 100644 index 00000000000..dc414d63ed2 --- /dev/null +++ b/Libplanet.Action/State/AccountDiff.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Store.Trie; +using Libplanet.Types.Assets; +using Libplanet.Types.Consensus; + +namespace Libplanet.Action.State +{ + /// + /// Represents a difference between two s. + /// This is a partial interpretation of a raw difference obtained by + /// from 's perspective. Keep in mind of the following properties: + /// + /// + /// Any value, which is equivalent to non-existent value in + /// the underlying storage, in the source is ignored. That is, even if + /// the value in the target and the value in the source are different while + /// the value in the source is , this will not be + /// part of the resulting . + /// + /// + /// Any value, again, which is equivalent to non-existent value + /// in the underlying storage, in the target for + /// and is interpreted accordingly. That is, + /// 0 amount of and empty + /// are used. This is in accordance with how + /// and would behave. + /// + /// + /// Due to the reason mentioned directly above, the size of + /// derived from may not be the same. Moreover, + /// an being empty does not guarantee + /// that the data are the same as is not capable of + /// distinguishing between and 0 + /// and so on and so forth. + /// + /// + /// As information is in the domain of an application using + /// this library, only the hash of a is directly stored in + /// the underlying storage. As such, each and + /// are handled as raw values, that is, as hash and + /// , for an . + /// + /// + /// + public class AccountDiff + { + private static readonly int _addressKeyLength = Address.Size * 2; + + private static readonly int _currencyKeyLength = HashDigest.Size * 2; + + private static readonly int _stateKeyLength = _addressKeyLength; + + private static readonly int _fungibleAssetKeyLength = + _addressKeyLength + _currencyKeyLength + 2; + + private static readonly int _totalSupplyKeyLength = _currencyKeyLength + 2; + + private static readonly int _validatorSetKeyLength = 3; + + private static readonly ImmutableDictionary _reverseConversionTable = + new Dictionary() + { + [48] = 0, // '0' + [49] = 1, // '1' + [50] = 2, // '2' + [51] = 3, // '3' + [52] = 4, // '4' + [53] = 5, // '5' + [54] = 6, // '6' + [55] = 7, // '7' + [56] = 8, // '8' + [57] = 9, // '9' + [97] = 10, // 'a' + [98] = 11, // 'b' + [99] = 12, // 'c' + [100] = 13, // 'd' + [101] = 14, // 'e' + [102] = 15, // 'f' + }.ToImmutableDictionary(); + + private AccountDiff( + ImmutableDictionary stateDiff, + ImmutableDictionary<(Address, HashDigest), (Integer, Integer)> + fungibleAssetValueDiff, + ImmutableDictionary, (Integer, Integer)> + totalSupplyDiff, + (ValidatorSet, ValidatorSet)? validatorSetDiff) + { + StateDiffs = stateDiff; + FungibleAssetValueDiffs = fungibleAssetValueDiff; + TotalSupplyDiffs = totalSupplyDiff; + ValidatorSetDiff = validatorSetDiff; + } + + public ImmutableDictionary StateDiffs { get; } + + public ImmutableDictionary<(Address, HashDigest), (Integer, Integer)> + FungibleAssetValueDiffs { get; } + + public ImmutableDictionary, (Integer, Integer)> + TotalSupplyDiffs { get; } + + public (ValidatorSet, ValidatorSet)? ValidatorSetDiff { get; } + + /// + /// Creates an instance from given parameters. + /// + /// The to use as the target. + /// The to use as the source. + /// An created from given parameters. + /// Note that the ordering of the parameters are flipped compared to + /// for syntactical reasons. + /// + /// + public static AccountDiff Create(IAccountState target, IAccountState source) + => Create(target.Trie, source.Trie); + + /// + /// Creates an instance from given parameters. + /// + /// The to use as the target. + /// The to use as the source. + /// An created from given parameters. + /// Thrown when the diff internally obtained from + /// cannot be properly interpreted. + /// Note that the ordering of the parameters are flipped compared to + /// for syntactical reasons. + /// + /// + public static AccountDiff Create(ITrie target, ITrie source) + { + var rawDiffs = source.Diff(target).ToList(); + + Dictionary stateDiffs = + new Dictionary(); + Dictionary<(Address, HashDigest), (Integer, Integer)> favDiffs = + new Dictionary<(Address, HashDigest), (Integer, Integer)>(); + Dictionary, (Integer, Integer)> totalSupplyDiffs = + new Dictionary, (Integer, Integer)>(); + (ValidatorSet, ValidatorSet)? validatorSetDiff = null; + + foreach (var diff in rawDiffs) + { + // NOTE: Cannot use switch as some lengths cannot be derived as const. + if (diff.Path.Length == _stateKeyLength) + { + var sd = ToStateDiff(diff); + stateDiffs[sd.Address] = (sd.TargetValue, sd.SourceValue); + } + else if (diff.Path.Length == _fungibleAssetKeyLength) + { + var favd = ToFAVDiff(diff); + + // NOTE: Only add when different. Actual stored data may be different + // as 0 value can also be represented as null. + if (!favd.SourceValue.Equals(favd.TargetValue)) + { + favDiffs[(favd.Address, favd.Currency)] = + (favd.TargetValue, favd.SourceValue); + } + } + else if (diff.Path.Length == _totalSupplyKeyLength) + { + var tsd = ToTotalSupplyDiff(diff); + + // NOTE: Only add when different. Actual stored data may be different + // as 0 value can also be represented as null. + if (!tsd.SourceValue.Equals(tsd.TargetValue)) + { + totalSupplyDiffs[tsd.Currency] = (tsd.TargetValue, tsd.SourceValue); + } + } + else if (diff.Path.Length == _validatorSetKeyLength) + { + var vsd = ToValidatorSetDiff(diff); + + // NOTE: Only set when different. Actual stored data may be different + // as empty validator set can also be represented as null. + if (!vsd.SourceValue.Equals(vsd.TargetValue)) + { + validatorSetDiff = (vsd.TargetValue, vsd.SourceValue); + } + } + else + { + throw new ArgumentException( + $"Encountered different values at an invalid location: {diff.Path}"); + } + } + + return new AccountDiff( + stateDiffs.ToImmutableDictionary(), + favDiffs.ToImmutableDictionary(), + totalSupplyDiffs.ToImmutableDictionary(), + validatorSetDiff); + } + + internal static (Address Address, IValue? TargetValue, IValue SourceValue) + ToStateDiff((KeyBytes Path, IValue? TargetValue, IValue SourceValue) encoded) + { + return ( + ToAddress(encoded.Path.ToByteArray()), + encoded.TargetValue, + encoded.SourceValue); + } + + internal static ( + Address Address, + HashDigest Currency, + Integer TargetValue, + Integer SourceValue) ToFAVDiff( + (KeyBytes Path, IValue? TargetValue, IValue SourceValue) encoded) + { + Address address = + ToAddress( + encoded.Path.ByteArray.Skip(1).Take(_addressKeyLength).ToArray()); + HashDigest currencyHash = + ToCurrencyHash( + encoded.Path.ByteArray + .Skip(_addressKeyLength + 2) + .Take(_currencyKeyLength) + .ToArray()); + Integer sourceValue = encoded.SourceValue is Integer sourceInteger + ? sourceInteger + : throw new ArgumentException( + $"Expected an {nameof(Integer)} but encountered an invalid value " + + $"{encoded.SourceValue} at {encoded.Path}"); + Integer targetValue = encoded.TargetValue is { } value + ? value is Integer targetInteger + ? targetInteger + : throw new ArgumentException( + $"Expected an {nameof(Integer)} but encountered " + + $"an invalid value {encoded.TargetValue} at {encoded.Path}") + : new Integer(0); + + return (address, currencyHash, targetValue, sourceValue); + } + + internal static ( + HashDigest Currency, + Integer TargetValue, + Integer SourceValue) ToTotalSupplyDiff( + (KeyBytes Path, IValue? TargetValue, IValue SourceValue) encoded) + { + HashDigest currencyHash = + ToCurrencyHash( + encoded.Path.ByteArray + .Skip(2) + .Take(_currencyKeyLength) + .ToArray()); + Integer sourceValue = encoded.SourceValue is Integer sourceInteger + ? sourceInteger + : throw new ArgumentException( + $"Expected an {nameof(Integer)} but encountered an invalid value " + + $"{encoded.SourceValue} at {encoded.Path}"); + Integer targetValue = encoded.TargetValue is { } value + ? value is Integer targetInteger + ? targetInteger + : throw new ArgumentException( + $"Expected an {nameof(Integer)} but encountered " + + $"an invalid value {encoded.TargetValue} at {encoded.Path}") + : new Integer(0); + + return (currencyHash, targetValue, sourceValue); + } + + internal static (ValidatorSet TargetValue, ValidatorSet SourceValue) + ToValidatorSetDiff( + (KeyBytes Path, IValue? TargetValue, IValue SourceValue) encoded) + { + if (encoded.Path.Equals(KeyConverters.ValidatorSetKey)) + { + ValidatorSet sourceVS = new ValidatorSet(encoded.SourceValue); + ValidatorSet targetVS = encoded.TargetValue is { } value + ? new ValidatorSet(value) + : new ValidatorSet(); + return (targetVS, sourceVS); + } + else + { + throw new ArgumentException( + $"Encountered different values at an invalid location: {encoded.Path}"); + } + } + + internal static Address FromStateKey(KeyBytes key) + { + if (key.Length != _stateKeyLength) + { + throw new ArgumentException( + $"Given {nameof(key)} must be of length {_stateKeyLength}: {key.Length}"); + } + + byte[] buffer = new byte[Address.Size]; + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = Pack(key.ByteArray[i * 2], key.ByteArray[i * 2 + 1]); + } + + return new Address(buffer); + } + + internal static Address ToAddress(byte[] bytes) + { + if (bytes.Length != _stateKeyLength) + { + throw new ArgumentException( + $"Given {nameof(bytes)} must be of length {_stateKeyLength}: {bytes.Length}"); + } + + byte[] buffer = new byte[Address.Size]; + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = Pack(bytes[i * 2], bytes[i * 2 + 1]); + } + + return new Address(buffer); + } + + // FIXME: This assumes to know that hash algorithm used by Currency is SHA1. + internal static HashDigest ToCurrencyHash(byte[] bytes) + { + var expectedLength = _currencyKeyLength; + if (bytes.Length != expectedLength) + { + throw new ArgumentException( + $"Given {nameof(bytes)} must be of length {_currencyKeyLength}: " + + $"{bytes.Length}"); + } + + byte[] buffer = new byte[HashDigest.Size]; + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = Pack(bytes[i * 2], bytes[i * 2 + 1]); + } + + return new HashDigest(buffer); + } + + // FIXME: Assumes both x and y are less than 16. + private static byte Pack(byte x, byte y) => + (byte)((_reverseConversionTable[x] << 4) + _reverseConversionTable[y]); + } +} diff --git a/Libplanet.Action/State/IBlockChainStates.cs b/Libplanet.Action/State/IBlockChainStates.cs index 3e2fa0b344d..a33f010fa1a 100644 --- a/Libplanet.Action/State/IBlockChainStates.cs +++ b/Libplanet.Action/State/IBlockChainStates.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using Bencodex.Types; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Store; using Libplanet.Store.Trie; @@ -150,5 +152,20 @@ FungibleAssetValue GetTotalSupply( /// /// IAccountState GetAccountState(BlockHash? offset); + + /// + /// Returns the in the 's state storage + /// with . + /// + /// The state root hash for which to create + /// an . + /// + /// The with as its state root hash. + /// + /// Thrown when no with + /// as its state root hash is found. + /// + /// + IAccountState GetAccountState(HashDigest? hash); } } diff --git a/Libplanet.Analyzers.Tests/ActionAnalyzerTest.cs b/Libplanet.Analyzers.Tests/ActionAnalyzerTest.cs index 734e08d059b..52eb38179d9 100644 --- a/Libplanet.Analyzers.Tests/ActionAnalyzerTest.cs +++ b/Libplanet.Analyzers.Tests/ActionAnalyzerTest.cs @@ -43,7 +43,7 @@ public static void Main() {} { Id = "LAA1001", Message = "The System.Random makes an IAction indeterministic; " + - "use IActionContext.Random property instead.", + "use IActionContext.GetRandom method instead.", Severity = DiagnosticSeverity.Warning, Locations = new[] { new DiagnosticResultLocation(12, 29) }, }; diff --git a/Libplanet.Analyzers/ActionAnalyzer.cs b/Libplanet.Analyzers/ActionAnalyzer.cs index 192a7578660..b3c83e09e4a 100644 --- a/Libplanet.Analyzers/ActionAnalyzer.cs +++ b/Libplanet.Analyzers/ActionAnalyzer.cs @@ -37,7 +37,7 @@ public class ActionAnalyzer : DiagnosticAnalyzer title: "SystemRandomBreaksActionDeterminism", messageFormat: $"The {{0}} makes an {nameof(IAction)} indeterministic; use " + - $"{nameof(IActionContext)}.{nameof(IActionContext.Random)} property instead.", + $"{nameof(IActionContext)}.{nameof(IActionContext.GetRandom)} method instead.", category: "Determinism", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, diff --git a/Libplanet.Benchmarks/AppendBlock.cs b/Libplanet.Benchmarks/AppendBlock.cs index e3ceddc40b8..d028c3f67ff 100644 --- a/Libplanet.Benchmarks/AppendBlock.cs +++ b/Libplanet.Benchmarks/AppendBlock.cs @@ -30,7 +30,7 @@ public AppendBlock() fx.GenesisBlock, new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: new BlockChainStates(fx.Store, fx.StateStore), + stateStore: fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); _privateKey = new PrivateKey(); } diff --git a/Libplanet.Benchmarks/BlockChain.cs b/Libplanet.Benchmarks/BlockChain.cs index 0de92be08c2..7f78615a258 100644 --- a/Libplanet.Benchmarks/BlockChain.cs +++ b/Libplanet.Benchmarks/BlockChain.cs @@ -36,7 +36,7 @@ public void SetupChain() _fx.GenesisBlock, new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: new BlockChainStates(_fx.Store, _fx.StateStore), + stateStore: _fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); var key = new PrivateKey(); for (var i = 0; i < 500; i++) diff --git a/Libplanet.Benchmarks/ProposeBlock.cs b/Libplanet.Benchmarks/ProposeBlock.cs index 9522527b9f4..ab29b8214c2 100644 --- a/Libplanet.Benchmarks/ProposeBlock.cs +++ b/Libplanet.Benchmarks/ProposeBlock.cs @@ -29,7 +29,7 @@ public ProposeBlock() fx.GenesisBlock, new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: new BlockChainStates(fx.Store, fx.StateStore), + stateStore: fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); _privateKey = new PrivateKey(); } diff --git a/Libplanet.Explorer.Executable/Program.cs b/Libplanet.Explorer.Executable/Program.cs index dbc4aa3c5bf..45e7367bfb8 100644 --- a/Libplanet.Explorer.Executable/Program.cs +++ b/Libplanet.Explorer.Executable/Program.cs @@ -227,7 +227,7 @@ If omitted (default) explorer only the local blockchain store.")] blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + stateStore, new SingleActionLoader(typeof(NullAction)))); Startup.PreloadedSingleton = false; Startup.BlockChainSingleton = blockChain; diff --git a/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs b/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs index efb64174f74..8f35c9797cd 100644 --- a/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs +++ b/Libplanet.Explorer.Tests/GeneratedBlockChainFixture.cs @@ -85,7 +85,7 @@ public GeneratedBlockChainFixture( IStore store = new MemoryStore(); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, TypedActionLoader.Create(typeof(SimpleAction).Assembly, typeof(SimpleAction))); Block genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, diff --git a/Libplanet.Explorer.Tests/GraphTypes/TxResultTypeTest.cs b/Libplanet.Explorer.Tests/GraphTypes/TxResultTypeTest.cs index 0781e99d71f..a97d1325598 100644 --- a/Libplanet.Explorer.Tests/GraphTypes/TxResultTypeTest.cs +++ b/Libplanet.Explorer.Tests/GraphTypes/TxResultTypeTest.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -using System.Collections.Immutable; +using System.Security.Cryptography; using GraphQL; using GraphQL.Execution; -using Libplanet.Crypto; -using Libplanet.Types.Assets; +using Libplanet.Common; using Libplanet.Explorer.GraphTypes; using Xunit; using static Libplanet.Explorer.Tests.GraphQLTestUtils; @@ -21,21 +20,9 @@ public async void Query(TxResult txResult, IDictionary expected) txStatus blockIndex blockHash - exceptionName - updatedStates { - address - state - } - updatedFungibleAssets { - address - fungibleAssetValues { - currency { - ticker - decimalPlaces - } - quantity - } - } + inputState + outputState + exceptionNames }"; var txResultType = new TxResultType(); @@ -52,67 +39,85 @@ public async void Query(TxResult txResult, IDictionary expected) } public static IEnumerable TestCases() { - Currency KRW = Currency.Uncapped("KRW", 18, null); - Address address = new Address("76ca86fa821c8241f9422c22b1386021047faf0d"); return new object[][] { new object[] { new TxResult( TxStatus.SUCCESS, 0, "45bcaa4c0b00f4f31eb61577e595ea58fb69c7df3ee612aa6eea945bbb0ce39d", - null, - ImmutableDictionary.Empty, - ImmutableDictionary>.Empty + HashDigest.FromString( + "7146ddfb3594089795f6992a668a3ce7fde089aacdda68075e1bc37b14ebb06f"), + HashDigest.FromString( + "72bb2e17da644cbca9045f5e689fae0323b6af56a0acab9fd828d2243b50df1c"), + new List() { "" } ), new Dictionary { ["txStatus"] = "SUCCESS", ["blockIndex"] = 0L, ["blockHash"] = "45bcaa4c0b00f4f31eb61577e595ea58fb69c7df3ee612aa6eea945bbb0ce39d", - ["exceptionName"] = null, - ["updatedStates"] = new object[0], - ["updatedFungibleAssets"] = new object[0], + ["inputState"] = + "7146ddfb3594089795f6992a668a3ce7fde089aacdda68075e1bc37b14ebb06f", + ["outputState"] = + "72bb2e17da644cbca9045f5e689fae0323b6af56a0acab9fd828d2243b50df1c", + ["exceptionNames"] = new string[] { "" }, } }, new object[] { new TxResult( - TxStatus.SUCCESS, + TxStatus.FAILURE, 0, "45bcaa4c0b00f4f31eb61577e595ea58fb69c7df3ee612aa6eea945bbb0ce39d", - null, - ImmutableDictionary.Empty - .Add(address, Bencodex.Types.Null.Value), - ImmutableDictionary>.Empty - .Add( - address, - ImmutableDictionary.Empty - .Add(KRW, KRW * 20000) - ) + HashDigest.FromString( + "7146ddfb3594089795f6992a668a3ce7fde089aacdda68075e1bc37b14ebb06f"), + HashDigest.FromString( + "7146ddfb3594089795f6992a668a3ce7fde089aacdda68075e1bc37b14ebb06f"), + new List() { "" } ), new Dictionary { - ["txStatus"] = "SUCCESS", + ["txStatus"] = "FAILURE", ["blockIndex"] = 0L, + ["inputState"] = + "7146ddfb3594089795f6992a668a3ce7fde089aacdda68075e1bc37b14ebb06f", + ["outputState"] = + "7146ddfb3594089795f6992a668a3ce7fde089aacdda68075e1bc37b14ebb06f", ["blockHash"] = "45bcaa4c0b00f4f31eb61577e595ea58fb69c7df3ee612aa6eea945bbb0ce39d", - ["exceptionName"] = null, - ["updatedStates"] = new object[] { - new Dictionary { - ["address"] = address.ToString(), - ["state"] = new byte[] { 110, }, - }, - }, - ["updatedFungibleAssets"] = new object[] { - new Dictionary { - ["address"] = address.ToString(), - ["fungibleAssetValues"] = new object[] { - new Dictionary { - ["currency"] = new Dictionary { - ["ticker"] = KRW.Ticker, - ["decimalPlaces"] = KRW.DecimalPlaces, - }, - ["quantity"] = "20000", - }, - }, - }, - }, + ["exceptionNames"] = new string[] { "" }, + } + }, + new object[] { + new TxResult( + TxStatus.INVALID, + null, + null, + null, + null, + new List() { "" } + ), + new Dictionary { + ["txStatus"] = "INVALID", + ["blockIndex"] = null, + ["blockHash"] = null, + ["inputState"] = null, + ["outputState"] = null, + ["exceptionNames"] = new string[] { "" }, + } + }, + new object[] { + new TxResult( + TxStatus.STAGING, + null, + null, + null, + null, + new List() { "" } + ), + new Dictionary { + ["txStatus"] = "STAGING", + ["blockIndex"] = null, + ["blockHash"] = null, + ["inputState"] = null, + ["outputState"] = null, + ["exceptionNames"] = new string[] { "" }, } } }; diff --git a/Libplanet.Explorer.Tests/Queries/StateQueryTest.cs b/Libplanet.Explorer.Tests/Queries/StateQueryTest.cs index 7319e5ed934..2a8d24bd448 100644 --- a/Libplanet.Explorer.Tests/Queries/StateQueryTest.cs +++ b/Libplanet.Explorer.Tests/Queries/StateQueryTest.cs @@ -17,6 +17,8 @@ using Libplanet.Store.Trie; using Xunit; using static Libplanet.Explorer.Tests.GraphQLTestUtils; +using Libplanet.Common; +using System.Security.Cryptography; namespace Libplanet.Explorer.Tests.Queries; @@ -172,6 +174,178 @@ public async Task Validators() Assert.Equal("032038e153d344773986c039ba5dbff12ae70cfdf6ea8beb7c5ea9b361a72a9233", validatorDict["publicKey"]); Assert.Equal(new BigInteger(1), validatorDict["power"]); } + + [Fact] + public async Task ThrowExecutionErrorIfViolateMutualExclusive() + { + (IBlockChainStates, IBlockPolicy) source = ( + new MockChainStates(), new BlockPolicy() + ); + ExecutionResult result = await ExecuteQueryAsync(@" + { + states( + addresses: [""0x5003712B63baAB98094aD678EA2B24BcE445D076"", ""0x0000000000000000000000000000000000000000""], + offsetBlockHash: + ""01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"", + offsetStateRootHash: + ""c33b27773104f75ac9df5b0533854108bd498fab31e5236b6f1e1f6404d5ef64"" + ) + } + ", source: source); + Assert.IsType(result.Errors); + } + + [Fact] + public async Task StatesBySrh() + { + var currency = Currency.Uncapped("ABC", 2, minters: null); + (IBlockChainStates, IBlockPolicy) source = ( + new MockChainStates(), new BlockPolicy() + ); + ExecutionResult result = await ExecuteQueryAsync(@" + { + states( + addresses: [""0x5003712B63baAB98094aD678EA2B24BcE445D076"", ""0x0000000000000000000000000000000000000000""], + offsetStateRootHash: + ""c33b27773104f75ac9df5b0533854108bd498fab31e5236b6f1e1f6404d5ef64"" + ) + } + ", source: source); + Assert.Null(result.Errors); + ExecutionNode resultData = Assert.IsAssignableFrom(result.Data); + IDictionary resultDict = + Assert.IsAssignableFrom>(resultData!.ToValue()); + object[] states = + Assert.IsAssignableFrom(resultDict["states"]); + Assert.Equal(new[] { new byte[] { 110, }, null }, states); + } + + [Fact] + public async Task BalanceBySrh() + { + (IBlockChainStates, IBlockPolicy) source = ( + new MockChainStates(), + new BlockPolicy() + ); + ExecutionResult result = await ExecuteQueryAsync(@" + { + balance( + owner: ""0x5003712B63baAB98094aD678EA2B24BcE445D076"", + currency: { ticker: ""ABC"", decimalPlaces: 2, totalSupplyTrackable: true }, + offsetStateRootHash: + ""c33b27773104f75ac9df5b0533854108bd498fab31e5236b6f1e1f6404d5ef64"" + ) { + currency { ticker, hash } + sign + majorUnit + minorUnit + quantity + string + } + } + ", source: source); + Assert.Null(result.Errors); + ExecutionNode resultData = Assert.IsAssignableFrom(result.Data); + IDictionary resultDict = + Assert.IsAssignableFrom>(resultData!.ToValue()); + IDictionary balanceDict = + Assert.IsAssignableFrom>(resultDict["balance"]); + IDictionary currencyDict = + Assert.IsAssignableFrom>(balanceDict["currency"]); + Assert.Equal("ABC", currencyDict["ticker"]); + Assert.Equal("84ba810ca5ac342c122eb7ef455939a8a05d1d40", currencyDict["hash"]); + Assert.Equal(1, Assert.IsAssignableFrom(balanceDict["sign"])); + Assert.Equal(123, Assert.IsAssignableFrom(balanceDict["majorUnit"])); + Assert.Equal(0, Assert.IsAssignableFrom(balanceDict["minorUnit"])); + Assert.Equal("123", balanceDict["quantity"]); + Assert.Equal("123 ABC", balanceDict["string"]); + } + + [Fact] + public async Task TotalSupplyBySrh() + { + var currency = Currency.Uncapped("ABC", 2, minters: null); +#pragma warning disable CS0618 // Legacy, which is obsolete, is the only way to test this: + var legacyToken = Currency.Legacy("LEG", 0, null); +#pragma warning restore CS0618 + (IBlockChainStates, IBlockPolicy) source = ( + new MockChainStates(), new BlockPolicy()); + ExecutionResult result = await ExecuteQueryAsync(@" + { + totalSupply( + currency: { ticker: ""ABC"", decimalPlaces: 2, totalSupplyTrackable: true }, + offsetStateRootHash: + ""c33b27773104f75ac9df5b0533854108bd498fab31e5236b6f1e1f6404d5ef64"" + ) { + currency { ticker, hash } + sign + majorUnit + minorUnit + quantity + string + } + } + ", source: source); + Assert.Null(result.Errors); + ExecutionNode resultData = Assert.IsAssignableFrom(result.Data); + IDictionary resultDict = + Assert.IsAssignableFrom>(resultData!.ToValue()); + IDictionary totalSupplyDict = + Assert.IsAssignableFrom>(resultDict["totalSupply"]); + IDictionary currencyDict = + Assert.IsAssignableFrom>(totalSupplyDict["currency"]); + Assert.Equal("ABC", currencyDict["ticker"]); + Assert.Equal("84ba810ca5ac342c122eb7ef455939a8a05d1d40", currencyDict["hash"]); + Assert.Equal(1, Assert.IsAssignableFrom(totalSupplyDict["sign"])); + Assert.Equal(10000, Assert.IsAssignableFrom(totalSupplyDict["majorUnit"])); + Assert.Equal(0, Assert.IsAssignableFrom(totalSupplyDict["minorUnit"])); + Assert.Equal("10000", totalSupplyDict["quantity"]); + Assert.Equal("10000 ABC", totalSupplyDict["string"]); + + result = await ExecuteQueryAsync(@" + { + totalSupply( + currency: { ticker: ""LEG"", decimalPlaces: 0, totalSupplyTrackable: false }, + offsetBlockHash: + ""01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"" + ) { + quantity + } + } + ", source: source); + Assert.Single(result.Errors); + Assert.Contains("not trackable", result.Errors[0].Message); + } + + [Fact] + public async Task ValidatorsBySrh() + { + (IBlockChainStates, IBlockPolicy) source = ( + new MockChainStates(), + new BlockPolicy() + ); + ExecutionResult result = await ExecuteQueryAsync(@" + { + validators( + offsetStateRootHash: + ""c33b27773104f75ac9df5b0533854108bd498fab31e5236b6f1e1f6404d5ef64"" + ) { + publicKey + power + } + } + ", source: source); + Assert.Null(result.Errors); + ExecutionNode resultData = Assert.IsAssignableFrom(result.Data); + IDictionary resultDict = + Assert.IsAssignableFrom>(resultData!.ToValue()); + object[] validators = Assert.IsAssignableFrom(resultDict["validators"]); + IDictionary validatorDict = + Assert.IsAssignableFrom>(validators[0]); + Assert.Equal("032038e153d344773986c039ba5dbff12ae70cfdf6ea8beb7c5ea9b361a72a9233", validatorDict["publicKey"]); + Assert.Equal(new BigInteger(1), validatorDict["power"]); + } + private class MockChainStates : IBlockChainStates { @@ -192,7 +366,11 @@ public FungibleAssetValue GetTotalSupply(Currency currency, BlockHash? offset) = public ValidatorSet GetValidatorSet(BlockHash? offset) => GetAccountState(offset).GetValidatorSet(); - public IAccountState GetAccountState(BlockHash? offset) => new MockAccount(offset); + public IAccountState GetAccountState(BlockHash? offset) => + new MockAccount(offset, null); + + public IAccountState GetAccountState(HashDigest? hash) => + new MockAccount(null, hash); public ITrie GetTrie(BlockHash? offset) { @@ -202,9 +380,10 @@ public ITrie GetTrie(BlockHash? offset) private class MockAccount : IAccount { - public MockAccount(BlockHash? blockHash) + public MockAccount(BlockHash? blockHash, HashDigest? stateRootHash) { BlockHash = blockHash ?? default; + StateRootHash = stateRootHash ?? default; } public ITrie Trie { get; } @@ -213,6 +392,8 @@ public MockAccount(BlockHash? blockHash) public BlockHash BlockHash { get; } + public HashDigest StateRootHash { get; } + public IImmutableSet<(Address, Currency)> TotalUpdatedFungibleAssets { get; } public IAccount SetState(Address address, IValue state) { @@ -244,26 +425,24 @@ public IAccount SetValidator(Validator validator) public IValue GetState(Address address) => GetStates(new[] { address }).First(); public IReadOnlyList GetStates(IReadOnlyList
addresses) => - BlockHash is { } _ - ? addresses.Select(address => address.ToString() switch - { - "0x5003712B63baAB98094aD678EA2B24BcE445D076" => (IValue)Null.Value, - _ => null, - }).ToImmutableList() - : addresses.Select(address => (IValue)null).ToList(); + addresses.Select(address => address.ToString() switch + { + "0x5003712B63baAB98094aD678EA2B24BcE445D076" => (IValue)Null.Value, + _ => null, + }).ToImmutableList(); public FungibleAssetValue GetBalance(Address address, Currency currency) => - BlockHash is { } _ + BlockHash is { } && StateRootHash is { } ? currency * 123 : currency * 0; public FungibleAssetValue GetTotalSupply(Currency currency) => - BlockHash is { } _ + BlockHash is { } && StateRootHash is { } ? currency * 10000 : currency * 0; public ValidatorSet GetValidatorSet() => - BlockHash is { } _ + BlockHash is { } && StateRootHash is { } ? new ValidatorSet(new List { new( diff --git a/Libplanet.Explorer.Tests/Queries/TransactionQueryGeneratedTest.cs b/Libplanet.Explorer.Tests/Queries/TransactionQueryGeneratedTest.cs index db2021cce63..fda7e5b6f05 100644 --- a/Libplanet.Explorer.Tests/Queries/TransactionQueryGeneratedTest.cs +++ b/Libplanet.Explorer.Tests/Queries/TransactionQueryGeneratedTest.cs @@ -64,24 +64,24 @@ public async Task TransactionResult() Assert.Equal("SUCCESS", queryResult.TxStatus); Assert.Equal(successBlock.Index, queryResult.BlockIndex); Assert.Equal(successBlock.Hash.ToString(), queryResult.BlockHash); - Assert.Null(queryResult.ExceptionName); + Assert.Equal(new string?[] { null }, queryResult.ExceptionNames); queryResult = await ExecuteTransactionResultQueryAsync(failTx.Id); Assert.Equal("FAILURE", queryResult.TxStatus); Assert.Equal(failBlock.Index, queryResult.BlockIndex); Assert.Equal(failBlock.Hash.ToString(), queryResult.BlockHash); Assert.Equal( - "Libplanet.Action.State.CurrencyPermissionException", - queryResult.ExceptionName); + new string[] { "Libplanet.Action.State.CurrencyPermissionException" }, + queryResult.ExceptionNames); queryResult = await ExecuteTransactionResultQueryAsync(new TxId()); Assert.Equal("INVALID", queryResult.TxStatus); Assert.Null(queryResult.BlockIndex); Assert.Null(queryResult.BlockHash); - Assert.Null(queryResult.ExceptionName); + Assert.Null(queryResult.ExceptionNames); queryResult = await ExecuteTransactionResultQueryAsync(stagingTx.Id); Assert.Equal("STAGING", queryResult.TxStatus); Assert.Null(queryResult.BlockIndex); Assert.Null(queryResult.BlockHash); - Assert.Null(queryResult.ExceptionName); + Assert.Null(queryResult.ExceptionNames); } [Fact] @@ -300,7 +300,7 @@ await Source.Index.GetContainedBlockHashByTxIdAsync(expected[i].Id) } private async Task< - (string TxStatus, long? BlockIndex, string? BlockHash, string? ExceptionName)> + (string TxStatus, long? BlockIndex, string? BlockHash, string?[]? ExceptionNames)> ExecuteTransactionResultQueryAsync(TxId txId) { ExecutionResult result = await ExecuteQueryAsync(@$" @@ -310,7 +310,7 @@ private async Task< txStatus blockIndex blockHash - exceptionName + exceptionNames }} }} ", QueryGraph, source: Source); @@ -319,10 +319,12 @@ private async Task< IDictionary resultDict = (IDictionary)Assert.IsAssignableFrom>( resultData.ToValue())["transactionResult"]; + var exceptionNames = ((object[]?)resultDict["exceptionNames"])?.Select( + name => name is { } n ? Assert.IsType(n) : null).ToArray(); return ( (string)resultDict["txStatus"], (long?)resultDict["blockIndex"], (string?)resultDict["blockHash"], - (string?)resultDict["exceptionName"]); + (string?[]?)exceptionNames); } } diff --git a/Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs b/Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs new file mode 100644 index 00000000000..45d8f0ccf48 --- /dev/null +++ b/Libplanet.Explorer/GraphTypes/HashDigestSHA256Type.cs @@ -0,0 +1,44 @@ +#nullable disable +using System; +using System.Security.Cryptography; +using GraphQL.Language.AST; +using GraphQL.Types; +using Libplanet.Common; + +namespace Libplanet.Explorer.GraphTypes +{ + public class HashDigestSHA256Type : StringGraphType + { + public HashDigestSHA256Type() + { + Name = "HashDigest_SHA256"; + } + + public override object Serialize(object value) + { + if (value is HashDigest hash) + { + return hash.ToString(); + } + + return value; + } + + public override object ParseValue(object value) => + value switch + { + null => null, + string hex => HashDigest.FromString(hex), + _ => throw new ArgumentException( + $"Expected a hexadecimal string but {value}", nameof(value)), + }; + + public override object ParseLiteral(IValue value) => + value switch + { + StringValue str => ParseValue(str.Value), + _ => throw new ArgumentException( + $"Expected a hexadecimal string but {value}", nameof(value)), + }; + } +} diff --git a/Libplanet.Explorer/GraphTypes/TxResult.cs b/Libplanet.Explorer/GraphTypes/TxResult.cs index eb9a518762a..958f2a2d9ba 100644 --- a/Libplanet.Explorer/GraphTypes/TxResult.cs +++ b/Libplanet.Explorer/GraphTypes/TxResult.cs @@ -1,8 +1,6 @@ -using System.Collections.Immutable; -using Bencodex.Types; -using Libplanet.Crypto; -using Libplanet.Types.Assets; -using FAV = Libplanet.Types.Assets.FungibleAssetValue; +using System.Collections.Generic; +using System.Security.Cryptography; +using Libplanet.Common; namespace Libplanet.Explorer.GraphTypes { @@ -12,18 +10,16 @@ public TxResult( TxStatus status, long? blockIndex, string? blockHash, - string? exceptionName, - IImmutableDictionary? updatedStates, - IImmutableDictionary>? - updatedFungibleAssets - ) + HashDigest? inputState, + HashDigest? outputState, + List? exceptionNames) { TxStatus = status; BlockIndex = blockIndex; BlockHash = blockHash; - ExceptionName = exceptionName; - UpdatedStates = updatedStates; - UpdatedFungibleAssets = updatedFungibleAssets; + InputState = inputState; + OutputState = outputState; + ExceptionNames = exceptionNames; } public TxStatus TxStatus { get; private set; } @@ -32,11 +28,10 @@ public TxResult( public string? BlockHash { get; private set; } - public string? ExceptionName { get; private set; } + public HashDigest? InputState { get; private set; } - public IImmutableDictionary? UpdatedStates { get; } + public HashDigest? OutputState { get; private set; } - public IImmutableDictionary>? - UpdatedFungibleAssets { get; } + public List? ExceptionNames { get; private set; } } } diff --git a/Libplanet.Explorer/GraphTypes/TxResultType.cs b/Libplanet.Explorer/GraphTypes/TxResultType.cs index 70a1a16022d..679c061d2dc 100644 --- a/Libplanet.Explorer/GraphTypes/TxResultType.cs +++ b/Libplanet.Explorer/GraphTypes/TxResultType.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Linq; using GraphQL.Types; -using Libplanet.Crypto; -using Libplanet.Types.Assets; namespace Libplanet.Explorer.GraphTypes { @@ -28,58 +24,25 @@ public TxResultType() resolve: context => context.Source.BlockHash ); - Field( - nameof(TxResult.ExceptionName), - description: "The name of exception. (when only failed)", - resolve: context => context.Source.ExceptionName + Field( + nameof(TxResult.InputState), + description: "The input state's root hash " + + "which the target transaction executed.", + resolve: context => context.Source.InputState ); - Field>>( - nameof(TxResult.UpdatedStates), - resolve: context => context.Source.UpdatedStates? - .Select(pair => new UpdatedState(pair.Key, pair.Value)) + Field( + nameof(TxResult.OutputState), + description: "The output state's root hash " + + "which the target transaction executed.", + resolve: context => context.Source.OutputState ); - Field>>( - nameof(TxResult.UpdatedFungibleAssets), - resolve: context => context.Source.UpdatedFungibleAssets? - .Select(pair => new FungibleAssetBalances(pair.Key, pair.Value.Values)) + Field>( + nameof(TxResult.ExceptionNames), + description: "The name of exception. (when only failed)", + resolve: context => context.Source.ExceptionNames ); } - - public record UpdatedState(Address Address, Bencodex.Types.IValue? State); - - public class UpdatedStateType : ObjectGraphType - { - public UpdatedStateType() - { - Field>( - nameof(UpdatedState.Address), - resolve: context => context.Source.Address - ); - Field( - nameof(UpdatedState.State), - resolve: context => context.Source.State - ); - } - } - - public record FungibleAssetBalances( - Address Address, IEnumerable FungibleAssetValues); - - public class FungibleAssetBalancesType : ObjectGraphType - { - public FungibleAssetBalancesType() - { - Field>( - nameof(FungibleAssetBalances.Address), - resolve: context => context.Source.Address - ); - Field>>>( - nameof(FungibleAssetBalances.FungibleAssetValues), - resolve: context => context.Source.FungibleAssetValues - ); - } - } } } diff --git a/Libplanet.Explorer/Queries/StateQuery.cs b/Libplanet.Explorer/Queries/StateQuery.cs index 2895ae0c30a..4b2e9ce4e52 100644 --- a/Libplanet.Explorer/Queries/StateQuery.cs +++ b/Libplanet.Explorer/Queries/StateQuery.cs @@ -1,8 +1,10 @@ using System; +using System.Security.Cryptography; using GraphQL; using GraphQL.Types; using Libplanet.Action.State; using Libplanet.Blockchain.Policies; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Explorer.GraphTypes; using Libplanet.Types.Assets; @@ -21,7 +23,8 @@ public StateQuery() arguments: new QueryArguments( new QueryArgument>>> { Name = "addresses" }, - new QueryArgument> { Name = "offsetBlockHash" } + new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveStates ); @@ -30,7 +33,8 @@ public StateQuery() arguments: new QueryArguments( new QueryArgument> { Name = "owner" }, new QueryArgument> { Name = "currency" }, - new QueryArgument> { Name = "offsetBlockHash" } + new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveBalance ); @@ -38,14 +42,16 @@ public StateQuery() "totalSupply", arguments: new QueryArguments( new QueryArgument> { Name = "currency" }, - new QueryArgument> { Name = "offsetBlockHash" } + new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveTotalSupply ); Field>>( "validators", arguments: new QueryArguments( - new QueryArgument> { Name = "offsetBlockHash" } + new QueryArgument { Name = "offsetBlockHash" }, + new QueryArgument { Name = "offsetStateRootHash" } ), resolve: ResolveValidatorSet ); @@ -55,25 +61,45 @@ private static object ResolveStates( IResolveFieldContext<(IBlockChainStates ChainStates, IBlockPolicy Policy)> context) { Address[] addresses = context.GetArgument("addresses"); - string offsetBlockHash = context.GetArgument("offsetBlockHash"); + string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + HashDigest? offsetStateRootHash = context + .GetArgument?>("offsetStateRootHash"); - BlockHash offset; - try + switch (blockhash: offsetBlockHash, srh: offsetStateRootHash) { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } + case (blockhash: not null, srh: not null): + throw new ExecutionError( + "offsetBlockHash and offsetStateRootHash cannot be specified at the same time." + ); + case (blockhash: null, srh: null): + throw new ExecutionError( + "Either offsetBlockHash or offsetStateRootHash must be specified." + ); + case (blockhash: not null, _): + { + BlockHash offset; + try + { + offset = BlockHash.FromString(offsetBlockHash); + } + catch (Exception e) + { + throw new ExecutionError( + "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, + e + ); + } - return context.Source.ChainStates.GetStates( - addresses, - offset - ); + return context.Source.ChainStates.GetAccountState( + offset + ).GetStates(addresses); + } + + case (_, srh: not null): + return context.Source.ChainStates.GetAccountState( + offsetStateRootHash + ).GetStates(addresses); + } } private static object ResolveBalance( @@ -81,33 +107,54 @@ private static object ResolveBalance( { Address owner = context.GetArgument
("owner"); Currency currency = context.GetArgument("currency"); - string offsetBlockHash = context.GetArgument("offsetBlockHash"); + string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + HashDigest? offsetStateRootHash = context + .GetArgument?>("offsetStateRootHash"); - BlockHash offset; - try + switch (blockhash: offsetBlockHash, srh: offsetStateRootHash) { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } + case (blockhash: not null, srh: not null): + throw new ExecutionError( + "offsetBlockHash and offsetStateRootHash cannot be specified at the same time." + ); + case (blockhash: null, srh: null): + throw new ExecutionError( + "Either offsetBlockHash or offsetStateRootHash must be specified." + ); + case (blockhash: not null, _): + { + BlockHash offset; + try + { + offset = BlockHash.FromString(offsetBlockHash); + } + catch (Exception e) + { + throw new ExecutionError( + "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, + e + ); + } - return context.Source.ChainStates.GetBalance( - owner, - currency, - offset - ); + return context.Source.ChainStates.GetAccountState( + offset + ).GetBalance(owner, currency); + } + + case (_, srh: not null): + return context.Source.ChainStates.GetAccountState( + offsetStateRootHash + ).GetBalance(owner, currency); + } } private static object? ResolveTotalSupply( IResolveFieldContext<(IBlockChainStates ChainStates, IBlockPolicy Policy)> context) { Currency currency = context.GetArgument("currency"); - string offsetBlockHash = context.GetArgument("offsetBlockHash"); + string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + HashDigest? offsetStateRootHash = context + .GetArgument?>("offsetStateRootHash"); if (!currency.TotalSupplyTrackable) { @@ -118,42 +165,84 @@ private static object ResolveBalance( throw new ExecutionError(msg); } - BlockHash offset; - try + switch (blockhash: offsetBlockHash, srh: offsetStateRootHash) { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) - { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } + case (blockhash: not null, srh: not null): + throw new ExecutionError( + "offsetBlockHash and offsetStateRootHash cannot be specified at the same time." + ); + case (blockhash: null, srh: null): + throw new ExecutionError( + "Either offsetBlockHash or offsetStateRootHash must be specified." + ); + case (blockhash: not null, _): + { + BlockHash offset; + try + { + offset = BlockHash.FromString(offsetBlockHash); + } + catch (Exception e) + { + throw new ExecutionError( + "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, + e + ); + } - return context.Source.ChainStates.GetTotalSupply( - currency, - offset - ); + return context.Source.ChainStates.GetAccountState( + offset + ).GetTotalSupply(currency); + } + + case (_, srh: not null): + return context.Source.ChainStates.GetAccountState( + offsetStateRootHash + ).GetTotalSupply(currency); + } } private static object? ResolveValidatorSet( IResolveFieldContext<(IBlockChainStates ChainStates, IBlockPolicy Policy)> context) { - string offsetBlockHash = context.GetArgument("offsetBlockHash"); - BlockHash offset; - try - { - offset = BlockHash.FromString(offsetBlockHash); - } - catch (Exception e) + string? offsetBlockHash = context.GetArgument("offsetBlockHash"); + HashDigest? offsetStateRootHash = context + .GetArgument?>("offsetStateRootHash"); + + switch (blockhash: offsetBlockHash, srh: offsetStateRootHash) { - throw new ExecutionError( - "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, - e - ); - } + case (blockhash: not null, srh: not null): + throw new ExecutionError( + "offsetBlockHash and offsetStateRootHash cannot be specified at the same time." + ); + case (blockhash: null, srh: null): + throw new ExecutionError( + "Either offsetBlockHash or offsetStateRootHash must be specified." + ); + case (blockhash: not null, _): + { + BlockHash offset; + try + { + offset = BlockHash.FromString(offsetBlockHash); + } + catch (Exception e) + { + throw new ExecutionError( + "offsetBlockHash must consist of hexadecimal digits.\n" + e.Message, + e + ); + } + + return context.Source.ChainStates.GetAccountState( + offset + ).GetValidatorSet().Validators; + } - return context.Source.ChainStates.GetValidatorSet(offset).Validators; + case (_, srh: not null): + return context.Source.ChainStates.GetAccountState( + offsetStateRootHash + ).GetValidatorSet().Validators; + } } } diff --git a/Libplanet.Explorer/Queries/TransactionQuery.cs b/Libplanet.Explorer/Queries/TransactionQuery.cs index 88342431640..5b8c5198db6 100644 --- a/Libplanet.Explorer/Queries/TransactionQuery.cs +++ b/Libplanet.Explorer/Queries/TransactionQuery.cs @@ -268,30 +268,13 @@ index is null ); var txExecutedBlock = blockChain[txExecutedBlockHashValue]; - return execution switch - { - TxSuccess txSuccess => new TxResult( - TxStatus.SUCCESS, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - null, - txSuccess.UpdatedStates, - txSuccess.UpdatedFungibleAssets - ), - TxFailure txFailure => new TxResult( - TxStatus.FAILURE, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - txFailure.ExceptionName, - null, - null - ), - _ => throw new NotSupportedException( - #pragma warning disable format - $"{nameof(execution)} is not expected concrete class." - #pragma warning restore format - ), - }; + return new TxResult( + execution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, + txExecutedBlock.Index, + txExecutedBlock.Hash.ToString(), + execution.InputState, + execution.OutputState, + execution.ExceptionNames); } catch (Exception) { @@ -301,8 +284,7 @@ index is null null, null, null, - null - ); + null); } } ); diff --git a/Libplanet.Explorer/Store/LiteDBRichStore.cs b/Libplanet.Explorer/Store/LiteDBRichStore.cs index 9e666f7e795..f2001263837 100644 --- a/Libplanet.Explorer/Store/LiteDBRichStore.cs +++ b/Libplanet.Explorer/Store/LiteDBRichStore.cs @@ -90,13 +90,9 @@ public LiteDBRichStore( return _store.GetBlockIndex(blockHash); } - /// - public void PutTxExecution(TxSuccess txSuccess) => - _store.PutTxExecution(txSuccess); - - /// - public void PutTxExecution(TxFailure txFailure) => - _store.PutTxExecution(txFailure); + /// + public void PutTxExecution(TxExecution txExecution) => + _store.PutTxExecution(txExecution); /// public TxExecution GetTxExecution(BlockHash blockHash, TxId txid) => diff --git a/Libplanet.Explorer/Store/MySQLRichStore.cs b/Libplanet.Explorer/Store/MySQLRichStore.cs index a78d4c3eb87..e51cdf18555 100644 --- a/Libplanet.Explorer/Store/MySQLRichStore.cs +++ b/Libplanet.Explorer/Store/MySQLRichStore.cs @@ -53,13 +53,9 @@ public MySQLRichStore(IStore store, MySQLRichStoreOptions options) public long? GetBlockIndex(BlockHash blockHash) => _store.GetBlockIndex(blockHash); - /// - public void PutTxExecution(TxSuccess txSuccess) => - _store.PutTxExecution(txSuccess); - - /// - public void PutTxExecution(TxFailure txFailure) => - _store.PutTxExecution(txFailure); + /// + public void PutTxExecution(TxExecution txExecution) => + _store.PutTxExecution(txExecution); /// public TxExecution GetTxExecution(BlockHash blockHash, TxId txid) => diff --git a/Libplanet.Extensions.Cocona/Commands/BlockCommand.cs b/Libplanet.Extensions.Cocona/Commands/BlockCommand.cs index 6a42f1fff81..882c869fb6e 100644 --- a/Libplanet.Extensions.Cocona/Commands/BlockCommand.cs +++ b/Libplanet.Extensions.Cocona/Commands/BlockCommand.cs @@ -157,8 +157,7 @@ public void GenerateGenesis( var blockAction = blockPolicyParams.GetBlockAction(); var actionEvaluator = new ActionEvaluator( _ => blockAction, - new BlockChainStates( - new MemoryStore(), new TrieStateStore(new DefaultKeyValueStore(null))), + new TrieStateStore(new DefaultKeyValueStore(null)), new SingleActionLoader(typeof(NullAction))); Block genesis = BlockChain.ProposeGenesisBlock( actionEvaluator, privateKey: key, transactions: txs); diff --git a/Libplanet.Net.Tests/Consensus/ConsensusReactorTest.cs b/Libplanet.Net.Tests/Consensus/ConsensusReactorTest.cs index ed870e36fb8..4b4318f2338 100644 --- a/Libplanet.Net.Tests/Consensus/ConsensusReactorTest.cs +++ b/Libplanet.Net.Tests/Consensus/ConsensusReactorTest.cs @@ -67,7 +67,7 @@ public async void StartAsync() fx.GenesisBlock, new ActionEvaluator( policyBlockActionGetter: _ => TestUtils.Policy.BlockAction, - blockChainStates: new BlockChainStates(stores[i], stateStore), + stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); } diff --git a/Libplanet.Net.Tests/SwarmTest.Broadcast.cs b/Libplanet.Net.Tests/SwarmTest.Broadcast.cs index 0cb4a03d958..e866975e125 100644 --- a/Libplanet.Net.Tests/SwarmTest.Broadcast.cs +++ b/Libplanet.Net.Tests/SwarmTest.Broadcast.cs @@ -465,7 +465,7 @@ public async Task BroadcastTxAsyncMany() fxs[i].GenesisBlock, new ActionEvaluator( policyBlockActionGetter: _ => policy.BlockAction, - blockChainStates: new BlockChainStates(fxs[i].Store, fxs[i].StateStore), + stateStore: fxs[i].StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); swarms[i] = await CreateSwarm(blockChains[i]).ConfigureAwait(false); } diff --git a/Libplanet.Net/Consensus/Context.cs b/Libplanet.Net/Consensus/Context.cs index a6492264786..e9177de1ca2 100644 --- a/Libplanet.Net/Consensus/Context.cs +++ b/Libplanet.Net/Consensus/Context.cs @@ -96,7 +96,9 @@ public partial class Context : IDisposable private readonly ILogger _logger; private readonly - LRUCache EvaluatedActions)> + LRUCache< + BlockHash, + (bool IsValid, IReadOnlyList EvaluatedActions)> _blockValidationCache; private Block? _lockedValue; @@ -191,7 +193,7 @@ private Context( _validatorSet = validators; _cancellationTokenSource = new CancellationTokenSource(); _blockValidationCache = - new LRUCache)>( + new LRUCache)>( cacheSize, Math.Max(cacheSize / 64, 8)); _contextTimeoutOption = contextTimeoutOptions ?? new ContextTimeoutOption(); @@ -454,7 +456,8 @@ private void PublishMessage(ConsensusMsg message) /// /// if block is valid, otherwise . /// - private bool IsValid(Block block, out IReadOnlyList evaluatedActions) + private bool IsValid( + Block block, out IReadOnlyList evaluatedActions) { if (_blockValidationCache.TryGet(block.Hash, out var cached)) { @@ -466,11 +469,11 @@ private bool IsValid(Block block, out IReadOnlyList evaluated // Need to get txs from store, lock? // TODO: Remove ChainId, enhancing lock management. _blockChain._rwlock.EnterUpgradeableReadLock(); - IReadOnlyList actionEvaluations; + IReadOnlyList actionEvaluations; if (block.Index != Height) { - evaluatedActions = ImmutableArray.Empty; + evaluatedActions = ImmutableArray.Empty; _blockValidationCache.AddReplace(block.Hash, (false, evaluatedActions)); return false; } @@ -515,7 +518,7 @@ e is InvalidTxException || "Block #{Index} {Hash} is invalid", block.Index, block.Hash); - evaluatedActions = ImmutableArray.Empty; + evaluatedActions = ImmutableArray.Empty; _blockValidationCache.AddReplace(block.Hash, (false, evaluatedActions)); return false; } diff --git a/Libplanet.RocksDBStore.Tests/RocksDBStoreTest.cs b/Libplanet.RocksDBStore.Tests/RocksDBStoreTest.cs index 05c51dedff6..d11c0f37b6d 100644 --- a/Libplanet.RocksDBStore.Tests/RocksDBStoreTest.cs +++ b/Libplanet.RocksDBStore.Tests/RocksDBStoreTest.cs @@ -87,7 +87,7 @@ public void ReopenStoreAfterDispose() Fx.GenesisBlock, new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: new BlockChainStates(store, stateStore), + stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); store.Dispose(); diff --git a/Libplanet.RocksDBStore/RocksDBStore.cs b/Libplanet.RocksDBStore/RocksDBStore.cs index 533c15cee99..484fbade7dd 100644 --- a/Libplanet.RocksDBStore/RocksDBStore.cs +++ b/Libplanet.RocksDBStore/RocksDBStore.cs @@ -986,18 +986,11 @@ public override IEnumerable IterateTxIdBlockHashIndex(TxId txId) } } - /// - public override void PutTxExecution(TxSuccess txSuccess) => + /// + public override void PutTxExecution(TxExecution txExecution) => _txExecutionDb.Put( - TxExecutionKey(txSuccess), - Codec.Encode(SerializeTxExecution(txSuccess)) - ); - - /// - public override void PutTxExecution(TxFailure txFailure) => - _txExecutionDb.Put( - TxExecutionKey(txFailure), - Codec.Encode(SerializeTxExecution(txFailure)) + TxExecutionKey(txExecution), + Codec.Encode(SerializeTxExecution(txExecution)) ); /// diff --git a/Libplanet.Store/BaseStore.cs b/Libplanet.Store/BaseStore.cs index c2b7146154a..6faca3dca3e 100644 --- a/Libplanet.Store/BaseStore.cs +++ b/Libplanet.Store/BaseStore.cs @@ -99,10 +99,7 @@ public Block GetBlock(BlockHash blockHash) public abstract bool ContainsBlock(BlockHash blockHash); /// - public abstract void PutTxExecution(TxSuccess txSuccess); - - /// - public abstract void PutTxExecution(TxFailure txFailure); + public abstract void PutTxExecution(TxExecution txExecution); /// public abstract TxExecution GetTxExecution(BlockHash blockHash, TxId txid); @@ -178,32 +175,9 @@ public virtual long CountBlocks() /// public abstract IEnumerable GetBlockCommitHashes(); - protected static IValue SerializeTxExecution(TxSuccess txSuccess) - { - var sDelta = new Dictionary( - txSuccess.UpdatedStates.Select(kv => - new KeyValuePair( - new Binary(kv.Key.ByteArray), - kv.Value is { } v ? List.Empty.Add(v) : List.Empty - ) - ) - ); - var updatedFAVs = SerializeGroupedFAVs(txSuccess.UpdatedFungibleAssets); - var serialized = Dictionary.Empty - .Add("fail", false) - .Add("sDelta", sDelta) - .Add("updatedFAVs", new Dictionary(updatedFAVs)); - - return serialized; - } - - protected static IValue SerializeTxExecution(TxFailure txFailure) + protected static IValue SerializeTxExecution(TxExecution txExecution) { - Dictionary d = Dictionary.Empty - .Add("fail", true) - .Add("exc", txFailure.ExceptionName); - - return txFailure.ExceptionMetadata is { } v ? d.Add("excMeta", v) : d; + return txExecution.ToBencodex(); } protected static TxExecution DeserializeTxExecution( @@ -223,28 +197,7 @@ ILogger logger try { - bool fail = d.GetValue("fail"); - - if (fail) - { - string excName = d.GetValue("exc"); - return new TxFailure(blockHash, txid, excName); - } - - ImmutableDictionary sDelta = d.GetValue("sDelta") - .ToImmutableDictionary( - kv => new Address((IValue)kv.Key), - kv => kv.Value is List l && l.Any() ? l[0] : null - ); - IImmutableDictionary> - updatedFAVs = DeserializeGroupedFAVs(d.GetValue("updatedFAVs")); - - return new TxSuccess( - blockHash, - txid, - sDelta, - updatedFAVs - ); + return new TxExecution(blockHash, txid, d); } catch (Exception e) { diff --git a/Libplanet.Store/DefaultStore.cs b/Libplanet.Store/DefaultStore.cs index f7b8c37846b..8a83a5002af 100644 --- a/Libplanet.Store/DefaultStore.cs +++ b/Libplanet.Store/DefaultStore.cs @@ -489,26 +489,15 @@ public override bool ContainsBlock(BlockHash blockHash) return _blocks.FileExists(blockPath); } - /// - public override void PutTxExecution(TxSuccess txSuccess) + /// + public override void PutTxExecution(TxExecution txExecution) { - UPath path = TxExecutionPath(txSuccess); + UPath path = TxExecutionPath(txExecution); UPath dirPath = path.GetDirectory(); CreateDirectoryRecursively(_txExecutions, dirPath); using Stream f = _txExecutions.OpenFile(path, System.IO.FileMode.Create, FileAccess.Write); - Codec.Encode(SerializeTxExecution(txSuccess), f); - } - - /// - public override void PutTxExecution(TxFailure txFailure) - { - UPath path = TxExecutionPath(txFailure); - UPath dirPath = path.GetDirectory(); - CreateDirectoryRecursively(_txExecutions, dirPath); - using Stream f = - _txExecutions.OpenFile(path, System.IO.FileMode.Create, FileAccess.Write); - Codec.Encode(SerializeTxExecution(txFailure), f); + Codec.Encode(SerializeTxExecution(txExecution), f); } /// diff --git a/Libplanet.Store/IStore.cs b/Libplanet.Store/IStore.cs index d84e802ca7f..bbcbde2fd7e 100644 --- a/Libplanet.Store/IStore.cs +++ b/Libplanet.Store/IStore.cs @@ -162,30 +162,16 @@ public interface IStore : IDisposable bool ContainsBlock(BlockHash blockHash); /// - /// Records the given . + /// Records the given . /// /// If there is already the record for the same /// and , the record is silently overwritten. - /// The successful transaction execution summary to record. + /// The transaction execution summary to record. /// Must not be . - /// Thrown when is + /// Thrown when is /// . - /// /// - void PutTxExecution(TxSuccess txSuccess); - - /// - /// Records the given . - /// - /// If there is already the record for the same - /// and , the record is silently overwritten. - /// The failed transaction execution summary to record. - /// Must not be . - /// Thrown when is - /// . - /// - /// - void PutTxExecution(TxFailure txFailure); + void PutTxExecution(TxExecution txExecution); /// /// Retrieves the recorded transaction execution summary. @@ -196,8 +182,7 @@ public interface IStore : IDisposable /// execution to retrieve. /// The recorded transaction execution summary. If it has been never recorded /// is returned instead. - /// - /// + /// TxExecution GetTxExecution(BlockHash blockHash, TxId txid); /// diff --git a/Libplanet.Store/MemoryStore.cs b/Libplanet.Store/MemoryStore.cs index 091747b3977..755f5c4733b 100644 --- a/Libplanet.Store/MemoryStore.cs +++ b/Libplanet.Store/MemoryStore.cs @@ -186,11 +186,8 @@ bool IStore.DeleteBlock(BlockHash blockHash) => bool IStore.ContainsBlock(BlockHash blockHash) => _blocks.ContainsKey(blockHash); - void IStore.PutTxExecution(TxSuccess txSuccess) => - _txExecutions[(txSuccess.BlockHash, txSuccess.TxId)] = txSuccess; - - void IStore.PutTxExecution(TxFailure txFailure) => - _txExecutions[(txFailure.BlockHash, txFailure.TxId)] = txFailure; + void IStore.PutTxExecution(TxExecution txExecution) => + _txExecutions[(txExecution.BlockHash, txExecution.TxId)] = txExecution; TxExecution IStore.GetTxExecution(BlockHash blockHash, TxId txid) => _txExecutions.TryGetValue((blockHash, txid), out TxExecution e) ? e : null; diff --git a/Libplanet.Store/StateStoreExtensions.cs b/Libplanet.Store/StateStoreExtensions.cs deleted file mode 100644 index 3a5b6a2585c..00000000000 --- a/Libplanet.Store/StateStoreExtensions.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Security.Cryptography; -using Bencodex.Types; -using Libplanet.Common; -using Libplanet.Store.Trie; - -namespace Libplanet.Store -{ - /// - /// Convenient extension methods for . - /// - public static class StateStoreExtensions - { - /// - /// Records which is based on the previous state - /// root, and returns the new state root. - /// - /// The to records - /// the . - /// The state root hash on which - /// the is based. - /// The raw states delta to be recorded. - /// The new state root. - public static ITrie Commit( - this IStateStore stateStore, - HashDigest? previousStateRootHash, - IImmutableDictionary rawStatesDelta - ) - { - ITrie trie = stateStore.GetStateRoot(previousStateRootHash); - foreach (KeyValuePair pair in rawStatesDelta) - { - trie = trie.Set(pair.Key, pair.Value); - } - - ITrie stage = stateStore.Commit(trie); - return stateStore.GetStateRoot(stage.Hash); - } - - /// - /// Gets multiple states at once. - /// - /// The to get states. - /// The root hash of the state trie to look up states from. - /// - /// State keys to get. - /// The state values associated to the specified . - /// The associated values are ordered in the same way to the corresponding - /// . Absent values are represented as - /// . - public static IReadOnlyList GetStates( - this IStateStore stateStore, - HashDigest? stateRootHash, - IReadOnlyList rawStateKeys - ) - { - ITrie trie = stateStore.GetStateRoot(stateRootHash); - KeyBytes[] keys = rawStateKeys.ToArray(); - return trie.Get(keys); - } - - /// - /// Checks if the state root is recorded in the . - /// - /// The to check if the state root is - /// recorded in it. - /// The hash of the state root to check if it is recorded. - /// - /// if the state root exists in the - /// ; otherwise, . - public static bool ContainsStateRoot( - this IStateStore stateStore, - HashDigest stateRootHash - ) => - stateStore.GetStateRoot(stateRootHash).Recorded; - } -} diff --git a/Libplanet.Store/Trie/MerkleTrie.Path.cs b/Libplanet.Store/Trie/MerkleTrie.Path.cs index e9331377f27..1ef133c199f 100644 --- a/Libplanet.Store/Trie/MerkleTrie.Path.cs +++ b/Libplanet.Store/Trie/MerkleTrie.Path.cs @@ -9,7 +9,9 @@ public partial class MerkleTrie node switch { null => null, - ValueNode valueNode => valueNode.Value, + ValueNode valueNode => !cursor.RemainingAnyNibbles + ? valueNode.Value + : null, ShortNode shortNode => cursor.RemainingNibblesStartWith(shortNode.Key) ? ResolveToValue(shortNode.Value, cursor.Next(shortNode.Key.Length)) : null, diff --git a/Libplanet.Tests/Action/AccountDiffTest.cs b/Libplanet.Tests/Action/AccountDiffTest.cs new file mode 100644 index 00000000000..16c902ad210 --- /dev/null +++ b/Libplanet.Tests/Action/AccountDiffTest.cs @@ -0,0 +1,172 @@ +using System.Collections.Generic; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Store; +using Libplanet.Store.Trie; +using Libplanet.Types.Assets; +using Libplanet.Types.Blocks; +using Libplanet.Types.Consensus; +using Xunit; + +namespace Libplanet.Tests.Action +{ + public class AccountDiffTest + { + public AccountDiffTest() + { + USD = Currency.Uncapped("USD", 2, null); + KRW = Currency.Uncapped("KRW", 0, null); + JPY = Currency.Uncapped("JPY", 0, null); + } + + public Currency USD { get; } + + public Currency KRW { get; } + + public Currency JPY { get; } + + [Fact] + public void EmptyAccountStateSource() + { + IStateStore stateStore = new TrieStateStore(new MemoryKeyValueStore()); + ITrie targetTrie = stateStore.GetStateRoot(null); + ITrie sourceTrie = stateStore.GetStateRoot(null); + + AccountDiff diff = AccountDiff.Create(targetTrie, sourceTrie); + Assert.Empty(diff.StateDiffs); + Assert.Empty(diff.FungibleAssetValueDiffs); + Assert.Empty(diff.TotalSupplyDiffs); + Assert.Null(diff.ValidatorSetDiff); + + IAccount targetAccount = new Account(new AccountState(targetTrie)); + PrivateKey signer = new PrivateKey(); + IActionContext context = CreateActionContext(signer.ToAddress(), targetTrie); + targetAccount = targetAccount.MintAsset( + context, signer.ToAddress(), new FungibleAssetValue(USD, 123, 45)); + targetAccount = targetAccount.SetState(signer.ToAddress(), new Text("Foo")); + + targetTrie = Commit(stateStore, targetTrie, targetAccount.Delta); + + diff = AccountDiff.Create(targetTrie, sourceTrie); + Assert.Empty(diff.StateDiffs); + Assert.Empty(diff.FungibleAssetValueDiffs); + Assert.Empty(diff.TotalSupplyDiffs); + Assert.Null(diff.ValidatorSetDiff); + } + + [Fact] + public void Diff() + { + IStateStore stateStore = new TrieStateStore(new MemoryKeyValueStore()); + ITrie targetTrie = stateStore.GetStateRoot(null); + ITrie sourceTrie = stateStore.GetStateRoot(null); + + Address addr1 = new Address(TestUtils.GetRandomBytes(Address.Size)); + Address addr2 = new Address(TestUtils.GetRandomBytes(Address.Size)); + Address addr3 = new Address(TestUtils.GetRandomBytes(Address.Size)); + + AccountDiff diff = AccountDiff.Create(targetTrie, sourceTrie); + Assert.Empty(diff.StateDiffs); + Assert.Empty(diff.FungibleAssetValueDiffs); + Assert.Empty(diff.TotalSupplyDiffs); + Assert.Null(diff.ValidatorSetDiff); + + IAccount targetAccount = new Account(new AccountState(targetTrie)); + PrivateKey signer = new PrivateKey(); + IActionContext context = CreateActionContext(signer.ToAddress(), targetTrie); + targetAccount = targetAccount.SetState(addr1, new Text("One")); + targetAccount = targetAccount.SetState(addr2, new Text("Two")); + targetAccount = targetAccount.MintAsset( + context, signer.ToAddress(), new FungibleAssetValue(USD, 123, 45)); + + targetTrie = Commit(stateStore, targetTrie, targetAccount.Delta); + sourceTrie = targetTrie; + + IAccount sourceAccount = new Account(new AccountState(sourceTrie)); + sourceAccount = sourceAccount.SetState(addr2, new Text("Two_")); + sourceAccount = sourceAccount.SetState(addr3, new Text("Three")); + sourceAccount = sourceAccount.MintAsset( + context, signer.ToAddress(), new FungibleAssetValue(USD, 456, 78)); + sourceAccount = sourceAccount.MintAsset( + context, signer.ToAddress(), new FungibleAssetValue(KRW, 10, 0)); + sourceAccount = sourceAccount.BurnAsset( + context, signer.ToAddress(), new FungibleAssetValue(KRW, 10, 0)); + sourceAccount = sourceAccount.MintAsset( + context, signer.ToAddress(), new FungibleAssetValue(JPY, 321, 0)); + sourceAccount = sourceAccount.SetValidator(new Validator(signer.PublicKey, 1)); + + sourceTrie = Commit(stateStore, sourceTrie, sourceAccount.Delta); + + diff = AccountDiff.Create(targetTrie, sourceTrie); + Assert.Equal(2, diff.StateDiffs.Count); + Assert.Equal((new Text("Two"), new Text("Two_")), diff.StateDiffs[addr2]); + Assert.Equal((null, new Text("Three")), diff.StateDiffs[addr3]); + + Assert.Equal(2, diff.FungibleAssetValueDiffs.Count); // KRW is treated as unchanged + Assert.Equal( + (new Integer(12345), new Integer(12345 + 45678)), + diff.FungibleAssetValueDiffs[(signer.ToAddress(), USD.Hash)]); + Assert.Equal( + (new Integer(0), new Integer(321)), + diff.FungibleAssetValueDiffs[(signer.ToAddress(), JPY.Hash)]); + + Assert.Equal(2, diff.TotalSupplyDiffs.Count); // KRW is treated as unchanged + Assert.Equal( + (new Integer(12345), new Integer(12345 + 45678)), + diff.TotalSupplyDiffs[USD.Hash]); + Assert.Equal( + (new Integer(0), new Integer(321)), + diff.TotalSupplyDiffs[JPY.Hash]); + + Assert.Equal( + ( + new ValidatorSet(), + new ValidatorSet(new List() { new Validator(signer.PublicKey, 1) }) + ), + diff.ValidatorSetDiff); + + diff = AccountDiff.Create(sourceTrie, targetTrie); + Assert.Single(diff.StateDiffs); // Note addr3 is not tracked + Assert.Equal((new Text("Two_"), new Text("Two")), diff.StateDiffs[addr2]); + Assert.Single(diff.FungibleAssetValueDiffs); // Only USD is tracked + Assert.Equal( + (new Integer(12345 + 45678), new Integer(12345)), + diff.FungibleAssetValueDiffs[(signer.ToAddress(), USD.Hash)]); + Assert.Single(diff.TotalSupplyDiffs); // Only USD is tracked + Assert.Equal( + (new Integer(12345 + 45678), new Integer(12345)), + diff.TotalSupplyDiffs[USD.Hash]); + Assert.Null(diff.ValidatorSetDiff); // Note ValidatorSet is not tracked + } + + public IActionContext CreateActionContext(Address signer, ITrie trie) => + new ActionContext( + signer, + null, + signer, + 0, + Block.CurrentProtocolVersion, + new Account(new AccountState(trie)), + 0, + 0, + false); + + public ITrie Commit( + IStateStore stateStore, + ITrie baseTrie, + IAccountDelta accountDelta) + { + var trie = baseTrie; + var rawDelta = accountDelta.ToRawDelta(); + + foreach (var kv in rawDelta) + { + trie = trie.Set(kv.Key, kv.Value); + } + + return stateStore.Commit(trie); + } + } +} diff --git a/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/Libplanet.Tests/Action/ActionEvaluatorTest.cs index 57432ae30ec..ca5f9d2eb08 100644 --- a/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -86,27 +86,30 @@ public void Idempotent() transactions: txs).Propose(); var actionEvaluator = new ActionEvaluator( _ => null, - NullChainStates.Instance, + stateStore, new SingleActionLoader(typeof(RandomAction))); Block stateRootBlock = noStateRootBlock.Sign( GenesisProposer, BlockChain.DetermineGenesisStateRootHash( actionEvaluator, noStateRootBlock, - out IReadOnlyList evals)); - stateStore.Commit(null, evals.GetRawTotalDelta()); + out IReadOnlyList evals)); var generatedRandomNumbers = new List(); AssertPreEvaluationBlocksEqual(stateRootBlock, noStateRootBlock); for (int i = 0; i < repeatCount; ++i) { - var actionEvaluations = actionEvaluator.Evaluate(noStateRootBlock); + var actionEvaluations = actionEvaluator.Evaluate(noStateRootBlock, null); generatedRandomNumbers.Add( - (Integer)actionEvaluations[0].OutputState.GetState(txAddress)); - actionEvaluations = actionEvaluator.Evaluate(stateRootBlock); + (Integer)new AccountState( + stateStore.GetStateRoot(actionEvaluations[0].OutputState)) + .GetState(txAddress)); + actionEvaluations = actionEvaluator.Evaluate(stateRootBlock, null); generatedRandomNumbers.Add( - (Integer)actionEvaluations[0].OutputState.GetState(txAddress)); + (Integer)new AccountState( + stateStore.GetStateRoot(actionEvaluations[0].OutputState)) + .GetState(txAddress)); } for (int i = 1; i < generatedRandomNumbers.Count; ++i) @@ -146,7 +149,8 @@ public void Evaluate() Block block = chain.ProposeBlock(miner); chain.Append(block, CreateBlockCommit(block)); - var evaluations = chain.ActionEvaluator.Evaluate(chain.Tip); + var evaluations = chain.ActionEvaluator.Evaluate( + chain.Tip, chain.Store.GetStateRootHash(chain.Tip.PreviousHash)); Assert.False(evaluations[0].InputContext.BlockAction); Assert.Single(evaluations); @@ -180,7 +184,8 @@ public void EvaluateWithException() chain.StageTransaction(tx); Block block = chain.ProposeBlock(new PrivateKey()); chain.Append(block, CreateBlockCommit(block)); - var evaluations = chain.ActionEvaluator.Evaluate(chain.Tip); + var evaluations = chain.ActionEvaluator.Evaluate( + chain.Tip, chain.Store.GetStateRootHash(chain.Tip.PreviousHash)); Assert.False(evaluations[0].InputContext.BlockAction); Assert.Single(evaluations); @@ -229,7 +234,7 @@ public void EvaluateWithCriticalException() txHash: BlockContent.DeriveTxHash(txs), lastCommit: null), transactions: txs).Propose(); - IAccount previousState = actionEvaluator.PrepareInitialDelta(block); + IAccount previousState = actionEvaluator.PrepareInitialDelta(genesis.StateRootHash); Assert.Throws( () => actionEvaluator.EvaluateTx( @@ -237,7 +242,8 @@ public void EvaluateWithCriticalException() tx: tx, previousState: previousState).ToList()); Assert.Throws( - () => chain.ActionEvaluator.Evaluate(block).ToList()); + () => chain.ActionEvaluator.Evaluate( + block, chain.Store.GetStateRootHash(block.PreviousHash)).ToList()); } [Fact] @@ -267,7 +273,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu Block genesis = ProposeGenesisBlock(TestUtils.GenesisProposer); var actionEvaluator = new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: NullChainStates.Instance, + stateStore: new TrieStateStore(new MemoryKeyValueStore()), actionTypeLoader: new SingleActionLoader(typeof(DumbAction))); Transaction[] block1Txs = @@ -300,7 +306,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu timestamp: DateTimeOffset.MinValue.AddSeconds(7)), }; foreach ((var tx, var i) in block1Txs.Zip( - Enumerable.Range(0, block1Txs.Count()), (x, y) => (x, y))) + Enumerable.Range(0, block1Txs.Length), (x, y) => (x, y))) { _logger.Debug("{0}[{1}] = {2}", nameof(block1Txs), i, tx.Id); } @@ -309,7 +315,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu genesis, GenesisProposer, block1Txs); - IAccount previousState = actionEvaluator.PrepareInitialDelta(block1); + IAccount previousState = actionEvaluator.PrepareInitialDelta(null); var evals = actionEvaluator.EvaluateBlock( block1, previousState).ToImmutableArray(); @@ -332,7 +338,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu Assert.Equal(expect.Signer, eval.InputContext.Signer); Assert.Equal(GenesisProposer.ToAddress(), eval.InputContext.Miner); Assert.Equal(block1.Index, eval.InputContext.BlockIndex); - randomValue = eval.InputContext.Random.Next(); + randomValue = eval.InputContext.GetRandom().Next(); Assert.Equal( (Integer)eval.OutputState.GetState( DumbAction.RandomRecordsAddress), @@ -343,7 +349,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu .Select(x => x is Text t ? t.Value : null)); } - previousState = actionEvaluator.PrepareInitialDelta(block1); + previousState = actionEvaluator.PrepareInitialDelta(null); ActionEvaluation[] evals1 = actionEvaluator.EvaluateBlock(block1, previousState).ToArray(); IImmutableDictionary dirty1 = evals1.GetDirtyStates(); @@ -412,7 +418,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu timestamp: DateTimeOffset.MinValue.AddSeconds(4)), }; foreach ((var tx, var i) in block2Txs.Zip( - Enumerable.Range(0, block2Txs.Count()), (x, y) => (x, y))) + Enumerable.Range(0, block2Txs.Length), (x, y) => (x, y))) { _logger.Debug("{0}[{1}] = {2}", nameof(block2Txs), i, tx.Id); } @@ -456,7 +462,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu Assert.Equal(block2.Index, eval.InputContext.BlockIndex); Assert.False(eval.InputContext.Rehearsal); Assert.Null(eval.Exception); - randomValue = eval.InputContext.Random.Next(); + randomValue = eval.InputContext.GetRandom().Next(); Assert.Equal( eval.OutputState.GetState( DumbAction.RandomRecordsAddress), @@ -523,12 +529,12 @@ public void EvaluateTx() transactions: txs).Propose(); var actionEvaluator = new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: NullChainStates.Instance, + stateStore: new TrieStateStore(new MemoryKeyValueStore()), actionTypeLoader: new SingleActionLoader(typeof(DumbAction))); DumbAction.RehearsalRecords.Value = ImmutableList<(Address, string)>.Empty; - IAccount previousState = actionEvaluator.PrepareInitialDelta(block); + IAccount previousState = actionEvaluator.PrepareInitialDelta(null); var evaluations = actionEvaluator.EvaluateTx( blockHeader: block, tx: tx, @@ -564,7 +570,7 @@ public void EvaluateTx() Assert.Equal( (Integer)eval.OutputState.GetState( DumbAction.RandomRecordsAddress), - (Integer)eval.InputContext.Random.Next()); + (Integer)eval.InputContext.GetRandom().Next()); IActionEvaluation prevEval = i > 0 ? evaluations[i - 1] : null; Assert.Equal( prevEval is null @@ -594,7 +600,7 @@ prevEval is null DumbAction.RehearsalRecords.Value = ImmutableList<(Address, string)>.Empty; - previousState = actionEvaluator.PrepareInitialDelta(block); + previousState = actionEvaluator.PrepareInitialDelta(null); IAccount delta = actionEvaluator.EvaluateTx( blockHeader: block, tx: tx, @@ -625,7 +631,7 @@ public void EvaluateTxResultThrowingException() var hash = new BlockHash(GetRandomBytes(BlockHash.Size)); var actionEvaluator = new ActionEvaluator( policyBlockActionGetter: _ => null, - blockChainStates: NullChainStates.Instance, + stateStore: new TrieStateStore(new MemoryKeyValueStore()), actionTypeLoader: new SingleActionLoader(typeof(ThrowException)) ); var block = new BlockContent( @@ -637,13 +643,13 @@ public void EvaluateTxResultThrowingException() txHash: BlockContent.DeriveTxHash(txs), lastCommit: CreateBlockCommit(hash, 122, 0)), transactions: txs).Propose(); - IAccount previousState = actionEvaluator.PrepareInitialDelta(block); - var nextStates = actionEvaluator.EvaluateTx( + IAccount previousState = actionEvaluator.PrepareInitialDelta(null); + var nextState = actionEvaluator.EvaluateTx( blockHeader: block, tx: tx, previousState: previousState).Last().OutputState; - Assert.Empty(nextStates.GetUpdatedStates()); + Assert.Empty(nextState.GetUpdatedStates()); } [Fact] @@ -768,7 +774,7 @@ public void EvaluatePolicyBlockAction() var block = chain.ProposeBlock( GenesisProposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip)); - IAccount previousState = actionEvaluator.PrepareInitialDelta(genesis); + IAccount previousState = actionEvaluator.PrepareInitialDelta(null); var evaluation = actionEvaluator.EvaluatePolicyBlockAction(genesis, previousState); Assert.Equal(chain.Policy.BlockAction, evaluation.Action); @@ -787,7 +793,7 @@ public void EvaluatePolicyBlockAction() Assert.True(evaluation.InputContext.BlockAction); chain.Append(block, CreateBlockCommit(block), render: true); - previousState = actionEvaluator.PrepareInitialDelta(block); + previousState = actionEvaluator.PrepareInitialDelta(genesis.StateRootHash); var txEvaluations = actionEvaluator.EvaluateBlock( block, previousState).ToList(); @@ -918,10 +924,12 @@ public void TotalUpdatedFungibleAssets() var block = chain.ProposeBlock( GenesisProposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip)); - var evals = chain.EvaluateBlock(block); + var evals = actionEvaluator.EvaluateBlock( + block, + new Account(chain.GetAccountState(block.PreviousHash))); - // Includes policy block action - Assert.Equal(4, evals.Count); + // Does not include policy block action + Assert.Equal(3, evals.Count()); var latest = evals.Last().OutputState; // Only addresses[0] and addresses[1] succeeded in minting @@ -980,17 +988,19 @@ public void EvaluateActionAndCollectFee() var miner = new PrivateKey(); Block block = chain.ProposeBlock(miner); - var evaluations = chain.ActionEvaluator.Evaluate(block); + var evaluations = chain.ActionEvaluator.Evaluate( + block, chain.Store.GetStateRootHash(block.PreviousHash)); Assert.False(evaluations[0].InputContext.BlockAction); Assert.Single(evaluations); Assert.Null(evaluations.Single().Exception); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 9), - evaluations.Single().OutputState.GetBalance(address, foo)); + chain.GetAccountState(evaluations.Single().OutputState).GetBalance(address, foo)); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 1), - evaluations.Single().OutputState.GetBalance(miner.ToAddress(), foo)); + chain.GetAccountState( + evaluations.Single().OutputState).GetBalance(miner.ToAddress(), foo)); } [Fact] @@ -1043,7 +1053,9 @@ public void EvaluateThrowingExceedGasLimit() var miner = new PrivateKey(); Block block = chain.ProposeBlock(miner); - var evaluations = chain.ActionEvaluator.Evaluate(block); + var evaluations = chain.ActionEvaluator.Evaluate( + block, + chain.Store.GetStateRootHash(block.PreviousHash)); Assert.False(evaluations[0].InputContext.BlockAction); Assert.Single(evaluations); @@ -1054,10 +1066,11 @@ public void EvaluateThrowingExceedGasLimit() evaluations.Single().Exception?.InnerException?.GetType()); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 5), - evaluations.Single().OutputState.GetBalance(address, foo)); + chain.GetAccountState(evaluations.Single().OutputState).GetBalance(address, foo)); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 5), - evaluations.Single().OutputState.GetBalance(miner.ToAddress(), foo)); + chain.GetAccountState( + evaluations.Single().OutputState).GetBalance(miner.ToAddress(), foo)); } [Fact] @@ -1110,7 +1123,8 @@ public void EvaluateThrowingInsufficientBalanceForGasFee() var miner = new PrivateKey(); Block block = chain.ProposeBlock(miner); - var evaluations = chain.ActionEvaluator.Evaluate(block); + var evaluations = chain.ActionEvaluator.Evaluate( + block, chain.Store.GetStateRootHash(block.PreviousHash)); Assert.False(evaluations[0].InputContext.BlockAction); Assert.Single(evaluations); @@ -1171,7 +1185,8 @@ public void EvaluateMinusGasFee() var miner = new PrivateKey(); Block block = chain.ProposeBlock(miner); - var evaluations = chain.ActionEvaluator.Evaluate(block); + var evaluations = chain.ActionEvaluator.Evaluate( + block, chain.Store.GetStateRootHash(block.PreviousHash)); Assert.False(evaluations[0].InputContext.BlockAction); Assert.Single(evaluations); @@ -1295,7 +1310,7 @@ private void CheckRandomSeedInAction() { IActionEvaluation eval = evalsA[i]; IActionContext context = eval.InputContext; - Assert.Equal(initialRandomSeed + i, context.Random.Seed); + Assert.Equal(initialRandomSeed + i, context.RandomSeed); } } diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index 7bf98ff7fa8..4ff7204fe4a 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -6,6 +6,7 @@ using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.Loader; +using Libplanet.Action.State; using Libplanet.Action.Tests.Common; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; @@ -15,7 +16,6 @@ using Libplanet.Store; using Libplanet.Store.Trie; using Libplanet.Tests.Store; -using Libplanet.Types.Assets; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using Xunit; @@ -80,44 +80,48 @@ Func getTxExecution Assert.Equal(2, renders[0].Context.BlockIndex); Assert.Equal( new IValue[] { null, null, null, null, (Integer)1 }, - addresses.Select(renders[0].Context.PreviousState.GetState) + addresses.Select( + _blockChain.GetAccountState(renders[0].Context.PreviousState).GetState) ); Assert.Equal( new IValue[] { (Text)"foo", null, null, null, (Integer)1 }, - addresses.Select(renders[0].NextStates.GetState) + addresses.Select(_blockChain.GetAccountState(renders[0].NextState).GetState) ); Assert.Equal("bar", actions[1].Item); Assert.Equal(2, renders[1].Context.BlockIndex); Assert.Equal( - addresses.Select(renders[0].NextStates.GetState), - addresses.Select(renders[1].Context.PreviousState.GetState) + addresses.Select(_blockChain.GetAccountState(renders[0].NextState).GetState), + addresses.Select( + _blockChain.GetAccountState(renders[1].Context.PreviousState).GetState) ); Assert.Equal( new IValue[] { (Text)"foo", (Text)"bar", null, null, (Integer)1 }, - addresses.Select(renders[1].NextStates.GetState) + addresses.Select(_blockChain.GetAccountState(renders[1].NextState).GetState) ); Assert.Equal("baz", actions[2].Item); Assert.Equal(2, renders[2].Context.BlockIndex); Assert.Equal( - addresses.Select(renders[1].NextStates.GetState), - addresses.Select(renders[2].Context.PreviousState.GetState) + addresses.Select(_blockChain.GetAccountState(renders[1].NextState).GetState), + addresses.Select( + _blockChain.GetAccountState(renders[2].Context.PreviousState).GetState) ); Assert.Equal( new IValue[] { (Text)"foo", (Text)"bar", (Text)"baz", null, (Integer)1 }, - addresses.Select(renders[2].NextStates.GetState) + addresses.Select(_blockChain.GetAccountState(renders[2].NextState).GetState) ); Assert.Equal("qux", actions[3].Item); Assert.Equal(2, renders[3].Context.BlockIndex); Assert.Equal( - addresses.Select(renders[2].NextStates.GetState), - addresses.Select(renders[3].Context.PreviousState.GetState) + addresses.Select(_blockChain.GetAccountState(renders[2].NextState).GetState), + addresses.Select( + _blockChain.GetAccountState(renders[3].Context.PreviousState).GetState) ); Assert.Equal( new IValue[] { (Text)"foo", (Text)"bar", (Text)"baz", (Text)"qux", (Integer)1, }, - renders[3].NextStates.GetStates(addresses) + _blockChain.GetAccountState(renders[3].NextState).GetStates(addresses) ); Address minerAddress = addresses[4]; @@ -133,15 +137,18 @@ Func getTxExecution Assert.Equal( (Integer)1, - (Integer)blockRenders[0].NextStates.GetState(minerAddress) + (Integer)_blockChain.GetAccountState( + blockRenders[0].NextState).GetState(minerAddress) ); Assert.Equal( (Integer)1, - (Integer)blockRenders[1].Context.PreviousState.GetState(minerAddress) + (Integer)_blockChain.GetAccountState(blockRenders[1].Context.PreviousState) + .GetState(minerAddress) ); Assert.Equal( (Integer)2, - (Integer)blockRenders[1].NextStates.GetState(minerAddress) + (Integer)_blockChain.GetAccountState( + blockRenders[1].NextState).GetState(minerAddress) ); foreach (Transaction tx in txs) @@ -150,11 +157,15 @@ Func getTxExecution Assert.Null(getTxExecution(block1.Hash, tx.Id)); TxExecution e = getTxExecution(block2.Hash, tx.Id); - Assert.IsType(e); - var s = (TxSuccess)e; - Assert.Equal(block2.Hash, s.BlockHash); - Assert.Equal(tx.Id, s.TxId); - Assert.Empty(s.UpdatedFungibleAssets); + Assert.False(e.Fail); + Assert.Equal(block2.Hash, e.BlockHash); + Assert.Equal(tx.Id, e.TxId); + var inputAccount = + _blockChain.GetAccountState(Assert.IsType>(e.InputState)); + var outputAccount = + _blockChain.GetAccountState(Assert.IsType>(e.OutputState)); + var accountDiff = AccountDiff.Create(inputAccount, outputAccount); + Assert.Empty(accountDiff.FungibleAssetValueDiffs); } var pk = new PrivateKey(); @@ -192,66 +203,57 @@ Func getTxExecution _blockChain.Append(block3, TestUtils.CreateBlockCommit(block3)); var txExecution1 = getTxExecution(block3.Hash, tx1Transfer.Id); _logger.Verbose(nameof(txExecution1) + " = {@TxExecution}", txExecution1); - Assert.IsType(txExecution1); - var txSuccess1 = (TxSuccess)txExecution1; + Assert.False(txExecution1.Fail); + var inputAccount1 = _blockChain.GetAccountState( + Assert.IsType>(txExecution1.InputState)); + var outputAccount1 = _blockChain.GetAccountState( + Assert.IsType>(txExecution1.OutputState)); + var accountDiff1 = AccountDiff.Create(inputAccount1, outputAccount1); + Assert.Equal( - addresses.Take(3).Append(pk.ToAddress()).ToImmutableHashSet(), - txSuccess1.UpdatedAddresses - ); + (new Address[] { addresses[0], pk.ToAddress() }).ToImmutableHashSet(), + accountDiff1.StateDiffs.Select(kv => kv.Key).ToImmutableHashSet()); Assert.Equal( - ImmutableDictionary.Empty - .Add(pk.ToAddress(), (Text)"foo") - .Add(addresses[0], (Text)"foo,bar"), - txSuccess1.UpdatedStates - ); + (new Address[] { addresses[1], addresses[2], pk.ToAddress() }) + .ToImmutableHashSet(), + accountDiff1.FungibleAssetValueDiffs.Select(kv => kv.Key.Item1) + .ToImmutableHashSet()); Assert.Equal( - ImmutableDictionary>.Empty - .Add( - pk.ToAddress(), - ImmutableDictionary.Empty - .Add(DumbAction.DumbCurrency, DumbAction.DumbCurrency * -30) - ) - .Add( - addresses[1], - ImmutableDictionary.Empty - .Add(DumbAction.DumbCurrency, DumbAction.DumbCurrency * 10) - ) - .Add( - addresses[2], - ImmutableDictionary.Empty - .Add(DumbAction.DumbCurrency, DumbAction.DumbCurrency * 20) - ), - txSuccess1.UpdatedFungibleAssets - ); + new Text("foo"), + outputAccount1.GetState(pk.ToAddress())); + Assert.Equal( + new Text("foo,bar"), + outputAccount1.GetState(addresses[0])); + Assert.Equal( + DumbAction.DumbCurrency * -30, + outputAccount1.GetBalance(pk.ToAddress(), DumbAction.DumbCurrency)); + Assert.Equal( + DumbAction.DumbCurrency * 10, + outputAccount1.GetBalance(addresses[1], DumbAction.DumbCurrency)); + Assert.Equal( + DumbAction.DumbCurrency * 20, + outputAccount1.GetBalance(addresses[2], DumbAction.DumbCurrency)); + var txExecution2 = getTxExecution(block3.Hash, tx2Error.Id); _logger.Verbose(nameof(txExecution2) + " = {@TxExecution}", txExecution2); - Assert.IsType(txExecution2); - var txFailure = (TxFailure)txExecution2; - Assert.Equal(block3.Hash, txFailure.BlockHash); - Assert.Equal(tx2Error.Id, txFailure.TxId); - Assert.Equal( + Assert.True(txExecution2.Fail); + Assert.Equal(block3.Hash, txExecution2.BlockHash); + Assert.Equal(tx2Error.Id, txExecution2.TxId); + Assert.Contains( $"{nameof(System)}.{nameof(ArgumentOutOfRangeException)}", - txFailure.ExceptionName - ); - Assert.Null(txFailure.ExceptionMetadata); + txExecution2.ExceptionNames); + var txExecution3 = getTxExecution(block3.Hash, tx3Transfer.Id); _logger.Verbose(nameof(txExecution3) + " = {@TxExecution}", txExecution3); - Assert.IsType(txExecution3); - var txSuccess3 = (TxSuccess)txExecution3; + Assert.False(txExecution3.Fail); + var outputAccount3 = _blockChain.GetAccountState( + Assert.IsType>(txExecution3.OutputState)); Assert.Equal( - ImmutableDictionary>.Empty - .Add( - pk.ToAddress(), - ImmutableDictionary.Empty - .Add(DumbAction.DumbCurrency, DumbAction.DumbCurrency * -35) - ) - .Add( - addresses[1], - ImmutableDictionary.Empty - .Add(DumbAction.DumbCurrency, DumbAction.DumbCurrency * 15) - ), - txSuccess3.UpdatedFungibleAssets - ); + DumbAction.DumbCurrency * -35, + outputAccount3.GetBalance(pk.ToAddress(), DumbAction.DumbCurrency)); + Assert.Equal( + DumbAction.DumbCurrency * 15, + outputAccount3.GetBalance(addresses[1], DumbAction.DumbCurrency)); } [SkippableFact] @@ -312,7 +314,7 @@ public void AppendFailDueToInvalidTxCount() miner, manyTxs.ToImmutableList(), TestUtils.CreateBlockCommit(_blockChain.Tip)); - Assert.Equal(manyTxs.Count, block.Transactions.Count()); + Assert.Equal(manyTxs.Count, block.Transactions.Count); var e = Assert.Throws(() => _blockChain.Append(block, TestUtils.CreateBlockCommit(block)) @@ -374,11 +376,11 @@ TxPolicyViolationException IsSignerValid( fx.GenesisBlock, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates: new BlockChainStates(fx.Store, fx.StateStore), + stateStore: fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); - var validTx = blockChain.MakeTransaction(validKey, new DumbAction[] { }); - var invalidTx = blockChain.MakeTransaction(invalidKey, new DumbAction[] { }); + var validTx = blockChain.MakeTransaction(validKey, Array.Empty()); + var invalidTx = blockChain.MakeTransaction(invalidKey, Array.Empty()); var miner = new PrivateKey(); @@ -403,7 +405,6 @@ public void UnstageAfterAppendComplete() PrivateKey privateKey = new PrivateKey(); (Address[] addresses, Transaction[] txs) = MakeFixturesForAppendTests(privateKey, epoch: DateTimeOffset.UtcNow); - var genesis = _blockChain.Genesis; Assert.Empty(_blockChain.GetStagedTransactionIds()); // Mining with empty staged. @@ -450,7 +451,6 @@ public void DoesNotUnstageOnAppendForForkedChain() PrivateKey privateKey = new PrivateKey(); (_, Transaction[] txs) = MakeFixturesForAppendTests(privateKey, epoch: DateTimeOffset.UtcNow); - var genesis = _blockChain.Genesis; Assert.Empty(_blockChain.GetStagedTransactionIds()); var workspace = _blockChain.Fork(_blockChain.Genesis.Hash); Assert.Empty(_blockChain.ListStagedTransactions()); @@ -506,7 +506,7 @@ public void AppendValidatesBlock() blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + _fx.StateStore, new SingleActionLoader(typeof(DumbAction)))); Assert.Throws( () => blockChain.Append(_fx.Block1, TestUtils.CreateBlockCommit(_fx.Block1))); @@ -608,7 +608,8 @@ public void CachedActionEvaluationWrittenOnAppend() _blockChain.StageTransaction(txA0); _blockChain.StageTransaction(txA1); Block block = _blockChain.ProposeBlock(miner); - IReadOnlyList actionEvaluations = _blockChain.EvaluateBlock(block); + IReadOnlyList actionEvaluations = + _blockChain.EvaluateBlock(block); Assert.Equal(0L, _blockChain.Tip.Index); _blockChain.Append( block, diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs b/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs index c4b96ce5638..4a560f1c5f0 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.Internals.cs @@ -2,18 +2,18 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.Tests.Common; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Store; -using Libplanet.Types.Assets; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using Xunit; using static Libplanet.Action.State.KeyConverters; using static Libplanet.Tests.TestUtils; -using FAV = Libplanet.Types.Assets.FungibleAssetValue; namespace Libplanet.Tests.Blockchain { @@ -131,10 +131,9 @@ public void ExecuteActions() { AssertBencodexEqual( pair.Value, - _fx.StateStore.GetStates( - block1.StateRootHash, - new[] { ToStateKey(pair.Key) } - )[0] + _fx.StateStore + .GetStateRoot(block1.StateRootHash) + .Get(new[] { ToStateKey(pair.Key) })[0] ); } } @@ -144,24 +143,14 @@ public void ExecuteActions() [InlineData(false)] public void UpdateTxExecutions(bool getTxExecutionViaStore) { - void AssertTxSuccessesEqual(TxSuccess expected, TxExecution actual) + void AssertTxExecutionEqual(TxExecution expected, TxExecution actual) { - Assert.IsType(actual); - var success = (TxSuccess)actual; - Assert.Equal(expected.TxId, success.TxId); - Assert.Equal(expected.BlockHash, success.BlockHash); - Assert.Equal(expected.UpdatedStates, success.UpdatedStates); - Assert.Equal(expected.UpdatedFungibleAssets, success.UpdatedFungibleAssets); - } - - void AssertTxFailuresEqual(TxFailure expected, TxExecution actual) - { - Assert.IsType(actual); - var failure = (TxFailure)actual; - Assert.Equal(expected.TxId, failure.TxId); - Assert.Equal(expected.BlockHash, failure.BlockHash); - Assert.Equal(expected.ExceptionName, failure.ExceptionName); - Assert.Equal(expected.ExceptionMetadata, failure.ExceptionMetadata); + Assert.Equal(expected.Fail, actual.Fail); + Assert.Equal(expected.TxId, actual.TxId); + Assert.Equal(expected.BlockHash, actual.BlockHash); + Assert.Equal(expected.InputState, actual.InputState); + Assert.Equal(expected.OutputState, actual.OutputState); + Assert.Equal(expected.ExceptionNames, actual.ExceptionNames); } Func getTxExecution @@ -175,37 +164,32 @@ Func getTxExecution Assert.Null(getTxExecution(_fx.Hash2, _fx.TxId2)); var random = new System.Random(); - var inputA = new TxSuccess( + var inputA = new TxExecution( _fx.Hash1, _fx.TxId1, - ImmutableDictionary.Empty.Add( - random.NextAddress(), - (Text)"state value" - ), - ImmutableDictionary>.Empty - .Add( - random.NextAddress(), - ImmutableDictionary.Empty.Add( - DumbAction.DumbCurrency, - DumbAction.DumbCurrency * 10 - ) - ) - ); - var inputB = new TxFailure( + false, + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new List() { string.Empty }); + var inputB = new TxExecution( _fx.Hash1, _fx.TxId2, - "AnExceptionName" - ); - var inputC = new TxFailure( + true, + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new List() { "AnExceptionName" }); + var inputC = new TxExecution( _fx.Hash2, _fx.TxId1, - "AnotherExceptionName" - ); + true, + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new List() { "AnotherExceptionName", "YetAnotherExceptionName" }); _blockChain.UpdateTxExecutions(new TxExecution[] { inputA, inputB, inputC }); - AssertTxSuccessesEqual(inputA, getTxExecution(_fx.Hash1, _fx.TxId1)); - AssertTxFailuresEqual(inputB, getTxExecution(_fx.Hash1, _fx.TxId2)); - AssertTxFailuresEqual(inputC, getTxExecution(_fx.Hash2, _fx.TxId1)); + AssertTxExecutionEqual(inputA, getTxExecution(_fx.Hash1, _fx.TxId1)); + AssertTxExecutionEqual(inputB, getTxExecution(_fx.Hash1, _fx.TxId2)); + AssertTxExecutionEqual(inputC, getTxExecution(_fx.Hash2, _fx.TxId1)); Assert.Null(getTxExecution(_fx.Hash2, _fx.TxId2)); } } diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs b/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs index 2d7ac97d3be..8c892a46f3f 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.ProposeBlock.cs @@ -125,7 +125,7 @@ public void CanProposeInvalidGenesisBlock() var policy = new BlockPolicy(); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(fx.Store, fx.StateStore), + fx.StateStore, new SingleActionLoader(typeof(DumbAction))); var genesis = BlockChain.ProposeGenesisBlock( actionEvaluator, @@ -165,7 +165,7 @@ public void CanProposeInvalidBlock() fx.GenesisBlock, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates: new BlockChainStates(fx.Store, fx.StateStore), + stateStore: fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); var txs = new[] { @@ -314,7 +314,7 @@ public void ProposeBlockWithPendingTxs() tx.Id, txx ); - Assert.IsType(txx); + Assert.False(txx.Fail); Assert.Equal(block.Hash, txx.BlockHash); Assert.Equal(tx.Id, txx.TxId); Assert.Null(_blockChain.GetTxExecution(_blockChain.Genesis.Hash, tx.Id)); @@ -347,7 +347,7 @@ TxPolicyViolationException IsSignerValid( fx.GenesisBlock, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates: new BlockChainStates(fx.Store, fx.StateStore), + stateStore: fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); var validTx = blockChain.MakeTransaction(validKey, new DumbAction[] { }); @@ -459,7 +459,7 @@ public void ProposeBlockWithBlockAction() blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + _fx.StateStore, new SingleActionLoader(typeof(DumbAction)))); blockChain.MakeTransaction(privateKey2, new[] { new DumbAction(address2, "baz") }); diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs b/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs index 86a1f4dc992..ce70e70b331 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.ValidateNextBlock.cs @@ -182,7 +182,7 @@ public void ValidateNextBlockInvalidStateRootHash() IStore store = new MemoryStore(); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new SingleActionLoader(typeof(DumbAction))); var genesisBlock = TestUtils.ProposeGenesisBlock( actionEvaluator, @@ -226,7 +226,7 @@ public void ValidateNextBlockInvalidStateRootHash() blockChainStates, new ActionEvaluator( _ => policyWithBlockAction.BlockAction, - blockChainStates, + stateStore, new SingleActionLoader(typeof(DumbAction)))); Assert.Throws(() => chain2.Append(block1, TestUtils.CreateBlockCommit(block1))); diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index e5b833820f8..fa2355a049d 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -65,7 +65,7 @@ public BlockChainTest(ITestOutputHelper output) _fx.GenesisBlock, new ActionEvaluator( _ => _policy.BlockAction, - blockChainStates: new BlockChainStates(_fx.Store, _fx.StateStore), + stateStore: _fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction))), renderers: new[] { new LoggedActionRenderer(_renderer, Log.Logger) } ); @@ -98,7 +98,6 @@ public void ValidatorSet() "GenesisBlock is {Hash}, Transactions: {Txs}", _blockChain.Genesis, _blockChain.Genesis.Transactions); - var transactions = _blockChain.Genesis.Transactions.ToList(); Assert.Equal(TestUtils.ValidatorSet.TotalCount, validatorSet.TotalCount); } @@ -139,7 +138,7 @@ public void CanonicalId() blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + _fx.StateStore, new SingleActionLoader(typeof(DumbAction)))); Assert.Equal(chain1.Id, z.Id); @@ -185,7 +184,7 @@ public void ProcessActions() typeof(BaseAction).Assembly, typeof(BaseAction)); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + stateStore, actionLoader); var nonce = 0; var txs = TestUtils.ValidatorSet.Validators @@ -311,10 +310,10 @@ public void ActionRenderersHaveDistinctContexts() new LoggedActionRenderer( new AnonymousActionRenderer { - ActionRenderer = (act, context, nextStates) => + ActionRenderer = (act, context, nextState) => // Consuming the random state through IRandom.Next() should not // affect contexts passed to other action renderers. - generatedRandomValueLogs.Add(context.Random.Next()), + generatedRandomValueLogs.Add(context.GetRandom().Next()), }, Log.Logger.ForContext("RendererIndex", i) ) @@ -382,7 +381,7 @@ public void RenderActionsAfterAppendComplete() IActionRenderer renderer = new AnonymousActionRenderer { - ActionRenderer = (a, __, nextStates) => + ActionRenderer = (a, __, nextState) => { if (!(a is Dictionary dictionary && dictionary.TryGetValue((Text)"type_id", out IValue typeId) && @@ -596,7 +595,7 @@ public void ForkShouldSkipExecuteAndRenderGenesis() { var actionEvaluator = new ActionEvaluator( _ => _policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new SingleActionLoader(typeof(DumbAction))); var genesis = ProposeGenesisBlock( actionEvaluator, @@ -627,7 +626,7 @@ public void ForkShouldSkipExecuteAndRenderGenesis() genesis, new ActionEvaluator( _ => _policy.BlockAction, - blockChainStates: new BlockChainStates(store, stateStore), + stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction))), renderers: new[] { renderer } ); @@ -1017,7 +1016,7 @@ public void ReorgIsUnableToHeterogenousChain(bool render) { var actionEvaluator = new ActionEvaluator( _ => _policy.BlockAction, - blockChainStates: new BlockChainStates(fx2.Store, fx2.StateStore), + stateStore: fx2.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction))); Block genesis2 = ProposeGenesisBlock( actionEvaluator, @@ -1074,7 +1073,7 @@ public void GetStatesOnCreatingBlockChain() var stateStore = new TrieStateStore(new MemoryKeyValueStore()); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new SingleActionLoader(typeof(DumbAction))); Block genesisWithTx = ProposeGenesisBlock( actionEvaluator, @@ -1117,7 +1116,7 @@ public void GetStateOnlyDrillsDownUntilRequestedAddressesAreFound() blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + _fx.StateStore, new SingleActionLoader(typeof(DumbAction)))); Block b = chain.Genesis; @@ -1171,7 +1170,7 @@ public void GetStateReturnsEarlyForNonexistentAccount() blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + _fx.StateStore, new SingleActionLoader(typeof(DumbAction)))); Block b = chain.Genesis; @@ -1246,7 +1245,7 @@ public void GetStateReturnsLatestStatesWhenMultipleAddresses() blockChainStates, new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + _fx.StateStore, new SingleActionLoader(typeof(DumbAction)))); Assert.All(chain.GetStates(addresses), Assert.Null); @@ -1318,7 +1317,7 @@ public void FindBranchPoint() emptyFx.GenesisBlock, new ActionEvaluator( _ => _blockChain.Policy.BlockAction, - blockChainStates: new BlockChainStates(emptyFx.Store, emptyFx.StateStore), + stateStore: emptyFx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); var fork = BlockChain.Create( _blockChain.Policy, @@ -1328,7 +1327,7 @@ public void FindBranchPoint() forkFx.GenesisBlock, new ActionEvaluator( _ => _blockChain.Policy.BlockAction, - blockChainStates: new BlockChainStates(forkFx.Store, forkFx.StateStore), + stateStore: forkFx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); fork.Append(b1, CreateBlockCommit(b1)); fork.Append(b2, CreateBlockCommit(b2)); @@ -1677,7 +1676,7 @@ internal static (Address, Address[] Addresses, BlockChain Chain) var chainStates = new BlockChainStates(store, stateStore); var actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - blockChainStates: chainStates, + stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction))); Block genesisBlock = ProposeGenesisBlock( actionEvaluator, @@ -1707,7 +1706,7 @@ void BuildIndex(Guid id, Block block) // Build a store with incomplete states Block b = chain.Genesis; - IAccount previousState = actionEvaluator.PrepareInitialDelta(b); + IAccount previousState = actionEvaluator.PrepareInitialDelta(null); ActionEvaluation[] evals = actionEvaluator.EvaluateBlock(b, previousState).ToArray(); IImmutableDictionary dirty = evals.GetDirtyStates(); @@ -1901,7 +1900,7 @@ private void CreateWithGenesisBlock() storeFixture.Store, storeFixture.StateStore); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + storeFixture.StateStore, new SingleActionLoader(typeof(DumbAction))); BlockChain blockChain = BlockChain.Create( policy, @@ -1941,7 +1940,7 @@ private void ConstructWithUnexpectedGenesisBlock() var blockChainStates = new BlockChainStates(store, stateStore); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + stateStore, new SingleActionLoader(typeof(DumbAction))); var genesisBlockA = BlockChain.ProposeGenesisBlock(actionEvaluator); var genesisBlockB = BlockChain.ProposeGenesisBlock(actionEvaluator); @@ -2020,7 +2019,7 @@ private void CheckIfTxPolicyExceptionHasInnerException() List.Empty); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new SingleActionLoader(typeof(DumbAction))); var genesisWithTx = ProposeGenesisBlock( actionEvaluator, @@ -2083,7 +2082,7 @@ private void ValidateNextBlockCommitOnValidatorSetChange() var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(storeFixture.Store, storeFixture.StateStore), + storeFixture.StateStore, new SingleActionLoader(typeof(SetValidator))); Block genesis = BlockChain.ProposeGenesisBlock( actionEvaluator, diff --git a/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs b/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs index 82fa140c973..947c0bf12d5 100644 --- a/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs +++ b/Libplanet.Tests/Blockchain/Policies/BlockPolicyTest.cs @@ -43,7 +43,7 @@ public BlockPolicyTest(ITestOutputHelper output) _fx.GenesisBlock, new ActionEvaluator( _ => _policy.BlockAction, - blockChainStates: new BlockChainStates(_fx.Store, _fx.StateStore), + stateStore: _fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); } diff --git a/Libplanet.Tests/Blockchain/Policies/StagePolicyTest.cs b/Libplanet.Tests/Blockchain/Policies/StagePolicyTest.cs index 6ce4f8c7d47..421547e65f5 100644 --- a/Libplanet.Tests/Blockchain/Policies/StagePolicyTest.cs +++ b/Libplanet.Tests/Blockchain/Policies/StagePolicyTest.cs @@ -32,7 +32,7 @@ protected StagePolicyTest() _fx.GenesisBlock, new ActionEvaluator( _ => _policy.BlockAction, - blockChainStates: new BlockChainStates(_fx.Store, _fx.StateStore), + stateStore: _fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction)))); _key = new PrivateKey(); _txs = Enumerable.Range(0, 5).Select(i => diff --git a/Libplanet.Tests/Blockchain/Renderers/AnonymousActionRendererTest.cs b/Libplanet.Tests/Blockchain/Renderers/AnonymousActionRendererTest.cs index 4dc584e9ce5..1966eb0cf09 100644 --- a/Libplanet.Tests/Blockchain/Renderers/AnonymousActionRendererTest.cs +++ b/Libplanet.Tests/Blockchain/Renderers/AnonymousActionRendererTest.cs @@ -1,10 +1,12 @@ using System; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.State; using Libplanet.Action.Tests.Common; using Libplanet.Action.Tests.Mocks; using Libplanet.Blockchain.Renderers; +using Libplanet.Common; using Libplanet.Types.Blocks; using Xunit; @@ -16,8 +18,8 @@ public class AnonymousActionRendererTest private static IAccount _account = new Account(MockAccountState.Empty); - private static IActionContext _actionContext = - new ActionContext( + private static ICommittedActionContext _actionContext = + new CommittedActionContext(new ActionContext( default, default, default, @@ -25,7 +27,7 @@ public class AnonymousActionRendererTest default, _account, default, - 0); + 0)); private static Exception _exception = new Exception(); @@ -41,11 +43,11 @@ public class AnonymousActionRendererTest [Fact] public void ActionRenderer() { - (IValue, IActionContext, IAccount)? record = null; + (IValue, ICommittedActionContext, HashDigest)? record = null; var renderer = new AnonymousActionRenderer { - ActionRenderer = (action, context, nextStates) => - record = (action, context, nextStates), + ActionRenderer = (action, context, nextState) => + record = (action, context, nextState), }; renderer.RenderActionError(_action, _actionContext, _exception); @@ -53,24 +55,24 @@ record = (action, context, nextStates), renderer.RenderBlock(_genesis, _blockA); Assert.Null(record); - renderer.RenderAction(_action, _actionContext, _account); + renderer.RenderAction(_action, _actionContext, _account.Trie.Hash); Assert.NotNull(record); Assert.Same(_action, record?.Item1); Assert.Same(_actionContext, record?.Item2); - Assert.Same(_account, record?.Item3); + Assert.Equal(_account.Trie.Hash, record?.Item3); } [Fact] public void ActionErrorRenderer() { - (IValue, IActionContext, Exception)? record = null; + (IValue, ICommittedActionContext, Exception)? record = null; var renderer = new AnonymousActionRenderer { ActionErrorRenderer = (action, context, exception) => record = (action, context, exception), }; - renderer.RenderAction(_action, _actionContext, _account); + renderer.RenderAction(_action, _actionContext, _account.Trie.Hash); Assert.Null(record); renderer.RenderBlock(_genesis, _blockA); Assert.Null(record); @@ -91,7 +93,7 @@ public void BlockRenderer() BlockRenderer = (oldTip, newTip) => record = (oldTip, newTip), }; - renderer.RenderAction(_action, _actionContext, _account); + renderer.RenderAction(_action, _actionContext, _account.Trie.Hash); Assert.Null(record); renderer.RenderActionError(_action, _actionContext, _exception); Assert.Null(record); diff --git a/Libplanet.Tests/Blockchain/Renderers/LoggedActionRendererTest.cs b/Libplanet.Tests/Blockchain/Renderers/LoggedActionRendererTest.cs index a6a20d8fa03..4c8220d8616 100644 --- a/Libplanet.Tests/Blockchain/Renderers/LoggedActionRendererTest.cs +++ b/Libplanet.Tests/Blockchain/Renderers/LoggedActionRendererTest.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.State; using Libplanet.Action.Tests.Common; using Libplanet.Action.Tests.Mocks; using Libplanet.Blockchain.Renderers; +using Libplanet.Common; using Libplanet.Types.Blocks; using Serilog; using Serilog.Events; @@ -23,8 +25,6 @@ public class LoggedActionRendererTest : IDisposable private static IAccount _account = new Account(MockAccountState.Empty); - private static Exception _exception = new Exception(); - private static Block _genesis = TestUtils.ProposeGenesisBlock(TestUtils.GenesisProposer); @@ -68,8 +68,8 @@ public void ActionRenderings(bool error, bool rehearsal, bool exception) { bool called = false; LogEvent firstLog = null; - IActionContext actionContext = - new ActionContext( + ICommittedActionContext actionContext = + new CommittedActionContext(new ActionContext( default, default, default, @@ -78,13 +78,12 @@ public void ActionRenderings(bool error, bool rehearsal, bool exception) _account, default, 0, - rehearsal - ); + rehearsal)); Exception actionError = new Exception(); IActionRenderer actionRenderer; if (error) { - Action render = (action, cxt, e) => + Action render = (action, cxt, e) => { LogEvent[] logs = LogEvents.ToArray(); Assert.Single(logs); @@ -105,20 +104,21 @@ public void ActionRenderings(bool error, bool rehearsal, bool exception) } else { - Action render = (action, cxt, next) => - { - LogEvent[] logs = LogEvents.ToArray(); - Assert.Single(logs); - firstLog = logs[0]; - Assert.Same(_action, action); - Assert.Same(actionContext, cxt); - Assert.Same(_account, next); - called = true; - if (exception) + Action> render = + (action, cxt, next) => { - throw new ThrowException.SomeException(string.Empty); - } - }; + LogEvent[] logs = LogEvents.ToArray(); + Assert.Single(logs); + firstLog = logs[0]; + Assert.Same(_action, action); + Assert.Same(actionContext, cxt); + Assert.Equal(_account.Trie.Hash, next); + called = true; + if (exception) + { + throw new ThrowException.SomeException(string.Empty); + } + }; actionRenderer = new AnonymousActionRenderer { ActionRenderer = render, @@ -147,7 +147,7 @@ public void ActionRenderings(bool error, bool rehearsal, bool exception) } else { - actionRenderer.RenderAction(_action, actionContext, _account); + actionRenderer.RenderAction(_action, actionContext, _account.Trie.Hash); } } catch (ThrowException.SomeException e) diff --git a/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs b/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs index f4a3ab9d9a8..ccde686aa6f 100644 --- a/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs +++ b/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs @@ -43,7 +43,7 @@ public void Evaluate() { var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(fx.Store, fx.StateStore), + fx.StateStore, new SingleActionLoader(typeof(Arithmetic))); Block genesis = preEvalGenesis.Sign( _contents.GenesisKey, @@ -106,11 +106,11 @@ public void DetermineStateRootHash() { var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates: new BlockChainStates(fx.Store, fx.StateStore), + stateStore: fx.StateStore, actionTypeLoader: new SingleActionLoader(typeof(Arithmetic))); HashDigest genesisStateRootHash = BlockChain.DetermineGenesisStateRootHash( - actionEvaluator, preEvalGenesis, out _); + actionEvaluator, preEvalGenesis, out var _); _output.WriteLine("#0 StateRootHash: {0}", genesisStateRootHash); Block genesis = preEvalGenesis.Sign(_contents.GenesisKey, genesisStateRootHash); diff --git a/Libplanet.Tests/Fixtures/IntegerSet.cs b/Libplanet.Tests/Fixtures/IntegerSet.cs index 60404bed62e..89bdc2592e1 100644 --- a/Libplanet.Tests/Fixtures/IntegerSet.cs +++ b/Libplanet.Tests/Fixtures/IntegerSet.cs @@ -76,7 +76,7 @@ public IntegerSet( StateStore = new TrieStateStore(KVStore); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(Store, StateStore), + StateStore, new SingleActionLoader(typeof(Arithmetic))); Genesis = TestUtils.ProposeGenesisBlock( actionEvaluator, diff --git a/Libplanet.Tests/Store/ProxyStore.cs b/Libplanet.Tests/Store/ProxyStore.cs index 6325b207b02..dde51f23d54 100644 --- a/Libplanet.Tests/Store/ProxyStore.cs +++ b/Libplanet.Tests/Store/ProxyStore.cs @@ -114,13 +114,9 @@ public virtual bool DeleteBlock(BlockHash blockHash) => public virtual bool ContainsBlock(BlockHash blockHash) => Store.ContainsBlock(blockHash); - /// - public virtual void PutTxExecution(TxSuccess txSuccess) => - Store.PutTxExecution(txSuccess); - - /// - public virtual void PutTxExecution(TxFailure txFailure) => - Store.PutTxExecution(txFailure); + /// + public virtual void PutTxExecution(TxExecution txExecution) => + Store.PutTxExecution(txExecution); /// public virtual TxExecution GetTxExecution(BlockHash blockHash, TxId txid) => diff --git a/Libplanet.Tests/Store/StateStoreExtensionsTest.cs b/Libplanet.Tests/Store/StateStoreExtensionsTest.cs deleted file mode 100644 index 9af7cbb9002..00000000000 --- a/Libplanet.Tests/Store/StateStoreExtensionsTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Bencodex.Types; -using Libplanet.Store; -using Libplanet.Store.Trie; -using Xunit; -using static Libplanet.Tests.TestUtils; - -namespace Libplanet.Tests.Store -{ - [SuppressMessage( - "Usage", - "xUnit1026", - Justification = "Labels purpose to distinguish " + nameof(IStateStore) + " impls.")] - public class StateStoreExtensionsTest - { - public static IImmutableDictionary ZeroDelta => - ImmutableDictionary.Empty; - - public static KeyBytes KeyFoo => new KeyBytes("foo"); - - public static KeyBytes KeyBar => new KeyBytes("bar"); - - public static KeyBytes KeyBaz => new KeyBytes("baz"); - - public static IImmutableDictionary DeltaA => ZeroDelta - .Add(KeyFoo, (Text)"abc") - .Add(KeyBar, (Text)"def"); - - public static IImmutableDictionary DeltaB => ZeroDelta - .Add(KeyFoo, (Text)"ABC") - .Add(KeyBaz, (Text)"ghi"); - - [Fact] - public void Commit() - { - IStateStore stateStore = new TrieStateStore(new DefaultKeyValueStore(null)); - ITrie a = stateStore.Commit(null, DeltaA); - Assert.True(a.Recorded); - AssertBencodexEqual((Text)"abc", a.Get(new[] { KeyFoo })[0]); - AssertBencodexEqual((Text)"def", a.Get(new[] { KeyBar })[0]); - Assert.Null(a.Get(new[] { KeyBaz })[0]); - - ITrie recordA = stateStore.GetStateRoot(a.Hash); - AssertBytesEqual(a.Hash, recordA.Hash); - AssertBencodexEqual((Text)"abc", recordA.Get(new[] { KeyFoo })[0]); - AssertBencodexEqual((Text)"def", recordA.Get(new[] { KeyBar })[0]); - Assert.Null(recordA.Get(new[] { KeyBaz })[0]); - - ITrie b = stateStore.Commit(a.Hash, DeltaB); - Assert.True(b.Recorded); - AssertBencodexEqual((Text)"ABC", b.Get(new[] { KeyFoo })[0]); - AssertBencodexEqual((Text)"def", b.Get(new[] { KeyBar })[0]); - AssertBencodexEqual((Text)"ghi", b.Get(new[] { KeyBaz })[0]); - } - - [Fact] - public void GetState() - { - IStateStore stateStore = new TrieStateStore(new DefaultKeyValueStore(null)); - ITrie a = stateStore.Commit(null, DeltaA); - ITrie b = stateStore.Commit(a.Hash, DeltaB); - - AssertBencodexEqual( - (Text)"abc", stateStore.GetStates(a.Hash, new[] { new KeyBytes("foo") })[0]); - AssertBencodexEqual( - (Text)"def", stateStore.GetStates(a.Hash, new[] { new KeyBytes("bar") })[0]); - AssertBencodexEqual( - null, stateStore.GetStates(a.Hash, new[] { new KeyBytes("baz") })[0]); - - AssertBencodexEqual( - (Text)"ABC", stateStore.GetStates(b.Hash, new[] { new KeyBytes("foo") })[0]); - AssertBencodexEqual( - (Text)"def", stateStore.GetStates(b.Hash, new[] { new KeyBytes("bar") })[0]); - AssertBencodexEqual( - (Text)"ghi", stateStore.GetStates(b.Hash, new[] { new KeyBytes("baz") })[0]); - } - - [Fact] - public void ContainsStateRoot() - { - IStateStore stateStore = new TrieStateStore(new DefaultKeyValueStore(null)); - ITrie a = stateStore.Commit(null, DeltaA); - Assert.True(stateStore.ContainsStateRoot(a.Hash)); - } - } -} diff --git a/Libplanet.Tests/Store/StoreFixture.cs b/Libplanet.Tests/Store/StoreFixture.cs index 5592cc2e9ab..6661eb2b7ca 100644 --- a/Libplanet.Tests/Store/StoreFixture.cs +++ b/Libplanet.Tests/Store/StoreFixture.cs @@ -105,15 +105,14 @@ protected StoreFixture(IAction blockAction = null) validatorSet: TestUtils.ValidatorSet); var actionEvaluator = new ActionEvaluator( _ => blockAction, - new BlockChainStates(new MemoryStore(), stateStore), + stateStore, new SingleActionLoader(typeof(DumbAction))); GenesisBlock = preEval.Sign( Proposer, BlockChain.DetermineGenesisStateRootHash( actionEvaluator, preEval, - out IReadOnlyList evals)); - stateStore.Commit(null, evals.GetRawTotalDelta()); + out IReadOnlyList evals)); stateRootHashes[GenesisBlock.Hash] = GenesisBlock.StateRootHash; Block1 = TestUtils.ProposeNextBlock( GenesisBlock, diff --git a/Libplanet.Tests/Store/StoreTest.cs b/Libplanet.Tests/Store/StoreTest.cs index 38a4dab3f64..380b207ec1e 100644 --- a/Libplanet.Tests/Store/StoreTest.cs +++ b/Libplanet.Tests/Store/StoreTest.cs @@ -11,9 +11,9 @@ using Libplanet.Action.Tests.Common; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Store; -using Libplanet.Types.Assets; using Libplanet.Types.Blocks; using Libplanet.Types.Consensus; using Libplanet.Types.Tx; @@ -21,7 +21,6 @@ using Xunit; using Xunit.Abstractions; using static Libplanet.Tests.TestUtils; -using FAV = Libplanet.Types.Assets.FungibleAssetValue; namespace Libplanet.Tests.Store { @@ -393,24 +392,14 @@ public void StoreBlock() [SkippableFact] public void TxExecution() { - void AssertTxSuccessesEqual(TxSuccess expected, TxExecution actual) + void AssertTxExecutionEqual(TxExecution expected, TxExecution actual) { - Assert.IsType(actual); - var success = (TxSuccess)actual; - Assert.Equal(expected.TxId, success.TxId); - Assert.Equal(expected.BlockHash, success.BlockHash); - Assert.Equal(expected.UpdatedStates, success.UpdatedStates); - Assert.Equal(expected.UpdatedFungibleAssets, success.UpdatedFungibleAssets); - } - - void AssertTxFailuresEqual(TxFailure expected, TxExecution actual) - { - Assert.IsType(actual); - var failure = (TxFailure)actual; - Assert.Equal(expected.TxId, failure.TxId); - Assert.Equal(expected.BlockHash, failure.BlockHash); - Assert.Equal(expected.ExceptionName, failure.ExceptionName); - Assert.Equal(expected.ExceptionMetadata, failure.ExceptionMetadata); + Assert.Equal(expected.Fail, actual.Fail); + Assert.Equal(expected.TxId, actual.TxId); + Assert.Equal(expected.BlockHash, actual.BlockHash); + Assert.Equal(expected.InputState, actual.InputState); + Assert.Equal(expected.OutputState, actual.OutputState); + Assert.Equal(expected.ExceptionNames, actual.ExceptionNames); } Assert.Null(Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); @@ -418,52 +407,46 @@ void AssertTxFailuresEqual(TxFailure expected, TxExecution actual) Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId1)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId2)); - var random = new System.Random(); - var inputA = new TxSuccess( + var inputA = new TxExecution( Fx.Hash1, Fx.TxId1, - ImmutableDictionary.Empty.Add( - random.NextAddress(), - (Text)"state value" - ), - ImmutableDictionary>.Empty - .Add( - random.NextAddress(), - ImmutableDictionary.Empty.Add( - DumbAction.DumbCurrency, - DumbAction.DumbCurrency * 10 - ) - ) - ); + false, + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new List() { string.Empty }); Fx.Store.PutTxExecution(inputA); - AssertTxSuccessesEqual(inputA, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); + AssertTxExecutionEqual(inputA, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId2)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId1)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId2)); - var inputB = new TxFailure( + var inputB = new TxExecution( Fx.Hash1, Fx.TxId2, - "AnExceptionName" - ); + true, + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new List() { "AnExceptionName" }); Fx.Store.PutTxExecution(inputB); - AssertTxSuccessesEqual(inputA, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); - AssertTxFailuresEqual(inputB, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId2)); + AssertTxExecutionEqual(inputA, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); + AssertTxExecutionEqual(inputB, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId2)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId1)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId2)); - var inputC = new TxFailure( + var inputC = new TxExecution( Fx.Hash2, Fx.TxId1, - "AnotherExceptionName" - ); + true, + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new HashDigest(TestUtils.GetRandomBytes(HashDigest.Size)), + new List() { "AnotherExceptionName", "YetAnotherExceptionName" }); Fx.Store.PutTxExecution(inputC); - AssertTxSuccessesEqual(inputA, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); - AssertTxFailuresEqual(inputB, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId2)); - AssertTxFailuresEqual(inputC, Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId1)); + AssertTxExecutionEqual(inputA, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId1)); + AssertTxExecutionEqual(inputB, Fx.Store.GetTxExecution(Fx.Hash1, Fx.TxId2)); + AssertTxExecutionEqual(inputC, Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId1)); Assert.Null(Fx.Store.GetTxExecution(Fx.Hash2, Fx.TxId2)); } @@ -1027,7 +1010,7 @@ public void Copy() var preEval = ProposeGenesis(proposer: GenesisProposer.PublicKey); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(s1, fx.StateStore), + fx.StateStore, new SingleActionLoader(typeof(DumbAction))); var genesis = preEval.Sign( GenesisProposer, diff --git a/Libplanet.Tests/Store/StoreTracker.cs b/Libplanet.Tests/Store/StoreTracker.cs index 241a801b8d5..c2faff600b1 100644 --- a/Libplanet.Tests/Store/StoreTracker.cs +++ b/Libplanet.Tests/Store/StoreTracker.cs @@ -47,16 +47,10 @@ public bool ContainsBlock(BlockHash blockHash) return _store.ContainsBlock(blockHash); } - public void PutTxExecution(TxSuccess txSuccess) + public void PutTxExecution(TxExecution txExecution) { - Log(nameof(PutTxExecution), txSuccess); - _store.PutTxExecution(txSuccess); - } - - public void PutTxExecution(TxFailure txFailure) - { - Log(nameof(PutTxExecution), txFailure); - _store.PutTxExecution(txFailure); + Log(nameof(PutTxExecution), txExecution); + _store.PutTxExecution(txExecution); } public TxExecution GetTxExecution(BlockHash blockHash, TxId txid) diff --git a/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs b/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs index 4f309fb9a04..e22c25f7b91 100644 --- a/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs +++ b/Libplanet.Tests/Store/Trie/MerkleTrieTest.cs @@ -288,6 +288,22 @@ public void GetNode() Assert.IsType(trie.GetNode(Nibbles.FromHex("0010"))); } + [Fact] + public void ResolveToValueAtTheEndOfShortNode() + { + IStateStore stateStore = new TrieStateStore(new MemoryKeyValueStore()); + ITrie trie = stateStore.GetStateRoot(null); + + KeyBytes key00 = new KeyBytes(new byte[] { 0x00 }); + IValue value00 = new Text("00"); + KeyBytes key0000 = new KeyBytes(new byte[] { 0x00, 0x00 }); + + trie = trie.Set(key00, value00); + trie = stateStore.Commit(trie); + + Assert.Null(trie.Get(key0000)); + } + [Fact] public void SetValueToExtendedKey() { diff --git a/Libplanet.Tests/Store/TrieStateStoreCommitTest.cs b/Libplanet.Tests/Store/TrieStateStoreCommitTest.cs index 4d7381821e8..2ba0aa13cf3 100644 --- a/Libplanet.Tests/Store/TrieStateStoreCommitTest.cs +++ b/Libplanet.Tests/Store/TrieStateStoreCommitTest.cs @@ -19,13 +19,13 @@ public void CommitEmptyDoesNotWrite() HashDigest emptyRootHash = emptyTrie.Hash; Assert.Null(emptyTrie.Root); - Assert.True(stateStore.ContainsStateRoot(emptyRootHash)); + Assert.True(stateStore.GetStateRoot(emptyRootHash).Recorded); Assert.False(keyValueStore.Exists(new KeyBytes(emptyRootHash.ByteArray))); emptyTrie = stateStore.Commit(emptyTrie); Assert.Null(emptyTrie.Root); Assert.Equal(emptyRootHash, emptyTrie.Hash); - Assert.True(stateStore.ContainsStateRoot(emptyRootHash)); + Assert.True(stateStore.GetStateRoot(emptyRootHash).Recorded); Assert.False(keyValueStore.Exists(new KeyBytes(emptyRootHash.ByteArray))); } @@ -47,8 +47,8 @@ public void Commit() Assert.NotEqual(hashBeforeCommit, hashAfterCommitOnce); Assert.Equal(hashAfterCommitOnce, hashAfterCommitTwice); - Assert.False(stateStore.ContainsStateRoot(hashBeforeCommit)); - Assert.True(stateStore.ContainsStateRoot(hashAfterCommitOnce)); + Assert.False(stateStore.GetStateRoot(hashBeforeCommit).Recorded); + Assert.True(stateStore.GetStateRoot(hashAfterCommitOnce).Recorded); Assert.False(keyValueStore.Exists(new KeyBytes(hashBeforeCommit.ByteArray))); Assert.True(keyValueStore.Exists(new KeyBytes(hashAfterCommitOnce.ByteArray))); diff --git a/Libplanet.Tests/Store/TrieStateStoreTest.cs b/Libplanet.Tests/Store/TrieStateStoreTest.cs index 5bfd9b65ae6..4c38a56662c 100644 --- a/Libplanet.Tests/Store/TrieStateStoreTest.cs +++ b/Libplanet.Tests/Store/TrieStateStoreTest.cs @@ -50,7 +50,11 @@ public void GetStateRoot() .Add(barKey, (Text)ByteUtil.Hex(GetRandomBytes(32))) .Add(bazKey, (Bencodex.Types.Boolean)false) .Add(quxKey, Bencodex.Types.Dictionary.Empty); - HashDigest hash = stateStore.Commit(null, values).Hash; + ITrie trie = stateStore.Commit( + values.Aggregate( + stateStore.GetStateRoot(null), + (prev, kv) => prev.Set(kv.Key, kv.Value))); + HashDigest hash = trie.Hash; ITrie found = stateStore.GetStateRoot(hash); Assert.True(found.Recorded); AssertBencodexEqual(values[fooKey], found.Get(new[] { KeyFoo })[0]); @@ -77,12 +81,18 @@ public void PruneStates() .Add("text", ByteUtil.Hex(GetRandomBytes(2048)))); var stateStore = new TrieStateStore(_stateKeyValueStore); - ITrie first = stateStore.Commit(null, values); + ITrie first = stateStore.Commit( + values.Aggregate( + stateStore.GetStateRoot(null), + (prev, kv) => prev.Set(kv.Key, kv.Value))); int prevStatesCount = _stateKeyValueStore.ListKeys().Count(); - ImmutableDictionary nextStates = + ImmutableDictionary nextState = values.SetItem(new KeyBytes("foo"), (Binary)GetRandomBytes(4096)); - ITrie second = stateStore.Commit(first.Hash, nextStates); + ITrie second = stateStore.Commit( + nextState.Aggregate( + first, + (prev, kv) => prev.Set(kv.Key, kv.Value))); // foo = 0x666f6f // updated branch node (0x6, aka root) + updated branch node (0x66) + @@ -115,7 +125,10 @@ public void CopyStates() IKeyValueStore targetStateKeyValueStore = new MemoryKeyValueStore(); var targetStateStore = new TrieStateStore(targetStateKeyValueStore); - ITrie trie = stateStore.Commit(null, values); + ITrie trie = stateStore.Commit( + values.Aggregate( + stateStore.GetStateRoot(null), + (prev, kv) => prev.Set(kv.Key, kv.Value))); int prevStatesCount = _stateKeyValueStore.ListKeys().Count(); _stateKeyValueStore.Set( diff --git a/Libplanet.Tests/TestUtils.cs b/Libplanet.Tests/TestUtils.cs index 93f5687f106..3c03fc7e686 100644 --- a/Libplanet.Tests/TestUtils.cs +++ b/Libplanet.Tests/TestUtils.cs @@ -609,7 +609,7 @@ public static (BlockChain BlockChain, ActionEvaluator ActionEvaluator) var blockChainStates = new BlockChainStates(store, stateStore); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates: blockChainStates, + stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(T))); if (genesisBlock is null) diff --git a/Libplanet.Tests/Tx/TxExecutionTest.cs b/Libplanet.Tests/Tx/TxExecutionTest.cs new file mode 100644 index 00000000000..d85f45b19e5 --- /dev/null +++ b/Libplanet.Tests/Tx/TxExecutionTest.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Types.Assets; +using Libplanet.Types.Tx; +using Xunit; + +namespace Libplanet.Tests.Tx +{ + public class TxExecutionTest + { + [Theory] + [InlineData(false, "")] + [InlineData(true, "SomeException")] + public void Constructor(bool fail, string exceptionName) + { + var random = new Random(); + var blockHash = random.NextBlockHash(); + var txId = random.NextTxId(); + var inputState = random.NextHashDigest(); + var outputState = random.NextHashDigest(); + var exceptionNames = new List() { exceptionName }; + var execution = new TxExecution( + blockHash, + txId, + fail, + inputState, + outputState, + exceptionNames); + Assert.Equal(blockHash, execution.BlockHash); + Assert.Equal(txId, execution.TxId); + Assert.Equal(fail, execution.Fail); + Assert.Equal(inputState, execution.InputState); + Assert.Equal(outputState, execution.OutputState); + Assert.Equal(exceptionNames, execution.ExceptionNames); + } + + [Fact] + public void EncodeDecode() + { + var random = new Random(); + var execution = new TxExecution( + random.NextBlockHash(), + random.NextTxId(), + true, + random.NextHashDigest(), + random.NextHashDigest(), + new List() { null, "SomeException", "AnotherException" }); + var encoded = execution.ToBencodex(); + var decoded = new TxExecution( + execution.BlockHash, + execution.TxId, + encoded); + Assert.Equal(execution.BlockHash, decoded.BlockHash); + Assert.Equal(execution.TxId, decoded.TxId); + Assert.Equal(execution.Fail, decoded.Fail); + Assert.Equal(execution.InputState, decoded.InputState); + Assert.Equal(execution.OutputState, decoded.OutputState); + Assert.Equal(execution.ExceptionNames, decoded.ExceptionNames); + } + + [Fact] + public void ConstructorWithExceptions() + { + var random = new Random(); + var blockHash = random.NextBlockHash(); + var txId = random.NextTxId(); + var inputState = random.NextHashDigest(); + var outputState = random.NextHashDigest(); + var exceptions = new List() { null, new ArgumentException("Message") }; + var execution = new TxExecution( + blockHash, + txId, + true, + inputState, + outputState, + exceptions); + Assert.Equal(blockHash, execution.BlockHash); + Assert.Equal(txId, execution.TxId); + Assert.True(execution.Fail); + Assert.Equal(inputState, execution.InputState); + Assert.Equal(outputState, execution.OutputState); + Assert.Equal( + exceptions + .Select(exception => exception is Exception e + ? e.GetType().FullName + : null), + execution.ExceptionNames); + } + + [Fact] + public void DecodeFailLegacy() + { + var random = new Random(); + var blockHash = random.NextBlockHash(); + var txId = random.NextTxId(); + + Dictionary legacyEncoded = Dictionary.Empty + .Add("fail", true) + .Add("exc", "SomeException"); + var failExecution = new TxExecution( + blockHash, + txId, + legacyEncoded); + Assert.Equal(blockHash, failExecution.BlockHash); + Assert.Equal(txId, failExecution.TxId); + Assert.True(failExecution.Fail); + Assert.Null(failExecution.InputState); + Assert.Null(failExecution.OutputState); + Assert.Null(failExecution.ExceptionNames); + } + + [Fact] + public void DecodeSuccessLegacy() + { + var random = new Random(); + var blockHash = random.NextBlockHash(); + var txId = random.NextTxId(); + + // Note: Actual format for sDelta and updatedFAVs doesn't really matter, + // it is important decoding doesn't throw an exception. + var currency = Currency.Uncapped("FOO", 0, null); + Dictionary legacyEncoded = Dictionary.Empty + .Add("fail", false) + .Add("sDelta", Dictionary.Empty + .Add(random.NextAddress().ByteArray, random.NextAddress().ByteArray)) + .Add("updatedFAVs", List.Empty + .Add(currency.Serialize()) + .Add(123)); + var successExecution = new TxExecution( + blockHash, + txId, + legacyEncoded); + Assert.Equal(blockHash, successExecution.BlockHash); + Assert.Equal(txId, successExecution.TxId); + Assert.False(successExecution.Fail); + Assert.Null(successExecution.InputState); + Assert.Null(successExecution.OutputState); + Assert.Null(successExecution.ExceptionNames); + } + } +} diff --git a/Libplanet.Tests/Tx/TxFailureTest.cs b/Libplanet.Tests/Tx/TxFailureTest.cs deleted file mode 100644 index 0caebf920e5..00000000000 --- a/Libplanet.Tests/Tx/TxFailureTest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; -using Libplanet.Types.Blocks; -using Libplanet.Types.Tx; -using Xunit; - -namespace Libplanet.Tests.Tx -{ - public class TxFailureTest - { - private readonly BlockHash _blockHash; - private readonly TxId _txid; - private readonly TxFailure _fx; - - [SuppressMessage( - "Major Code Smell", - "S3928", - Justification = "The ArgumentNullException instance made here is a just example." - )] - public TxFailureTest() - { - var random = new Random(); - _blockHash = random.NextBlockHash(); - _txid = random.NextTxId(); - _fx = new TxFailure( - _blockHash, - _txid, - new ArgumentNullException("foo")); - } - - [Fact] - public void ConstructorWithExceptionObject() - { - Assert.Equal(_blockHash, _fx.BlockHash); - Assert.Equal(_txid, _fx.TxId); - Assert.Equal( - $"{nameof(System)}.{nameof(ArgumentNullException)}", - _fx.ExceptionName); - Assert.Null(_fx.ExceptionMetadata); - } - - [Fact] - public void Constructor() - { - var f = new TxFailure( - _blockHash, - _txid, - nameof(ArgumentNullException) - ); - Assert.Equal(_blockHash, f.BlockHash); - Assert.Equal(_txid, f.TxId); - Assert.Equal(nameof(ArgumentNullException), f.ExceptionName); - Assert.Null(f.ExceptionMetadata); - } - - [Fact] - public void Serialization() - { - var formatter = new BinaryFormatter(); - var stream = new MemoryStream(); - formatter.Serialize(stream, _fx); - stream.Seek(0, SeekOrigin.Begin); - object deserialized = formatter.Deserialize(stream); - Assert.IsType(deserialized); - var f = (TxFailure)deserialized; - Assert.Equal(_blockHash, f.BlockHash); - Assert.Equal(_txid, f.TxId); - Assert.Equal(_fx.ExceptionName, f.ExceptionName); - Assert.Equal(_fx.ExceptionMetadata, f.ExceptionMetadata); - } - } -} diff --git a/Libplanet.Tests/Tx/TxSuccessTest.cs b/Libplanet.Tests/Tx/TxSuccessTest.cs deleted file mode 100644 index 6418da8edbe..00000000000 --- a/Libplanet.Tests/Tx/TxSuccessTest.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; -using Bencodex.Types; -using Libplanet.Crypto; -using Libplanet.Types.Assets; -using Libplanet.Types.Blocks; -using Libplanet.Types.Tx; -using Xunit; -using FAV = Libplanet.Types.Assets.FungibleAssetValue; - -namespace Libplanet.Tests.Tx -{ - public class TxSuccessTest - { - private static readonly Currency[] Currencies = new[] { "FOO", "BAR", "BAZ", "QUX" } - .Select(ticker => Currency.Uncapped(ticker, 0, null)) - .ToArray(); - - private readonly BlockHash _blockHash; - private readonly TxId _txid; - private readonly ImmutableDictionary _updatedStates; - - private readonly ImmutableDictionary> - _updatedFungibleAssets; - - private readonly TxSuccess _fx; - - public TxSuccessTest() - { - var random = new Random(); - _blockHash = random.NextBlockHash(); - _txid = random.NextTxId(); - _updatedStates = Enumerable.Repeat(random, 5).ToImmutableDictionary( - RandomExtensions.NextAddress, - _ => (IValue)new Bencodex.Types.Integer(random.Next()) - ); - var currencies = Currencies.ToList(); - _updatedFungibleAssets = Enumerable.Repeat(random, 5).ToImmutableDictionary( - RandomExtensions.NextAddress, - _ => (IImmutableDictionary)Enumerable.Repeat( - 0, - random.Next(currencies.Count)) - .Select(__ => - { - int i = random.Next(currencies.Count); - Currency c = currencies[i]; - currencies.RemoveAt(i); - return c; - }) - .ToImmutableDictionary( - c => c, - c => c * random.Next() - ) - ); - _fx = new TxSuccess( - _blockHash, - _txid, - _updatedStates, - _updatedFungibleAssets - ); - } - - [Fact] - public void ConstructorBuilder() - { - Assert.Equal(_blockHash, _fx.BlockHash); - Assert.Equal(_txid, _fx.TxId); - Assert.Equal(_updatedStates, _fx.UpdatedStates); - Assert.Equal(_updatedFungibleAssets, _fx.UpdatedFungibleAssets); - } - - [Fact] - public void Serialization() - { - var formatter = new BinaryFormatter(); - var stream = new MemoryStream(); - formatter.Serialize(stream, _fx); - stream.Seek(0, SeekOrigin.Begin); - object deserialized = formatter.Deserialize(stream); - Assert.IsType(deserialized); - var s = (TxSuccess)deserialized; - Assert.Equal(_blockHash, s.BlockHash); - Assert.Equal(_txid, s.TxId); - Assert.Equal(_fx.UpdatedStates, s.UpdatedStates); - Assert.Equal(_fx.UpdatedFungibleAssets, s.UpdatedFungibleAssets); - } - } -} diff --git a/Libplanet.Types/Blocks/BlockMarshaler.cs b/Libplanet.Types/Blocks/BlockMarshaler.cs index a6b41708d0f..f731804df38 100644 --- a/Libplanet.Types/Blocks/BlockMarshaler.cs +++ b/Libplanet.Types/Blocks/BlockMarshaler.cs @@ -17,27 +17,56 @@ namespace Libplanet.Types.Blocks public static class BlockMarshaler { // Block fields: - internal static readonly byte[] HeaderKey = { 0x48 }; // 'H' - internal static readonly byte[] TransactionsKey = { 0x54 }; // 'T' + internal static readonly Binary HeaderKey = new Binary(new byte[] { 0x48 }); // 'H' + internal static readonly Binary TransactionsKey = new Binary(new byte[] { 0x54 }); // 'T' private const string TimestampFormat = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; // Header fields: - private static readonly byte[] ProtocolVersionKey = { 0x00 }; - private static readonly byte[] IndexKey = { 0x69 }; // 'i' - private static readonly byte[] TimestampKey = { 0x74 }; // 't' - private static readonly byte[] DifficultyKey = { 0x64 }; // 'd'; Legacy, unused. - private static readonly byte[] TotalDifficultyKey = { 0x54 }; // 'T'; Legacy, unused. - private static readonly byte[] NonceKey = { 0x6e }; // 'n'; Legacy, unused. - private static readonly byte[] MinerKey = { 0x6d }; // 'm' - private static readonly byte[] PublicKeyKey = { 0x50 }; // 'P' - private static readonly byte[] PreviousHashKey = { 0x70 }; // 'p' - private static readonly byte[] TxHashKey = { 0x78 }; // 'x' - private static readonly byte[] HashKey = { 0x68 }; // 'h' - private static readonly byte[] StateRootHashKey = { 0x73 }; // 's' - private static readonly byte[] SignatureKey = { 0x53 }; // 'S' - private static readonly byte[] PreEvaluationHashKey = { 0x63 }; // 'c' - private static readonly byte[] LastCommitKey = { 0x43 }; // 'C' + private static readonly Binary ProtocolVersionKey = + new Binary(new byte[] { 0x00 }); + + private static readonly Binary IndexKey = + new Binary(new byte[] { 0x69 }); // 'i' + + private static readonly Binary TimestampKey = + new Binary(new byte[] { 0x74 }); // 't' + + private static readonly Binary DifficultyKey = + new Binary(new byte[] { 0x64 }); // 'd'; Legacy, unused. + + private static readonly Binary TotalDifficultyKey = + new Binary(new byte[] { 0x54 }); // 'T'; Legacy, unused. + + private static readonly Binary NonceKey = + new Binary(new byte[] { 0x6e }); // 'n'; Legacy, unused. + + private static readonly Binary MinerKey = + new Binary(new byte[] { 0x6d }); // 'm' + + private static readonly Binary PublicKeyKey = + new Binary(new byte[] { 0x50 }); // 'P' + + private static readonly Binary PreviousHashKey = + new Binary(new byte[] { 0x70 }); // 'p' + + private static readonly Binary TxHashKey = + new Binary(new byte[] { 0x78 }); // 'x' + + private static readonly Binary HashKey = + new Binary(new byte[] { 0x68 }); // 'h' + + private static readonly Binary StateRootHashKey = + new Binary(new byte[] { 0x73 }); // 's' + + private static readonly Binary SignatureKey = + new Binary(new byte[] { 0x53 }); // 'S' + + private static readonly Binary PreEvaluationHashKey = + new Binary(new byte[] { 0x63 }); // 'c' + + private static readonly Binary LastCommitKey = + new Binary(new byte[] { 0x43 }); // 'C' public static Dictionary MarshalBlockMetadata(IBlockMetadata metadata) { @@ -150,7 +179,7 @@ public static Dictionary MarshalBlock(this Block block) => MarshalTransactions(block.Transactions)); public static long UnmarshalBlockMetadataIndex(Dictionary marshaledMetadata) => - marshaledMetadata.GetValue(IndexKey); + (Integer)marshaledMetadata[IndexKey]; public static BlockMetadata UnmarshalBlockMetadata(Dictionary marshaled) { @@ -158,40 +187,40 @@ public static BlockMetadata UnmarshalBlockMetadata(Dictionary marshaled) PublicKey? publicKey = null; if (marshaled.ContainsKey(PublicKeyKey)) { - publicKey = new PublicKey(marshaled.GetValue(PublicKeyKey).ByteArray); + publicKey = new PublicKey(((Binary)marshaled[PublicKeyKey]).ByteArray); miner = publicKey.ToAddress(); } else { - miner = new Address(marshaled.GetValue(MinerKey).ByteArray); + miner = new Address(marshaled[MinerKey]); } #pragma warning disable SA1118 // The parameter spans multiple lines return new BlockMetadata( protocolVersion: marshaled.ContainsKey(ProtocolVersionKey) - ? (int)marshaled.GetValue(ProtocolVersionKey) + ? (int)(Integer)marshaled[ProtocolVersionKey] : 0, index: UnmarshalBlockMetadataIndex(marshaled), timestamp: DateTimeOffset.ParseExact( - marshaled.GetValue(TimestampKey), + (Text)marshaled[TimestampKey], TimestampFormat, CultureInfo.InvariantCulture), miner: miner, publicKey: publicKey, previousHash: marshaled.ContainsKey(PreviousHashKey) - ? new BlockHash(marshaled.GetValue(PreviousHashKey)) + ? new BlockHash(marshaled[PreviousHashKey]) : (BlockHash?)null, txHash: marshaled.ContainsKey(TxHashKey) - ? new HashDigest(marshaled.GetValue(TxHashKey).ByteArray) + ? new HashDigest(((Binary)marshaled[TxHashKey]).ByteArray) : (HashDigest?)null, lastCommit: marshaled.ContainsKey(LastCommitKey) - ? new BlockCommit(marshaled.GetValue(LastCommitKey)) + ? new BlockCommit(marshaled[LastCommitKey]) : (BlockCommit?)null); #pragma warning restore SA1118 } public static HashDigest UnmarshalPreEvaluationHash(Dictionary marshaled) => - new HashDigest(marshaled.GetValue(PreEvaluationHashKey).ByteArray); + new HashDigest(((Binary)marshaled[PreEvaluationHashKey]).ByteArray); public static PreEvaluationBlockHeader UnmarshalPreEvaluationBlockHeader( Dictionary marshaled) @@ -203,25 +232,24 @@ public static PreEvaluationBlockHeader UnmarshalPreEvaluationBlockHeader( public static BlockHash UnmarshalBlockHash(Dictionary marshaledBlock) { - Dictionary blockHeader = marshaledBlock.GetValue(HeaderKey); + Dictionary blockHeader = (Dictionary)marshaledBlock[HeaderKey]; return UnmarshalBlockHeaderHash(blockHeader); } public static BlockHash UnmarshalBlockHeaderHash(Dictionary marshaledBlockHeader) => - new BlockHash(marshaledBlockHeader.GetValue(HashKey)); + new BlockHash(marshaledBlockHeader[HashKey]); public static HashDigest UnmarshalBlockHeaderStateRootHash( Dictionary marshaledBlockHeader ) => new HashDigest( - marshaledBlockHeader.GetValue(StateRootHashKey).ByteArray - ); + ((Binary)marshaledBlockHeader[StateRootHashKey]).ByteArray); public static ImmutableArray? UnmarshalBlockHeaderSignature( Dictionary marshaledBlockHeader ) => marshaledBlockHeader.ContainsKey(SignatureKey) - ? marshaledBlockHeader.GetValue(SignatureKey).ByteArray + ? ((Binary)marshaledBlockHeader[SignatureKey]).ByteArray : (ImmutableArray?)null; public static BlockHeader UnmarshalBlockHeader(Dictionary marshaled) @@ -241,12 +269,12 @@ public static IReadOnlyList UnmarshalTransactions(List marshaled) = public static IReadOnlyList UnmarshalBlockTransactions( Dictionary marshaledBlock) => marshaledBlock.ContainsKey(TransactionsKey) - ? UnmarshalTransactions(marshaledBlock.GetValue(TransactionsKey)) + ? UnmarshalTransactions((List)marshaledBlock[TransactionsKey]) : ImmutableArray.Empty; public static Block UnmarshalBlock(Dictionary marshaled) { - BlockHeader header = UnmarshalBlockHeader(marshaled.GetValue(HeaderKey)); + BlockHeader header = UnmarshalBlockHeader((Dictionary)marshaled[HeaderKey]); IReadOnlyList txs = UnmarshalBlockTransactions(marshaled); return new Block(header, txs); } diff --git a/Libplanet.Types/Tx/TxExecution.cs b/Libplanet.Types/Tx/TxExecution.cs index c7ed50ba23b..5d98cc0b35b 100644 --- a/Libplanet.Types/Tx/TxExecution.cs +++ b/Libplanet.Types/Tx/TxExecution.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Runtime.Serialization; -using Bencodex; -using Libplanet.Common.Serialization; +using System.Linq; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Common; using Libplanet.Types.Blocks; namespace Libplanet.Types.Tx @@ -13,24 +15,133 @@ namespace Libplanet.Types.Tx /// a , and even if it's the same its /// result can vary depending on that it is executed within. /// - /// - /// - [Serializable] - public abstract class TxExecution : ISerializable + public sealed class TxExecution { - protected static readonly Codec _codec = new Codec(); + internal static readonly Text FailKey = new Text("fail"); - protected TxExecution(SerializationInfo info, StreamingContext context) + internal static readonly Text InputStateKey = new Text("i"); + + internal static readonly Text OutputStateKey = new Text("o"); + + internal static readonly Text ExceptionNamesKey = new Text("e"); + + public TxExecution( + BlockHash blockHash, + TxId txId, + bool fail, + HashDigest inputState, + HashDigest outputState, + List exceptions) + : this( + blockHash, + txId, + fail, + inputState, + outputState, + exceptions + .Select(exception => exception is { } e + ? e.GetType().FullName + : null) + .ToList()) + { + } + + public TxExecution( + BlockHash blockHash, + TxId txId, + bool fail, + HashDigest inputState, + HashDigest outputState, + List exceptionNames) + { + BlockHash = blockHash; + TxId = txId; + Fail = fail; + InputState = inputState; + OutputState = outputState; + ExceptionNames = exceptionNames; + } + +#pragma warning disable SA1118 // The parameter spans multiple lines. + public TxExecution( + BlockHash blockHash, + TxId txId, + IValue bencoded) : this( - info.GetValue(nameof(BlockHash)), - info.GetValue(nameof(TxId))) + blockHash, + txId, + bencoded is Dictionary dict + ? dict + : throw new ArgumentException( + $"Given {nameof(bencoded)} must be of type " + + $"{typeof(Bencodex.Types.Dictionary)}: {bencoded.GetType()}", + nameof(bencoded))) +#pragma warning restore SA1118 { } - private protected TxExecution(BlockHash blockHash, TxId txId) + private TxExecution( + BlockHash blockHash, + TxId txId, + Dictionary bencoded) { BlockHash = blockHash; TxId = txId; + + if (!bencoded.TryGetValue(FailKey, out IValue fail)) + { + throw new ArgumentException( + $"Given {nameof(bencoded)} is missing fail value", + nameof(bencoded)); + } + else if (!(fail is Bencodex.Types.Boolean failBoolean)) + { + throw new ArgumentException( + $"Given {nameof(bencoded)} has an invalid fail value: {fail}", + nameof(bencoded)); + } + else + { + Fail = failBoolean.Value; + } + + if (bencoded.TryGetValue(InputStateKey, out IValue input) && + input is Binary inputBinary) + { + InputState = new HashDigest(inputBinary.ByteArray); + } + else + { + InputState = null; + } + + if (bencoded.TryGetValue(OutputStateKey, out IValue output) && + output is Binary outputBinary) + { + OutputState = new HashDigest(outputBinary.ByteArray); + } + else + { + OutputState = null; + } + + if (bencoded.TryGetValue(ExceptionNamesKey, out IValue exceptions) && + exceptions is List exceptionsList) + { + ExceptionNames = exceptionsList + .Select(value => value is Text t + ? (string?)t.Value + : value is Null + ? (string?)null + : throw new ArgumentException( + $"Expected either {nameof(Text)} or {nameof(Null)} " + + $"but got {value.GetType()}")) + .ToList(); + } + else + { + ExceptionNames = null; + } } /// @@ -46,11 +157,65 @@ private protected TxExecution(BlockHash blockHash, TxId txId) [Pure] public TxId TxId { get; } - /// - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + /// + /// Whether every action in the was + /// executed without throwing and . + /// + public bool Fail { get; } + + /// + /// The state before the execution of the . + /// + /// + /// This is marked -able for backward compatibility. + /// + public HashDigest? InputState { get; } + + /// + /// The state after the execution of the . + /// + /// + /// This is marked -able for backward compatibility. + /// + public HashDigest? OutputState { get; } + + /// + /// The list of names thrown by actions + /// in the . A value of + /// as an element represents no being thrown for + /// the action of the same index. + /// + /// + /// This is marked -able for backward compatibility. + /// + public List? ExceptionNames { get; } + + public IValue ToBencodex() { - info.AddValue(nameof(BlockHash), BlockHash); - info.AddValue(nameof(TxId), TxId); + Dictionary dict = Dictionary.Empty + .Add(FailKey, Fail); + + if (InputState is { } inputState) + { + dict = dict.Add(InputStateKey, inputState.ByteArray); + } + + if (OutputState is { } outputState) + { + dict = dict.Add(OutputStateKey, outputState.ByteArray); + } + + if (ExceptionNames is { } exceptionNames) + { + dict = dict.Add( + ExceptionNamesKey, + new List(exceptionNames + .Select(exceptionName => exceptionName is { } name + ? (IValue)new Text(exceptionName) + : (IValue)Null.Value))); + } + + return dict; } } } diff --git a/Libplanet.Types/Tx/TxFailure.cs b/Libplanet.Types/Tx/TxFailure.cs deleted file mode 100644 index f15e4c744e1..00000000000 --- a/Libplanet.Types/Tx/TxFailure.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Diagnostics.Contracts; -using System.Runtime.Serialization; -using Bencodex.Types; -using Libplanet.Types.Blocks; - -namespace Libplanet.Types.Tx -{ - /// - /// Summarizes an execution result of a with any exception-throwing - /// actions. - /// - [Serializable] - public sealed class TxFailure : TxExecution - { - /// - /// Creates a instance. - /// - /// The of the - /// that the is executed within. - /// The executed 's . - /// The name of the exception type, - /// e.g., System.ArgumentException. - /// Optional metadata about the exception. - public TxFailure( - BlockHash blockHash, - TxId txId, - string exceptionName - ) - : base(blockHash, txId) - { - ExceptionName = exceptionName; - } - - /// - /// Creates a instance. - /// - /// The of the - /// that the is executed within. - /// The executed 's . - /// The uncaught exception thrown by an action in the transaction. - /// - public TxFailure( - BlockHash blockHash, - TxId txId, - Exception exception) - : this( - blockHash, - txId, - exception.GetType().FullName ?? string.Empty - ) - { - } - - private TxFailure(SerializationInfo info, StreamingContext context) - : base(info, context) - { - ExceptionName = info.GetString(nameof(ExceptionName)) ?? string.Empty; - } - - /// - /// The name of the exception type, e.g., System.ArgumentException. - /// - [Pure] - public string ExceptionName { get; } - - /// - /// Optional metadata about the exception. - /// - [Pure] - public IValue? ExceptionMetadata => null; - - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue(nameof(ExceptionName), ExceptionName); - info.AddValue( - nameof(ExceptionMetadata), - ExceptionMetadata is { } m ? _codec.Encode(m) : null - ); - } - } -} diff --git a/Libplanet.Types/Tx/TxSuccess.cs b/Libplanet.Types/Tx/TxSuccess.cs deleted file mode 100644 index e35bd0c1382..00000000000 --- a/Libplanet.Types/Tx/TxSuccess.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Numerics; -using System.Runtime.Serialization; -using Bencodex.Types; -using Libplanet.Common.Serialization; -using Libplanet.Crypto; -using Libplanet.Types.Assets; -using Libplanet.Types.Blocks; -using FAV = Libplanet.Types.Assets.FungibleAssetValue; - -namespace Libplanet.Types.Tx -{ - /// - /// Summarizes an execution result of a successful . - /// - [Serializable] - public sealed class TxSuccess : TxExecution, ISerializable - { - /// - /// Creates a instance. - /// - /// The of the - /// that the is executed within. - /// The executed 's . - /// The states delta made by the actions in - /// the transaction within the block. - /// es and sets of - /// whose fungible assets have been updated by the actions in - /// the transaction within the block. Included s are - /// the delta values (plus or minus) that the transaction makes. - /// es and sets of - /// whose fungible assets have been updated by the actions in - /// the transaction within the block. Included s are - /// the final balances right after the transaction is executed. - public TxSuccess( - BlockHash blockHash, - TxId txId, - IImmutableDictionary updatedStates, - IImmutableDictionary> - updatedFungibleAssets - ) - : base(blockHash, txId) - { - UpdatedStates = updatedStates; - UpdatedFungibleAssets = updatedFungibleAssets; - } - - private TxSuccess(SerializationInfo info, StreamingContext context) - : base(info, context) - { - var updatedStates = - (Dictionary)_codec.Decode(info.GetValue(nameof(UpdatedStates))); - UpdatedStates = updatedStates.ToImmutableDictionary( - kv => new Address(kv.Key), - kv => kv.Value - ); - UpdatedFungibleAssets = DecodeFungibleAssetGroups( - info.GetValue(nameof(UpdatedFungibleAssets)) - ); - } - - /// - /// The states delta made by the actions in the transaction within the block. - /// - [Pure] - public IImmutableDictionary UpdatedStates { get; } - - /// - /// es and sets of whose fungible assets have - /// been updated by the actions in the transaction within the block. Included - /// s are the final balances right after the transaction is - /// executed. - /// - [Pure] - public IImmutableDictionary> - UpdatedFungibleAssets { get; } - - /// - /// All es of the accounts that have been updated by the actions - /// in the transaction within the block. - /// - [Pure] - public IImmutableSet
UpdatedAddresses => - UpdatedStates.Keys.ToImmutableHashSet().Union(UpdatedFungibleAssets.Keys); - - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue( - nameof(UpdatedStates), - _codec.Encode( - new Dictionary( - UpdatedStates.Select(kv => - new KeyValuePair( - new Binary(kv.Key.ToByteArray()), - kv.Value)))) - ); - info.AddValue( - nameof(UpdatedFungibleAssets), - EncodeFungibleAssetGroups(UpdatedFungibleAssets) - ); - } - - private static byte[] EncodeFungibleAssetGroups( - IImmutableDictionary> g - ) => - _codec.Encode( - new Dictionary( - g.Select(kv => - new KeyValuePair( - kv.Key.ByteArray, - new List( - kv.Value.Select(fav => - List.Empty - .Add(fav.Key.Serialize()) - .Add(fav.Value.RawValue) - ) - ) - ) - ) - ) - ); - - private static IImmutableDictionary> - DecodeFungibleAssetGroups(byte[] encoded) => - ((Dictionary)_codec.Decode(encoded)).ToImmutableDictionary( - kv => new Address(kv.Key), - kv => (IImmutableDictionary)((List)kv.Value) - .Cast() - .Select(pair => (new Currency(pair[0]), (Bencodex.Types.Integer)pair[1])) - .ToImmutableDictionary( - pair => pair.Item1, - pair => FungibleAssetValue.FromRawValue(pair.Item1, (BigInteger)pair.Item2) - ) - ); - } -} diff --git a/Libplanet/Blockchain/BlockChain.Evaluate.cs b/Libplanet/Blockchain/BlockChain.Evaluate.cs index 5efc9112965..12c857ef779 100644 --- a/Libplanet/Blockchain/BlockChain.Evaluate.cs +++ b/Libplanet/Blockchain/BlockChain.Evaluate.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.Contracts; +using System.Linq; using System.Security.Cryptography; -using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.Loader; using Libplanet.Common; @@ -38,13 +37,12 @@ public partial class BlockChain public static HashDigest DetermineGenesisStateRootHash( IActionEvaluator actionEvaluator, IPreEvaluationBlock preEvaluationBlock, - out IReadOnlyList evaluations) + out IReadOnlyList evaluations) { evaluations = EvaluateGenesis(actionEvaluator, preEvaluationBlock); - IImmutableDictionary delta = evaluations.GetRawTotalDelta(); - IStateStore stateStore = new TrieStateStore(new DefaultKeyValueStore(null)); - ITrie trie = stateStore.Commit(stateStore.GetStateRoot(null).Hash, delta); - return trie.Hash; + return evaluations.Count > 0 + ? evaluations.Last().OutputState + : new TrieStateStore(new DefaultKeyValueStore(null)).GetStateRoot(null).Hash; } /// @@ -54,13 +52,13 @@ public static HashDigest DetermineGenesisStateRootHash( /// evaluate the proposed . /// The to /// evaluate. - /// An of s + /// An of s /// resulting from evaluating using /// . /// Thrown if s /// is not zero. [Pure] - public static IReadOnlyList EvaluateGenesis( + public static IReadOnlyList EvaluateGenesis( IActionEvaluator actionEvaluator, IPreEvaluationBlock preEvaluationBlock) { @@ -71,7 +69,7 @@ public static IReadOnlyList EvaluateGenesis( nameof(preEvaluationBlock)); } - return actionEvaluator.Evaluate(preEvaluationBlock); + return actionEvaluator.Evaluate(preEvaluationBlock, null); } /// @@ -97,7 +95,7 @@ public static IReadOnlyList EvaluateGenesis( /// /// public HashDigest DetermineBlockStateRootHash( - IPreEvaluationBlock block, out IReadOnlyList evaluations) + IPreEvaluationBlock block, out IReadOnlyList evaluations) { _rwlock.EnterWriteLock(); try @@ -105,37 +103,18 @@ public HashDigest DetermineBlockStateRootHash( Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); evaluations = EvaluateBlock(block); - var totalDelta = evaluations.GetRawTotalDelta(); + _logger.Debug( - "Took {DurationMs} ms to summarize the states delta with {KeyCount} key " + - "changes made by block #{BlockIndex} pre-evaluation hash {PreEvaluationHash}", + "Took {DurationMs} ms to evaluate block #{BlockIndex} " + + "pre-evaluation hash {PreEvaluationHash} with {Count} action evaluations", stopwatch.ElapsedMilliseconds, - totalDelta.Count, block.Index, - block.PreEvaluationHash); - - ITrie trie = GetAccountState(block.PreviousHash).Trie; - foreach (var kv in totalDelta) - { - trie = trie.Set(kv.Key, kv.Value); - } - - trie = StateStore.Commit(trie); - HashDigest rootHash = trie.Hash; - _logger - .ForContext("Tag", "Metric") - .ForContext("Subtag", "StateUpdateDuration") - .Information( - "Took {DurationMs} ms to update the states with {KeyCount} key changes " + - "and resulting in state root hash {StateRootHash} for " + - "block #{BlockIndex} pre-evaluation hash {PreEvaluationHash}", - stopwatch.ElapsedMilliseconds, - totalDelta.Count, - rootHash, - block.Index, - block.PreEvaluationHash); + block.PreEvaluationHash, + evaluations.Count); - return rootHash; + return evaluations.Count > 0 + ? evaluations.Last().OutputState + : GetAccountState(block.PreviousHash).Trie.Hash; } finally { @@ -147,14 +126,14 @@ public HashDigest DetermineBlockStateRootHash( /// Evaluates the s in given . /// /// The to execute. - /// An of s for - /// given . + /// An of s + /// for given . /// Thrown when given /// contains an action that cannot be loaded with . /// [Pure] - public IReadOnlyList EvaluateBlock(IPreEvaluationBlock block) => - ActionEvaluator.Evaluate(block); + public IReadOnlyList EvaluateBlock(IPreEvaluationBlock block) => + ActionEvaluator.Evaluate(block, Store.GetStateRootHash(block.PreviousHash)); /// /// Evaluates all actions in the and diff --git a/Libplanet/Blockchain/BlockChain.Swap.cs b/Libplanet/Blockchain/BlockChain.Swap.cs index fbfc30203b1..1e543ac4050 100644 --- a/Libplanet/Blockchain/BlockChain.Swap.cs +++ b/Libplanet/Blockchain/BlockChain.Swap.cs @@ -1,11 +1,12 @@ #nullable disable using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using Libplanet.Action; using Libplanet.Blockchain.Renderers; +using Libplanet.Common; using Libplanet.Store; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; @@ -122,7 +123,6 @@ HashSet GetTxIdsWithRange( oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, - rewindPath: rewindPath, fastForwardPath: fastForwardPath); } finally @@ -138,7 +138,6 @@ internal void RenderSwap( Block oldTip, Block newTip, Block branchpoint, - IReadOnlyList rewindPath, IReadOnlyList fastForwardPath) { if (render) @@ -170,23 +169,23 @@ internal void RenderFastForward( { _logger.Debug("Rendering actions in new chain"); - long count = 0; foreach (BlockHash hash in fastForwardPath) { Block block = Store.GetBlock(hash); - ImmutableList evaluations = - ActionEvaluator.Evaluate(block).ToImmutableList(); + HashDigest? baseStateRootHash = + Store.GetStateRootHash(block.PreviousHash); + IReadOnlyList evaluations = + ActionEvaluator.Evaluate(block, baseStateRootHash); - count += RenderActions( + RenderActions( evaluations: evaluations, - block: block - ); + block: block); } _logger.Debug( - "{MethodName}() completed rendering {Count} actions", + "{MethodName}() completed rendering actions in {Count} blocks", nameof(Swap), - count); + fastForwardPath.Count); foreach (IActionRenderer renderer in ActionRenderers) { @@ -201,11 +200,15 @@ internal void RenderFastForward( /// s of the block. If it is /// , evaluate actions of the again. /// to render actions. - /// The number of actions rendered. - internal long RenderActions( - IReadOnlyList evaluations, + internal void RenderActions( + IReadOnlyList evaluations, Block block) { + if (evaluations is null) + { + throw new NullReferenceException(nameof(evaluations)); + } + Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); _logger.Debug( @@ -213,33 +216,23 @@ internal long RenderActions( block.Index, block.Hash); - if (evaluations is null) - { - evaluations = ActionEvaluator.Evaluate(block); - } - long count = 0; foreach (var evaluation in evaluations) { - if (evaluation.InputContext.BlockAction && Policy.BlockAction is null) - { - continue; - } - foreach (IActionRenderer renderer in ActionRenderers) { if (evaluation.Exception is null) { renderer.RenderAction( evaluation.Action, - evaluation.InputContext.GetUnconsumedContext(), + evaluation.InputContext, evaluation.OutputState); } else { renderer.RenderActionError( evaluation.Action, - evaluation.InputContext.GetUnconsumedContext(), + evaluation.InputContext, evaluation.Exception); } @@ -257,7 +250,6 @@ internal long RenderActions( block.Index, block.Hash, stopwatch.ElapsedMilliseconds); - return count; } /// diff --git a/Libplanet/Blockchain/BlockChain.TxExecution.cs b/Libplanet/Blockchain/BlockChain.TxExecution.cs index 8b85b895eaa..7f381cbc0a9 100644 --- a/Libplanet/Blockchain/BlockChain.TxExecution.cs +++ b/Libplanet/Blockchain/BlockChain.TxExecution.cs @@ -1,11 +1,9 @@ +using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Globalization; using System.Linq; using Libplanet.Action; using Libplanet.Action.State; -using Libplanet.Types.Assets; +using Libplanet.Store.Trie; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; @@ -21,51 +19,60 @@ public partial class BlockChain /// The corresponding s. internal IEnumerable MakeTxExecutions( Block block, - IReadOnlyList evaluations + IReadOnlyList evaluations ) { - IEnumerable> evaluationsPerTxs = evaluations - .Where(e => e.InputContext.TxId is { }) - .GroupBy(e => e.InputContext.TxId!.Value); - int count = 0; - foreach (IGrouping txEvals in evaluationsPerTxs) + List<(TxId?, List)> groupedEvals = + new List<(TxId?, List)>(); + foreach (ICommittedActionEvaluation eval in evaluations) { - TxId txid = txEvals.Key; - IAccount prevStates = txEvals.First().InputContext.PreviousState; - IActionEvaluation evalSum = txEvals.Last(); - TxExecution txExecution; - if (evalSum.Exception is { } e) + if (groupedEvals.Count == 0) { - txExecution = new TxFailure( - block.Hash, - txid, - e.InnerException ?? e); + groupedEvals.Add( + (eval.InputContext.TxId, new List() { eval })); } else { - IAccount outputStates = evalSum.OutputState; - txExecution = new TxSuccess( - block.Hash, - txid, - outputStates.GetUpdatedStates(), - outputStates.Delta.UpdatedFungibleAssets - .Select(pair => - ( - pair.Item1, - pair.Item2, - outputStates.GetBalance(pair.Item1, pair.Item2) - )) - .GroupBy(triple => triple.Item1) - .ToImmutableDictionary( - group => group.Key, - group => (IImmutableDictionary)group - .ToImmutableDictionary( - triple => triple.Item2, - triple => triple.Item3))); + if (groupedEvals.Last().Item1.Equals(eval.InputContext.TxId)) + { + groupedEvals.Last().Item2.Add(eval); + } + else + { + groupedEvals.Add( + ( + eval.InputContext.TxId, + new List() { eval } + )); + } } + } - yield return txExecution; - count++; + ITrie trie = GetAccountState(block.PreviousHash).Trie; + + int count = 0; + foreach (var group in groupedEvals) + { + if (group.Item1 is { } txId) + { + // If txId is not null, group has at least one element. + List exceptions = group.Item2 + .Select(eval => eval.Exception) + .Select(exception => exception is { } e && e.InnerException is { } i + ? i + : exception) + .ToList(); + + yield return new TxExecution( + block.Hash, + txId, + exceptions.Any(exception => exception is { }), + group.Item2.First().InputContext.PreviousState, + group.Item2.Last().OutputState, + exceptions.ToList()); + + count++; + } } _logger.Verbose( @@ -73,8 +80,7 @@ IReadOnlyList evaluations "s for {Txs} transactions within the block #{BlockIndex} {BlockHash}", count, block.Index, - block.Hash - ); + block.Hash); } internal void UpdateTxExecutions(IEnumerable txExecutions) @@ -82,42 +88,13 @@ internal void UpdateTxExecutions(IEnumerable txExecutions) int count = 0; foreach (TxExecution txExecution in txExecutions) { - // Note that there are two overloaded methods of the same name PutTxExecution() - // in IStore. As those two have different signatures, run-time polymorphism - // does not work. Instead, we need the following hard-coded branch: - switch (txExecution) - { - case TxSuccess s: - Store.PutTxExecution(s); // IStore.PutTxExecution(TxSuccess) - _logger.Verbose( - "Updated " + nameof(TxSuccess) + - " for tx {TxId} within block {BlockHash}", - s.TxId, - s.BlockHash - ); - break; - case TxFailure f: - Store.PutTxExecution(f); // IStore.PutTxExecution(TxFailure) - _logger.Verbose( - "Updated " + nameof(TxFailure) + - " for tx {TxId} within block {BlockHash}", - f.TxId, - f.BlockHash - ); - break; - default: - // In theory, this case must not happen. The following case is for just in - // case. (For example, we might add a new subtype for TxExecution.) - const string msg = "Unexpected subtype of " + nameof(TxExecution) + ": {0}"; - _logger.Fatal(msg, txExecution); - Trace.Assert( - false, - string.Format(CultureInfo.InvariantCulture, msg, txExecution) - ); - break; - } - + Store.PutTxExecution(txExecution); count++; + + _logger.Verbose( + "Updated " + nameof(TxExecution) + " for tx {TxId} within block {BlockHash}", + txExecution.TxId, + txExecution.BlockHash); } _logger.Verbose( diff --git a/Libplanet/Blockchain/BlockChain.Validate.cs b/Libplanet/Blockchain/BlockChain.Validate.cs index 4242a8f5a43..3e008b8ad71 100644 --- a/Libplanet/Blockchain/BlockChain.Validate.cs +++ b/Libplanet/Blockchain/BlockChain.Validate.cs @@ -318,7 +318,7 @@ internal void ValidateBlock(Block block) /// /// internal void ValidateBlockStateRootHash( - Block block, out IReadOnlyList evaluations) + Block block, out IReadOnlyList evaluations) { var rootHash = DetermineBlockStateRootHash(block, out evaluations); if (!rootHash.Equals(block.StateRootHash)) diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index bef8f501cc2..1989564468c 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Security.Cryptography; using System.Threading; using Bencodex.Types; using Libplanet.Action; @@ -383,7 +384,7 @@ public static BlockChain Create( var computedStateRootHash = DetermineGenesisStateRootHash( actionEvaluator, preEval, - out IReadOnlyList evals); + out var _); if (!genesisBlock.StateRootHash.Equals(computedStateRootHash)) { throw new InvalidBlockStateRootHashException( @@ -413,9 +414,6 @@ public static BlockChain Create( store.SetCanonicalChainId(id); - var delta = evals.GetRawTotalDelta(); - stateStore.Commit(null, delta); - blockChainStates ??= new BlockChainStates(store, stateStore); return new BlockChain( @@ -560,10 +558,14 @@ public FungibleAssetValue GetTotalSupply(Currency currency, BlockHash? offset) = public ValidatorSet GetValidatorSet(BlockHash? offset) => _blockChainStates.GetValidatorSet(offset); - /// + /// public IAccountState GetAccountState(BlockHash? offset) => _blockChainStates.GetAccountState(offset); + /// + public IAccountState GetAccountState(HashDigest? hash) => + _blockChainStates.GetAccountState(hash); + /// /// Queries the recorded for a successful or failed /// within a . @@ -959,7 +961,7 @@ internal void Append( Block block, BlockCommit blockCommit, bool render, - IReadOnlyList actionEvaluations = null + IReadOnlyList actionEvaluations = null ) { if (Count == 0) diff --git a/Libplanet/Blockchain/BlockChainStates.cs b/Libplanet/Blockchain/BlockChainStates.cs index 6eb46aa30a1..69e2b05dbc5 100644 --- a/Libplanet/Blockchain/BlockChainStates.cs +++ b/Libplanet/Blockchain/BlockChainStates.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Store; using Libplanet.Store.Trie; @@ -48,10 +50,14 @@ public FungibleAssetValue GetTotalSupply(Currency currency, BlockHash? offset) = public ValidatorSet GetValidatorSet(BlockHash? offset) => GetAccountState(offset).GetValidatorSet(); - /// + /// public IAccountState GetAccountState(BlockHash? offset) => new AccountState(GetTrie(offset)); + /// + public IAccountState GetAccountState(HashDigest? hash) => + new AccountState(GetTrie(hash)); + /// /// Returns the state root associated with /// . @@ -84,17 +90,7 @@ private ITrie GetTrie(BlockHash? offset) } else if (_store.GetStateRootHash(hash) is { } stateRootHash) { - if (_stateStore.ContainsStateRoot(stateRootHash)) - { - return _stateStore.GetStateRoot(stateRootHash); - } - else - { - throw new ArgumentException( - $"Could not find state root {stateRootHash} associated with " + - $"block hash {offset} in {nameof(IStateStore)}.", - nameof(offset)); - } + return GetTrie(stateRootHash); } else { @@ -103,5 +99,15 @@ private ITrie GetTrie(BlockHash? offset) nameof(offset)); } } + + private ITrie GetTrie(HashDigest? hash) + { + ITrie trie = _stateStore.GetStateRoot(hash); + return trie.Recorded + ? trie + : throw new ArgumentException( + $"Could not find state root {hash} in {nameof(IStateStore)}.", + nameof(hash)); + } } } diff --git a/Libplanet/Blockchain/NullChainStates.cs b/Libplanet/Blockchain/NullChainStates.cs deleted file mode 100644 index 6e9a2e72b8d..00000000000 --- a/Libplanet/Blockchain/NullChainStates.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using Bencodex.Types; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Libplanet.Store.Trie; -using Libplanet.Types.Assets; -using Libplanet.Types.Blocks; -using Libplanet.Types.Consensus; - -namespace Libplanet.Blockchain -{ - internal class NullChainStates : IBlockChainStates - { - public static readonly NullChainStates Instance = new NullChainStates(); - private static readonly IKeyValueStore _keyValueStore = new MemoryKeyValueStore(); - - private NullChainStates() - { - } - - public IValue? GetState( - Address address, BlockHash? offset) => - GetAccountState(offset).GetState(address); - - public IReadOnlyList GetStates( - IReadOnlyList
addresses, BlockHash? offset) => - GetAccountState(offset).GetStates(addresses); - - public FungibleAssetValue GetBalance( - Address address, Currency currency, BlockHash? offset) => - GetAccountState(offset).GetBalance(address, currency); - - public FungibleAssetValue GetTotalSupply(Currency currency, BlockHash? offset) => - GetAccountState(offset).GetTotalSupply(currency); - - public ValidatorSet GetValidatorSet(BlockHash? offset) => - GetAccountState(offset).GetValidatorSet(); - - public IAccountState GetAccountState(BlockHash? offset) => - new AccountState(new MerkleTrie(_keyValueStore)); - } -} diff --git a/Libplanet/Blockchain/Renderers/AnonymousActionRenderer.cs b/Libplanet/Blockchain/Renderers/AnonymousActionRenderer.cs index 12cdad41dfc..42fd7be8aa0 100644 --- a/Libplanet/Blockchain/Renderers/AnonymousActionRenderer.cs +++ b/Libplanet/Blockchain/Renderers/AnonymousActionRenderer.cs @@ -1,7 +1,8 @@ using System; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Types.Blocks; namespace Libplanet.Blockchain.Renderers @@ -18,7 +19,7 @@ namespace Libplanet.Blockchain.Renderers /// + /// ActionRenderer = (action, context, nextState) => /// { /// // Implement RenderAction() here. /// }; @@ -29,15 +30,16 @@ public sealed class AnonymousActionRenderer : AnonymousRenderer, IActionRenderer { /// /// A callback function to be invoked together with - /// . + /// . /// - public Action? ActionRenderer { get; set; } + public Action>? ActionRenderer + { get; set; } /// /// A callback function to be invoked together with - /// . + /// . /// - public Action? ActionErrorRenderer { get; set; } + public Action? ActionErrorRenderer { get; set; } /// /// A callback function to be invoked together with @@ -45,19 +47,21 @@ public sealed class AnonymousActionRenderer : AnonymousRenderer, IActionRenderer /// public Action? BlockEndRenderer { get; set; } - /// + /// public void RenderAction( IValue action, - IActionContext context, - IAccount nextStates + ICommittedActionContext context, + HashDigest nextState ) => - ActionRenderer?.Invoke(action, context, nextStates); + ActionRenderer?.Invoke(action, context, nextState); /// - public void RenderActionError(IValue action, IActionContext context, Exception exception) - => ActionErrorRenderer?.Invoke(action, context, exception); + /// cref="IActionRenderer.RenderActionError(IValue, ICommittedActionContext, Exception)"/> + public void RenderActionError( + IValue action, + ICommittedActionContext context, + Exception exception) => + ActionErrorRenderer?.Invoke(action, context, exception); /// public void RenderBlockEnd(Block oldTip, Block newTip) => diff --git a/Libplanet/Blockchain/Renderers/AtomicActionRenderer.cs b/Libplanet/Blockchain/Renderers/AtomicActionRenderer.cs index 1073452edec..7b4b7608dcd 100644 --- a/Libplanet/Blockchain/Renderers/AtomicActionRenderer.cs +++ b/Libplanet/Blockchain/Renderers/AtomicActionRenderer.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; @@ -20,7 +21,7 @@ namespace Libplanet.Blockchain.Renderers /// public sealed class AtomicActionRenderer : IActionRenderer { - private readonly List<(IValue, IActionContext, IAccount)> _eventBuffer; + private readonly List<(IValue, ICommittedActionContext, HashDigest)> _eventBuffer; private TxId? _lastTxId; private bool _errored; @@ -35,7 +36,7 @@ public AtomicActionRenderer(IActionRenderer actionRenderer) { ActionRenderer = actionRenderer; _lastTxId = null; - _eventBuffer = new List<(IValue, IActionContext, IAccount)>(); + _eventBuffer = new List<(IValue, ICommittedActionContext, HashDigest)>(); _errored = false; } @@ -58,12 +59,11 @@ public void RenderBlockEnd(Block oldTip, Block newTip) ActionRenderer.RenderBlockEnd(oldTip, newTip); } - /// + /// public void RenderAction( IValue action, - IActionContext context, - IAccount nextStates + ICommittedActionContext context, + HashDigest nextState ) { if (!context.TxId.Equals(_lastTxId)) @@ -73,17 +73,19 @@ IAccount nextStates if (context.TxId is null) { - ActionRenderer.RenderAction(action, context, nextStates); + ActionRenderer.RenderAction(action, context, nextState); } else if (!_errored) { - _eventBuffer.Add((action, context, nextStates)); + _eventBuffer.Add((action, context, nextState)); } } - /// - public void RenderActionError(IValue action, IActionContext context, Exception exception) + /// + public void RenderActionError( + IValue action, + ICommittedActionContext context, + Exception exception) { if (!context.TxId.Equals(_lastTxId)) { @@ -102,7 +104,7 @@ public void RenderActionError(IValue action, IActionContext context, Exception e private void FlushBuffer( TxId? newTxId, - Action render + Action> render ) { if (!_errored) diff --git a/Libplanet/Blockchain/Renderers/Debug/RecordingActionRenderer.cs b/Libplanet/Blockchain/Renderers/Debug/RecordingActionRenderer.cs index bdf29a86930..731860a599f 100644 --- a/Libplanet/Blockchain/Renderers/Debug/RecordingActionRenderer.cs +++ b/Libplanet/Blockchain/Renderers/Debug/RecordingActionRenderer.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Types.Blocks; using Serilog; @@ -59,8 +60,8 @@ public void ResetRecords() /// public virtual void RenderAction( IValue action, - IActionContext context, - IAccount nextStates + ICommittedActionContext context, + HashDigest nextState ) { _records.Add( @@ -69,7 +70,7 @@ IAccount nextStates stackTrace: RemoveFirstLine(Environment.StackTrace).TrimEnd(), action: action, context: context, - nextStates: nextStates + nextState: nextState ) ); @@ -79,7 +80,7 @@ IAccount nextStates /// public virtual void RenderActionError( IValue action, - IActionContext context, + ICommittedActionContext context, Exception exception ) => _records.Add( diff --git a/Libplanet/Blockchain/Renderers/Debug/RenderRecord.cs b/Libplanet/Blockchain/Renderers/Debug/RenderRecord.cs index 4b7ba015ea0..3c269daa5b2 100644 --- a/Libplanet/Blockchain/Renderers/Debug/RenderRecord.cs +++ b/Libplanet/Blockchain/Renderers/Debug/RenderRecord.cs @@ -1,7 +1,8 @@ using System; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Types.Blocks; namespace Libplanet.Blockchain.Renderers.Debug @@ -39,7 +40,7 @@ protected ActionBase( long index, string stackTrace, IValue action, - IActionContext context, + ICommittedActionContext context, bool unrender = false ) : base(index, stackTrace) @@ -57,7 +58,7 @@ protected ActionBase( /// /// The action evaluation context. /// - public IActionContext Context { get; } + public ICommittedActionContext Context { get; } /// /// Whether it is not an unrender event, but a render event. @@ -87,25 +88,25 @@ public class ActionSuccess : ActionBase /// The stack trace of the render event. /// The rendered action. /// The action evaluation context. - /// The resulting states after the action is evaluated. + /// The resulting state after the action is evaluated. /// Whether it is an unrender event. public ActionSuccess( long index, string stackTrace, IValue action, - IActionContext context, - IAccount nextStates, + ICommittedActionContext context, + HashDigest nextState, bool unrender = false ) : base(index, stackTrace, action, context, unrender: unrender) { - NextStates = nextStates; + NextState = nextState; } /// /// The resulting states after the action is evaluated. /// - public IAccount NextStates { get; } + public HashDigest NextState { get; } /// public override string ToString() => $"{base.ToString()} [success]"; @@ -129,7 +130,7 @@ public ActionError( long index, string stackTrace, IValue action, - IActionContext context, + ICommittedActionContext context, Exception exception, bool unrender = false ) diff --git a/Libplanet/Blockchain/Renderers/Debug/ValidatingActionRenderer.cs b/Libplanet/Blockchain/Renderers/Debug/ValidatingActionRenderer.cs index 36f7f864319..11a4c32a35e 100644 --- a/Libplanet/Blockchain/Renderers/Debug/ValidatingActionRenderer.cs +++ b/Libplanet/Blockchain/Renderers/Debug/ValidatingActionRenderer.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Types.Blocks; namespace Libplanet.Blockchain.Renderers.Debug @@ -40,23 +41,21 @@ public override void RenderBlock(Block oldTip, Block newTip) Validate(); } - /// + /// public override void RenderAction( IValue action, - IActionContext context, - IAccount nextStates + ICommittedActionContext context, + HashDigest nextState ) { - base.RenderAction(action, context, nextStates); + base.RenderAction(action, context, nextState); Validate(); } - /// + /// public override void RenderActionError( IValue action, - IActionContext context, + ICommittedActionContext context, Exception exception ) { diff --git a/Libplanet/Blockchain/Renderers/IActionRenderer.cs b/Libplanet/Blockchain/Renderers/IActionRenderer.cs index 50d527865d8..90c9c8c2ab2 100644 --- a/Libplanet/Blockchain/Renderers/IActionRenderer.cs +++ b/Libplanet/Blockchain/Renderers/IActionRenderer.cs @@ -1,7 +1,8 @@ using System; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Store; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; @@ -17,8 +18,8 @@ namespace Libplanet.Blockchain.Renderers /// /// (one time) /// - /// - /// & (zero or more + /// + /// & (zero or more /// times) /// /// (one time) @@ -48,26 +49,28 @@ public interface IActionRenderer : IRenderer /// the 's method. /// That means are the states right /// before this action executed. For the states after this action executed, - /// use the argument instead. - /// The states right after this action executed, + /// use the argument instead. + /// The state root hash right after this action executed, /// which means it is equivalent to the states 's /// method returned. /// /// It is guaranteed to be called only once for an , /// and only after applied to the blockchain, unless an exception is thrown during executing /// the (in that case is called instead) or + /// cref="RenderActionError"/> is called instead) or /// once the has been unrendered. /// Also note that this method is invoked after method is called /// (where its second parameter newTip contains a transaction the belongs to). /// - void RenderAction(IValue action, IActionContext context, IAccount nextStates); + void RenderAction( + IValue action, + ICommittedActionContext context, + HashDigest nextState); /// - /// Does the similar things to , except that this method + /// Does the similar things to , except that this method /// is invoked when has terminated with an exception. /// /// An action which threw an exception during execution. @@ -83,7 +86,7 @@ public interface IActionRenderer : IRenderer /// (where its second parameter newTip contains a transaction the belongs to). /// - void RenderActionError(IValue action, IActionContext context, Exception exception); + void RenderActionError(IValue action, ICommittedActionContext context, Exception exception); /// /// Does things that should be done right all actions in a new are diff --git a/Libplanet/Blockchain/Renderers/LoggedActionRenderer.cs b/Libplanet/Blockchain/Renderers/LoggedActionRenderer.cs index 45a0d776350..be0220f80aa 100644 --- a/Libplanet/Blockchain/Renderers/LoggedActionRenderer.cs +++ b/Libplanet/Blockchain/Renderers/LoggedActionRenderer.cs @@ -1,7 +1,8 @@ using System; +using System.Security.Cryptography; using Bencodex.Types; using Libplanet.Action; -using Libplanet.Action.State; +using Libplanet.Common; using Libplanet.Types.Blocks; using Serilog; using Serilog.Events; @@ -67,25 +68,23 @@ Block newTip ActionRenderer.RenderBlockEnd ); - /// + /// public void RenderAction( IValue action, - IActionContext context, - IAccount nextStates + ICommittedActionContext context, + HashDigest nextState ) => LogActionRendering( nameof(RenderAction), action, context, - () => ActionRenderer.RenderAction(action, context, nextStates) + () => ActionRenderer.RenderAction(action, context, nextState) ); - /// + /// public void RenderActionError( IValue action, - IActionContext context, + ICommittedActionContext context, Exception exception ) => LogActionRendering( @@ -98,7 +97,7 @@ Exception exception private void LogActionRendering( string methodName, IValue action, - IActionContext context, + ICommittedActionContext context, System.Action callback ) { diff --git a/Libplanet/Libplanet.csproj b/Libplanet/Libplanet.csproj index d972c96dd32..5f002332417 100644 --- a/Libplanet/Libplanet.csproj +++ b/Libplanet/Libplanet.csproj @@ -2,7 +2,7 @@ Libplanet Libplanet - 3.4.0 + 3.7.0