diff --git a/CHANGES.md b/CHANGES.md index 2cceface83..33c0766108 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,14 @@ To be released. disposed to clean up its internal resources. [[#485]] - `IStore.IterateStateReferences()` method became to receive `highestIndex`, `lowestIndex`, and `limit` parameters. [[#447], [#545]] - - Reworked `BlockChain.GetStates()` into `GetState()` which takes only one `Address` instead of `IEnumerable
`. [[#510], [#563]] + - Reworked `BlockChain.GetStates()` into `GetState()` which takes only + one `Address` instead of `IEnumerable
`. [[#510], [#563]] + - Types of `IAction.PlainValue` and states became restricted to + `Bencodex.Types.IValue`. [[#541], [#552]] + - `IAction.LoadPlainValue(IImmutableDictionary)` method + became replaced by `LoadPlainValue(IValue)`. + - `AccountStateGetter` became to return `IValue`, not `object`. + - Added `BencodexExtension` static class. ### Added interfaces @@ -48,8 +55,10 @@ To be released. [#485]: https://github.com/planetarium/libplanet/pull/485 [#510]: https://github.com/planetarium/libplanet/issues/510 [#528]: https://github.com/planetarium/libplanet/issues/528 +[#541]: https://github.com/planetarium/libplanet/issues/541 [#545]: https://github.com/planetarium/libplanet/pull/545 [#550]: https://github.com/planetarium/libplanet/issues/550 +[#552]: https://github.com/planetarium/libplanet/pull/552 [#555]: https://github.com/planetarium/libplanet/issues/555 [#558]: https://github.com/planetarium/libplanet/pull/558 [#560]: https://github.com/planetarium/libplanet/pull/560 diff --git a/Libplanet.Tests/Action/AccountStateDeltaImplTest.cs b/Libplanet.Tests/Action/AccountStateDeltaImplTest.cs index 1903dbf980..484dd8f43e 100644 --- a/Libplanet.Tests/Action/AccountStateDeltaImplTest.cs +++ b/Libplanet.Tests/Action/AccountStateDeltaImplTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Crypto; using Xunit; @@ -9,7 +10,7 @@ namespace Libplanet.Tests.Action public class AccountStateDeltaImplTest { private readonly Address[] _addr; - private readonly IImmutableDictionary _states; + private readonly IImmutableDictionary _states; public AccountStateDeltaImplTest() { @@ -22,10 +23,10 @@ public AccountStateDeltaImplTest() Addr(), }; - _states = new Dictionary() + _states = new Dictionary { - [_addr[0]] = "a", - [_addr[1]] = "b", + [_addr[0]] = (Text)"a", + [_addr[1]] = (Text)"b", }.ToImmutableDictionary(); } @@ -34,8 +35,8 @@ public void CreateNullDelta() { IAccountStateDelta delta = new AccountStateDeltaImpl(GetState); Assert.Empty(delta.UpdatedAddresses); - Assert.Equal("a", delta.GetState(_addr[0])); - Assert.Equal("b", delta.GetState(_addr[1])); + Assert.Equal("a", (Text)delta.GetState(_addr[0])); + Assert.Equal("b", (Text)delta.GetState(_addr[1])); Assert.Null(delta.GetState(_addr[2])); } @@ -43,11 +44,11 @@ public void CreateNullDelta() public void GetSetState() { IAccountStateDelta init = new AccountStateDeltaImpl(GetState); - IAccountStateDelta a = init.SetState(_addr[0], "A"); - Assert.Equal("A", a.GetState(_addr[0])); - Assert.Equal("a", init.GetState(_addr[0])); - Assert.Equal("b", a.GetState(_addr[1])); - Assert.Equal("b", init.GetState(_addr[1])); + IAccountStateDelta a = init.SetState(_addr[0], (Text)"A"); + Assert.Equal("A", (Text)a.GetState(_addr[0])); + Assert.Equal("a", (Text)init.GetState(_addr[0])); + Assert.Equal("b", (Text)a.GetState(_addr[1])); + Assert.Equal("b", (Text)init.GetState(_addr[1])); Assert.Null(a.GetState(_addr[2])); Assert.Null(init.GetState(_addr[2])); Assert.Equal( @@ -56,12 +57,12 @@ public void GetSetState() ); Assert.Empty(init.UpdatedAddresses); - IAccountStateDelta b = a.SetState(_addr[0], "z"); - Assert.Equal("z", b.GetState(_addr[0])); - Assert.Equal("A", a.GetState(_addr[0])); - Assert.Equal("a", init.GetState(_addr[0])); - Assert.Equal("b", b.GetState(_addr[1])); - Assert.Equal("b", a.GetState(_addr[1])); + IAccountStateDelta b = a.SetState(_addr[0], (Text)"z"); + Assert.Equal("z", (Text)b.GetState(_addr[0])); + Assert.Equal("A", (Text)a.GetState(_addr[0])); + Assert.Equal("a", (Text)init.GetState(_addr[0])); + Assert.Equal("b", (Text)b.GetState(_addr[1])); + Assert.Equal("b", (Text)a.GetState(_addr[1])); Assert.Null(b.GetState(_addr[2])); Assert.Null(a.GetState(_addr[2])); Assert.Equal( @@ -70,13 +71,13 @@ public void GetSetState() ); Assert.Empty(init.UpdatedAddresses); - IAccountStateDelta c = b.SetState(_addr[0], "a"); - Assert.Equal("a", c.GetState(_addr[0])); - Assert.Equal("z", b.GetState(_addr[0])); + IAccountStateDelta c = b.SetState(_addr[0], (Text)"a"); + Assert.Equal("a", (Text)c.GetState(_addr[0])); + Assert.Equal("z", (Text)b.GetState(_addr[0])); Assert.Empty(init.UpdatedAddresses); } - private object GetState(Address address) + private IValue GetState(Address address) { try { diff --git a/Libplanet.Tests/Action/ActionContextTest.cs b/Libplanet.Tests/Action/ActionContextTest.cs index 20cec3f4f7..20bd92c58a 100644 --- a/Libplanet.Tests/Action/ActionContextTest.cs +++ b/Libplanet.Tests/Action/ActionContextTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; using Xunit; @@ -104,12 +105,12 @@ private class DumbAccountStateDelta : IAccountStateDelta public IImmutableSet
UpdatedAddresses => ImmutableHashSet
.Empty; - public object GetState(Address address) + public IValue GetState(Address address) { return null; } - public IAccountStateDelta SetState(Address address, object state) + public IAccountStateDelta SetState(Address address, IValue state) { return this; } diff --git a/Libplanet.Tests/Action/ActionEvaluationTest.cs b/Libplanet.Tests/Action/ActionEvaluationTest.cs index a3be74dc66..19e71fae9e 100644 --- a/Libplanet.Tests/Action/ActionEvaluationTest.cs +++ b/Libplanet.Tests/Action/ActionEvaluationTest.cs @@ -1,3 +1,4 @@ +using Bencodex.Types; using Libplanet.Action; using Libplanet.Crypto; using Libplanet.Tests.Common.Action; @@ -22,7 +23,7 @@ public void Constructor() false ), new AccountStateDeltaImpl( - a => a.Equals(address) ? "item" : null + a => a.Equals(address) ? (Text)"item" : null ) ); var action = (DumbAction)evaluation.Action; @@ -36,7 +37,7 @@ public void Constructor() evaluation.InputContext.PreviousStates.GetState(address) ); Assert.Equal( - "item", + (Text)"item", evaluation.OutputStates.GetState(address) ); } diff --git a/Libplanet.Tests/Action/PolymorphicActionTest.cs b/Libplanet.Tests/Action/PolymorphicActionTest.cs index 894d5827d3..4055b4d87a 100644 --- a/Libplanet.Tests/Action/PolymorphicActionTest.cs +++ b/Libplanet.Tests/Action/PolymorphicActionTest.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Crypto; using Libplanet.Tests.Common.Action; -using Libplanet.Tx; using Xunit; namespace Libplanet.Tests.Action @@ -23,19 +22,16 @@ public void PlainValue() } ); Assert.Equal( - new Dictionary + new Dictionary(new Dictionary { - { "type_id", "attack" }, + [(Text)"type_id"] = (Text)"attack", + [(Text)"values"] = new Dictionary(new Dictionary { - "values", - new Dictionary - { - { "weapon", "frying pan" }, - { "target", "mosquito" }, - { "target_address", addr.ToByteArray() }, - }.ToImmutableDictionary() - }, - }.ToImmutableDictionary(), + [(Text)"weapon"] = (Text)"frying pan", + [(Text)"target"] = (Text)"mosquito", + [(Text)"target_address"] = new Binary(addr.ToByteArray()), + }), + }), pa.PlainValue ); } @@ -48,19 +44,16 @@ public void LoadPlainValue() var pa = new PolymorphicAction(); #pragma warning restore 612 pa.LoadPlainValue( - new Dictionary + new Dictionary(new Dictionary { - { "type_id", "attack" }, + [(Text)"type_id"] = (Text)"attack", + [(Text)"values"] = new Dictionary(new Dictionary { - "values", - new Dictionary - { - { "weapon", "frying pan" }, - { "target", "mosquito" }, - { "target_address", addr.ToByteArray() }, - }.ToImmutableDictionary() - }, - }.ToImmutableDictionary() + [(Text)"weapon"] = (Text)"frying pan", + [(Text)"target"] = (Text)"mosquito", + [(Text)"target_address"] = new Binary(addr.ToByteArray()), + }), + }) ); Assert.IsType(pa.InnerAction); @@ -100,14 +93,19 @@ public ActionNotAttributeAnnotated() { } - public IImmutableDictionary PlainValue => - new Dictionary().ToImmutableDictionary(); + public IValue PlainValue => + default(Dictionary); public void LoadPlainValue( - IImmutableDictionary plainValue) + Dictionary plainValue) { } + public void LoadPlainValue(IValue plainValue) + { + LoadPlainValue((Dictionary)plainValue); + } + public IAccountStateDelta Execute(IActionContext context) { return context.PreviousStates; diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index 90b73cf781..8a794f3054 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Async; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; @@ -237,7 +237,7 @@ public async void ProcessActions() AddressStateMap states = chain.GetState(_fx.Address1); Assert.NotEmpty(states); - var result = (BattleResult)states[_fx.Address1]; + var result = BattleResult.FromBencodex((Dictionary)states[_fx.Address1]); Assert.Contains("sword", result.UsedWeapons); Assert.Contains("staff", result.UsedWeapons); Assert.Contains("orc", result.Targets); @@ -264,7 +264,7 @@ public async void ProcessActions() await chain.MineBlock(_fx.Address1); states = chain.GetState(_fx.Address1); - result = (BattleResult)states[_fx.Address1]; + result = BattleResult.FromBencodex((Dictionary)states[_fx.Address1]); Assert.Contains("bow", result.UsedWeapons); var tx3 = Transaction>.Create( @@ -318,11 +318,11 @@ public void Append() Assert.Equal("foo", actions[0].Item); Assert.Equal(1, renders[0].Context.BlockIndex); Assert.Equal( - new object[] { null, null, null, null, 1 }, + new IValue[] { null, null, null, null, (Integer)1 }, addresses.Select(renders[0].Context.PreviousStates.GetState) ); Assert.Equal( - new object[] { "foo", null, null, null, 1 }, + new IValue[] { (Text)"foo", null, null, null, (Integer)1 }, addresses.Select(renders[0].NextStates.GetState) ); Assert.Equal("bar", actions[1].Item); @@ -332,7 +332,7 @@ public void Append() addresses.Select(renders[1].Context.PreviousStates.GetState) ); Assert.Equal( - new object[] { "foo", "bar", null, null, 1 }, + new IValue[] { (Text)"foo", (Text)"bar", null, null, (Integer)1 }, addresses.Select(renders[1].NextStates.GetState) ); Assert.Equal("baz", actions[2].Item); @@ -342,7 +342,7 @@ public void Append() addresses.Select(renders[2].Context.PreviousStates.GetState) ); Assert.Equal( - new object[] { "foo", "bar", "baz", null, 1 }, + new IValue[] { (Text)"foo", (Text)"bar", (Text)"baz", null, (Integer)1 }, addresses.Select(renders[2].NextStates.GetState) ); Assert.Equal("qux", actions[3].Item); @@ -352,26 +352,30 @@ public void Append() addresses.Select(renders[3].Context.PreviousStates.GetState) ); Assert.Equal( - new object[] { "foo", "bar", "baz", "qux", 1 }, + new IValue[] { (Text)"foo", (Text)"bar", (Text)"baz", (Text)"qux", (Integer)1 }, addresses.Select(renders[3].NextStates.GetState) ); var minerAddress = addresses[4]; var blockRenders = MinerReward.RenderRecords.Value; - Assert.Equal(2, _blockChain.GetState(minerAddress)[minerAddress]); + Assert.Equal( + 2, + (Integer)_blockChain.GetState(minerAddress)[minerAddress]); Assert.Equal(2, blockRenders.Count); Assert.True(blockRenders.All(r => r.Render)); Assert.Equal(0, blockRenders[0].Context.BlockIndex); Assert.Equal(1, blockRenders[1].Context.BlockIndex); - Assert.Equal(1, blockRenders[0].NextStates.GetState(minerAddress)); Assert.Equal( 1, - blockRenders[1].Context.PreviousStates.GetState(minerAddress)); + (Integer)blockRenders[0].NextStates.GetState(minerAddress)); + Assert.Equal( + 1, + (Integer)blockRenders[1].Context.PreviousStates.GetState(minerAddress)); Assert.Equal( 2, - blockRenders[1].NextStates.GetState(minerAddress)); + (Integer)blockRenders[1].NextStates.GetState(minerAddress)); } finally { @@ -439,14 +443,14 @@ public void ExecuteActions() renderActions: false ); - var expectedStates = new Dictionary + var expectedStates = new Dictionary { - { addresses[0], "foo" }, - { addresses[1], "bar" }, - { addresses[2], "baz" }, - { addresses[3], "qux" }, - { addresses[4], 2 }, - { MinerReward.RewardRecordAddress, $"{addresses[4]},{addresses[4]}" }, + { addresses[0], (Text)"foo" }, + { addresses[1], (Text)"bar" }, + { addresses[2], (Text)"baz" }, + { addresses[3], (Text)"qux" }, + { addresses[4], (Integer)2 }, + { MinerReward.RewardRecordAddress, (Text)$"{addresses[4]},{addresses[4]}" }, }; _blockChain.ExecuteActions(blocks[1], true); @@ -884,7 +888,7 @@ public void Swap(bool render) Assert.Equal( totalBlockCount, - _blockChain.GetState(minerAddress)[minerAddress]); + (Integer)_blockChain.GetState(minerAddress)[minerAddress]); Assert.Equal(totalBlockCount, blockRenders.Count); Assert.True(blockRenders.Take(unRenderBlockCount).All(r => r.Unrender)); Assert.True(blockRenders.Skip(unRenderBlockCount).All(r => r.Render)); @@ -998,7 +1002,7 @@ public void GetStateThrowsIncompleteBlockStatesException() Address lastAddress = addresses.Last(); AddressStateMap states = chain.GetState(lastAddress); Assert.NotEmpty(states); - Assert.Equal("9", states[lastAddress]); + Assert.Equal("9", states[lastAddress].ToString()); // As the store lacks the states for blocks other than the tip, // the following GetState() calls should throw an exception. @@ -1104,12 +1108,12 @@ public async void GetStateReturnsLatestStatesWhenMultipleAddresses() foreach (var address in addresses) { - Assert.Equal("1", chain.GetState(address)[address]); + Assert.Equal("1", chain.GetState(address)[address].ToString()); } chain.MakeTransaction(privateKeys[0], new[] { new DumbAction(addresses[0], "2") }); await chain.MineBlock(addresses[0]); - Assert.Equal("1,2", chain.GetState(addresses[0])[addresses[0]]); + Assert.Equal("1,2", chain.GetState(addresses[0])[addresses[0]].ToString()); } [Fact] @@ -1169,14 +1173,17 @@ public async void EvaluateActions() await chain.MineBlock(_fx.Address1); Assert.Equal( - chain.GetState(TestEvaluateAction.SignerKey)[TestEvaluateAction.SignerKey], + chain.GetState(TestEvaluateAction.SignerKey)[TestEvaluateAction.SignerKey] + .ToString(), fromAddress.ToHex() ); Assert.Equal( - chain.GetState(TestEvaluateAction.MinerKey)[TestEvaluateAction.MinerKey], + chain.GetState(TestEvaluateAction.MinerKey)[TestEvaluateAction.MinerKey].ToString(), _fx.Address1.ToHex()); + var state = + chain.GetState(TestEvaluateAction.BlockIndexKey)[TestEvaluateAction.BlockIndexKey]; Assert.Equal( - chain.GetState(TestEvaluateAction.BlockIndexKey)[TestEvaluateAction.BlockIndexKey], + (long)((Integer)state).Value, blockIndex); } @@ -1250,7 +1257,7 @@ public void GetNextTxNonce() public void ValidateTxNonces() { var privateKey = new PrivateKey(); - var actions = new[] { new DumbAction() }; + var actions = new[] { new DumbAction(_fx.Address1, string.Empty) }; Block genesis = TestUtils.MineGenesis(); _blockChain.Append(genesis); @@ -1338,8 +1345,8 @@ public async void MineBlockWithBlockAction() Assert.Equal(0, blockChain.GetNextTxNonce(address1)); Assert.Equal(1, blockChain.GetNextTxNonce(address2)); - Assert.Equal("foo", states1); - Assert.Equal("baz", states2); + Assert.Equal("foo", states1.ToString()); + Assert.Equal("baz", states2.ToString()); blockChain.MakeTransaction(privateKey1, new[] { new DumbAction(address1, "bar") }); await blockChain.MineBlock(address1); @@ -1349,8 +1356,8 @@ public async void MineBlockWithBlockAction() Assert.Equal(1, blockChain.GetNextTxNonce(address1)); Assert.Equal(1, blockChain.GetNextTxNonce(address2)); - Assert.Equal("foo,bar,foo", states1); - Assert.Equal("baz", states2); + Assert.Equal("foo,bar,foo", states1.ToString()); + Assert.Equal("baz", states2.ToString()); } [Fact] @@ -1366,7 +1373,7 @@ public void EvaluateBlockAction() var miner = addresses[4]; var blockActionEvaluation = _blockChain.EvaluateBlockAction(blocks[0], null); Assert.Equal(_blockChain.Policy.BlockAction, blockActionEvaluation.Action); - Assert.Equal(1, blockActionEvaluation.OutputStates.GetState(miner)); + Assert.Equal(1, (Integer)blockActionEvaluation.OutputStates.GetState(miner)); _blockChain.ExecuteActions(blocks[0], true); _blockChain.Append( @@ -1380,7 +1387,7 @@ public void EvaluateBlockAction() .Select(te => te.Item2).ToList(); blockActionEvaluation = _blockChain.EvaluateBlockAction(blocks[1], txEvaluations); - Assert.Equal(2, blockActionEvaluation.OutputStates.GetState(miner)); + Assert.Equal(2, (Integer)blockActionEvaluation.OutputStates.GetState(miner)); } [Fact] @@ -1398,9 +1405,9 @@ public async void BlockActionWithMultipleAddress() AddressStateMap miner2states = _blockChain.GetState(miner2); AddressStateMap rewardStates = _blockChain.GetState(rewardRecordAddress); - int reward1 = (int)miner1states[miner1]; - int reward2 = (int)miner2states[miner2]; - string rewardRecord = (string)rewardStates[rewardRecordAddress]; + int reward1 = (int)((Integer)miner1states[miner1]).Value; + int reward2 = (int)((Integer)miner2states[miner2]).Value; + string rewardRecord = rewardStates[rewardRecordAddress].ToString(); Assert.Equal(2, reward1); Assert.Equal(1, reward2); @@ -1453,12 +1460,12 @@ public async void BlockActionWithMultipleAddress() var privateKey = new PrivateKey(); Address signer = privateKey.PublicKey.ToAddress(); - IImmutableDictionary GetDirty( + IImmutableDictionary GetDirty( IEnumerable evaluations) => evaluations.Select( a => a.OutputStates ).Aggregate( - ImmutableDictionary.Empty, + ImmutableDictionary.Empty, (x, y) => x.SetItems(y.GetUpdatedStates()) ); @@ -1476,7 +1483,7 @@ void BuildIndex(Guid id, Block block) Block b = TestUtils.MineGenesis(); chain.Blocks[b.Hash] = b; BuildIndex(chainId, b); - IImmutableDictionary dirty = + IImmutableDictionary dirty = GetDirty(b.Evaluate(DateTimeOffset.UtcNow, _ => null)); const int accountsCount = 5; Address[] addresses = Enumerable.Repeat(null, accountsCount) @@ -1672,20 +1679,20 @@ public TestEvaluateAction() { } - public IImmutableDictionary PlainValue => - new Dictionary().ToImmutableDictionary(); + public IValue PlainValue => + default(Dictionary); public void LoadPlainValue( - IImmutableDictionary plainValue) + IValue plainValue) { } public IAccountStateDelta Execute(IActionContext context) { return context.PreviousStates - .SetState(SignerKey, context.Signer.ToHex()) - .SetState(MinerKey, context.Miner.ToHex()) - .SetState(BlockIndexKey, context.BlockIndex); + .SetState(SignerKey, (Text)context.Signer.ToHex()) + .SetState(MinerKey, (Text)context.Miner.ToHex()) + .SetState(BlockIndexKey, (Integer)context.BlockIndex); } public void Render( diff --git a/Libplanet.Tests/Blocks/BlockTest.cs b/Libplanet.Tests/Blocks/BlockTest.cs index b5114892a5..9f56e7fd21 100644 --- a/Libplanet.Tests/Blocks/BlockTest.cs +++ b/Libplanet.Tests/Blocks/BlockTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security.Cryptography; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Blocks; using Libplanet.Crypto; @@ -270,27 +271,30 @@ DumbAction MakeAction(Address address, char identifier) => Assert.False(eval.InputContext.Rehearsal); randomValue = eval.InputContext.Random.Next(); Assert.Equal( - eval.OutputStates.GetState( + (Integer)eval.OutputStates.GetState( DumbAction.RandomRecordsAddress ), randomValue ); - Assert.Equal(expect.Item3, addresses.Select(eval.OutputStates.GetState)); + Assert.Equal( + expect.Item3, + addresses.Select(eval.OutputStates.GetState) + .Select(x => x is Text t ? t.Value : null)); } - IImmutableDictionary dirty1 = blockIdx1 + IImmutableDictionary dirty1 = blockIdx1 .Evaluate(DateTimeOffset.UtcNow, address => null) .Aggregate( - ImmutableDictionary.Empty, + ImmutableDictionary.Empty, (dirty, ev) => dirty.SetItems(ev.OutputStates.GetUpdatedStates()) ); Assert.Equal( - new Dictionary + new Dictionary { - [addresses[0]] = "A", - [addresses[1]] = "B", - [addresses[2]] = "C", - [DumbAction.RandomRecordsAddress] = randomValue, + [addresses[0]] = (Text)"A", + [addresses[1]] = (Text)"B", + [addresses[2]] = (Text)"C", + [DumbAction.RandomRecordsAddress] = (Integer)randomValue, }.ToImmutableDictionary(), dirty1 ); @@ -358,24 +362,28 @@ DumbAction MakeAction(Address address, char identifier) => eval.OutputStates.GetState( DumbAction.RandomRecordsAddress ), - randomValue + (Integer)randomValue ); - Assert.Equal(expect.Item3, addresses.Select(eval.OutputStates.GetState)); + Assert.Equal( + expect.Item3, + addresses + .Select(eval.OutputStates.GetState) + .Select(x => x is Text t ? t.Value : null)); } - IImmutableDictionary dirty2 = blockIdx2 + IImmutableDictionary dirty2 = blockIdx2 .Evaluate(DateTimeOffset.UtcNow, dirty1.GetValueOrDefault) .Aggregate( - ImmutableDictionary.Empty, + ImmutableDictionary.Empty, (dirty, ev) => dirty.SetItems(ev.OutputStates.GetUpdatedStates()) ); Assert.Equal( - new Dictionary + new Dictionary { - [addresses[0]] = "A,D", - [addresses[3]] = "E", - [addresses[4]] = "RecordRehearsal:False", - [DumbAction.RandomRecordsAddress] = randomValue, + [addresses[0]] = (Text)"A,D", + [addresses[3]] = (Text)"E", + [addresses[4]] = (Text)"RecordRehearsal:False", + [DumbAction.RandomRecordsAddress] = (Integer)randomValue, }.ToImmutableDictionary(), dirty2 ); @@ -390,7 +398,7 @@ public void EvaluateInvalidTxSignature() ImmutableArray>.Empty, _fx.TxFixture.PublicKey1.Format(false).ToImmutableArray(), DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.ffffffZ"), - new IImmutableDictionary[0], + default(List), new byte[10].ToImmutableArray() ); var invalidTx = new Transaction(rawTx); @@ -412,7 +420,7 @@ public void EvaluateInvalidTxPublicKey() ImmutableArray>.Empty, _fx.TxFixture.PublicKey1.Format(false).ToImmutableArray(), DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.ffffffZ"), - new IImmutableDictionary[0], + default(List), ImmutableArray.Empty ); byte[] sig = _fx.TxFixture.PrivateKey1.Sign( @@ -441,7 +449,7 @@ public void EvaluateInvalidTxPublicKey() [Fact] public void EvaluateInvalidTxUpdatedAddresses() { - ImmutableArray> rawActions = + ImmutableArray rawActions = _fx.TxFixture.TxWithActions .ToRawTransaction(false).Actions.ToImmutableArray(); RawTransaction rawTxWithoutSig = new RawTransaction( diff --git a/Libplanet.Tests/Common/Action/Attack.cs b/Libplanet.Tests/Common/Action/Attack.cs index e3bf2134a0..90bf0d6003 100644 --- a/Libplanet.Tests/Common/Action/Attack.cs +++ b/Libplanet.Tests/Common/Action/Attack.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; namespace Libplanet.Tests.Common.Action @@ -7,13 +8,13 @@ namespace Libplanet.Tests.Common.Action [ActionType("attack")] public class Attack : BaseAction { - public override IImmutableDictionary PlainValue => - new Dictionary() + public override IValue PlainValue => + new Bencodex.Types.Dictionary(new Dictionary { - { "weapon", Weapon }, - { "target", Target }, - { "target_address", TargetAddress.ToByteArray() }, - }.ToImmutableDictionary(); + [(Text)"weapon"] = (Text)Weapon, + [(Text)"target"] = (Text)Target, + [(Text)"target_address"] = new Binary(TargetAddress.ToByteArray()), + }); public string Weapon { get; set; } @@ -22,11 +23,16 @@ public class Attack : BaseAction public Address TargetAddress { get; set; } public override void LoadPlainValue( - IImmutableDictionary plainValue) + IValue plainValue) { - Weapon = (string)plainValue["weapon"]; - Target = (string)plainValue["target"]; - TargetAddress = new Address((byte[])plainValue["target_address"]); + LoadPlainValue((Bencodex.Types.Dictionary)plainValue); + } + + public void LoadPlainValue(Bencodex.Types.Dictionary plainValue) + { + Weapon = (Text)plainValue[(Text)"weapon"]; + Target = (Text)plainValue[(Text)"target"]; + TargetAddress = new Address(((Binary)plainValue[(Text)"target_address"]).Value); } public override IAccountStateDelta Execute(IActionContext context) @@ -38,7 +44,7 @@ public override IAccountStateDelta Execute(IActionContext context) object value = previousStates.GetState(TargetAddress); if (!ReferenceEquals(value, null)) { - var previousResult = (BattleResult)value; + var previousResult = BattleResult.FromBencodex((Bencodex.Types.Dictionary)value); usedWeapons = previousResult.UsedWeapons; targets = previousResult.Targets; } @@ -47,7 +53,7 @@ public override IAccountStateDelta Execute(IActionContext context) targets = targets.Add(Target); var result = new BattleResult(usedWeapons, targets); - return previousStates.SetState(TargetAddress, result); + return previousStates.SetState(TargetAddress, result.ToBencodex()); } } } diff --git a/Libplanet.Tests/Common/Action/BaseAction.cs b/Libplanet.Tests/Common/Action/BaseAction.cs index fe3d8c6933..0a684595ab 100644 --- a/Libplanet.Tests/Common/Action/BaseAction.cs +++ b/Libplanet.Tests/Common/Action/BaseAction.cs @@ -1,11 +1,12 @@ using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; namespace Libplanet.Tests.Common.Action { public abstract class BaseAction : IAction { - public abstract IImmutableDictionary PlainValue { get; } + public abstract IValue PlainValue { get; } public abstract IAccountStateDelta Execute(IActionContext context); @@ -21,6 +22,6 @@ public virtual void Unrender( { } - public abstract void LoadPlainValue(IImmutableDictionary plainValue); + public abstract void LoadPlainValue(IValue plainValue); } } diff --git a/Libplanet.Tests/Common/Action/BattleResult.cs b/Libplanet.Tests/Common/Action/BattleResult.cs index 7558299a12..4e35cf4143 100644 --- a/Libplanet.Tests/Common/Action/BattleResult.cs +++ b/Libplanet.Tests/Common/Action/BattleResult.cs @@ -3,7 +3,9 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; +using System.Numerics; using System.Runtime.Serialization; +using Bencodex.Types; using Libplanet.Serialization; namespace Libplanet.Tests.Common.Action @@ -34,6 +36,21 @@ private BattleResult(SerializationInfo info, StreamingContext context) public IImmutableSet Targets { get; } + public static BattleResult FromBencodex(Bencodex.Types.Dictionary dictionary) + { + return new BattleResult( + dictionary.GetValue("used_weapons").Select(x => x.ToString()), + dictionary.GetValue("targets").Select(x => x.ToString())); + } + + public Bencodex.Types.Dictionary ToBencodex() => + new Bencodex.Types.Dictionary(new Dictionary + { + [(Text)"used_weapons"] = new List( + UsedWeapons.Select(x => (IValue)((Text)x))), + [(Text)"targets"] = new List(Targets.Select(x => (IValue)((Text)x))), + }); + public override bool Equals(object other) { return other != null && other is IEquatable o && diff --git a/Libplanet.Tests/Common/Action/DetectRehearsal.cs b/Libplanet.Tests/Common/Action/DetectRehearsal.cs index cd4f31e41a..8162d68c66 100644 --- a/Libplanet.Tests/Common/Action/DetectRehearsal.cs +++ b/Libplanet.Tests/Common/Action/DetectRehearsal.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; namespace Libplanet.Tests.Common.Action @@ -7,27 +7,35 @@ namespace Libplanet.Tests.Common.Action [ActionType("detect_rehearsal")] public class DetectRehearsal : BaseAction { - public override IImmutableDictionary PlainValue => - new Dictionary() + public override IValue PlainValue => + new Bencodex.Types.Dictionary(new Dictionary { - { "target_address", TargetAddress.ToByteArray() }, - }.ToImmutableDictionary(); + { (Text)"target_address", new Binary(TargetAddress.ToByteArray()) }, + }); public bool ResultState { get; set; } public Address TargetAddress { get; set; } public override void LoadPlainValue( - IImmutableDictionary plainValue) + IValue plainValue) { - TargetAddress = new Address((byte[])plainValue["target_address"]); + LoadPlainValue((Bencodex.Types.Dictionary)plainValue); + } + + public void LoadPlainValue(Bencodex.Types.Dictionary plainValue) + { + TargetAddress = new Address(plainValue.GetValue("target_address").Value); } public override IAccountStateDelta Execute(IActionContext context) { IAccountStateDelta previousStates = context.PreviousStates; ResultState = context.Rehearsal; - return previousStates.SetState(TargetAddress, context.Rehearsal); + return previousStates.SetState( + TargetAddress, + new Bencodex.Types.Boolean(context.Rehearsal) + ); } } } diff --git a/Libplanet.Tests/Common/Action/DumbAction.cs b/Libplanet.Tests/Common/Action/DumbAction.cs index ba06d7806e..3ac3ff7e59 100644 --- a/Libplanet.Tests/Common/Action/DumbAction.cs +++ b/Libplanet.Tests/Common/Action/DumbAction.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; +using Bencodex.Types; using Libplanet.Action; +using Boolean = Bencodex.Types.Boolean; namespace Libplanet.Tests.Common.Action { @@ -48,24 +51,32 @@ public static AsyncLocal> public bool Idempotent { get; private set; } - public IImmutableDictionary PlainValue + public IValue PlainValue { get { - var plainValue = ImmutableDictionary.Empty - .Add("item", Item) - .Add("target_address", TargetAddress.ToByteArray()) - .Add("record_rehearsal", RecordRehearsal); + var plainValue = new Bencodex.Types.Dictionary(new Dictionary + { + [(Text)"item"] = (Text)Item, + [(Text)"target_address"] = new Binary(TargetAddress.ToByteArray()), + [(Text)"record_rehearsal"] = new Bencodex.Types.Boolean(RecordRehearsal), + }); if (RecordRandom) { // In order to avoid changing tx signatures in many test // fixtures, adds field only if RecordRandom = true. - plainValue = plainValue.Add("record_random", true); + plainValue = + (Dictionary)plainValue.Add( + (Text)"record_random", + new Bencodex.Types.Boolean(true)); } if (Idempotent) { - plainValue = plainValue.Add("idempotent", Idempotent); + plainValue = + (Dictionary)plainValue.Add( + (Text)"idempotent", + new Bencodex.Types.Boolean(Idempotent)); } return plainValue; @@ -91,7 +102,7 @@ public IAccountStateDelta Execute(IActionContext context) return states; } - string items = (string)states.GetState(TargetAddress); + string items = (Text?)states.GetState(TargetAddress); string item = RecordRehearsal ? $"{Item}:{context.Rehearsal}" : Item; @@ -110,11 +121,11 @@ public IAccountStateDelta Execute(IActionContext context) { states = states.SetState( RandomRecordsAddress, - context.Random.Next() + (Integer)context.Random.Next() ); } - return states.SetState(TargetAddress, items); + return states.SetState(TargetAddress, (Text)items); } public void Render( @@ -153,21 +164,26 @@ public void Unrender( }); } + public void LoadPlainValue(IValue plainValue) + { + LoadPlainValue((Bencodex.Types.Dictionary)plainValue); + } + public void LoadPlainValue( - IImmutableDictionary plainValue + Dictionary plainValue ) { - Item = (string)plainValue["item"]; - TargetAddress = new Address((byte[])plainValue["target_address"]); - RecordRehearsal = (bool)plainValue["record_rehearsal"]; + Item = plainValue.GetValue("item"); + TargetAddress = new Address(plainValue.GetValue("target_address").Value); + RecordRehearsal = plainValue.GetValue("record_rehearsal").Value; RecordRandom = - plainValue.ContainsKey("record_random") && - plainValue["record_random"] is bool r && - r; + plainValue.ContainsKey((Text)"record_random") && + plainValue[(Text)"record_random"] is Boolean r && + r.Value; - if (plainValue.ContainsKey("idempotent")) + if (plainValue.ContainsKey((Text)"idempotent")) { - Idempotent = (bool)plainValue["idempotent"]; + Idempotent = plainValue.GetValue("idempotent").Value; } } diff --git a/Libplanet.Tests/Common/Action/MinerReward.cs b/Libplanet.Tests/Common/Action/MinerReward.cs index aba1a88965..dbcf8803ad 100644 --- a/Libplanet.Tests/Common/Action/MinerReward.cs +++ b/Libplanet.Tests/Common/Action/MinerReward.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; +using Bencodex.Types; using Libplanet.Action; namespace Libplanet.Tests.Common.Action @@ -24,33 +25,39 @@ public static AsyncLocal> public int Reward { get; private set; } - public IImmutableDictionary PlainValue => - new Dictionary + public IValue PlainValue => + new Bencodex.Types.Dictionary(new Dictionary { - ["reward"] = Reward, - }.ToImmutableDictionary(); + [(Text)"reward"] = (Integer)Reward, + }); + + public void LoadPlainValue(IValue plainValue) + { + LoadPlainValue((Dictionary)plainValue); + } - public void LoadPlainValue(IImmutableDictionary plainValue) + public void LoadPlainValue(Dictionary plainValue) { - Reward = (int)plainValue["reward"]; + Reward = (int)plainValue.GetValue("reward").Value; } public IAccountStateDelta Execute(IActionContext ctx) { IAccountStateDelta states = ctx.PreviousStates; - string rewardRecord = (string)states.GetState(RewardRecordAddress); + string rewardRecord = (Text?)states.GetState(RewardRecordAddress); rewardRecord = rewardRecord is null ? ctx.Miner.ToString() : $"{rewardRecord},{ctx.Miner}"; - states = states.SetState(RewardRecordAddress, rewardRecord); + states = states.SetState(RewardRecordAddress, (Text)rewardRecord); - int previousReward = (int?)states?.GetState(ctx.Miner) ?? 0; + IValue tempQualifier1 = states?.GetState(ctx.Miner); + int previousReward = tempQualifier1 is Integer i ? (int)i.Value : 0; int reward = previousReward + Reward; - return states.SetState(ctx.Miner, reward); + return states.SetState(ctx.Miner, (Integer)reward); } public void Render(IActionContext context, IAccountStateDelta nextStates) diff --git a/Libplanet.Tests/Common/Action/Sleep.cs b/Libplanet.Tests/Common/Action/Sleep.cs index 11fe07b3d3..1bcb8286c3 100644 --- a/Libplanet.Tests/Common/Action/Sleep.cs +++ b/Libplanet.Tests/Common/Action/Sleep.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Numerics; +using Bencodex.Types; using Libplanet.Action; namespace Libplanet.Tests.Common.Action @@ -11,11 +12,11 @@ public class Sleep : BaseAction { public int ZoneId { get; set; } - public override IImmutableDictionary PlainValue => - new Dictionary() + public override IValue PlainValue => + new Bencodex.Types.Dictionary(new Dictionary { - { "zone_id", ZoneId }, - }.ToImmutableDictionary(); + { (Text)"zone_id", (Integer)ZoneId }, + }); public override IAccountStateDelta Execute(IActionContext context) { @@ -23,14 +24,15 @@ public override IAccountStateDelta Execute(IActionContext context) return context.PreviousStates; } - public override void LoadPlainValue( - IImmutableDictionary plainValue) + public override void LoadPlainValue(IValue plainValue) { - object serialized = plainValue["zone_id"]; + LoadPlainValue((Dictionary)plainValue); + } - // FIXME: The reason why the type of the serialized value is not - // consistent should be analyzed. - ZoneId = serialized is BigInteger v ? (int)v : (int)serialized; + public void LoadPlainValue( + Dictionary plainValue) + { + ZoneId = (int)plainValue.GetValue("zone_id").Value; } } } diff --git a/Libplanet.Tests/Common/Action/ThrowException.cs b/Libplanet.Tests/Common/Action/ThrowException.cs index 204cccc73c..54d3038514 100644 --- a/Libplanet.Tests/Common/Action/ThrowException.cs +++ b/Libplanet.Tests/Common/Action/ThrowException.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using Bencodex.Types; using Libplanet.Action; +using Boolean = Bencodex.Types.Boolean; namespace Libplanet.Tests.Common.Action { @@ -15,15 +17,20 @@ public ThrowException() public bool ThrowOnExecution { get; set; } = false; - public IImmutableDictionary PlainValue => - new Dictionary() + public IValue PlainValue => + new Bencodex.Types.Dictionary(new Dictionary { - { "throw", ThrowOnRehearsal }, - }.ToImmutableDictionary(); + [(Text)"throw"] = new Bencodex.Types.Boolean(ThrowOnRehearsal), + }); - public void LoadPlainValue(IImmutableDictionary plainValue) + public void LoadPlainValue(IValue plainValue) { - ThrowOnRehearsal = (bool)plainValue["throw"]; + LoadPlainValue((Dictionary)plainValue); + } + + public void LoadPlainValue(Dictionary plainValue) + { + ThrowOnRehearsal = plainValue.GetValue("throw").Value; } public IAccountStateDelta Execute(IActionContext context) diff --git a/Libplanet.Tests/Net/Messages/RecentStatesTest.cs b/Libplanet.Tests/Net/Messages/RecentStatesTest.cs index ad0a0d16f7..366a9927f8 100644 --- a/Libplanet.Tests/Net/Messages/RecentStatesTest.cs +++ b/Libplanet.Tests/Net/Messages/RecentStatesTest.cs @@ -6,6 +6,7 @@ using System.Net; using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; +using Bencodex.Types; using Libplanet.Crypto; using Libplanet.Net; using Libplanet.Net.Messages; @@ -21,7 +22,7 @@ public void Constructor() { var emptyBlockStates = ImmutableDictionary< HashDigest, - IImmutableDictionary + IImmutableDictionary >.Empty; Assert.Throws(() => new RecentStates( @@ -52,22 +53,28 @@ public void DataFrames() RandomNumberGenerator rng = RandomNumberGenerator.Create(); var randomBytesBuffer = new byte[HashDigest.Size]; - (HashDigest, IImmutableDictionary)[] blockStates = + (HashDigest, IImmutableDictionary)[] blockStates = accounts.SelectMany(address => { rng.GetNonZeroBytes(randomBytesBuffer); var blockHash1 = new HashDigest(randomBytesBuffer); rng.GetNonZeroBytes(randomBytesBuffer); var blockHash2 = new HashDigest(randomBytesBuffer); - IImmutableDictionary emptyState = - ImmutableDictionary.Empty; + IImmutableDictionary emptyState = + ImmutableDictionary.Empty; return new[] { - (blockHash1, emptyState.Add(address, $"A:{blockHash1}:{address}")), - (blockHash2, emptyState.Add(address, $"B:{blockHash2}:{address}")), + ( + blockHash1, + emptyState.Add(address, (Text)$"A:{blockHash1}:{address}") + ), + ( + blockHash2, + emptyState.Add(address, (Text)$"B:{blockHash2}:{address}") + ), }; }).ToArray(); - IImmutableDictionary, IImmutableDictionary> + IImmutableDictionary, IImmutableDictionary> compressedBlockStates = blockStates.Where( (_, i) => i % 2 == 1 ).ToImmutableDictionary(p => p.Item1, p => p.Item2); @@ -122,7 +129,7 @@ public void DataFrames() Assert.Empty(accounts); Assert.Equal(compressedBlockStates.Count, msg[blockStatesOffset].ConvertToInt32()); - var formatter = new BinaryFormatter(); + var codec = new Bencodex.Codec(); for (int i = 0; i < compressedBlockStates.Count; i++) { int offset = blockStatesOffset + 1 + (i * 4); @@ -138,7 +145,7 @@ public void DataFrames() { stream.Write(msg[offset + 3].Buffer, 0, msg[offset + 3].BufferSize); stream.Seek(0, SeekOrigin.Begin); - string state = (string)formatter.Deserialize(stream); + string state = ((Text)codec.Decode(stream)).Value; Assert.Equal($"B:{hash}:{addr}", state); } } diff --git a/Libplanet.Tests/Net/SwarmTest.cs b/Libplanet.Tests/Net/SwarmTest.cs index f304908787..e07cba3248 100644 --- a/Libplanet.Tests/Net/SwarmTest.cs +++ b/Libplanet.Tests/Net/SwarmTest.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; @@ -1401,7 +1402,7 @@ public async Task InitialBlockDownloadStates() await receiverSwarm.PreloadAsync(true); var states = receiverChain.GetState(address); - Assert.Equal("foo,bar,baz", states[address]); + Assert.Equal("foo,bar,baz", (Text)states[address]); Assert.Equal(minerChain.AsEnumerable(), receiverChain.AsEnumerable()); } finally @@ -1680,7 +1681,7 @@ AddressStateMap TryToGetDeepStates() => receiverChain.GetState( { var deepStates = TryToGetDeepStates(); Assert.Single(deepStates); - Assert.Equal($"Item0.{i},Item1.{i}", deepStates[target]); + Assert.Equal($"Item0.{i},Item1.{i}", (Text)deepStates[target]); } i++; @@ -1695,7 +1696,7 @@ AddressStateMap TryToGetDeepStates() => receiverChain.GetState( completeStates: false ); Assert.Single(states); - Assert.Equal("Genesis", states[genesisTarget]); + Assert.Equal("Genesis", (Text)states[genesisTarget]); } } @@ -1707,7 +1708,7 @@ AddressStateMap TryToGetDeepStates() => receiverChain.GetState( Assert.Single(minerState); Assert.Equal( (genesisWithAction ? 1 : 0) + repeat * fixturePairs.Length, - minerState[minerSwarm.Address] + (Integer)minerState[minerSwarm.Address] ); } } @@ -1846,7 +1847,7 @@ await receiverSwarm.PreloadAsync( string.Join(",", Enumerable.Range(0, 5).Select(j => $"Item{i}.{j}")) ) ), - receiverChain.GetState(address)[address] + receiverChain.GetState(address)[address].ToString() ); } } @@ -2045,8 +2046,7 @@ await swarm.BootstrapAsync( private class Sleep : IAction { - public IImmutableDictionary PlainValue => - ImmutableDictionary.Empty; + public IValue PlainValue => default(Null); public IAccountStateDelta Execute(IActionContext context) { @@ -2054,7 +2054,7 @@ public IAccountStateDelta Execute(IActionContext context) return context.PreviousStates; } - public void LoadPlainValue(IImmutableDictionary plainValue) + public void LoadPlainValue(IValue plainValue) { } diff --git a/Libplanet.Tests/Store/StoreTest.cs b/Libplanet.Tests/Store/StoreTest.cs index 5bcffee222..03e20acf98 100644 --- a/Libplanet.Tests/Store/StoreTest.cs +++ b/Libplanet.Tests/Store/StoreTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Blockchain; using Libplanet.Blocks; @@ -624,16 +625,16 @@ public void BlockState() { Assert.Null(Fx.Store.GetBlockStates(Fx.Hash1)); AddressStateMap states = new AddressStateMap( - new Dictionary() + new Dictionary() { - [Fx.Address1] = new Dictionary() + [Fx.Address1] = new Bencodex.Types.Dictionary(new Dictionary { - { "a", 1 }, - }, - [Fx.Address2] = new Dictionary() + { (Text)"a", (Integer)1 }, + }), + [Fx.Address2] = new Bencodex.Types.Dictionary(new Dictionary { - { "b", 2 }, - }, + { (Text)"b", (Integer)2 }, + }), }.ToImmutableDictionary() ); Fx.Store.SetBlockStates(Fx.Hash1, states); @@ -776,17 +777,22 @@ private class AtomicityTestAction : IAction public ImmutableArray Md5Digest { get; set; } - public IImmutableDictionary PlainValue => - new Dictionary + public IValue PlainValue => + new Bencodex.Types.Dictionary(new Dictionary { - { "bytes", ArbitraryBytes.ToBuilder().ToArray() }, - { "md5", Md5Digest.ToBuilder().ToArray() }, - }.ToImmutableDictionary(); + { (Text)"bytes", new Binary(ArbitraryBytes.ToBuilder().ToArray()) }, + { (Text)"md5", new Binary(Md5Digest.ToBuilder().ToArray()) }, + }); + + public void LoadPlainValue(IValue plainValue) + { + LoadPlainValue((Dictionary)plainValue); + } - public void LoadPlainValue(IImmutableDictionary plainValue) + public void LoadPlainValue(Dictionary plainValue) { - ArbitraryBytes = (plainValue["bytes"] as byte[]).ToImmutableArray(); - Md5Digest = (plainValue["md5"] as byte[]).ToImmutableArray(); + ArbitraryBytes = plainValue.GetValue("bytes").ToImmutableArray(); + Md5Digest = plainValue.GetValue("md5").ToImmutableArray(); } public IAccountStateDelta Execute(IActionContext context) diff --git a/Libplanet.Tests/Tx/TransactionTest.cs b/Libplanet.Tests/Tx/TransactionTest.cs index ad507b3f71..e0a63fcd77 100644 --- a/Libplanet.Tests/Tx/TransactionTest.cs +++ b/Libplanet.Tests/Tx/TransactionTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security.Cryptography; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Crypto; using Libplanet.Tests.Common.Action; @@ -582,25 +583,29 @@ public void FromBencodexWithActions() Assert.Equal(2, tx.Actions.Count()); Assert.IsType(tx.Actions[0].InnerAction); + + var targetAddress = + ((Bencodex.Types.Dictionary)tx.Actions[0].InnerAction.PlainValue) + .GetValue("target_address").Value; AssertBytesEqual( new Address(publicKey).ToByteArray(), - (byte[])tx.Actions[0].InnerAction.PlainValue["target_address"] + targetAddress ); Assert.Equal( - new Dictionary() + new Bencodex.Types.Dictionary(new Dictionary { - { "weapon", "wand" }, - { "target", "orc" }, - { "target_address", new Address(publicKey).ToByteArray() }, - }, + { (Text)"weapon", (Text)"wand" }, + { (Text)"target", (Text)"orc" }, + { (Text)"target_address", (Binary)new Address(publicKey).ToByteArray() }, + }), tx.Actions[0].InnerAction.PlainValue ); Assert.IsType(tx.Actions[1].InnerAction); Assert.Equal( - new Dictionary() + new Bencodex.Types.Dictionary(new Dictionary { - { "zone_id", 10 }, - }, + { (Text)"zone_id", (Integer)10 }, + }), tx.Actions[1].InnerAction.PlainValue ); } @@ -657,7 +662,7 @@ public void EvaluateActions() Assert.Equal(1, eval.InputContext.BlockIndex); Assert.Equal(rehearsal, eval.InputContext.Rehearsal); Assert.Equal( - eval.OutputStates.GetState( + (Integer)eval.OutputStates.GetState( DumbAction.RandomRecordsAddress ), eval.InputContext.Random.Next() @@ -665,7 +670,7 @@ public void EvaluateActions() Assert.Equal( i > 0 ? addresses.Select( evaluations[i - 1].OutputStates.GetState - ) : new object[] { null, null, null }, + ) : new IValue[] { null, null, null }, addresses.Select( eval.InputContext.PreviousStates.GetState ) @@ -673,6 +678,7 @@ public void EvaluateActions() Assert.Equal( expectedStates[i], addresses.Select(eval.OutputStates.GetState) + .Select(x => x is Text t ? t.Value : null) ); } @@ -944,7 +950,7 @@ internal RawTransaction GetExpectedRawTransaction(bool includeSingature) 0xae, 0xd3, 0xaa, 0xa2, 0x96, }.ToImmutableArray(), timestamp: "2018-11-21T00:00:00.000000Z", - actions: new List>() + actions: default(List) ); if (!includeSingature) { diff --git a/Libplanet.sln.DotSettings b/Libplanet.sln.DotSettings index f6fcb5c5e4..224dcf53b9 100644 --- a/Libplanet.sln.DotSettings +++ b/Libplanet.sln.DotSettings @@ -5,6 +5,7 @@ xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + True True True True diff --git a/Libplanet/Action/AccountStateDeltaImpl.cs b/Libplanet/Action/AccountStateDeltaImpl.cs index 66c0df3356..5bf49ec686 100644 --- a/Libplanet/Action/AccountStateDeltaImpl.cs +++ b/Libplanet/Action/AccountStateDeltaImpl.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; namespace Libplanet.Action { @@ -9,7 +11,7 @@ namespace Libplanet.Action internal class AccountStateDeltaImpl : IAccountStateDelta { private readonly AccountStateGetter _accountStateGetter; - private IImmutableDictionary _updatedStates; + private Dictionary _updatedStates; /// /// Creates a null delta from the given @@ -20,19 +22,19 @@ internal class AccountStateDeltaImpl : IAccountStateDelta internal AccountStateDeltaImpl(AccountStateGetter accountStateGetter) { _accountStateGetter = accountStateGetter; - _updatedStates = ImmutableDictionary.Empty; + _updatedStates = default(Dictionary); } /// IImmutableSet
IAccountStateDelta.UpdatedAddresses => - _updatedStates.Keys.ToImmutableHashSet(); + _updatedStates.Keys.Select(key => new Address(key.ToString())).ToImmutableHashSet(); /// - object IAccountStateDelta.GetState(Address address) + IValue IAccountStateDelta.GetState(Address address) { try { - return _updatedStates[address]; + return _updatedStates[(Text)address.ToHex()]; } catch (KeyNotFoundException) { @@ -43,12 +45,13 @@ object IAccountStateDelta.GetState(Address address) /// IAccountStateDelta IAccountStateDelta.SetState( Address address, - object state + IValue state ) { return new AccountStateDeltaImpl(_accountStateGetter) { - _updatedStates = _updatedStates.SetItem(address, state), + _updatedStates = (Dictionary)_updatedStates.SetItem( + (Text)address.ToHex(), state), }; } } diff --git a/Libplanet/Action/AccountStateGetter.cs b/Libplanet/Action/AccountStateGetter.cs index 65d70e2aae..ea1af9c27b 100644 --- a/Libplanet/Action/AccountStateGetter.cs +++ b/Libplanet/Action/AccountStateGetter.cs @@ -1,3 +1,5 @@ +using Bencodex.Types; + namespace Libplanet.Action { /// @@ -12,5 +14,5 @@ namespace Libplanet.Action /// its state. /// The account state if exists. Otherwise null. /// - public delegate object AccountStateGetter(Address address); + public delegate IValue AccountStateGetter(Address address); } diff --git a/Libplanet/Action/AddressStateMap.cs b/Libplanet/Action/AddressStateMap.cs index 3b5e963d37..c55850775f 100644 --- a/Libplanet/Action/AddressStateMap.cs +++ b/Libplanet/Action/AddressStateMap.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime.Serialization; +using Bencodex.Types; namespace Libplanet.Action { @@ -16,15 +17,15 @@ namespace Libplanet.Action /// [Serializable] public class AddressStateMap - : IImmutableDictionary, ISerializable + : IImmutableDictionary, ISerializable { - private IImmutableDictionary _impl; + private IImmutableDictionary _impl; /// /// Creates an empty map. /// public AddressStateMap() - : this(new Dictionary().ToImmutableDictionary()) + : this(new Dictionary().ToImmutableDictionary()) { } @@ -34,7 +35,7 @@ public AddressStateMap() ///
/// A dictionary of items to /// fill the new map with. - public AddressStateMap(IImmutableDictionary dictionary) + public AddressStateMap(IImmutableDictionary dictionary) { _impl = dictionary; } @@ -44,11 +45,11 @@ protected AddressStateMap( StreamingContext context ) { - var dict = new Dictionary(); + var dict = new Dictionary(); foreach (SerializationEntry entry in info) { - dict[new Address(entry.Name)] = entry.Value; + dict[new Address(entry.Name)] = (IValue)entry.Value; } _impl = dict.ToImmutableDictionary(); @@ -58,39 +59,39 @@ StreamingContext context public IEnumerable
Keys => _impl.Keys; /// - public IEnumerable Values => _impl.Values; + public IEnumerable Values => _impl.Values; /// public int Count => _impl.Count; /// - public object this[Address key] => _impl[key]; + public IValue this[Address key] => _impl[key]; /// - public IImmutableDictionary Add( + public IImmutableDictionary Add( Address key, - object value + IValue value ) { return new AddressStateMap(_impl.Add(key, value)); } /// - public IImmutableDictionary AddRange( - IEnumerable> pairs + public IImmutableDictionary AddRange( + IEnumerable> pairs ) { return new AddressStateMap(_impl.AddRange(pairs)); } /// - public IImmutableDictionary Clear() + public IImmutableDictionary Clear() { return new AddressStateMap(_impl.Clear()); } /// - public bool Contains(KeyValuePair pair) + public bool Contains(KeyValuePair pair) { return _impl.Contains(pair); } @@ -102,19 +103,19 @@ public bool ContainsKey(Address key) } /// - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { return _impl.GetEnumerator(); } /// - public IImmutableDictionary Remove(Address key) + public IImmutableDictionary Remove(Address key) { return new AddressStateMap(_impl.Remove(key)); } /// - public IImmutableDictionary RemoveRange( + public IImmutableDictionary RemoveRange( IEnumerable
keys ) { @@ -122,17 +123,17 @@ IEnumerable
keys } /// - public IImmutableDictionary SetItem( + public IImmutableDictionary SetItem( Address key, - object value + IValue value ) { return new AddressStateMap(_impl.SetItem(key, value)); } /// - public IImmutableDictionary SetItems( - IEnumerable> items + public IImmutableDictionary SetItems( + IEnumerable> items ) { return new AddressStateMap(_impl.SetItems(items)); @@ -145,7 +146,7 @@ public bool TryGetKey(Address equalKey, out Address actualKey) } /// - public bool TryGetValue(Address key, out object value) + public bool TryGetValue(Address key, out IValue value) { return _impl.TryGetValue(key, out value); } diff --git a/Libplanet/Action/IAccountStateDelta.cs b/Libplanet/Action/IAccountStateDelta.cs index 56b7dc744f..6362c47646 100644 --- a/Libplanet/Action/IAccountStateDelta.cs +++ b/Libplanet/Action/IAccountStateDelta.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Diagnostics.Contracts; using System.Linq; +using Bencodex.Types; namespace Libplanet.Action { @@ -29,7 +30,7 @@ namespace Libplanet.Action /// /// /// - /// This interface is immutable. + /// This interface is immutable. /// method does not manipulate the instance, but returns a new /// instance with updated states. /// @@ -51,7 +52,7 @@ public interface IAccountStateDelta /// If it has never been set to any state it returns null /// instead. [Pure] - object GetState(Address address); + IValue GetState(Address address); /// /// Gets a new instance that the account state of the given @@ -70,7 +71,7 @@ public interface IAccountStateDelta /// with updated states instead. /// [Pure] - IAccountStateDelta SetState(Address address, object state); + IAccountStateDelta SetState(Address address, IValue state); } internal static class AccountStateDeltaExtensions @@ -82,12 +83,12 @@ internal static class AccountStateDeltaExtensions // the IAccountStateDelta interface exposes, it is quite inefficient // when an implementing class maintains their own dirty dictionary. // (See also AccountStateDeltaImpl.UpdatedStates field.) - public static IImmutableDictionary GetUpdatedStates( + public static IImmutableDictionary GetUpdatedStates( this IAccountStateDelta delta ) { return delta.UpdatedAddresses.Select(address => - new KeyValuePair( + new KeyValuePair( address, delta.GetState(address) ) diff --git a/Libplanet/Action/IAction.cs b/Libplanet/Action/IAction.cs index c982cd0f9f..f6179aaf1a 100644 --- a/Libplanet/Action/IAction.cs +++ b/Libplanet/Action/IAction.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using Bencodex.Types; namespace Libplanet.Action { @@ -41,30 +41,27 @@ namespace Libplanet.Action /// of in-game logic: /// is just for example, /// // As far as it is serializable, you can store any types. /// // (We recommend to use immutable types though.) - /// var state = (ImmutableDictionary) + /// var state = /// context.PreviousStates.GetState(TargetAddress); - /// + /// Dictionary dictionary; /// // This variable purposes to store the state /// // right after this action finishes. - /// ImmutableDictionary nextState; - /// + /// IImmutableDictionary nextState; /// // Does different things depending on the action's type. /// // This way is against the common principals of programming /// // as it is just an example. You could compare this with @@ -97,32 +93,38 @@ namespace Libplanet.Action /// else if (!(state is null)) /// throw new Exception( /// "Character was already created."); - /// - /// nextState = ImmutableDictionary.Empty - /// .Add("hp", 20); + /// nextState = ImmutableDictionary.Empty + /// .Add((Text)"hp", (Integer)20); /// break; - /// /// case ActType.Attack: + /// dictionary = (Bencodex.Types.Dictionary)state; /// nextState = - /// state.SetItem("hp", Math.Max(state["hp"] - 5, 0)); + /// dictionary.SetItem( + /// (Text)"hp", + /// (Integer)Math.Max( + /// (int)dictionary.GetValue("hp").Value - 5, + /// 0) + /// ); /// break; - /// /// case ActType.Heal: + /// dictionary = (Bencodex.Types.Dictionary)state; /// nextState = - /// state.SetItem("hp", Math.Min(state["hp"] + 5, 20)); + /// dictionary.SetItem( + /// (Text)"hp", + /// (Integer)Math.Min( + /// (int)dictionary.GetValue("hp").Value + 5, + /// 20) + /// ); /// break; - /// /// default: /// throw new Exception( /// "Properties are not properly initialized."); /// } - /// /// // Builds a delta (dirty) from previous to next states, and /// // returns it. /// return context.PreviousStates.SetState(TargetAddress, - /// nextState); + /// (Dictionary)nextState); /// } - /// /// // Side effects, i.e., any effects on other than states, are /// // done here. /// void IAction.Render( @@ -130,7 +132,6 @@ namespace Libplanet.Action /// IAccountStateDelta nextStates) /// { /// Character c; - /// /// // You could compare this with a better example of /// // PolymorphicAction class. /// switch (Type) @@ -142,20 +143,18 @@ namespace Libplanet.Action /// Hp = 0, /// }; /// break; - /// /// case ActType.Attack: /// case ActType.Heal: /// c = Character.GetByAddress(TargetAddress); /// break; - /// /// default: - /// break; + /// return; /// } - /// - /// c?.Hp = nextStates.GetState(TargetAddress)["hp"]; - /// c?.Draw(); + /// c.Hp = + /// (int)((Bencodex.Types.Dictionary)nextStates.GetState(TargetAddress)) + /// .GetValue("hp").Value; + /// c.Draw(); /// } - /// /// // Sometimes a block to which an action belongs can be /// // a "stale." If that action already has been rendered, /// // it should be undone. @@ -164,7 +163,6 @@ namespace Libplanet.Action /// IAccountStateDelta nextStates) /// { /// Character c = Character.GetByAddress(TargetAddress); - /// /// // You could compare this with a better example of /// // PolymorphicAction class. /// switch (Type) @@ -172,34 +170,34 @@ namespace Libplanet.Action /// case ActType.CreateCharacter: /// c.Hide(); /// break; - /// /// case ActType.Attack: /// case ActType.Heal: /// IAccountStateDelta prevStates = context.PreviousStates; - /// c.Hp = prevStates.GetState(TargetAddress)["hp"]; + /// c.Hp = (int)((Bencodex.Types.Dictionary)prevStates.GetState(TargetAddress)) + /// .GetValue("hp").Value; /// c.Draw(); /// break; - /// /// default: /// break; /// } /// } - /// /// // Serializes its "bound arguments" so that they are transmitted /// // over network or stored to the persistent storage. - /// // It uses .NET's built-in serialization mechanism. - /// IImmutableDictionary IAction.PlainValue => - /// ImmutableDictionary.Empty - /// .Add("type", Type) - /// .Add("target_address", TargetAddress); - /// + /// IValue IAction.PlainValue => + /// new Bencodex.Types.Dictionary(new Dictionary + /// { + /// [(Text)"type"] = (Integer)((int)Type), + /// [(Text)"target_address"] = (Binary)TargetAddress.ToByteArray(), + /// }); /// // Deserializes "bound arguments". That is, it is inverse /// // of PlainValue property. /// void IAction.LoadPlainValue( - /// IImmutableDictionary plainValue) + /// IValue plainValue) /// { - /// Type = (ActType)plainValue["type"]; - /// TargetAddress = (Address)plainValue["target_address"]; + /// var dictionary = (Bencodex.Types.Dictionary)plainValue; + /// Type = (ActType)(int)dictionary.GetValue("type").Value; + /// TargetAddress = + /// new Address(dictionary.GetValue("target_address").Value); /// } /// } /// ]]> @@ -213,20 +211,14 @@ public interface IAction /// Serializes values bound to an action, which is held by properties /// (or fields) of an action, so that they can be transmitted over /// network or saved to persistent storage. - /// Serialized values are deserialized by method - /// later. - /// It uses .NET's built-in serialization mechanism. + /// Serialized values are deserialized by + /// method later. /// - /// A value which encodes this action's bound values (held - /// by properties or fields). It has to be serializable. - /// - IImmutableDictionary PlainValue { get; } + /// A Bencodex value which encodes this action's bound values (held + /// by properties or fields). + /// + /// + IValue PlainValue { get; } /// /// Deserializes serialized data (i.e., data @@ -237,7 +229,7 @@ public interface IAction /// property) to be deserialized and assigned to this action's /// properties (or fields). /// - void LoadPlainValue(IImmutableDictionary plainValue); + void LoadPlainValue(IValue plainValue); /// /// Executes the main game logic of an action. This should be diff --git a/Libplanet/Action/PolymorphicAction.cs b/Libplanet/Action/PolymorphicAction.cs index 4c6427a06c..dc7737553b 100644 --- a/Libplanet/Action/PolymorphicAction.cs +++ b/Libplanet/Action/PolymorphicAction.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Reflection; +using Bencodex.Types; namespace Libplanet.Action { @@ -24,35 +24,30 @@ namespace Libplanet.Action /// subtype polymorphism): /// IAction.PlainValue => - /// ImmutableDictionary.Empty - /// .Add("target_address", TargetAddress); - /// + /// IValue IAction.PlainValue => + /// new Bencodex.Types.Dictionary(new Dictionary + /// { + /// [(Text)"target_address"] = (Binary)TargetAddress.ToByteArray(), + /// }); /// void IAction.LoadPlainValue( - /// IImmutableDictionary plainValue) + /// IValue plainValue) /// { - /// TargetAddress = (Address)plainValue["target_address"]; + /// var dictionary = (Bencodex.Types.Dictionary)plainValue; + /// TargetAddress = + /// new Address(dictionary.GetValue("target_address").Value); /// } /// } - /// /// // PolymorphicAction requires concrete action classes marked with /// // ActionTypeAttribute. /// // There is only one required parameter to ActionTypeAttribute, @@ -82,7 +78,7 @@ namespace Libplanet.Action /// { /// public override IAccountStateDelta Execute(IActionContext context) /// { - /// var state = (ImmutableDictionary) + /// var state = /// context.PreviousStates.GetState(TargetAddress); /// if (!TargetAddress.Equals(context.Signer)) /// throw new Exception( @@ -93,10 +89,12 @@ namespace Libplanet.Action /// throw new Exception("Character was already created."); /// return context.PreviousStates.SetState( /// TargetAddress, - /// ImmutableDictionary.Empty.Add("hp", 20) + /// new Bencodex.Types.Dictionary(new Dictionary + /// { + /// [(Text)"hp"] = (Integer)20, + /// }) /// ); /// } - /// /// void IAction.Render( /// IActionContext context, /// IAccountStateDelta nextStates) @@ -104,12 +102,11 @@ namespace Libplanet.Action /// var c = new Character /// { /// Address = TargetAddress, - /// Hp = nextStates.GetState(TargetAddress)["hp"], + /// Hp = (int)((Integer)nextStates.GetState(TargetAddress)).Value, /// }; /// c.Draw(); /// break; /// } - /// /// void IAction.Unrender( /// IActionContext context, /// IAccountStateDelta nextStates) @@ -118,67 +115,74 @@ namespace Libplanet.Action /// c.Hide(); /// } /// } - /// /// [ActionType("attack")] /// public sealed class Attack : ActionBase /// { /// public override IAccountStateDelta Execute(IActionContext context) /// { - /// var state = (ImmutableDictionary) - /// context.PreviousStates.GetState(TargetAddress); + /// var state = + /// (Bencodex.Types.Dictionary)context.PreviousStates.GetState(TargetAddress); /// return context.PreviousStates.SetState( /// TargetAddress, - /// state.SetItem("hp", Math.Max(state["hp"] - 5, 0)) + /// (Bencodex.Types.Dictionary)state + /// .SetItem( + /// (Text)"hp", + /// (Integer)Math.Max((int)state.GetValue("hp").Value - 5, 0)) /// ); /// } - /// /// void IAction.Render( /// IActionContext context, /// IAccountStateDelta nextStates) /// { /// Character c = Character.GetByAddress(TargetAddress); - /// c.Hp = nextStates.GetState(TargetAddress)["hp"]; + /// c.Hp = (int)((Bencodex.Types.Dictionary)nextStates.GetState(TargetAddress)) + /// .GetValue("hp").Value; /// c.Draw(); /// } - /// /// void IAction.Unrender( /// IActionContext context, /// IAccountStateDelta nextStates) /// { /// Character c = Character.GetByAddress(TargetAddress); - /// c.Hp = context.PreviousStates.GetState(TargetAddress)["hp"]; + /// var target = + /// (Bencodex.Types.Dictionary)context.PreviousStates.GetState(TargetAddress); + /// c.Hp = (int)target.GetValue("hp").Value; /// c.Draw(); /// } /// } - /// /// [ActionType("heal")] /// public sealed class Heal : ActionBase /// { /// public override IAccountStateDelta Execute(IActionContext context) /// { - /// var state = (ImmutableDictionary) - /// context.PreviousStates.GetState(TargetAddress); + /// var state = + /// (Bencodex.Types.Dictionary)context.PreviousStates.GetState(TargetAddress); /// return context.PreviousStates.SetState( /// TargetAddress, - /// state.SetItem("hp", Math.Min(state["hp"] + 5, 20)) + /// (Bencodex.Types.Dictionary)state + /// .SetItem( + /// (Text)"hp", + /// (Integer)Math.Min((int)state.GetValue("hp").Value + 5, 20)) /// ); /// } - /// /// void IAction.Render( /// IActionContext context, /// IAccountStateDelta nextStates) /// { /// Character c = Character.GetByAddress(TargetAddress); - /// c.Hp = nextStates.GetState(TargetAddress)["hp"]; + /// var target = + /// (Bencodex.Types.Dictionary)context.PreviousStates.GetState(TargetAddress); + /// c.Hp = (int)target.GetValue("hp").Value; /// c.Draw(); /// } - /// /// void IAction.Unrender( /// IActionContext context, /// IAccountStateDelta nextStates) /// { /// Character c = Character.GetByAddress(TargetAddress); - /// c.Hp = context.PreviousStates.GetState(TargetAddress)["hp"]; + /// var target = + /// (Bencodex.Types.Dictionary)context.PreviousStates.GetState(TargetAddress); + /// c.Hp = (int)target.GetValue("hp").Value; /// c.Draw(); /// } /// } @@ -246,19 +250,18 @@ private set } } - /// - public IImmutableDictionary PlainValue => - new Dictionary + public IValue PlainValue => + new Dictionary(new Dictionary { { - "type_id", - ActionTypeAttribute.ValueOf(InnerAction.GetType()) + (Text)"type_id", + (Text)ActionTypeAttribute.ValueOf(InnerAction.GetType()) }, { - "values", + (Text)"values", InnerAction.PlainValue }, - }.ToImmutableDictionary(); + }); /// /// For convenience, an inner action can be @@ -276,18 +279,23 @@ public static implicit operator PolymorphicAction(T innerAction) return new PolymorphicAction(innerAction); } - /// public void LoadPlainValue( - IImmutableDictionary plainValue + Dictionary plainValue ) { - var typeStr = (string)plainValue["type_id"]; - var innerAction = (T)Activator.CreateInstance(Types[typeStr]); - var values = (IDictionary)plainValue["values"]; - innerAction.LoadPlainValue(values.ToImmutableDictionary()); + var typeStr = plainValue[(Text)"type_id"]; + var innerAction = (T)Activator.CreateInstance(Types[typeStr.ToString()]); + var values = (Dictionary)plainValue[(Text)"values"]; + innerAction.LoadPlainValue(values); InnerAction = innerAction; } + /// + public void LoadPlainValue(IValue plainValue) + { + LoadPlainValue((Dictionary)plainValue); + } + /// public IAccountStateDelta Execute(IActionContext context) => InnerAction.Execute(context); diff --git a/Libplanet/BencodexExtension.cs b/Libplanet/BencodexExtension.cs new file mode 100644 index 0000000000..1d96170b25 --- /dev/null +++ b/Libplanet/BencodexExtension.cs @@ -0,0 +1,13 @@ +using Bencodex.Types; + +namespace Libplanet +{ + public static class BencodexExtension + { + public static T GetValue(this Dictionary dictionary, string name) + where T : IValue + { + return (T)dictionary[(Text)name]; + } + } +} diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 17e1aac647..89d5873c67 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -8,6 +8,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Blockchain.Policies; using Libplanet.Blocks; @@ -1073,9 +1074,9 @@ bool buildStateReferences ImmutableHashSet
.Empty, (a, b) => a.Union(b) ); - ImmutableDictionary totalDelta = + ImmutableDictionary totalDelta = updatedAddresses.Select( - a => new KeyValuePair( + a => new KeyValuePair( a, lastStates?.GetState(a) ) diff --git a/Libplanet/Net/Messages/RecentStates.cs b/Libplanet/Net/Messages/RecentStates.cs index 8adfeae496..4b101680ed 100644 --- a/Libplanet/Net/Messages/RecentStates.cs +++ b/Libplanet/Net/Messages/RecentStates.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; -using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; +using Bencodex; +using Bencodex.Types; using NetMQ; namespace Libplanet.Net.Messages @@ -14,7 +15,7 @@ public RecentStates( HashDigest blockHash, IImmutableDictionary< HashDigest, - IImmutableDictionary + IImmutableDictionary > blockStates, IImmutableDictionary>> stateReferences ) @@ -83,9 +84,9 @@ public RecentStates(NetMQFrame[] frames) int blocksLength = it.Current.ConvertToInt32(); // This is not height! var blockStates = - new Dictionary, IImmutableDictionary>( + new Dictionary, IImmutableDictionary>( blocksLength); - var formatter = new BinaryFormatter(); + var codec = new Codec(); for (int j = 0; j < blocksLength; j++) { @@ -95,7 +96,7 @@ public RecentStates(NetMQFrame[] frames) it.MoveNext(); int statesLength = it.Current.ConvertToInt32(); - var states = new Dictionary(statesLength); + var states = new Dictionary(statesLength); for (int k = 0; k < statesLength; k++) { it.MoveNext(); @@ -104,7 +105,7 @@ public RecentStates(NetMQFrame[] frames) it.MoveNext(); using (var stream = new MemoryStream(it.Current.Buffer)) { - states[address] = formatter.Deserialize(stream); + states[address] = codec.Decode(stream); } } @@ -121,7 +122,7 @@ public RecentStates(NetMQFrame[] frames) public IImmutableDictionary< HashDigest, - IImmutableDictionary + IImmutableDictionary > BlockStates { get; } /// @@ -175,7 +176,7 @@ Note that the data frames this property returns omit the very first three frames | | | 5.3.1. Key (20 bytes; account address) | | | An account address having the following updated state (7.3.2). | | + - | | | 5.3.2. Value (varying bytes; .NET binary serialization format) + | | | 5.3.2. Value (varying bytes; Bencodex format) | | | An updated state of the account (7.3.1). */ get @@ -207,23 +208,18 @@ Note that the data frames this property returns omit the very first three frames } yield return new NetMQFrame(NetworkOrderBitsConverter.GetBytes(BlockStates.Count)); - var formatter = new BinaryFormatter(); + var codec = new Codec(); foreach (var blockState in BlockStates) { yield return new NetMQFrame(blockState.Key.ToByteArray()); - IImmutableDictionary states = blockState.Value; + IImmutableDictionary states = blockState.Value; yield return new NetMQFrame(NetworkOrderBitsConverter.GetBytes(states.Count)); foreach (var addressState in states) { yield return new NetMQFrame(addressState.Key.ToByteArray()); - - using (var stream = new MemoryStream()) - { - formatter.Serialize(stream, addressState.Value); - yield return new NetMQFrame(stream.GetBuffer()); - } + yield return new NetMQFrame(codec.Encode(addressState.Value)); } } } diff --git a/Libplanet/Net/Protocols/KademliaProtocol.cs b/Libplanet/Net/Protocols/KademliaProtocol.cs index d8a92d62d1..fba488e9f3 100644 --- a/Libplanet/Net/Protocols/KademliaProtocol.cs +++ b/Libplanet/Net/Protocols/KademliaProtocol.cs @@ -7,6 +7,7 @@ using Libplanet.Action; using Libplanet.Net.Messages; using Serilog; +using Random = System.Random; namespace Libplanet.Net.Protocols { @@ -26,7 +27,7 @@ internal class KademliaProtocol : IProtocol private readonly Swarm _swarm; private readonly Address _address; private readonly int _appProtocolVersion; - private readonly System.Random _random; + private readonly Random _random; private readonly RoutingTable _routing; private readonly ILogger _logger; @@ -42,7 +43,7 @@ public KademliaProtocol( _logger = logger; _address = address; - _random = new System.Random(); + _random = new Random(); _routing = new RoutingTable( _address, TableSize, BucketSize, _random); } diff --git a/Libplanet/Net/Swarm.cs b/Libplanet/Net/Swarm.cs index bfe2c0a602..0142312b18 100644 --- a/Libplanet/Net/Swarm.cs +++ b/Libplanet/Net/Swarm.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using AsyncIO; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Blockchain; using Libplanet.Blocks; @@ -1817,7 +1818,7 @@ private void TransferRecentStates(GetRecentStates getRecentStates) HashDigest? @base = _blockChain.FindBranchPoint(baseLocator); HashDigest target = getRecentStates.TargetBlockHash; IImmutableDictionary, - IImmutableDictionary + IImmutableDictionary > blockStates = null; IImmutableDictionary>> stateRefs = null; @@ -1846,7 +1847,7 @@ private void TransferRecentStates(GetRecentStates getRecentStates) .Select(bh => new KeyValuePair< HashDigest, - IImmutableDictionary + IImmutableDictionary >(bh, store.GetBlockStates(bh)) ) .ToImmutableDictionary(); diff --git a/Libplanet/Serialization/BencodexFormatter.cs b/Libplanet/Serialization/BencodexFormatter.cs index 4e7947dbf2..19a69a5b9c 100644 --- a/Libplanet/Serialization/BencodexFormatter.cs +++ b/Libplanet/Serialization/BencodexFormatter.cs @@ -9,6 +9,9 @@ using System.Text; using Bencodex; using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Tx; +using Boolean = Bencodex.Types.Boolean; namespace Libplanet.Serialization { @@ -54,7 +57,22 @@ public object Deserialize(Stream serializationStream) foreach (var kv in bo) { var key = FromBencodexKey(kv.Key); - serializationInfo.AddValue(key, FromBencodexValue(kv.Value)); + object v; + + // FIXME: This is needed to prevent that some properties become .NET object. + // see issue #541, #552. + if ((typeof(T).Namespace == typeof(Transaction<>).Namespace && + typeof(T).Name == typeof(Transaction<>).Name && key == "actions") || + typeof(T) == typeof(AddressStateMap)) + { + v = kv.Value; + } + else + { + v = FromBencodexValue(kv.Value); + } + + serializationInfo.AddValue(key, v); } if (constructorInfo == null) @@ -126,7 +144,7 @@ private static object FromBencodexValue(IValue o) { case Null _: return null; - case Bencodex.Types.Boolean b: + case Boolean b: return b.Value; case Binary bin: return bin.Value; @@ -137,10 +155,15 @@ private static object FromBencodexValue(IValue o) case List l: return l.Select(FromBencodexValue).ToList(); case Dictionary d: - return d.ToDictionary( - e => FromBencodexKey(e.Key), - e => FromBencodexValue(e.Value) - ); + var dictionary = new Dictionary(); + foreach (var pair in d) + { + var key = FromBencodexKey(pair.Key); + var value = key != "actions" ? FromBencodexValue(pair.Value) : pair.Value; + dictionary.Add(key, value); + } + + return dictionary; default: throw new SerializationException( $"Can't convert {o} to plain object." @@ -177,7 +200,7 @@ private IValue ToBencodexValue(object o) case null: return default(Null); case bool b: - return new Bencodex.Types.Boolean(b); + return new Boolean(b); case string s: return ToBencodexKey(s); case long l: @@ -192,6 +215,8 @@ private IValue ToBencodexValue(object o) return new Integer(bi); case byte[] byteArray: return ToBencodexKey(byteArray); + case IValue value: + return value; case IDictionary d: return new Dictionary( d.Cast>().Select( @@ -210,7 +235,7 @@ private IValue ToBencodexValue(object o) } throw new SerializationException( - $"Can't convert {o} to IBObject." + $"Can't convert {o.GetType()} to IBObject." ); } diff --git a/Libplanet/Store/LiteDBStore.cs b/Libplanet/Store/LiteDBStore.cs index 1f3d8421c3..b5beaa5ea7 100644 --- a/Libplanet/Store/LiteDBStore.cs +++ b/Libplanet/Store/LiteDBStore.cs @@ -5,9 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; -using System.Text; using System.Threading; using Libplanet.Action; using Libplanet.Blocks; @@ -15,6 +13,7 @@ using Libplanet.Tx; using LiteDB; using Serilog; +using FileMode = LiteDB.FileMode; namespace Libplanet.Store { @@ -72,13 +71,13 @@ public LiteDBStore( if (readOnly) { - connectionString.Mode = LiteDB.FileMode.ReadOnly; + connectionString.Mode = FileMode.ReadOnly; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && Type.GetType("Mono.Runtime") is null) { // macOS + .NETCore doesn't support shared lock. - connectionString.Mode = LiteDB.FileMode.Exclusive; + connectionString.Mode = FileMode.Exclusive; } if (path is null) @@ -400,7 +399,7 @@ public override AddressStateMap GetBlockStates(HashDigest blockHash) { DownloadFile(file, stream); stream.Seek(0, SeekOrigin.Begin); - var formatter = new BinaryFormatter(); + var formatter = new BencodexFormatter(); return (AddressStateMap)formatter.Deserialize(stream); } } @@ -412,7 +411,7 @@ public override void SetBlockStates( { using (var stream = new MemoryStream()) { - var formatter = new BinaryFormatter(); + var formatter = new BencodexFormatter(); formatter.Serialize(stream, states); stream.Seek(0, SeekOrigin.Begin); _db.FileStorage.Upload( diff --git a/Libplanet/Tx/RawTransaction.cs b/Libplanet/Tx/RawTransaction.cs index a35e41e0cb..568264e56d 100644 --- a/Libplanet/Tx/RawTransaction.cs +++ b/Libplanet/Tx/RawTransaction.cs @@ -1,11 +1,11 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using Bencodex.Types; using Libplanet.Serialization; [assembly: InternalsVisibleTo("Libplanet.Tests")] @@ -24,14 +24,9 @@ public RawTransaction(SerializationInfo info, StreamingContext context) Address.Size), timestamp: info.GetString("timestamp"), signature: info.GetValue("signature").ToImmutableArray(), - actions: info.GetValue( - "actions").OfType>().Select(a => - a.ToImmutableDictionary( - kv => kv.Key, - kv => kv.Value - ) - ) - ) + actions: info.GetValue( + "actions") + ) { } @@ -41,7 +36,7 @@ public RawTransaction( ImmutableArray> updatedAddresses, ImmutableArray publicKey, string timestamp, - IEnumerable> actions + IEnumerable actions ) : this( nonce, @@ -61,7 +56,7 @@ public RawTransaction( ImmutableArray> updatedAddresses, ImmutableArray publicKey, string timestamp, - IEnumerable> actions, + IEnumerable actions, ImmutableArray signature ) { @@ -83,15 +78,9 @@ public RawTransaction(Dictionary dict) Address.Size); PublicKey = ((byte[])dict["public_key"]).ToImmutableArray(); Timestamp = (string)dict["timestamp"]; - Actions = ((IEnumerable)dict["actions"]) - .Cast>() - .Select(a => - a.ToImmutableDictionary( - kv => kv.Key, - kv => kv.Value - ) - ) - .ToList(); + + Actions = ((List)dict["actions"]).Value; + if (dict.TryGetValue("signature", out object signature)) { Signature = ((byte[])signature).ToImmutableArray(); @@ -114,7 +103,7 @@ public RawTransaction(Dictionary dict) public ImmutableArray Signature { get; } - public IEnumerable> Actions { get; } + public IEnumerable Actions { get; } public void GetObjectData( SerializationInfo info, @@ -140,12 +129,7 @@ StreamingContext context info.AddValue("public_key", PublicKey.ToArray()); info.AddValue("timestamp", Timestamp); - info.AddValue("actions", Actions.Select(a => - a.ToDictionary( - kv => kv.Key, - kv => kv.Value - ) - )); + info.AddValue("actions", Actions.ToImmutableList()); if (Signature != ImmutableArray.Empty) { diff --git a/Libplanet/Tx/Transaction.cs b/Libplanet/Tx/Transaction.cs index f14dc63358..895b675fc1 100644 --- a/Libplanet/Tx/Transaction.cs +++ b/Libplanet/Tx/Transaction.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.Serialization; using System.Security.Cryptography; +using Bencodex.Types; using Libplanet.Action; using Libplanet.Crypto; using Libplanet.Serialization; @@ -669,12 +670,7 @@ internal RawTransaction ToRawTransaction(bool includeSign) a.ByteArray).ToImmutableArray(), publicKey: PublicKey.Format(false).ToImmutableArray(), timestamp: Timestamp.ToString(TimestampFormat), - actions: Actions.Select(a => - a.PlainValue.ToImmutableDictionary( - kv => kv.Key, - kv => kv.Value - ) - ) + actions: Actions.Select(a => a.PlainValue) ); if (includeSign) @@ -685,10 +681,10 @@ internal RawTransaction ToRawTransaction(bool includeSign) return rawTx; } - private static T ToAction(IImmutableDictionary arg) + private static T ToAction(IValue value) { var action = new T(); - action.LoadPlainValue(arg.ToImmutableDictionary()); + action.LoadPlainValue(value); return action; }