From 5ae6c55e25fd89ebff4e6d4b9b6ea4cd7dcaf8fe Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Mon, 18 Dec 2023 21:36:51 +0900 Subject: [PATCH 01/36] =?UTF-8?q?=EF=BB=BFbump:=20lib9c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index eb4e8827d..d61bea413 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit eb4e8827dd6fd2e8b88812fdfed725e2c3beb334 +Subproject commit d61bea4133030986c07c942cfd34792f23c6f437 From e30aa4451fc17dc28b3ab39663d45c52af4e9ebb Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Thu, 28 Dec 2023 18:28:01 +0900 Subject: [PATCH 02/36] feat: apply API changes from lib9c --- .../ForkableActionEvaluatorTest.cs | 20 +- .../ForkableActionEvaluator.cs | 4 +- .../PluggedActionEvaluator.cs | 16 +- .../Hosting/LibplanetNodeServiceTest.cs | 2 +- .../Commands/ActionCommandTest.cs | 91 ------ .../Commands/TxCommandTest.cs | 14 +- .../Commands/AccountCommand.cs | 9 +- .../Commands/ActionCommand.cs | 76 +---- .../Commands/MarketCommand.cs | 3 +- .../Commands/ReplayCommand.Privates.cs | 180 +++++++---- .../Commands/ReplayCommand.cs | 9 +- .../Commands/TxCommand.cs | 21 -- .../AccountStateExtensionTest.cs | 54 ---- .../Common/Actions/EmptyAction.cs | 2 +- .../Common/KeyConverters.cs | 101 +++++++ .../Common/MockAccount.cs | 283 ++++++++++++++++++ .../Common/MockAccountState.cs | 153 ++++++++++ .../Common/MockState.cs | 201 ------------- .../Common/MockWorld.cs | 67 +++++ .../Common/MockWorldDelta.cs | 62 ++++ .../Common/MockWorldState.cs | 34 +++ .../GraphQLTestUtils.cs | 7 +- .../GraphTypes/ActionQueryTest.cs | 5 +- .../GraphTypes/StandaloneMutationTest.cs | 69 +---- .../GraphTypes/StandaloneQueryTest.cs | 7 +- .../GraphTypes/StateQueryTest.cs | 18 +- .../States/Models/AgentStateTypeTest.cs | 18 +- .../States/Models/AvatarStateTypeTest.cs | 37 ++- .../States/Models/StakeStateTypeTest.cs | 9 +- .../TransactionHeadlessQueryTest.cs | 4 +- .../GraphTypes/WorldBossScenarioTest.cs | 18 +- .../AccountStateExtension.cs | 90 ------ .../ArenaParticipantsWorker.cs | 42 +-- NineChronicles.Headless/BlockChainService.cs | 70 +++-- .../GraphTypes/ActionMutation.cs | 51 +--- .../GraphTypes/ActionQuery.cs | 3 +- .../GraphTypes/ActivationStatusMutation.cs | 6 +- .../GraphTypes/ActivationStatusQuery.cs | 9 +- .../GraphTypes/StandaloneMutation.cs | 8 +- .../GraphTypes/StandaloneQuery.cs | 41 +-- .../GraphTypes/StandaloneSubscription.cs | 26 +- .../GraphTypes/StateQuery.cs | 107 ++++--- .../GraphTypes/StateQueryFields/Garages.cs | 8 +- .../GraphTypes/States/AgentStateType.cs | 53 +--- .../GraphTypes/States/AvatarStateType.cs | 15 +- .../GraphTypes/States/StakeStateType.cs | 20 +- .../GraphTypes/States/StateContext.cs | 14 +- .../NineChroniclesNodeService.cs | 3 +- .../Utils/CurrencyFactory.cs | 9 +- 49 files changed, 1162 insertions(+), 1007 deletions(-) delete mode 100644 NineChronicles.Headless.Tests/AccountStateExtensionTest.cs create mode 100644 NineChronicles.Headless.Tests/Common/KeyConverters.cs create mode 100644 NineChronicles.Headless.Tests/Common/MockAccount.cs create mode 100644 NineChronicles.Headless.Tests/Common/MockAccountState.cs delete mode 100644 NineChronicles.Headless.Tests/Common/MockState.cs create mode 100644 NineChronicles.Headless.Tests/Common/MockWorld.cs create mode 100644 NineChronicles.Headless.Tests/Common/MockWorldDelta.cs create mode 100644 NineChronicles.Headless.Tests/Common/MockWorldState.cs delete mode 100644 NineChronicles.Headless/AccountStateExtension.cs diff --git a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs index 73ea081fa..14e225647 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs @@ -22,11 +22,11 @@ public void ForkEvaluation() ((101L, long.MaxValue), new PostActionEvaluator()), }, new SingleActionLoader(typeof(MockAction))); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(0), null)).Action); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(99), null)).Action); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(100), null)).Action); - Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(101), null)).Action); - Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(long.MaxValue), null)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(0), null, out _)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(99), null, out _)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(100), null, out _)).Action); + Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(101), null, out _)).Action); + Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(long.MaxValue), null, out _)).Action); } [Fact] @@ -62,8 +62,10 @@ public void CheckPairs() class PostActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateroothash) + public IReadOnlyList Evaluate( + IPreEvaluationBlock block, HashDigest? baseStateRootHash, out HashDigest stateRootHash) { + stateRootHash = new HashDigest(); return new ICommittedActionEvaluation[] { new CommittedActionEvaluation( @@ -85,8 +87,10 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock bl class PreActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) + public IReadOnlyList Evaluate( + IPreEvaluationBlock block, HashDigest? baseStateRootHash, out HashDigest stateRootHash) { + stateRootHash = new HashDigest(); return new ICommittedActionEvaluation[] { new CommittedActionEvaluation( @@ -113,7 +117,7 @@ public void LoadPlainValue(IValue plainValue) { } - public IAccount Execute(IActionContext context) => context.PreviousState; + public IWorld Execute(IActionContext context) => context.PreviousState; } class MockBlock : IPreEvaluationBlock diff --git a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs index bf067c6ca..637110766 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs @@ -22,9 +22,9 @@ public IActionLoader ActionLoader } public IReadOnlyList Evaluate( - IPreEvaluationBlock block, HashDigest? baseStateRootHash) + IPreEvaluationBlock block, HashDigest? baseStateRootHash, out HashDigest stateRootHash) { var actionEvaluator = _router.GetEvaluator(block.Index); - return actionEvaluator.Evaluate(block, baseStateRootHash); + return actionEvaluator.Evaluate(block, baseStateRootHash, out stateRootHash); } } diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs index e97732f96..dc72b34df 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs @@ -46,10 +46,18 @@ public static IPluginActionEvaluator CreateActionEvaluator(Assembly assembly, st public static IPluginActionEvaluator CreateActionEvaluator(string pluginPath, string typeName, IKeyValueStore keyValueStore) => CreateActionEvaluator(LoadPlugin(pluginPath), typeName, new PluginKeyValueStore(keyValueStore)); - public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) - => _pluginActionEvaluator.Evaluate( - PreEvaluationBlockMarshaller.Serialize(block), - baseStateRootHash is { } srh ? srh.ToByteArray() : null) + public IReadOnlyList Evaluate( + IPreEvaluationBlock block, + HashDigest? baseStateRootHash, + out HashDigest stateRootHash) + { + var evaluations = _pluginActionEvaluator.Evaluate( + PreEvaluationBlockMarshaller.Serialize(block), + baseStateRootHash is { } srh ? srh.ToByteArray() : null, + out var stateRootHashBytes) .Select(eval => ActionEvaluationMarshaller.Deserialize(eval)).ToList().AsReadOnly(); + stateRootHash = new HashDigest(stateRootHashBytes); + return evaluations; + } } } diff --git a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs index 7ccd5cd94..6be8af511 100644 --- a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs +++ b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs @@ -89,7 +89,7 @@ private class DummyAction : IAction { IValue IAction.PlainValue => Dictionary.Empty; - IAccount IAction.Execute(IActionContext context) + IWorld IAction.Execute(IActionContext context) { return context.PreviousState; } diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs index 6853ee931..0f7238ab6 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs @@ -5,7 +5,6 @@ using Libplanet.Common; using Libplanet.Crypto; using Nekoyume.Action; -using Nekoyume.Action.Factory; using Nekoyume.Model; using Nekoyume.Model.State; using NineChronicles.Headless.Executable.Commands; @@ -59,23 +58,6 @@ public void ActivateAccount(bool invalid, int expectedCode) } } - [Fact] - public void MonsterCollect() - { - var filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - var resultCode = _command.MonsterCollect(1, filePath); - Assert.Equal(0, resultCode); - var rawAction = Convert.FromBase64String(File.ReadAllText(filePath)); - var decoded = (List)_codec.Decode(rawAction); - string type = (Text)decoded[0]; - Assert.Equal(nameof(Nekoyume.Action.MonsterCollect), type); - - Dictionary plainValue = (Dictionary)decoded[1]; - var action = new MonsterCollect(); - action.LoadPlainValue(plainValue); - Assert.Equal(1, action.level); - } - [Theory] [InlineData("0xab1dce17dCE1Db1424BB833Af6cC087cd4F5CB6d", -1)] [InlineData("ab1dce17dCE1Db1424BB833Af6cC087cd4F5CB6d", 0)] @@ -185,79 +167,6 @@ public void ClaimStakeReward(string addressString, int expectedCode) } } - [Theory] - [InlineData(0L, typeof(ClaimStakeReward2))] - [InlineData(ClaimStakeReward2.ObsoletedIndex, typeof(ClaimStakeReward2))] - [InlineData(ClaimStakeReward2.ObsoletedIndex + 1, typeof(ClaimStakeReward3))] - [InlineData(ClaimStakeReward3.ObsoleteBlockIndex, typeof(ClaimStakeReward3))] - [InlineData(ClaimStakeReward3.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward4))] - [InlineData(ClaimStakeReward4.ObsoleteBlockIndex, typeof(ClaimStakeReward4))] - [InlineData(ClaimStakeReward4.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward5))] - [InlineData(ClaimStakeReward5.ObsoleteBlockIndex, typeof(ClaimStakeReward5))] - [InlineData(ClaimStakeReward5.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward6))] - [InlineData(ClaimStakeReward6.ObsoleteBlockIndex, typeof(ClaimStakeReward6))] - [InlineData(ClaimStakeReward6.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward7))] - [InlineData(ClaimStakeReward7.ObsoleteBlockIndex, typeof(ClaimStakeReward7))] - [InlineData(ClaimStakeReward7.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward8))] - [InlineData(ClaimStakeReward8.ObsoleteBlockIndex, typeof(ClaimStakeReward8))] - [InlineData(ClaimStakeReward8.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward))] - [InlineData(long.MaxValue, typeof(ClaimStakeReward))] - public void ClaimStakeRewardWithBlockIndex(long blockIndex, Type expectedActionType) - { - var filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - var addr = new PrivateKey().Address; - var resultCode = _command.ClaimStakeReward( - addr.ToHex(), - filePath, - blockIndex: blockIndex); - Assert.Equal(0, resultCode); - - var rawAction = Convert.FromBase64String(File.ReadAllText(filePath)); - var decoded = (List)_codec.Decode(rawAction); - var plainValue = Assert.IsType(decoded[1]); - var action = ClaimStakeRewardFactory.CreateByBlockIndex(blockIndex, addr); - Assert.NotNull(action); - var actionType = action.GetType(); - Assert.Equal(expectedActionType, actionType); - action.LoadPlainValue(plainValue); - string type = (Text)decoded[0]; - Assert.Equal(type, actionType.Name); - } - - [Theory] - [InlineData(0, 0, -1)] - [InlineData(1, 9, 0)] - [InlineData(10, 10, -1)] - public void ClaimStakeRewardWithActionVersion( - int actionVersionMin, - int actionVersionMax, - int expectedCode) - { - for (var i = actionVersionMin; i < actionVersionMax + 1; i++) - { - var filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - var addr = new PrivateKey().Address; - var resultCode = _command.ClaimStakeReward( - addr.ToHex(), - filePath, - actionVersion: i); - Assert.Equal(expectedCode, resultCode); - - if (expectedCode < 0) - { - continue; - } - - var rawAction = Convert.FromBase64String(File.ReadAllText(filePath)); - var decoded = (List)_codec.Decode(rawAction); - var plainValue = Assert.IsType(decoded[1]); - var action = ClaimStakeRewardFactory.CreateByVersion(i, addr); - action.LoadPlainValue(plainValue); - string type = (Text)decoded[0]; - Assert.Equal(action.GetType().Name, type); - } - } - [Theory] [InlineData("0xab1dce17dCE1Db1424BB833Af6cC087cd4F5CB6d", -1)] [InlineData("ab1dce17dCE1Db1424BB833Af6cC087cd4F5CB6d", 0)] diff --git a/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs index d9697dd19..b5fe1d3f3 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs @@ -45,15 +45,6 @@ public void Sign_ActivateAccount(int txNonce) Assert_Tx(txNonce, filePath, false); } - [Fact] - public void Sign_MonsterCollect() - { - var filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - var actionCommand = new ActionCommand(_console); - actionCommand.MonsterCollect(1, filePath); - Assert_Tx(1, filePath, false); - } - [Fact] public void Sign_ClaimMonsterCollectionReward() { @@ -97,7 +88,6 @@ public void Sign_Stake(bool gas) [InlineData(ClaimStakeReward2.ObsoletedIndex - 1, null, false)] [InlineData(ClaimStakeReward2.ObsoletedIndex, null, true)] [InlineData(ClaimStakeReward2.ObsoletedIndex + 1, null, false)] - [InlineData(ClaimStakeReward3.ObsoleteBlockIndex - 1, null, true)] [InlineData(long.MaxValue, null, true)] [InlineData(null, 1, false)] [InlineData(null, 2, true)] @@ -111,9 +101,7 @@ public void Sign_ClaimStakeReward(long? blockIndex, int? actionVersion, bool gas var avatarAddress = new Address(); actionCommand.ClaimStakeReward( avatarAddress.ToHex(), - filePath, - blockIndex, - actionVersion); + filePath); Assert_Tx(1, filePath, gas); } diff --git a/NineChronicles.Headless.Executable/Commands/AccountCommand.cs b/NineChronicles.Headless.Executable/Commands/AccountCommand.cs index 1b74370fe..9531c62f4 100644 --- a/NineChronicles.Headless.Executable/Commands/AccountCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/AccountCommand.cs @@ -3,6 +3,7 @@ using System.Linq; using Bencodex; using Cocona; +using Libplanet.Action.State; using Libplanet.Blockchain; using Libplanet.Crypto; using Libplanet.Store; @@ -11,6 +12,7 @@ using Libplanet.Types.Tx; using Nekoyume.Action; using Nekoyume.Model.State; +using Nekoyume.Module; using NineChronicles.Headless.Executable.IO; using Serilog.Core; using static NineChronicles.Headless.NCActionUtils; @@ -52,15 +54,16 @@ public void Balance( Block offset = DevExUtils.ParseBlockOffset(chain, block); _console.Error.WriteLine("The offset block: #{0} {1}.", offset.Index, offset.Hash); + IWorldState worldState = chain.GetWorldState(offset.Hash); Bencodex.Types.Dictionary goldCurrencyStateDict = (Bencodex.Types.Dictionary) - chain.GetState(GoldCurrencyState.Address); + worldState.GetLegacyState(GoldCurrencyState.Address); GoldCurrencyState goldCurrencyState = new GoldCurrencyState(goldCurrencyStateDict); Currency gold = goldCurrencyState.Currency; if (address is { } addrStr) { Address addr = DevExUtils.ParseAddress(addrStr); - FungibleAssetValue balance = chain.GetBalance(addr, gold, offset.Hash); + FungibleAssetValue balance = worldState.GetBalance(addr, gold); _console.Out.WriteLine("{0}\t{1}", addr, balance); return; } @@ -96,7 +99,7 @@ public void Balance( { if (!printed.Contains(addr)) { - FungibleAssetValue balance = chain.GetBalance(addr, gold, offset.Hash); + FungibleAssetValue balance = worldState.GetBalance(addr, gold); _console.Out.WriteLine("{0}\t{1}", addr, balance); printed.Add(addr); } diff --git a/NineChronicles.Headless.Executable/Commands/ActionCommand.cs b/NineChronicles.Headless.Executable/Commands/ActionCommand.cs index 086308aa5..219bb8722 100644 --- a/NineChronicles.Headless.Executable/Commands/ActionCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ActionCommand.cs @@ -12,7 +12,6 @@ using Libplanet.Crypto; using Libplanet.Types.Assets; using Nekoyume.Action; -using Nekoyume.Action.Factory; using Nekoyume.Model; using NineChronicles.Headless.Executable.IO; @@ -123,47 +122,6 @@ bool IsTarget(Type type) return typeIds.OrderBy(type => type); } - - [Command(Description = "Create MonsterCollect action.")] - public int MonsterCollect( - [Range(0, 7)] int level, - [Argument("PATH", Description = "A file path of base64 encoded action.")] - string? filePath = null - ) - { - try - { - Nekoyume.Action.MonsterCollect action = new MonsterCollect - { - level = level - }; - - byte[] raw = Codec.Encode(new List( - new[] - { - (Text) nameof(Nekoyume.Action.MonsterCollect), - action.PlainValue - } - )); - string encoded = Convert.ToBase64String(raw); - if (filePath is null) - { - _console.Out.Write(encoded); - } - else - { - File.WriteAllText(filePath, encoded); - } - - return 0; - } - catch (Exception e) - { - _console.Error.WriteLine(e); - return -1; - } - } - [Command(Description = "Create ClaimMonsterCollectionReward action.")] public int ClaimMonsterCollectionReward( [Argument("AVATAR-ADDRESS", Description = "A hex-encoded avatar address.")] @@ -303,44 +261,14 @@ public int ClaimStakeReward( [Argument("AVATAR-ADDRESS", Description = "A hex-encoded avatar address.")] string encodedAddress, [Argument("PATH", Description = "A file path of base64 encoded action.")] - string? filePath = null, - [Option("BLOCK-INDEX", Description = "A block index which is used to specifying the action version.")] - long? blockIndex = null, - [Option("ACTION-VERSION", Description = "A version of action.")] - int? actionVersion = null + string? filePath = null ) { try { - if (blockIndex.HasValue && actionVersion.HasValue) - { - throw new CommandExitedException( - "You can't specify both block index and action version at the same time.", - -1); - } - Address avatarAddress = new Address(ByteUtil.ParseHex(encodedAddress)); IClaimStakeReward? action = null; - if (blockIndex.HasValue) - { - action = ClaimStakeRewardFactory.CreateByBlockIndex( - blockIndex.Value, - avatarAddress); - } - else if (actionVersion.HasValue) - { - action = ClaimStakeRewardFactory.CreateByVersion( - actionVersion.Value, - avatarAddress); - } - - // NOTE: If neither block index nor action version is specified, - // it will be created by the type of the class. - // I considered to create action with max value of - // block index(i.e., long.MaxValue), but it is not good - // because the action of the next version may come along - // with the current version. - action ??= new ClaimStakeReward(avatarAddress); + action = new ClaimStakeReward(avatarAddress); byte[] raw = Codec.Encode(new List( new[] diff --git a/NineChronicles.Headless.Executable/Commands/MarketCommand.cs b/NineChronicles.Headless.Executable/Commands/MarketCommand.cs index 21fd70b8c..137ef4380 100644 --- a/NineChronicles.Headless.Executable/Commands/MarketCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/MarketCommand.cs @@ -14,6 +14,7 @@ using Libplanet.Types.Tx; using Nekoyume.Action; using Nekoyume.Model.Item; +using Nekoyume.Module; using NineChronicles.Headless.Executable.IO; using Serilog.Core; using static NineChronicles.Headless.NCActionUtils; @@ -130,7 +131,7 @@ public void Query( { int? quantity = null; if (p.OrderId is { } oid && - chain.GetState(GetOrderAddress(oid)) is Dictionary rawOrder) + chain.GetWorldState().GetLegacyState(GetOrderAddress(oid)) is Dictionary rawOrder) { if (OrderFactory.Deserialize(rawOrder) is FungibleOrder fo) { diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index ee3c1ff4b..9c5aa894a 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -35,7 +35,7 @@ public ActionContext( Address miner, long blockIndex, int blockProtocolVersion, - IAccount previousState, + IWorld previousState, int randomSeed, bool rehearsal = false) { @@ -61,7 +61,7 @@ public ActionContext( public bool Rehearsal { get; } - public IAccount PreviousState { get; } + public IWorld PreviousState { get; } public int RandomSeed { get; } @@ -100,73 +100,148 @@ public LocalCacheBlockChainStates(IBlockChainStates source, string cacheDirector var options = new DbOptions().SetCreateIfMissing(); _rocksDb = RocksDb.Open(options, cacheDirectory); } + public IWorldState GetWorldState(BlockHash? offset) + => new LocalCacheWorldState( + _source.GetWorldState(offset), + _source.GetAccountState, + _rocksDb); - public IValue? GetState(Address address, BlockHash? offset) - { - return GetAccountState(offset).GetState(address); - } + public IWorldState GetWorldState(HashDigest? hash) + => new LocalCacheWorldState( + _source.GetWorldState(hash), + _source.GetAccountState, + _rocksDb); + + public IAccountState GetAccountState(HashDigest? hash) + => new LocalCacheAccountState( + _source.GetAccountState(hash), + _rocksDb); + + public IAccountState GetAccountState(Address address, BlockHash? offset) + => new LocalCacheAccountState( + _source.GetAccountState(address, offset), + _rocksDb); + + public IValue? GetState(Address address, Address accountAddress, BlockHash? offset) + => GetAccountState(accountAddress, offset).GetState(address); + + public IValue? GetState(Address address, HashDigest? stateRootHash) + => GetAccountState(stateRootHash).GetState(address); + + public FungibleAssetValue GetBalance(Address address, Currency currency, HashDigest? stateRootHash) + => GetAccountState(stateRootHash).GetBalance(address, currency); + + public FungibleAssetValue GetBalance(Address address, Currency currency, Address accountAddress, BlockHash? offset) + => GetAccountState(accountAddress, offset).GetBalance(address, currency); + + public FungibleAssetValue GetTotalSupply(Currency currency, HashDigest? stateRootHash) + => GetAccountState(stateRootHash).GetTotalSupply(currency); - public IReadOnlyList GetStates(IReadOnlyList
addresses, BlockHash? offset) + public FungibleAssetValue GetTotalSupply(Currency currency, Address accountAddress, BlockHash? offset) + => GetAccountState(accountAddress, offset).GetTotalSupply(currency); + + public ValidatorSet GetValidatorSet(HashDigest? stateRootHash) + => GetAccountState(stateRootHash).GetValidatorSet(); + + public ValidatorSet GetValidatorSet(Address accountAddress, BlockHash? offset) + => GetAccountState(accountAddress, offset).GetValidatorSet(); + } + + private sealed class LocalCacheWorldState : IWorldState + { + private static readonly Codec Codec = new Codec(); + private readonly IWorldState _worldState; + private readonly Func?, IAccountState> _accountStateGetter; + private readonly RocksDb _rocksDb; + + public LocalCacheWorldState( + IWorldState worldState, + Func?, IAccountState> accountStateGetter, + RocksDb rocksDb) { - return GetAccountState(offset).GetStates(addresses); + _worldState = worldState; + _accountStateGetter = accountStateGetter; + _rocksDb = rocksDb; } - public FungibleAssetValue GetBalance(Address address, Currency currency, BlockHash? offset) + public ITrie Trie => _worldState.Trie; + + public bool Legacy => _worldState.Legacy; + + public IAccount GetAccount(Address address) { - return GetAccountState(offset).GetBalance(address, currency); + var key = WithStateRootHash(address.ToByteArray()); + try + { + return GetAccount(key); + } + catch (KeyNotFoundException) + { + var account = _worldState.GetAccount(address); + SetAccount(key, account); + return account; + } } - public FungibleAssetValue GetTotalSupply(Currency currency, BlockHash? offset) + public IAccount GetAccount(byte[] key) { - return GetAccountState(offset).GetTotalSupply(currency); + if (_rocksDb.Get(key) is not { } bytes) + { + throw new KeyNotFoundException(); + } + + return new Account(_accountStateGetter( + new HashDigest(((Binary)Codec.Decode(bytes)).ToImmutableArray()))); } - public ValidatorSet GetValidatorSet(BlockHash? offset) + private void SetAccount(byte[] key, IAccount? account) { - return GetAccountState(offset).GetValidatorSet(); + _rocksDb.Put(key, account is null ? new byte[] { 0x78 } : account.Trie.Hash.ToByteArray()); } - public IAccountState GetAccountState(BlockHash? offset) + private byte[] WithStateRootHash(params byte[][] suffixes) { - return new LocalCacheAccountState( - _rocksDb, - _source.GetAccountState, - offset); - } + if (Trie.Hash is { } stateRootHash) + { + var stream = new MemoryStream(HashDigest.Size + suffixes.Sum(s => s.Length)); + stream.Write(stateRootHash.ToByteArray()); + foreach (var suffix in suffixes) + { + stream.Write(suffix); + } - public IAccountState GetAccountState(HashDigest? hash) - => _source.GetAccountState(hash); + return stream.ToArray(); + } + throw new InvalidOperationException(); + } } private sealed class LocalCacheAccountState : IAccountState { private static readonly Codec _codec = new Codec(); + private readonly IAccountState _accountState; private readonly RocksDb _rocksDb; - private readonly Func _sourceAccountStateGetter; - private readonly BlockHash? _offset; public LocalCacheAccountState( - RocksDb rocksDb, - Func sourceAccountStateGetterWithBlockHash, - BlockHash? offset) + IAccountState accountState, + RocksDb rocksDb) { + _accountState = accountState; _rocksDb = rocksDb; - _sourceAccountStateGetter = sourceAccountStateGetterWithBlockHash; - _offset = offset; } - public ITrie Trie => _sourceAccountStateGetter(_offset).Trie; + public ITrie Trie => _accountState.Trie; public IValue? GetState(Address address) { - var key = WithBlockHash(address.ToByteArray()); + var key = WithStateRootHash(address.ToByteArray()); try { return GetValue(key); } catch (KeyNotFoundException) { - var state = _sourceAccountStateGetter(_offset).GetState(address); + var state = _accountState.GetState(address); SetValue(key, state); return state; } @@ -179,7 +254,7 @@ public LocalCacheAccountState( public FungibleAssetValue GetBalance(Address address, Currency currency) { - var key = WithBlockHash(address.ToByteArray(), currency.Hash.ToByteArray()); + var key = WithStateRootHash(address.ToByteArray(), currency.Hash.ToByteArray()); try { var state = GetValue(key); @@ -192,7 +267,7 @@ public FungibleAssetValue GetBalance(Address address, Currency currency) } catch (KeyNotFoundException) { - var fav = _sourceAccountStateGetter(_offset).GetBalance(address, currency); + var fav = _accountState.GetBalance(address, currency); SetValue(key, (Integer)fav.RawValue); return fav; } @@ -200,7 +275,7 @@ public FungibleAssetValue GetBalance(Address address, Currency currency) public FungibleAssetValue GetTotalSupply(Currency currency) { - var key = WithBlockHash(currency.Hash.ToByteArray()); + var key = WithStateRootHash(currency.Hash.ToByteArray()); try { var state = GetValue(key); @@ -213,7 +288,7 @@ public FungibleAssetValue GetTotalSupply(Currency currency) } catch (KeyNotFoundException) { - var fav = _sourceAccountStateGetter(_offset).GetTotalSupply(currency); + var fav = _accountState.GetTotalSupply(currency); SetValue(key, (Integer)fav.RawValue); return fav; } @@ -221,7 +296,7 @@ public FungibleAssetValue GetTotalSupply(Currency currency) public ValidatorSet GetValidatorSet() { - var key = WithBlockHash(new byte[] { 0x5f, 0x5f, 0x5f }); + var key = WithStateRootHash(new byte[] { 0x5f, 0x5f, 0x5f }); try { var state = GetValue(key); @@ -229,7 +304,7 @@ public ValidatorSet GetValidatorSet() } catch (KeyNotFoundException) { - var validatorSet = _sourceAccountStateGetter(_offset).GetValidatorSet(); + var validatorSet = _accountState.GetValidatorSet(); SetValue(key, validatorSet.Bencoded); return validatorSet; } @@ -250,21 +325,20 @@ private void SetValue(byte[] key, IValue? value) _rocksDb.Put(key, value is null ? new byte[] { 0x78 } : _codec.Encode(value)); } - private byte[] WithBlockHash(params byte[][] suffixes) + private byte[] WithStateRootHash(params byte[][] suffixes) { - if (_offset is not { } blockHash) + if (Trie.Hash is { } stateRootHash) { - throw new InvalidOperationException(); - } + var stream = new MemoryStream(HashDigest.Size + suffixes.Sum(s => s.Length)); + stream.Write(stateRootHash.ToByteArray()); + foreach (var suffix in suffixes) + { + stream.Write(suffix); + } - var stream = new MemoryStream(Libplanet.Types.Blocks.BlockHash.Size + suffixes.Sum(s => s.Length)); - stream.Write(blockHash.ToByteArray()); - foreach (var suffix in suffixes) - { - stream.Write(suffix); + return stream.ToArray(); } - - return stream.ToArray(); + throw new InvalidOperationException(); } } @@ -276,7 +350,7 @@ private static IEnumerable EvaluateActions( long blockIndex, int blockProtocolVersion, TxId? txid, - IAccount previousStates, + IWorld previousStates, Address miner, Address signer, byte[] signature, @@ -284,7 +358,7 @@ private static IEnumerable EvaluateActions( ILogger? logger = null) { ActionContext CreateActionContext( - IAccount prevState, + IWorld prevState, int randomSeed) { return new ActionContext( @@ -300,11 +374,11 @@ ActionContext CreateActionContext( byte[] preEvaluationHashBytes = preEvaluationHash.ToByteArray(); int seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, signature, 0); - IAccount states = previousStates; + IWorld states = previousStates; foreach (IAction action in actions) { Exception? exc = null; - IAccount nextStates = states; + IWorld nextStates = states; ActionContext context = CreateActionContext(nextStates, seed); try diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index dd06de842..0937a48eb 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -14,7 +14,6 @@ using Cocona.Help; using GraphQL.Client.Http; using GraphQL.Client.Serializer.SystemTextJson; -using Libplanet; using Libplanet.Action; using Libplanet.Action.Loader; using Libplanet.Blockchain; @@ -32,7 +31,6 @@ using Nekoyume.Blockchain.Policy; using NineChronicles.Headless.Executable.IO; using NineChronicles.Headless.Executable.Store; -using Serilog.Core; using static NineChronicles.Headless.NCActionUtils; namespace NineChronicles.Headless.Executable.Commands @@ -100,15 +98,14 @@ public int Tx( } // Evaluate tx. - IAccountState previousBlockStates = blockChain.GetAccountState(previousBlock.Hash); - IAccount previousStates = new Account(previousBlockStates); + IWorld previousWorld = new World(blockChain.GetWorldState(previousBlock.Hash)); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( preEvaluationHash: targetBlock.PreEvaluationHash, blockIndex: targetBlock.Index, blockProtocolVersion: targetBlock.ProtocolVersion, txid: tx.Id, - previousStates: previousStates, + previousStates: previousWorld, miner: targetBlock.Miner, signer: tx.Signer, signature: tx.Signature, @@ -400,7 +397,7 @@ public int RemoteTx( cacheDirectory ?? Path.Join(Path.GetTempPath(), "ncd-replay-remote-tx-cache")); var previousBlockHash = BlockHash.FromString(previousBlockHashValue); - var previousStates = new Account(blockChainStates.GetAccountState(previousBlockHash)); + var previousStates = new World(blockChainStates.GetWorldState(previousBlockHash)); var actions = transaction.Actions .Select(ToAction) diff --git a/NineChronicles.Headless.Executable/Commands/TxCommand.cs b/NineChronicles.Headless.Executable/Commands/TxCommand.cs index 07482d9b6..90b6ffecf 100644 --- a/NineChronicles.Headless.Executable/Commands/TxCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/TxCommand.cs @@ -66,17 +66,10 @@ public void Sign( ActionBase action = type switch { nameof(ActivateAccount) => new ActivateAccount(), - nameof(MonsterCollect) => new MonsterCollect(), nameof(ClaimMonsterCollectionReward) => new ClaimMonsterCollectionReward(), nameof(Stake) => new Stake(), // FIXME: This `ClaimStakeReward` cases need to reduce to one case. - nameof(ClaimStakeReward1) => new ClaimStakeReward1(), nameof(ClaimStakeReward2) => new ClaimStakeReward2(), - nameof(ClaimStakeReward3) => new ClaimStakeReward3(), - nameof(ClaimStakeReward4) => new ClaimStakeReward4(), - nameof(ClaimStakeReward5) => new ClaimStakeReward5(), - nameof(ClaimStakeReward7) => new ClaimStakeReward7(), - nameof(ClaimStakeReward8) => new ClaimStakeReward8(), nameof(ClaimStakeReward) => new ClaimStakeReward(), nameof(TransferAsset) => new TransferAsset(), nameof(MigrateMonsterCollection) => new MigrateMonsterCollection(), @@ -187,20 +180,6 @@ string tablePath _console.Out.WriteLine(ByteUtil.Hex(raw)); } - [Command(Description = "Create MigrationLegacyShop action and dump it.")] - public void MigrationLegacyShop() - { - var action = new MigrationLegacyShop(); - - var bencoded = new List( - (Text)nameof(Nekoyume.Action.MigrationLegacyShop), - action.PlainValue - ); - - byte[] raw = _codec.Encode(bencoded); - _console.Out.WriteLine(ByteUtil.Hex(raw)); - } - [Command(Description = "Create MigrationActivatedAccountsState action and dump it.")] public void MigrationActivatedAccountsState() { diff --git a/NineChronicles.Headless.Tests/AccountStateExtensionTest.cs b/NineChronicles.Headless.Tests/AccountStateExtensionTest.cs deleted file mode 100644 index 0ee93b1c1..000000000 --- a/NineChronicles.Headless.Tests/AccountStateExtensionTest.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Nekoyume.Action; -using NineChronicles.Headless.Tests.Common; -using Xunit; -using static Lib9c.SerializeKeys; - -namespace NineChronicles.Headless.Tests -{ - public class AccountStateExtensionTest - { - [Theory] - [InlineData(true, false, false, false, false)] - [InlineData(true, false, false, false, true)] - [InlineData(false, true, true, true, false)] - [InlineData(false, false, true, true, true)] - [InlineData(false, true, false, true, true)] - [InlineData(false, true, true, false, true)] - public void GetAvatarState(bool backward, bool inventoryExist, bool worldInformationExist, bool questListExist, bool exc) - { - MockState mockState = MockState.Empty; - - mockState = backward - ? mockState.SetState(Fixtures.AvatarAddress, Fixtures.AvatarStateFX.Serialize()) - : mockState.SetState(Fixtures.AvatarAddress, Fixtures.AvatarStateFX.SerializeV2()); - mockState = inventoryExist - ? mockState.SetState( - Fixtures.AvatarAddress.Derive(LegacyInventoryKey), - Fixtures.AvatarStateFX.inventory.Serialize()) - : mockState; - mockState = worldInformationExist - ? mockState.SetState( - Fixtures.AvatarAddress.Derive(LegacyWorldInformationKey), - Fixtures.AvatarStateFX.worldInformation.Serialize()) - : mockState; - mockState = questListExist - ? mockState.SetState( - Fixtures.AvatarAddress.Derive(LegacyQuestListKey), - Fixtures.AvatarStateFX.questList.Serialize()) - : mockState; - - if (exc) - { - Assert.Throws(() => mockState.GetAvatarState(default)); - } - else - { - var avatarState = mockState.GetAvatarState(Fixtures.AvatarAddress); - - Assert.NotNull(avatarState.inventory); - Assert.NotNull(avatarState.worldInformation); - Assert.NotNull(avatarState.questList); - } - } - } -} diff --git a/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs b/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs index a33c0ecb2..eda0bcf80 100644 --- a/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs +++ b/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs @@ -11,7 +11,7 @@ public void LoadPlainValue(IValue plainValue) { } - public IAccount Execute(IActionContext context) + public IWorld Execute(IActionContext context) { return context.PreviousState; } diff --git a/NineChronicles.Headless.Tests/Common/KeyConverters.cs b/NineChronicles.Headless.Tests/Common/KeyConverters.cs new file mode 100644 index 000000000..a1894fa22 --- /dev/null +++ b/NineChronicles.Headless.Tests/Common/KeyConverters.cs @@ -0,0 +1,101 @@ +using Libplanet.Crypto; +using Libplanet.Store.Trie; +using Libplanet.Types.Assets; + +namespace NineChronicles.Headless.Tests.Common +{ + + /// + /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/KeyConverters.cs + /// except this has its constructors exposed as public for testing. + /// + internal static class KeyConverters + { + // "___" + public static readonly KeyBytes ValidatorSetKey = + new KeyBytes(new byte[] { _underScore, _underScore, _underScore }); + + private const byte _underScore = 95; // '_' + + private static readonly byte[] _conversionTable = + { + 48, // '0' + 49, // '1' + 50, // '2' + 51, // '3' + 52, // '4' + 53, // '5' + 54, // '6' + 55, // '7' + 56, // '8' + 57, // '9' + 97, // 'a' + 98, // 'b' + 99, // 'c' + 100, // 'd' + 101, // 'e' + 102, // 'f' + }; + + // $"{ByteUtil.Hex(address.ByteArray)}" + public static KeyBytes ToStateKey(Address address) + { + var addressBytes = address.ByteArray; + byte[] buffer = new byte[addressBytes.Length * 2]; + for (int i = 0; i < addressBytes.Length; i++) + { + buffer[i * 2] = _conversionTable[addressBytes[i] >> 4]; + buffer[i * 2 + 1] = _conversionTable[addressBytes[i] & 0xf]; + } + + return new KeyBytes(buffer); + } + + // $"_{ByteUtil.Hex(address.ByteArray)}_{ByteUtil.Hex(currency.Hash.ByteArray)}" + public static KeyBytes ToFungibleAssetKey(Address address, Currency currency) + { + var addressBytes = address.ByteArray; + var currencyBytes = currency.Hash.ByteArray; + byte[] buffer = new byte[addressBytes.Length * 2 + currencyBytes.Length * 2 + 2]; + + buffer[0] = _underScore; + for (int i = 0; i < addressBytes.Length; i++) + { + buffer[1 + i * 2] = _conversionTable[addressBytes[i] >> 4]; + buffer[1 + i * 2 + 1] = _conversionTable[addressBytes[i] & 0xf]; + } + + var offset = addressBytes.Length * 2; + buffer[offset + 1] = _underScore; + for (int i = 0; i < currencyBytes.Length; i++) + { + buffer[offset + 2 + i * 2] = _conversionTable[currencyBytes[i] >> 4]; + buffer[offset + 2 + i * 2 + 1] = _conversionTable[currencyBytes[i] & 0xf]; + } + + return new KeyBytes(buffer); + } + + public static KeyBytes ToFungibleAssetKey( + (Address Address, Currency Currency) pair) => + ToFungibleAssetKey(pair.Address, pair.Currency); + + // $"__{ByteUtil.Hex(currency.Hash.ByteArray)}" + public static KeyBytes ToTotalSupplyKey(Currency currency) + { + var currencyBytes = currency.Hash.ByteArray; + byte[] buffer = new byte[currencyBytes.Length * 2 + 2]; + + buffer[0] = _underScore; + buffer[1] = _underScore; + + for (int i = 0; i < currencyBytes.Length; i++) + { + buffer[2 + i * 2] = _conversionTable[currencyBytes[i] >> 4]; + buffer[2 + i * 2 + 1] = _conversionTable[currencyBytes[i] & 0xf]; + } + + return new KeyBytes(buffer); + } + } +} diff --git a/NineChronicles.Headless.Tests/Common/MockAccount.cs b/NineChronicles.Headless.Tests/Common/MockAccount.cs new file mode 100644 index 000000000..668cc0c2a --- /dev/null +++ b/NineChronicles.Headless.Tests/Common/MockAccount.cs @@ -0,0 +1,283 @@ +namespace NineChronicles.Headless.Tests.Common +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Diagnostics.Contracts; + using System.Numerics; + using Bencodex.Types; + using Libplanet.Action; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Store.Trie; + using Libplanet.Types.Assets; + using Libplanet.Types.Consensus; + using static KeyConverters; + + /// + /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/Account.cs + /// except this has its constructors exposed as public for testing. + /// + [Pure] + public class MockAccount : IAccount + { + private readonly MockAccountState _state; + + public MockAccount(MockAccountState state) + : this(state, ImmutableHashSet<(Address, Currency)>.Empty) + { + } + + public MockAccount( + MockAccountState state, + IImmutableSet<(Address, Currency)> totalUpdatedFungibleAssets) + { + _state = state; + TotalUpdatedFungibleAssets = totalUpdatedFungibleAssets; + } + + /// + public ITrie Trie => throw new NotSupportedException(); + + public IImmutableDictionary MockTrie => _state.MockTrie; + + /// + public IImmutableSet<(Address, Currency)> TotalUpdatedFungibleAssets { get; } + + /// + [Pure] + public IValue? GetState(Address address) => _state.GetState(address); + + /// + [Pure] + public IReadOnlyList GetStates(IReadOnlyList
addresses) => + _state.GetStates(addresses); + + /// + [Pure] + public IAccount SetState(Address address, IValue state) => UpdateState(address, state); + + /// + [Pure] + public FungibleAssetValue GetBalance(Address address, Currency currency) => + _state.GetBalance(address, currency); + + /// + [Pure] + public FungibleAssetValue GetTotalSupply(Currency currency) => + _state.GetTotalSupply(currency); + + /// + [Pure] + public ValidatorSet GetValidatorSet() => _state.GetValidatorSet(); + + /// + [Pure] + public IAccount MintAsset( + IActionContext context, Address recipient, FungibleAssetValue value) + { + if (value.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "The value to mint has to be greater than zero." + ); + } + + Currency currency = value.Currency; + if (!currency.AllowsToMint(context.Signer)) + { + throw new CurrencyPermissionException( + $"The account {context.Signer} has no permission to mint currency {currency}.", + context.Signer, + currency + ); + } + + FungibleAssetValue balance = GetBalance(recipient, currency); + BigInteger rawBalance = (balance + value).RawValue; + + if (currency.TotalSupplyTrackable) + { + var currentTotalSupply = GetTotalSupply(currency); + if (currency.MaximumSupply < currentTotalSupply + value) + { + var msg = $"The amount {value} attempted to be minted added to the current" + + $" total supply of {currentTotalSupply} exceeds the" + + $" maximum allowed supply of {currency.MaximumSupply}."; + throw new SupplyOverflowException(msg, value); + } + + return UpdateFungibleAssets( + recipient, + currency, + rawBalance, + (currentTotalSupply + value).RawValue); + } + else + { + return UpdateFungibleAssets(recipient, currency, rawBalance); + } + } + + /// + [Pure] + public IAccount TransferAsset( + IActionContext context, + Address sender, + Address recipient, + FungibleAssetValue value, + bool allowNegativeBalance = false) => context.BlockProtocolVersion > 0 + ? TransferAssetV1(sender, recipient, value, allowNegativeBalance) + : TransferAssetV0(sender, recipient, value, allowNegativeBalance); + + /// + [Pure] + public IAccount BurnAsset( + IActionContext context, Address owner, FungibleAssetValue value) + { + string msg; + + if (value.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "The value to burn has to be greater than zero." + ); + } + + Currency currency = value.Currency; + if (!currency.AllowsToMint(context.Signer)) + { + msg = $"The account {context.Signer} has no permission to burn assets of " + + $"the currency {currency}."; + throw new CurrencyPermissionException(msg, context.Signer, currency); + } + + FungibleAssetValue balance = GetBalance(owner, currency); + + if (balance < value) + { + msg = $"The account {owner}'s balance of {currency} is insufficient to burn: " + + $"{balance} < {value}."; + throw new InsufficientBalanceException(msg, owner, balance); + } + + BigInteger rawBalance = (balance - value).RawValue; + if (currency.TotalSupplyTrackable) + { + var currentTotalSupply = GetTotalSupply(currency); + return UpdateFungibleAssets( + owner, + currency, + rawBalance, + (currentTotalSupply - value).RawValue); + } + else + { + return UpdateFungibleAssets(owner, currency, rawBalance); + } + } + + /// + [Pure] + public IAccount SetValidator(Validator validator) => + UpdateValidatorSet(GetValidatorSet().Update(validator)); + + [Pure] + private MockAccount UpdateState( + Address address, + IValue value) => + new MockAccount( + new MockAccountState( + MockTrie.Add(ToStateKey(address), value)), + TotalUpdatedFungibleAssets); + + [Pure] + private MockAccount UpdateFungibleAssets( + Address address, + Currency currency, + BigInteger amount, + BigInteger? supplyAmount = null) => supplyAmount is { } sa + ? new MockAccount( + new MockAccountState( + MockTrie + .Add(ToFungibleAssetKey(address, currency), new Integer(amount)) + .Add(ToTotalSupplyKey(currency), new Integer(sa))), + TotalUpdatedFungibleAssets.Add((address, currency))) + : new MockAccount( + new MockAccountState( + MockTrie.Add(ToFungibleAssetKey(address, currency), new Integer(amount))), + TotalUpdatedFungibleAssets.Add((address, currency))); + + [Pure] + private MockAccount UpdateValidatorSet(ValidatorSet validatorSet) => + new MockAccount( + new MockAccountState( + MockTrie.Add(ValidatorSetKey, validatorSet.Bencoded)), + TotalUpdatedFungibleAssets); + + [Pure] + private IAccount TransferAssetV0( + Address sender, + Address recipient, + FungibleAssetValue value, + bool allowNegativeBalance = false) + { + if (value.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "The value to transfer has to be greater than zero." + ); + } + + Currency currency = value.Currency; + FungibleAssetValue senderBalance = GetBalance(sender, currency); + FungibleAssetValue recipientBalance = GetBalance(recipient, currency); + + if (!allowNegativeBalance && senderBalance < value) + { + var msg = $"The account {sender}'s balance of {currency} is insufficient to " + + $"transfer: {senderBalance} < {value}."; + throw new InsufficientBalanceException(msg, sender, senderBalance); + } + + return UpdateFungibleAssets(sender, currency, (senderBalance - value).RawValue) + .UpdateFungibleAssets(recipient, currency, (recipientBalance + value).RawValue); + } + + [Pure] + private IAccount TransferAssetV1( + Address sender, + Address recipient, + FungibleAssetValue value, + bool allowNegativeBalance = false) + { + if (value.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "The value to transfer has to be greater than zero." + ); + } + + Currency currency = value.Currency; + FungibleAssetValue senderBalance = GetBalance(sender, currency); + + if (!allowNegativeBalance && senderBalance < value) + { + var msg = $"The account {sender}'s balance of {currency} is insufficient to " + + $"transfer: {senderBalance} < {value}."; + throw new InsufficientBalanceException(msg, sender, senderBalance); + } + + BigInteger senderRawBalance = (senderBalance - value).RawValue; + MockAccount intermediate = UpdateFungibleAssets(sender, currency, senderRawBalance); + FungibleAssetValue recipientBalance = intermediate.GetBalance(recipient, currency); + BigInteger recipientRawBalance = (recipientBalance + value).RawValue; + + return intermediate.UpdateFungibleAssets(recipient, currency, recipientRawBalance); + } + } +} diff --git a/NineChronicles.Headless.Tests/Common/MockAccountState.cs b/NineChronicles.Headless.Tests/Common/MockAccountState.cs new file mode 100644 index 000000000..4fd25d1a3 --- /dev/null +++ b/NineChronicles.Headless.Tests/Common/MockAccountState.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Numerics; +using Bencodex.Types; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Store.Trie; +using Libplanet.Types.Assets; +using Libplanet.Types.Consensus; +using static NineChronicles.Headless.Tests.Common.KeyConverters; + +namespace NineChronicles.Headless.Tests.Common +{ + /// + /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/AccountState.cs + /// except this has its constructors exposed as public for testing. + /// + public class MockAccountState : IAccountState + { + public MockAccountState() : this(ImmutableDictionary.Empty) + { + } + + public MockAccountState(IImmutableDictionary mockTrie) + { + MockTrie = mockTrie; + } + + /// + public ITrie Trie => throw new NotSupportedException(); + + /// + public IImmutableDictionary MockTrie { get; } + + /// + public IValue GetState(Address address) => MockTrie[ToStateKey(address)]; + + /// + public IReadOnlyList GetStates(IReadOnlyList
addresses) => + addresses.Select(address => GetState(address)).ToList(); + + /// + public FungibleAssetValue GetBalance(Address address, Currency currency) + { + IValue? value = MockTrie[ToFungibleAssetKey(address, currency)]; + return value is Integer i + ? FungibleAssetValue.FromRawValue(currency, i) + : currency * 0; + } + + /// + public FungibleAssetValue GetTotalSupply(Currency currency) + { + if (!currency.TotalSupplyTrackable) + { + throw TotalSupplyNotTrackableException.WithDefaultMessage(currency); + } + + IValue? value = MockTrie[ToTotalSupplyKey(currency)]; + return value is Integer i + ? FungibleAssetValue.FromRawValue(currency, i) + : currency * 0; + } + + /// + public ValidatorSet GetValidatorSet() + { + IValue? value = MockTrie[ValidatorSetKey]; + return value is List list + ? new ValidatorSet(list) + : new ValidatorSet(); + } + + // Methods used in unit tests + public MockAccountState SetState(Address address, IValue state) => + new MockAccountState(MockTrie.SetItem(ToStateKey(address), state)); + + public MockAccountState SetBalance(Address address, FungibleAssetValue amount) => + SetBalance((address, amount.Currency), amount.RawValue); + + public MockAccountState SetBalance(Address address, Currency currency, BigInteger rawAmount) => + SetBalance((address, currency), rawAmount); + + public MockAccountState SetBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) => + new MockAccountState(MockTrie.SetItem(ToFungibleAssetKey(pair), (Integer)rawAmount)); + + public MockAccountState AddBalance(Address address, FungibleAssetValue amount) => + AddBalance((address, amount.Currency), amount.RawValue); + + public MockAccountState AddBalance(Address address, Currency currency, BigInteger rawAmount) => + AddBalance((address, currency), rawAmount); + + public MockAccountState AddBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) + { + var amount = GetBalance(pair.Address, pair.Currency).RawValue + rawAmount; + return SetBalance(pair, amount); + } + + public MockAccountState SubtractBalance(Address address, FungibleAssetValue amount) => + SubtractBalance((address, amount.Currency), amount.RawValue); + + public MockAccountState SubtractBalance(Address address, Currency currency, BigInteger rawAmount) => + SubtractBalance((address, currency), rawAmount); + + public MockAccountState SubtractBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) + { + var amount = GetBalance(pair.Address, pair.Currency).RawValue - rawAmount; + return SetBalance(pair, amount); + } + + public MockAccountState TransferBalance(Address sender, Address recipient, FungibleAssetValue amount) => + TransferBalance(sender, recipient, amount.Currency, amount.RawValue); + + public MockAccountState TransferBalance(Address sender, Address recipient, Currency currency, BigInteger rawAmount) => + SubtractBalance(sender, currency, rawAmount).AddBalance(recipient, currency, rawAmount); + + public MockAccountState SetTotalSupply(FungibleAssetValue amount) => + SetTotalSupply(amount.Currency, amount.RawValue); + + public MockAccountState SetTotalSupply(Currency currency, BigInteger rawAmount) => + currency.TotalSupplyTrackable + ? !(currency.MaximumSupply is { } maximumSupply) || rawAmount <= maximumSupply.RawValue + ? new MockAccountState(MockTrie.SetItem(ToTotalSupplyKey(currency), (Integer)rawAmount)) + : throw new ArgumentException( + $"Given {currency}'s total supply is capped at {maximumSupply.RawValue} and " + + $"cannot be set to {rawAmount}.") + : throw new ArgumentException( + $"Given {currency} is not trackable."); + + public MockAccountState AddTotalSupply(FungibleAssetValue amount) => + AddTotalSupply(amount.Currency, amount.RawValue); + + public MockAccountState AddTotalSupply(Currency currency, BigInteger rawAmount) + { + var amount = GetTotalSupply(currency).RawValue + rawAmount; + return SetTotalSupply(currency, amount); + } + + public MockAccountState SubtractTotalSupply(FungibleAssetValue amount) => + SubtractTotalSupply(amount.Currency, amount.RawValue); + + public MockAccountState SubtractTotalSupply(Currency currency, BigInteger rawAmount) + { + var amount = GetTotalSupply(currency).RawValue - rawAmount; + return SetTotalSupply(currency, amount); + } + + public MockAccountState SetValidator(Validator validator) => + new MockAccountState(MockTrie.SetItem(ValidatorSetKey, GetValidatorSet().Update(validator).Bencoded)); + } +} diff --git a/NineChronicles.Headless.Tests/Common/MockState.cs b/NineChronicles.Headless.Tests/Common/MockState.cs deleted file mode 100644 index 2ca86f1b0..000000000 --- a/NineChronicles.Headless.Tests/Common/MockState.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Numerics; -using Bencodex.Types; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Libplanet.Store.Trie; -using Libplanet.Types.Assets; -using Libplanet.Types.Consensus; - -namespace NineChronicles.Headless.Tests.Common -{ - /// - /// A mock implementation of with various overloaded methods for - /// improving QoL. - /// - /// - /// All methods are pretty self-explanatory with no side-effects. There are some caveats: - /// - /// - /// Every balance related operation can accept a negative amount. Each behave as expected. - /// That is, adding negative amount would decrease the balance. - /// - /// - /// Negative balance is allowed for all cases. This includes total supply. - /// - /// - /// Total supply is not automatically tracked. That is, changing the balance associated - /// with an does not change the total supply in any way. - /// Total supply must be explicitly set if needed. - /// - /// - /// There are only few restrictions that apply for manipulating this object, mostly - /// pertaining to total supplies: - /// - /// - /// It is not possible to set a total supply amount for a currency that is - /// not trackable. - /// - /// - /// It is not possible to set a total supply amount that is over the currency's - /// capped maximum total supply. - /// - /// - /// - /// - /// - internal class MockState : IAccountState - { - private static readonly MockState _empty = new MockState(); - private readonly IImmutableDictionary _states; - private readonly IImmutableDictionary<(Address, Currency), BigInteger> _fungibles; - private readonly IImmutableDictionary _totalSupplies; - private readonly ValidatorSet _validatorSet; - - private MockState() - : this( - ImmutableDictionary.Empty, - ImmutableDictionary<(Address Address, Currency Currency), BigInteger>.Empty, - ImmutableDictionary.Empty, - new ValidatorSet()) - { - } - - private MockState( - IImmutableDictionary state, - IImmutableDictionary<(Address Address, Currency Currency), BigInteger> balance, - IImmutableDictionary totalSupplies, - ValidatorSet validatorSet) - { - _states = state; - _fungibles = balance; - _totalSupplies = totalSupplies; - _validatorSet = validatorSet; - } - - public static MockState Empty => _empty; - - public IImmutableDictionary States => _states; - - public IImmutableDictionary<(Address, Currency), BigInteger> Fungibles => _fungibles; - - public IImmutableDictionary TotalSupplies => _totalSupplies; - - public ValidatorSet ValidatorSet => _validatorSet; - - public ITrie Trie => throw new NotSupportedException(); - - public IValue? GetState(Address address) => _states.TryGetValue(address, out IValue? value) - ? value - : null; - - public IReadOnlyList GetStates(IReadOnlyList
addresses) => - addresses.Select(GetState).ToArray(); - - public FungibleAssetValue GetBalance(Address address, Currency currency) => - _fungibles.TryGetValue((address, currency), out BigInteger rawValue) - ? FungibleAssetValue.FromRawValue(currency, rawValue) - : FungibleAssetValue.FromRawValue(currency, 0); - - public FungibleAssetValue GetTotalSupply(Currency currency) - { - if (!currency.TotalSupplyTrackable) - { - var msg = - $"The total supply value of the currency {currency} is not trackable" - + " because it is a legacy untracked currency which might have been" - + " established before the introduction of total supply tracking support."; - throw new TotalSupplyNotTrackableException(msg, currency); - } - - return _totalSupplies.TryGetValue(currency, out var rawValue) - ? FungibleAssetValue.FromRawValue(currency, rawValue) - : FungibleAssetValue.FromRawValue(currency, 0); - } - - public ValidatorSet GetValidatorSet() => _validatorSet; - - public MockState SetState(Address address, IValue state) => - new MockState( - _states.SetItem(address, state), - _fungibles, - _totalSupplies, - _validatorSet); - - public MockState SetBalance(Address address, FungibleAssetValue amount) => - SetBalance((address, amount.Currency), amount.RawValue); - - public MockState SetBalance(Address address, Currency currency, BigInteger rawAmount) => - SetBalance((address, currency), rawAmount); - - public MockState SetBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) => - new MockState( - _states, - _fungibles.SetItem(pair, rawAmount), - _totalSupplies, - _validatorSet); - - public MockState AddBalance(Address address, FungibleAssetValue amount) => - AddBalance((address, amount.Currency), amount.RawValue); - - public MockState AddBalance(Address address, Currency currency, BigInteger rawAmount) => - AddBalance((address, currency), rawAmount); - - public MockState AddBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) => - SetBalance(pair, (_fungibles.TryGetValue(pair, out BigInteger amount) ? amount : 0) + rawAmount); - - public MockState SubtractBalance(Address address, FungibleAssetValue amount) => - SubtractBalance((address, amount.Currency), amount.RawValue); - - public MockState SubtractBalance(Address address, Currency currency, BigInteger rawAmount) => - SubtractBalance((address, currency), rawAmount); - - public MockState SubtractBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) => - SetBalance(pair, (_fungibles.TryGetValue(pair, out BigInteger amount) ? amount : 0) - rawAmount); - - public MockState TransferBalance(Address sender, Address recipient, FungibleAssetValue amount) => - TransferBalance(sender, recipient, amount.Currency, amount.RawValue); - - public MockState TransferBalance(Address sender, Address recipient, Currency currency, BigInteger rawAmount) => - SubtractBalance(sender, currency, rawAmount).AddBalance(recipient, currency, rawAmount); - - public MockState SetTotalSupply(FungibleAssetValue amount) => - SetTotalSupply(amount.Currency, amount.RawValue); - - public MockState SetTotalSupply(Currency currency, BigInteger rawAmount) => - currency.TotalSupplyTrackable - ? !(currency.MaximumSupply is { } maximumSupply) || rawAmount <= maximumSupply.RawValue - ? new MockState( - _states, - _fungibles, - _totalSupplies.SetItem(currency, rawAmount), - _validatorSet) - : throw new ArgumentException( - $"Given {currency}'s total supply is capped at {maximumSupply.RawValue} and " + - $"cannot be set to {rawAmount}.") - : throw new ArgumentException( - $"Given {currency} is not trackable."); - - public MockState AddTotalSupply(FungibleAssetValue amount) => - AddTotalSupply(amount.Currency, amount.RawValue); - - public MockState AddTotalSupply(Currency currency, BigInteger rawAmount) => - SetTotalSupply(currency, (_totalSupplies.TryGetValue(currency, out BigInteger amount) ? amount : 0) + rawAmount); - - public MockState SubtractTotalSupply(FungibleAssetValue amount) => - SubtractTotalSupply(amount.Currency, amount.RawValue); - - public MockState SubtractTotalSupply(Currency currency, BigInteger rawAmount) => - SetTotalSupply(currency, (_totalSupplies.TryGetValue(currency, out BigInteger amount) ? amount : 0) - rawAmount); - - public MockState SetValidator(Validator validator) => - new MockState( - _states, - _fungibles, - _totalSupplies, - _validatorSet.Update(validator)); - } -} diff --git a/NineChronicles.Headless.Tests/Common/MockWorld.cs b/NineChronicles.Headless.Tests/Common/MockWorld.cs new file mode 100644 index 000000000..1ed529553 --- /dev/null +++ b/NineChronicles.Headless.Tests/Common/MockWorld.cs @@ -0,0 +1,67 @@ +using Libplanet.Store.Trie; + +namespace NineChronicles.Headless.Tests.Common +{ + using System.Diagnostics.Contracts; + using Libplanet.Action.State; + using Libplanet.Crypto; + + /// + /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/World.cs + /// except this has its constructors exposed as public for testing. + /// + [Pure] + public class MockWorld : IWorld + { + private readonly IWorldState _baseState; + + public MockWorld(IWorldState baseState) + : this(baseState, new MockWorldDelta()) + { + } + + public MockWorld( + IWorldState baseState, + IWorldDelta delta) + { + _baseState = baseState; + Delta = delta; + Legacy = baseState.Legacy; + } + + /// + public IWorldDelta Delta { get; } + + /// + [Pure] + public ITrie Trie => _baseState.Trie; + + /// + [Pure] + public bool Legacy { get; private set; } + + /// + [Pure] + public IAccount GetAccount(Address address) + { + return Delta.Accounts.TryGetValue(address, out IAccount? account) + ? account + : _baseState.GetAccount(address); + } + + /// + [Pure] + public IWorld SetAccount(Address address, IAccount account) + { + if (!address.Equals(ReservedAddresses.LegacyAccount) + && account.TotalUpdatedFungibleAssets.Count > 0) + { + return this; + } + + return new World( + this, + Delta.SetAccount(address, account)); + } + } +} diff --git a/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs b/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs new file mode 100644 index 000000000..7df6501ed --- /dev/null +++ b/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs @@ -0,0 +1,62 @@ +using System.Linq; + +namespace NineChronicles.Headless.Tests.Common +{ + using System.Collections.Immutable; + using Libplanet.Action.State; + using Libplanet.Crypto; + + /// + /// Almost a replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/WorldDelta.cs + /// except this has its constructors exposed as public for testing. + /// + public class MockWorldDelta : IWorldDelta + { + private IImmutableDictionary _accounts; + + public MockWorldDelta() + { + _accounts = ImmutableDictionary.Empty; + } + + private MockWorldDelta(IImmutableDictionary accounts) + { + _accounts = accounts; + } + + /// + public IImmutableDictionary Accounts + => _accounts + .ToImmutableDictionary(item => item.Key, item => item.Value.Account); + + /// + public IImmutableDictionary Uncommitted + => _accounts + .Where(item => !item.Value.Committed) + .ToImmutableDictionary(item => item.Key, item => item.Value.Account); + + /// + public IWorldDelta SetAccount(Address address, IAccount account) + => new MockWorldDelta(_accounts.SetItem(address, new AccountItem(account, false))); + + /// + public IWorldDelta CommitAccount(Address address) + => _accounts.TryGetValue(address, out AccountItem accountItem) + ? new MockWorldDelta( + _accounts.SetItem(address, new AccountItem(accountItem.Account, true))) + : this; + + internal struct AccountItem + { + public AccountItem(IAccount account, bool committed) + { + Account = account; + Committed = committed; + } + + public IAccount Account { get; } + + public bool Committed { get; set; } + } + } +} diff --git a/NineChronicles.Headless.Tests/Common/MockWorldState.cs b/NineChronicles.Headless.Tests/Common/MockWorldState.cs new file mode 100644 index 000000000..0d3c2cbb9 --- /dev/null +++ b/NineChronicles.Headless.Tests/Common/MockWorldState.cs @@ -0,0 +1,34 @@ +using Libplanet.Store.Trie; + +namespace NineChronicles.Headless.Tests.Common +{ +#nullable enable + + using System.Collections.Immutable; + using Libplanet.Action.State; + using Libplanet.Crypto; + + public class MockWorldState : IWorldState + { + private readonly IImmutableDictionary _accounts; + + public MockWorldState() + : this(ImmutableDictionary.Empty) + { + } + + public MockWorldState(IImmutableDictionary accounts) + { + _accounts = accounts; + } + + public ITrie Trie { get; } + public bool Legacy => true; + + public IImmutableDictionary Accounts => _accounts; + + public IAccount GetAccount(Address address) => _accounts.TryGetValue(address, out IAccount? account) + ? account + : new MockAccount(new MockAccountState()); + } +} diff --git a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs index 2b8aab2ed..58c7f7eab 100644 --- a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs +++ b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs @@ -18,6 +18,7 @@ using Nekoyume.Action; using Nekoyume.Action.Loader; using Nekoyume.Model.State; +using Nekoyume.Module; using NineChronicles.Headless.Utils; namespace NineChronicles.Headless.Tests @@ -93,7 +94,7 @@ public static StandaloneContext CreateStandaloneContext() stateStore, genesisBlock, actionEvaluator); - var currencyFactory = new CurrencyFactory(() => blockchain.GetAccountState(blockchain.Tip.Hash)); + var currencyFactory = new CurrencyFactory(() => blockchain.GetWorldState(blockchain.Tip.Hash)); var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return new StandaloneContext { @@ -132,9 +133,9 @@ PrivateKey minerPrivateKey stateStore, genesisBlock, actionEvaluator); - var ncg = new GoldCurrencyState((Dictionary)blockchain.GetState(Addresses.GoldCurrency)) + var ncg = new GoldCurrencyState((Dictionary)blockchain.GetWorldState().GetLegacyState(Addresses.GoldCurrency)) .Currency; - var currencyFactory = new CurrencyFactory(() => blockchain.GetAccountState(blockchain.Tip.Hash), ncg); + var currencyFactory = new CurrencyFactory(() => blockchain.GetWorldState(blockchain.Tip.Hash), ncg); var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return new StandaloneContext { diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 457fbc1cb..d21ccddd0 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -21,6 +21,7 @@ using Nekoyume.Model; using Nekoyume.Model.EnumType; using Nekoyume.Model.State; +using Nekoyume.Module; using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes; using Xunit; @@ -253,7 +254,7 @@ public async Task TransferAssetWithCurrencyEnum(string currencyType, bool memo) Assert.IsType(plainValue); var actionBase = DeserializeNCAction(plainValue); var action = Assert.IsType(actionBase); - var rawState = _standaloneContext.BlockChain!.GetState(Addresses.GoldCurrency); + var rawState = _standaloneContext.BlockChain!.GetWorldState().GetLegacyState(Addresses.GoldCurrency); var goldCurrencyState = new GoldCurrencyState((Dictionary)rawState); Currency currency = currencyType == "NCG" ? goldCurrencyState.Currency : CrystalCalculator.CRYSTAL; @@ -277,7 +278,7 @@ public async Task TransferAssetWithCurrencyEnum(string currencyType, bool memo) [InlineData("{ ticker: \"CRYSTAL\", minters: [], decimalPlaces: 18 }", false)] public async Task TransferAsset(string valueType, bool memo) { - var rawState = _standaloneContext.BlockChain!.GetState(Addresses.GoldCurrency); + var rawState = _standaloneContext.BlockChain!.GetWorldState().GetLegacyState(Addresses.GoldCurrency); var goldCurrencyState = new GoldCurrencyState((Dictionary)rawState); var recipient = new PrivateKey().Address; diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs index 18af236b6..d70a8efd5 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs @@ -30,6 +30,7 @@ using Lib9c; using Lib9c.Tests; using Nekoyume; +using Nekoyume.Module; using NineChronicles.Headless.Executable.Commands; using NineChronicles.Headless.Executable.IO; using NineChronicles.Headless.Executable.Tests.IO; @@ -141,9 +142,7 @@ public async Task ActivateAccount() Assert.True(result); Address userAddress = StandaloneContextFx.NineChroniclesNodeService!.MinerPrivateKey!.Address; - IValue? state = BlockChain.GetState( - userAddress.Derive(ActivationKey.DeriveKey) - ); + IValue? state = BlockChain.GetWorldState().GetLegacyState(userAddress.Derive(ActivationKey.DeriveKey)); Assert.True((Bencodex.Types.Boolean)state); } @@ -156,7 +155,7 @@ public async Task Transfer(string? memo, bool error) { NineChroniclesNodeService service = StandaloneContextFx.NineChroniclesNodeService!; Currency goldCurrency = new GoldCurrencyState( - (Dictionary)BlockChain.GetState(GoldCurrencyState.Address) + (Dictionary)BlockChain.GetWorldState().GetLegacyState(GoldCurrencyState.Address) ).Currency; Address senderAddress = service.MinerPrivateKey!.Address; @@ -173,7 +172,7 @@ public async Task Transfer(string? memo, bool error) // 10 + 10 (mining rewards) Assert.Equal( 20 * goldCurrency, - BlockChain.GetBalance(senderAddress, goldCurrency) + BlockChain.GetWorldState().GetBalance(senderAddress, goldCurrency) ); var recipientKey = new PrivateKey(); @@ -227,13 +226,13 @@ public async Task Transfer(string? memo, bool error) // 10 + 10 - 17.5(transfer) Assert.Equal( FungibleAssetValue.Parse(goldCurrency, "2.5"), - BlockChain.GetBalance(senderAddress, goldCurrency) + BlockChain.GetWorldState().GetBalance(senderAddress, goldCurrency) ); // 0 + 17.5(transfer) + 10(mining reward) Assert.Equal( FungibleAssetValue.Parse(goldCurrency, "27.5"), - BlockChain.GetBalance(recipient, goldCurrency) + BlockChain.GetWorldState().GetBalance(recipient, goldCurrency) ); } } @@ -243,7 +242,7 @@ public async Task TransferGold() { NineChroniclesNodeService service = StandaloneContextFx.NineChroniclesNodeService!; Currency goldCurrency = new GoldCurrencyState( - (Dictionary)BlockChain.GetState(GoldCurrencyState.Address) + (Dictionary)BlockChain.GetWorldState().GetLegacyState(GoldCurrencyState.Address) ).Currency; Address senderAddress = service.MinerPrivateKey!.Address; @@ -261,7 +260,7 @@ public async Task TransferGold() // 10 + 10 (mining rewards) Assert.Equal( 20 * goldCurrency, - BlockChain.GetBalance(senderAddress, goldCurrency) + BlockChain.GetWorldState().GetBalance(senderAddress, goldCurrency) ); var recipientKey = new PrivateKey(); @@ -288,13 +287,13 @@ public async Task TransferGold() // 10 + 10 - 17.5(transfer) Assert.Equal( FungibleAssetValue.Parse(goldCurrency, "2.5"), - BlockChain.GetBalance(senderAddress, goldCurrency) + BlockChain.GetWorldState().GetBalance(senderAddress, goldCurrency) ); // 0 + 17.5(transfer) + 10(mining reward) Assert.Equal( FungibleAssetValue.Parse(goldCurrency, "27.5"), - BlockChain.GetBalance(recipient, goldCurrency) + BlockChain.GetWorldState().GetBalance(recipient, goldCurrency) ); } @@ -713,50 +712,6 @@ public async Task CombinationConsumable(Address avatarAddress, int recipeId, int }, }; - [Fact] - public async Task MonsterCollect() - { - const string query = @"mutation { - action { - monsterCollect(level: 1) - } - }"; - - ActionBase createAvatar = new CreateAvatar - { - index = 0, - hair = 0, - lens = 0, - ear = 0, - tail = 0, - name = "avatar", - }; - var playerPrivateKey = StandaloneContextFx.NineChroniclesNodeService!.MinerPrivateKey!; - BlockChain.MakeTransaction(playerPrivateKey, new[] { createAvatar }); - Block block = BlockChain.ProposeBlock(playerPrivateKey); - BlockChain.Append(block, GenerateBlockCommit(block.Index, block.Hash, GenesisValidators)); - - Assert.NotNull(BlockChain.GetState(playerPrivateKey.Address)); - var result = await ExecuteQueryAsync(query); - var data = (Dictionary)((ExecutionNode)result.Data!).ToValue()!; - Assert.Null(result.Errors); - - var txIds = BlockChain.GetStagedTransactionIds(); - Assert.Single(txIds); - var tx = BlockChain.GetTransaction(txIds.First()); - var expected = new Dictionary - { - ["action"] = new Dictionary - { - ["monsterCollect"] = tx.Id.ToString(), - } - }; - Assert.Equal(expected, data); - Assert.Single(tx.Actions); - var action = (MonsterCollect)ToAction(tx.Actions!.First()); - Assert.Equal(1, action.level); - } - [Fact] public async Task ClaimMonsterCollectionReward() { @@ -781,7 +736,7 @@ public async Task ClaimMonsterCollectionReward() Block block = BlockChain.ProposeBlock(playerPrivateKey); BlockChain.Append(block, GenerateBlockCommit(block.Index, block.Hash, GenesisValidators)); - Assert.NotNull(BlockChain.GetState(playerPrivateKey.Address)); + Assert.NotNull(BlockChain.GetWorldState().GetAgentState(playerPrivateKey.Address)); var result = await ExecuteQueryAsync(query); var data = (Dictionary)((ExecutionNode)result.Data!).ToValue()!; @@ -965,7 +920,7 @@ public async Task Tx_ActivateAccount() var result = (bool)data["stageTx"]; Assert.True(result); - IValue? state = BlockChain.GetState(privateKey.Address.Derive(ActivationKey.DeriveKey)); + IValue? state = BlockChain.GetWorldState().GetLegacyState(privateKey.Address.Derive(ActivationKey.DeriveKey)); Assert.True((Bencodex.Types.Boolean)state); } diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs index 1f39c76c3..c9f5e06b3 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs @@ -12,6 +12,7 @@ using Bencodex.Types; using GraphQL.Execution; using Libplanet.Action; +using Libplanet.Action.State; using Libplanet.Action.Sys; using Libplanet.Blockchain; using Libplanet.Common; @@ -31,6 +32,7 @@ using Nekoyume.Helper; using Nekoyume.Model; using Nekoyume.Model.State; +using Nekoyume.Module; using Nekoyume.TableData; using NineChronicles.Headless.Properties; using NineChronicles.Headless.Tests.Common; @@ -53,7 +55,7 @@ public StandaloneQueryTest(ITestOutputHelper output) : base(output) public async Task GetState() { Address adminStateAddress = AdminState.Address; - var result = await ExecuteQueryAsync($"query {{ state(address: \"{adminStateAddress}\") }}"); + var result = await ExecuteQueryAsync($"query {{ state(accountAddress: \"{ReservedAddresses.LegacyAccount}\", address: \"{adminStateAddress}\") }}"); var data = (Dictionary)((ExecutionNode)result.Data!).ToValue()!; IValue rawVal = new Codec().Decode(ByteUtil.ParseHex((string)data!["state"])); AdminState adminState = new AdminState((Dictionary)rawVal); @@ -611,7 +613,7 @@ public async Task TransferNCGHistories(string? memo) lastCommit: GenerateBlockCommit(BlockChain.Tip.Index, BlockChain.Tip.Hash, GenesisValidators)); BlockChain.Append(block, GenerateBlockCommit(block.Index, block.Hash, GenesisValidators)); - var currency = new GoldCurrencyState((Dictionary)BlockChain.GetState(Addresses.GoldCurrency)).Currency; + var currency = new GoldCurrencyState((Dictionary)BlockChain.GetWorldState().GetLegacyState(Addresses.GoldCurrency)).Currency; Transaction MakeTx(ActionBase action) { return BlockChain.MakeTransaction(ProposerPrivateKey, new ActionBase[] { action }); @@ -619,7 +621,6 @@ Transaction MakeTx(ActionBase action) var txs = new[] { MakeTx(new TransferAsset0(sender, recipient, new FungibleAssetValue(currency, 1, 0), memo)), - MakeTx(new TransferAsset2(sender, recipient, new FungibleAssetValue(currency, 1, 0), memo)), MakeTx(new TransferAsset(sender, recipient, new FungibleAssetValue(currency, 1, 0), memo)), }; diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index dbc0120a6..7a96f1766 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -8,6 +9,7 @@ using Bencodex.Types; using GraphQL.Execution; using Lib9c; +using Libplanet.Action.State; using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Types.Assets; @@ -90,10 +92,10 @@ public async Task Garage( 2, new Address("0x47D082a115c63E7b58B1532d20E631538eaFADde")); #pragma warning restore CS0618 - MockState mockState = MockState.Empty; + MockAccountState mockAccountState = new MockAccountState(); // NCG - mockState = mockState + mockAccountState = mockAccountState .SetState( Addresses.GoldCurrency, new GoldCurrencyState(goldCurrency).Serialize()); @@ -120,7 +122,7 @@ public async Task Garage( .SetItem("elemental_type", ElementalType.Normal.Serialize()) .SetItem("item_id", HashDigest.FromString(fid).Serialize())); - mockState = mockState + mockAccountState = mockAccountState .SetState( Addresses.GetGarageAddress( agentAddr, @@ -135,7 +137,7 @@ public async Task Garage( // testing without setting up any balance passes the tests; // also this is different from the original test setup as there is no way // to allow state to have "infinite" FAVs with all possible addresses having FAVs - mockState = mockState + mockAccountState = mockAccountState .SetBalance(agentAddr, new FungibleAssetValue(goldCurrency, 99, 99)) .SetBalance(agentAddr, new FungibleAssetValue(Currencies.Crystal, 99, 123456789012345678)) .SetBalance(agentAddr, new FungibleAssetValue(Currencies.Garage, 99, 123456789012345678)) @@ -144,7 +146,10 @@ public async Task Garage( var queryResult = await ExecuteQueryAsync( sb.ToString(), source: new StateContext( - mockState, + new MockWorld(new MockWorldState( + ImmutableDictionary.Empty.Add( + ReservedAddresses.LegacyAccount, + new MockAccount(mockAccountState)))), 0L, new StateMemoryCache())); Assert.Null(queryResult.Errors); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; @@ -237,11 +242,10 @@ public async Task CachedSheet(bool cached, string? expected) cache.SheetCache.SetSheet(cacheKey, (Text)expected, TimeSpan.FromMinutes(1)); } var query = $"{{ cachedSheet(tableName: \"{tableName}\") }}"; - MockState mockState = MockState.Empty; var queryResult = await ExecuteQueryAsync( query, source: new StateContext( - mockState, + new MockWorld(new MockWorldState()), 0L, cache)); Assert.Null(queryResult.Errors); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs index 506212f63..ec601bf20 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AgentStateTypeTest.cs @@ -1,13 +1,16 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using Bencodex.Types; using GraphQL.Execution; +using Libplanet.Action.State; using Libplanet.Crypto; using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Action; using Nekoyume.Helper; using Nekoyume.Model.State; +using Nekoyume.Module; using NineChronicles.Headless.GraphTypes.States; using NineChronicles.Headless.Tests.Common; using Xunit; @@ -57,10 +60,9 @@ public async Task Query(int goldBalance, string goldDecimalString, int crystalBa MonsterCollectionState monsterCollectionState = new MonsterCollectionState(monsterCollectionAddress, 7, 0, Fixtures.TableSheetsFX.MonsterCollectionRewardSheet); Address pledgeAddress = agentState.address.GetPledgeAddress(); - MockState mockState = MockState.Empty + MockAccountState mockAccountState = new MockAccountState() .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize()) .SetState(monsterCollectionAddress, monsterCollectionState.Serialize()) - .SetState(Fixtures.AvatarAddress, Fixtures.AvatarStateFX.Serialize()) .SetState( pledgeAddress, List.Empty @@ -69,10 +71,20 @@ public async Task Query(int goldBalance, string goldDecimalString, int crystalBa .Add(4.Serialize())) .SetBalance(agentState.address, CrystalCalculator.CRYSTAL * crystalBalance) .SetBalance(agentState.address, goldCurrency * goldBalance); + IWorld mockWorld = new MockWorld(new MockWorldState(ImmutableDictionary.Empty.Add( + ReservedAddresses.LegacyAccount, + new MockAccount(mockAccountState)))); + mockWorld = mockWorld.SetAvatarState( + Fixtures.AvatarAddress, + Fixtures.AvatarStateFX, + true, + true, + true, + true); var queryResult = await ExecuteQueryAsync( query, - source: new AgentStateType.AgentStateContext(agentState, mockState, 0, new StateMemoryCache()) + source: new AgentStateType.AgentStateContext(agentState, mockWorld, 0, new StateMemoryCache()) ); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; var expected = new Dictionary() diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs index e21968921..0ebc50e03 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/AvatarStateTypeTest.cs @@ -1,10 +1,15 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using GraphQL.Execution; using Lib9c; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume; using Nekoyume.Action; using Nekoyume.Model.State; +using Nekoyume.Module; using NineChronicles.Headless.GraphTypes.States; using NineChronicles.Headless.Tests.Common; using Xunit; @@ -25,14 +30,20 @@ public async Task Query(AvatarState avatarState, Dictionary expe index inventoryAddress }"; - MockState mockState = MockState.Empty - .SetState(Fixtures.AvatarAddress, Fixtures.AvatarStateFX.Serialize()) - .SetState(Fixtures.UserAddress, Fixtures.AgentStateFx.Serialize()); + IWorld mockWorld = new MockWorld(new MockWorldState()); + mockWorld = mockWorld.SetAvatarState( + Fixtures.AvatarAddress, + Fixtures.AvatarStateFX, + true, + true, + true, + true); + mockWorld = mockWorld.SetAgentState(Fixtures.UserAddress, Fixtures.AgentStateFx); var queryResult = await ExecuteQueryAsync( query, source: new AvatarStateType.AvatarStateContext( avatarState, - mockState, + mockWorld, 0, new StateMemoryCache())); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); @@ -54,14 +65,20 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction } } "; - MockState mockState = MockState.Empty - .SetState(Fixtures.AvatarAddress, Fixtures.AvatarStateFX.Serialize()) - .SetState(Fixtures.UserAddress, Fixtures.AgentStateFx.Serialize()); + IWorld mockWorld = new MockWorld(new MockWorldState()); + mockWorld = mockWorld.SetAvatarState( + Fixtures.AvatarAddress, + Fixtures.AvatarStateFX, + true, + true, + true, + true); + mockWorld = mockWorld.SetAgentState(Fixtures.UserAddress, Fixtures.AgentStateFx); for (int i = 0; i < Fixtures.AvatarStateFX.combinationSlotAddresses.Count; i++) { - mockState = mockState - .SetState( + mockWorld = mockWorld + .SetLegacyState( Fixtures.AvatarStateFX.combinationSlotAddresses[i], Fixtures.CombinationSlotStatesFx[i].Serialize()); } @@ -70,7 +87,7 @@ public async Task QueryWithCombinationSlotState(AvatarState avatarState, Diction query, source: new AvatarStateType.AvatarStateContext( avatarState, - mockState, + mockWorld, 0, new StateMemoryCache())); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs index dd8ae477d..9619249e9 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using GraphQL.Execution; +using Libplanet.Action.State; using Libplanet.Crypto; using Libplanet.Types.Assets; using Nekoyume; @@ -25,7 +27,7 @@ public async Task Query(StakeStateV2 stakeState, Address stakeStateAddress, long var goldCurrency = Currency.Legacy("NCG", 2, null); #pragma warning restore CS0618 - MockState mockState = MockState.Empty + MockAccountState mockAccountState = new MockAccountState() .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize()) .SetBalance(Fixtures.StakeStateAddress, goldCurrency, (goldCurrency * deposit).RawValue); @@ -43,7 +45,10 @@ public async Task Query(StakeStateV2 stakeState, Address stakeStateAddress, long source: new StakeStateType.StakeStateContext( stakeState, stakeStateAddress, - mockState, + new MockWorld(new MockWorldState( + ImmutableDictionary.Empty.Add( + ReservedAddresses.LegacyAccount, + new MockAccount(mockAccountState)))), blockIndex, new StateMemoryCache())); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index dc7d46b51..822579659 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -10,9 +10,7 @@ using GraphQL.Execution; using GraphQL.NewtonsoftJson; using Lib9c; -using Libplanet; using Libplanet.Action; -using Libplanet.Action.Loader; using Libplanet.Action.Sys; using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; @@ -403,7 +401,7 @@ public async Task NcTransactionsOnTip() private Task ExecuteAsync(string query) { - var currencyFactory = new CurrencyFactory(() => _blockChain.GetAccountState(_blockChain.Tip.Hash)); + var currencyFactory = new CurrencyFactory(() => _blockChain.GetWorldState(_blockChain.Tip.Hash)); var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return GraphQLTestUtils.ExecuteQueryAsync(query, standaloneContext: new StandaloneContext { diff --git a/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs b/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs index c1982fb9c..8078cda5f 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/WorldBossScenarioTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Numerics; using System.Threading.Tasks; using Bencodex.Types; @@ -298,17 +299,20 @@ public async Task RaiderList(bool stateExist, long blockIndex, bool prev) } } - private IAccountState GetMockState() + private IWorldState GetMockState() { - return MockState.Empty - .SetState(_raiderStateAddress, _raiderState.Serialize()) - .SetState(Addresses.GetSheetAddress(), @"id,boss_id,started_block_index,ended_block_index,fee,ticket_price,additional_ticket_price,max_purchase_count + return new MockWorld(new MockWorldState(ImmutableDictionary.Empty.Add( + ReservedAddresses.LegacyAccount, + new MockAccount(new MockAccountState() + .SetState(_raiderStateAddress, _raiderState.Serialize()) + .SetState(Addresses.GetSheetAddress(), + @"id,boss_id,started_block_index,ended_block_index,fee,ticket_price,additional_ticket_price,max_purchase_count 1,205005,0,100,300,200,100,10 2,205005,200,300,300,200,100,10 ".Serialize()) - .SetState(_worldBossAddress, _worldBossState.Serialize()) - .SetState(_worldBossKillRewardRecordAddress, _worldBossKillRewardRecord.Serialize()) - .SetState(_raiderListAddress, List.Empty.Add(_raiderStateAddress.Serialize())); + .SetState(_worldBossAddress, _worldBossState.Serialize()) + .SetState(_worldBossKillRewardRecordAddress, _worldBossKillRewardRecord.Serialize()) + .SetState(_raiderListAddress, List.Empty.Add(_raiderStateAddress.Serialize())))))); } private async Task GetRaidId(long blockIndex, bool prev) diff --git a/NineChronicles.Headless/AccountStateExtension.cs b/NineChronicles.Headless/AccountStateExtension.cs deleted file mode 100644 index 9d12ccfb8..000000000 --- a/NineChronicles.Headless/AccountStateExtension.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Bencodex.Types; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Action; -using Nekoyume.Model.State; -using static Lib9c.SerializeKeys; - -namespace NineChronicles.Headless -{ - public static class AccountStateExtension - { - private static readonly string[] AvatarLegacyKeys = - { - LegacyInventoryKey, - LegacyWorldInformationKey, - LegacyQuestListKey, - }; - - public static IReadOnlyList GetAvatarStates( - this IAccountState accountState, - IReadOnlyList
avatarAddresses - ) - { - IReadOnlyDictionary rawAvatarStates = GetRawAvatarStates(accountState, avatarAddresses); - var states = new AvatarState[rawAvatarStates.Count]; - var values = rawAvatarStates.Values.ToArray(); - for (int i = 0; i < rawAvatarStates.Count; i++) - { - states[i] = new AvatarState(values[i]); - } - - return states; - } - - public static AvatarState GetAvatarState(this IAccountState accountState, Address avatarAddress) => - accountState.GetAvatarStates(new[] { avatarAddress })[0]; - - public static IReadOnlyDictionary GetRawAvatarStates( - this IAccountState accountState, - IReadOnlyList
avatarAddresses - ) - { - // Suppose avatarAddresses = [a, b, c] - // Then, addresses = [a, b, c, - // aInventoryKey, bInventoryKey, cInventoryKey, - // aWorldInformationKey, bWorldInformationKey, cWorldInformationKey, - // aQuestListKey, bQuestListKey, cQuestListKey] - var addresses = new Address[avatarAddresses.Count * (AvatarLegacyKeys.Length + 1)]; - for (var i = 0; i < avatarAddresses.Count; i++) - { - var a = avatarAddresses[i]; - addresses[i] = a; - for (int j = 0; j < AvatarLegacyKeys.Length; j++) - { - addresses[avatarAddresses.Count * (j + 1) + i] = a.Derive(AvatarLegacyKeys[j]); - } - } - - IReadOnlyList values = accountState.GetStates(addresses); - var states = new Dictionary(avatarAddresses.Count); - for (var i = 0; i < avatarAddresses.Count; i++) - { - IValue? value = values[i]; - if (!(value is Dictionary serializedAvatar)) - { - throw new InvalidAddressException($"Can't find {nameof(AvatarState)} from {avatarAddresses[i]}"); - } - - Dictionary original = serializedAvatar; - bool v1 = false; - for (int j = 0; j < AvatarLegacyKeys.Length; j++) - { - if (!(values[avatarAddresses.Count * (j + 1) + i] is { } serialized)) - { - v1 = true; - break; - } - - serializedAvatar = serializedAvatar.SetItem(AvatarLegacyKeys[j], serialized); - } - - states[avatarAddresses[i]] = v1 ? original : serializedAvatar; - } - - return states; - } - } -} diff --git a/NineChronicles.Headless/ArenaParticipantsWorker.cs b/NineChronicles.Headless/ArenaParticipantsWorker.cs index 41c8e03ed..33b0d3e82 100644 --- a/NineChronicles.Headless/ArenaParticipantsWorker.cs +++ b/NineChronicles.Headless/ArenaParticipantsWorker.cs @@ -10,15 +10,13 @@ using Microsoft.Extensions.Hosting; using Nekoyume; using Nekoyume.Action; -using Nekoyume.Arena; using Nekoyume.Battle; using Nekoyume.Model.Arena; using Nekoyume.Model.EnumType; -using Nekoyume.Model.Item; using Nekoyume.Model.State; +using Nekoyume.Module; using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes; -using NineChronicles.Headless.Properties; using Serilog; using static Lib9c.SerializeKeys; @@ -77,12 +75,12 @@ public void GetArenaParticipants() var tip = blockChain.Tip; var blockIndex = blockChain.Tip.Index; - var accountState = blockChain.GetAccountState(tip.Hash); - var currentRoundData = accountState.GetSheet().GetRoundByBlockIndex(blockIndex); + var worldState = blockChain.GetWorldState(tip.Hash); + var currentRoundData = worldState.GetSheet().GetRoundByBlockIndex(blockIndex); var participantsAddr = ArenaParticipants.DeriveAddress( currentRoundData.ChampionshipId, currentRoundData.Round); - var participants = accountState.GetState(participantsAddr) is List participantsList + var participants = worldState.GetLegacyState(participantsAddr) is List participantsList ? new ArenaParticipants(participantsList) : null; var cacheKey = $"{currentRoundData.ChampionshipId}_{currentRoundData.Round}"; @@ -103,7 +101,7 @@ public void GetArenaParticipants() currentRoundData.Round))) .ToArray(); // NOTE: If addresses is too large, and split and get separately. - var scores = accountState.GetStates( + var scores = worldState.GetLegacyStates( avatarAndScoreAddrList.Select(tuple => tuple.Item2).ToList()); var avatarAddrAndScores = new List<(Address avatarAddr, int score)>(); for (int i = 0; i < avatarAddrList.Count; i++) @@ -176,10 +174,10 @@ public void GetArenaParticipants() currentRank + 1)); } - var runeListSheet = accountState.GetSheet(); - var costumeSheet = accountState.GetSheet(); - var characterSheet = accountState.GetSheet(); - var runeOptionSheet = accountState.GetSheet(); + var runeListSheet = worldState.GetSheet(); + var costumeSheet = worldState.GetSheet(); + var characterSheet = worldState.GetSheet(); + var runeOptionSheet = worldState.GetSheet(); var runeIds = runeListSheet.Values.Select(x => x.Id).ToList(); var row = characterSheet[GameConfig.DefaultAvatarCharacterId]; var addrBulk = avatarAddrAndScoresWithRank @@ -197,33 +195,19 @@ public void GetArenaParticipants() addrBulk.AddRange(runeIds.Select(x => RuneState.DeriveAddress(tuple.avatarAddr, x))); } - var states = accountState.GetStates(addrBulk); - var stateBulk = new Dictionary(); - for (int i = 0; i < addrBulk.Count; i++) - { - var address = addrBulk[i]; - var value = states[i]; - stateBulk.TryAdd(address, value ?? Null.Value); - } var runeStates = new List(); var result = avatarAddrAndScoresWithRank.Select(tuple => { var (avatarAddr, score, rank) = tuple; - var avatar = new AvatarState((Dictionary)stateBulk[avatarAddr]); - if (stateBulk[avatarAddr.Derive(LegacyInventoryKey)] is List inventoryList) - { - var inventory = new Inventory(inventoryList); - avatar.inventory = inventory; - } - + var avatar = worldState.GetAvatarState(avatarAddr); var itemSlotState = - stateBulk[ItemSlotState.DeriveAddress(avatarAddr, BattleType.Arena)] is + worldState.GetLegacyState(ItemSlotState.DeriveAddress(avatarAddr, BattleType.Arena)) is List itemSlotList ? new ItemSlotState(itemSlotList) : new ItemSlotState(BattleType.Arena); var runeSlotState = - stateBulk[RuneSlotState.DeriveAddress(avatarAddr, BattleType.Arena)] is + worldState.GetLegacyState(RuneSlotState.DeriveAddress(avatarAddr, BattleType.Arena)) is List runeSlotList ? new RuneSlotState(runeSlotList) : new RuneSlotState(BattleType.Arena); @@ -232,7 +216,7 @@ List runeSlotList foreach (var id in runeIds) { var address = RuneState.DeriveAddress(avatarAddr, id); - if (stateBulk[address] is List runeStateList) + if (worldState.GetLegacyState(address) is List runeStateList) { runeStates.Add(new RuneState(runeStateList)); } diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index a05b128f7..7e2a53f1a 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Bencodex; using Bencodex.Types; -using Libplanet.Action.State; using Libplanet.Blockchain; using Libplanet.Common; using Libplanet.Crypto; @@ -20,12 +19,11 @@ using Libplanet.Types.Tx; using MagicOnion; using MagicOnion.Server; -using MessagePack; using Microsoft.Extensions.Caching.Memory; using Nekoyume; using Nekoyume.Shared.Services; using Serilog; -using Nekoyume.Model.State; +using Nekoyume.Module; using Sentry; using static NineChronicles.Headless.NCActionUtils; using NodeExceptionType = Libplanet.Headless.NodeExceptionType; @@ -122,11 +120,12 @@ public UnaryResult PutTransaction(byte[] txBytes) } } - public UnaryResult GetState(byte[] addressBytes, byte[] blockHashBytes) + public UnaryResult GetState(byte[] addressBytes, byte[] accountAddressBytes, byte[] blockHashBytes) { var address = new Address(addressBytes); + var accountAddress = new Address(accountAddressBytes); var hash = new BlockHash(blockHashBytes); - IValue state = _blockChain.GetAccountState(hash).GetState(address); + IValue state = _blockChain.GetState(address, accountAddress, hash); // FIXME: Null과 null 구분해서 반환해야 할 듯 byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); @@ -136,24 +135,25 @@ public UnaryResult GetStateBySrh(byte[] addressBytes, byte[] stateRootHa { var stateRootHash = new HashDigest(stateRootHashBytes); var address = new Address(addressBytes); - IValue state = _blockChain.GetAccountState(stateRootHash).GetState(address); + IValue state = _blockChain.GetState(address, stateRootHash); byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); } - public async UnaryResult> GetAvatarStates(IEnumerable addressBytesList, byte[] blockHashBytes) + public async UnaryResult> GetAvatarStates( + IEnumerable addressBytesList, + byte[] blockHashBytes) { var hash = new BlockHash(blockHashBytes); - var accountState = _blockChain.GetAccountState(hash); + var worldState = _blockChain.GetWorldState(hash); var result = new ConcurrentDictionary(); var addresses = addressBytesList.Select(a => new Address(a)).ToList(); - var rawAvatarStates = accountState.GetRawAvatarStates(addresses); - var taskList = rawAvatarStates - .Select(pair => Task.Run(() => - { - result.TryAdd(pair.Key.ToByteArray(), _codec.Encode(pair.Value)); - })) - .ToList(); + var taskList = addresses.Select(address => Task.Run(() => + { + result.TryAdd( + address.ToByteArray(), + new Codec().Encode(worldState.GetAvatarState(address).SerializeList())); + })); await Task.WhenAll(taskList); return result.ToDictionary(kv => kv.Key, kv => kv.Value); @@ -161,17 +161,18 @@ public async UnaryResult> GetAvatarStates(IEnumerable public async UnaryResult> GetAvatarStatesBySrh( IEnumerable addressBytesList, - byte[] stateRootHashBytes) + byte[] worldStateRootHashBytes) { - var stateRootHash = new HashDigest(stateRootHashBytes); - var accountState = _blockChain.GetAccountState(stateRootHash); - var result = new ConcurrentDictionary(); var addresses = addressBytesList.Select(a => new Address(a)).ToList(); - var rawAvatarStates = accountState.GetRawAvatarStates(addresses); - var taskList = rawAvatarStates - .Select(pair => Task.Run(() => + var stateRootHash = new HashDigest(worldStateRootHashBytes); + var worldState = _blockChain.GetWorldState(stateRootHash); + var result = new ConcurrentDictionary(); + var taskList = addresses + .Select(address => Task.Run(() => { - result.TryAdd(pair.Key.ToByteArray(), _codec.Encode(pair.Value)); + result.TryAdd( + address.ToByteArray(), + new Codec().Encode(worldState.GetAvatarState(address).SerializeList())); })) .ToList(); @@ -179,12 +180,17 @@ public async UnaryResult> GetAvatarStatesBySrh( return result.ToDictionary(kv => kv.Key, kv => kv.Value); } - public UnaryResult> GetStateBulk(IEnumerable addressBytesList, byte[] blockHashBytes) + public UnaryResult> GetStateBulk( + IEnumerable addressBytesList, + byte[] accountBytes, + byte[] blockHashBytes) { var hash = new BlockHash(blockHashBytes); var result = new Dictionary(); Address[] addresses = addressBytesList.Select(b => new Address(b)).ToArray(); - IReadOnlyList values = _blockChain.GetStates(addresses, hash); + var accountAddress = new Address(accountBytes); + IReadOnlyList values = + addresses.Select(address => _blockChain.GetState(address, accountAddress, hash)).ToList(); for (int i = 0; i < addresses.Length; i++) { result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); @@ -236,7 +242,7 @@ public UnaryResult> GetSheets( if (addresses.Any()) { var stateRootHash = new BlockHash(stateRootHashBytes); - IReadOnlyList values = _blockChain.GetAccountState(stateRootHash).GetStates(addresses); + IReadOnlyList values = _blockChain.GetWorldState(stateRootHash).GetLegacyStates(addresses); sw.Stop(); Log.Information("[GetSheets]Get sheet from state: {Count}, Elapsed: {Elapsed}", addresses.Count, sw.Elapsed); sw.Restart(); @@ -252,13 +258,19 @@ public UnaryResult> GetSheets( return new UnaryResult>(result); } - public UnaryResult GetBalance(byte[] addressBytes, byte[] currencyBytes, byte[] blockHashBytes) + public UnaryResult GetBalance( + byte[] addressBytes, + byte[] currencyBytes, + byte[] accountBytes, + byte[] blockHashBytes) { var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); - var hash = new BlockHash(blockHashBytes); - FungibleAssetValue balance = _blockChain.GetAccountState(hash).GetBalance(address, currency); + var accountAddress = new Address(accountBytes); + var blockHash = new BlockHash(blockHashBytes); + FungibleAssetValue balance = _blockChain.GetWorldState(blockHash) + .GetAccount(accountAddress).GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( new IValue[] diff --git a/NineChronicles.Headless/GraphTypes/ActionMutation.cs b/NineChronicles.Headless/GraphTypes/ActionMutation.cs index 03da59e92..782b98bcd 100644 --- a/NineChronicles.Headless/GraphTypes/ActionMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActionMutation.cs @@ -1,4 +1,3 @@ -using Bencodex.Types; using GraphQL; using GraphQL.Types; using Libplanet.Crypto; @@ -10,6 +9,7 @@ using Serilog; using System; using System.Collections.Generic; +using Nekoyume.Module; namespace NineChronicles.Headless.GraphTypes { @@ -439,49 +439,6 @@ public ActionMutation(NineChroniclesNodeService service) } ); - Field>(nameof(MonsterCollect), - description: "Start monster collect.", - arguments: new QueryArguments( - new QueryArgument> - { - Name = "level", - Description = "The monster collection level.(1 ~ 7)" - } - ), - resolve: context => - { - try - { - BlockChain? blockChain = service.BlockChain; - if (blockChain is null) - { - throw new InvalidOperationException($"{nameof(blockChain)} is null."); - } - - if (service.MinerPrivateKey is null) - { - throw new InvalidOperationException($"{nameof(service.MinerPrivateKey)} is null."); - } - - int level = context.GetArgument("level"); - var action = new MonsterCollect - { - level = level, - }; - - var actions = new ActionBase[] { action }; - Transaction tx = blockChain.MakeTransaction(service.MinerPrivateKey, actions); - return tx.Id; - } - catch (Exception e) - { - var msg = $"Unexpected exception occurred during {typeof(ActionMutation)}: {e}"; - context.Errors.Add(new ExecutionError(msg, e)); - throw; - } - } - ); - Field>(nameof(ClaimMonsterCollectionReward), description: "Get monster collection reward.", arguments: new QueryArguments( @@ -509,8 +466,10 @@ public ActionMutation(NineChroniclesNodeService service) Address avatarAddress = context.GetArgument
("avatarAddress"); Address agentAddress = service.MinerPrivateKey.Address; - AgentState agentState = new AgentState((Dictionary)service.BlockChain.GetState(agentAddress)); - + AgentState agentState = + service.BlockChain.GetWorldState().GetAgentState(agentAddress) ?? + throw new InvalidOperationException($"Given agent of address {agentAddress} does not exist."); + var action = new ClaimMonsterCollectionReward { avatarAddress = avatarAddress, diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 7e092c6ba..81d34b1db 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -13,6 +13,7 @@ using Nekoyume.Action; using Nekoyume.Model; using Nekoyume.Model.State; +using Nekoyume.Module; using Nekoyume.TableData; namespace NineChronicles.Headless.GraphTypes @@ -437,7 +438,7 @@ public ActionQuery(StandaloneContext standaloneContext) { var activationCode = context.GetArgument("activationCode"); var activationKey = ActivationKey.Decode(activationCode); - if (standaloneContext.BlockChain!.GetState(activationKey.PendingAddress) is Dictionary dictionary) + if (standaloneContext.BlockChain!.GetWorldState().GetLegacyState(activationKey.PendingAddress) is Dictionary dictionary) { var pending = new PendingActivationState(dictionary); var action = activationKey.CreateActivateAccount(pending.Nonce); diff --git a/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs b/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs index 00eb229d2..4575105f5 100644 --- a/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActivationStatusMutation.cs @@ -2,9 +2,11 @@ using Bencodex.Types; using GraphQL; using GraphQL.Types; +using Libplanet.Action; using Nekoyume.Action; using Nekoyume.Model; using Nekoyume.Model.State; +using Nekoyume.Module; namespace NineChronicles.Headless.GraphTypes { @@ -39,7 +41,7 @@ public ActivationStatusMutation(NineChroniclesNodeService service) throw new InvalidOperationException($"{nameof(blockChain)} is null."); } - IValue state = blockChain.GetState(activationKey.PendingAddress); + IValue state = blockChain.GetWorldState().GetLegacyState(activationKey.PendingAddress); if (!(state is Bencodex.Types.Dictionary asDict)) { @@ -51,7 +53,7 @@ public ActivationStatusMutation(NineChroniclesNodeService service) ActivateAccount action = activationKey.CreateActivateAccount( pendingActivationState.Nonce); - var actions = new ActionBase[] { action }; + var actions = new IAction[] { action }; blockChain.MakeTransaction(privateKey, actions); } catch (ArgumentException ae) diff --git a/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs b/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs index b0717d119..542fcdc69 100644 --- a/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActivationStatusQuery.cs @@ -7,6 +7,7 @@ using Nekoyume.Model.State; using System; using Libplanet.Explorer.GraphTypes; +using Nekoyume.Module; using Log = Serilog.Log; namespace NineChronicles.Headless.GraphTypes @@ -44,14 +45,14 @@ public ActivationStatusQuery(StandaloneContext standaloneContext) Address userAddress = privateKey.Address; Address activatedAddress = userAddress.Derive(ActivationKey.DeriveKey); - if (blockChain.GetState(activatedAddress) is Bencodex.Types.Boolean) + if (blockChain.GetWorldState().GetLegacyState(activatedAddress) is Bencodex.Types.Boolean) { return true; } // Preserve previous check code due to migration period. // TODO: Remove this code after v100061+ - IValue state = blockChain.GetState(ActivatedAccountsState.Address); + IValue state = blockChain.GetWorldState().GetLegacyState(ActivatedAccountsState.Address); if (state is Bencodex.Types.Dictionary asDict) { @@ -101,14 +102,14 @@ public ActivationStatusQuery(StandaloneContext standaloneContext) var userAddress = context.GetArgument
("address"); Address activatedAddress = userAddress.Derive(ActivationKey.DeriveKey); - if (blockChain.GetState(activatedAddress) is Bencodex.Types.Boolean) + if (blockChain.GetWorldState().GetLegacyState(activatedAddress) is Bencodex.Types.Boolean) { return true; } // backward for launcher E2E test. // TODO: Remove this code after launcher E2E test fixed. - IValue state = blockChain.GetState(ActivatedAccountsState.Address); + IValue state = blockChain.GetWorldState().GetLegacyState(ActivatedAccountsState.Address); if (state is Bencodex.Types.Dictionary asDict) { diff --git a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs index dd1b523c7..70ef414e6 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs @@ -13,6 +13,7 @@ using Nekoyume.Model.State; using Serilog; using System; +using Nekoyume.Module; namespace NineChronicles.Headless.GraphTypes { @@ -194,9 +195,8 @@ IConfiguration configuration } BlockChain blockChain = service.BlockChain; - var currency = new GoldCurrencyState( - (Dictionary)blockChain.GetState(new Address(context.GetArgument("currencyAddress"))) - ).Currency; + var currency = new GoldCurrencyState((Dictionary)blockChain.GetWorldState() + .GetLegacyState(new Address(context.GetArgument("currencyAddress")))).Currency; FungibleAssetValue amount = FungibleAssetValue.Parse(currency, context.GetArgument("amount")); @@ -253,7 +253,7 @@ IConfiguration configuration BlockChain blockChain = service.BlockChain; var currency = new GoldCurrencyState( - (Dictionary)blockChain.GetState(GoldCurrencyState.Address) + (Dictionary)blockChain.GetWorldState().GetLegacyState(GoldCurrencyState.Address) ).Currency; FungibleAssetValue amount = FungibleAssetValue.Parse(currency, context.GetArgument("amount")); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index 869f38e04..ca33c2acc 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -7,6 +7,7 @@ using GraphQL; using GraphQL.Types; using Lib9c; +using Libplanet.Action.State; using Libplanet.Blockchain; using Libplanet.Common; using Libplanet.Crypto; @@ -20,6 +21,7 @@ using Nekoyume.Model.State; using Nekoyume.TableData; using Nekoyume.Model; +using Nekoyume.Module; using NineChronicles.Headless.GraphTypes.States; using static NineChronicles.Headless.NCActionUtils; using Transaction = Libplanet.Types.Tx.Transaction; @@ -56,7 +58,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi } return new StateContext( - chain.GetAccountState(blockHash), + chain.GetWorldState(blockHash), blockHash switch { BlockHash bh => chain[bh].Index, @@ -70,7 +72,8 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi Field( name: "state", arguments: new QueryArguments( - new QueryArgument> { Name = "address", Description = "The address of state to fetch from the chain." }, + new QueryArgument> { Name = "address", Description = "The address of state to fetch from the account." }, + new QueryArgument> { Name = "accountAddress", Description = "The address of account to fetch from the chain." }, new QueryArgument { Name = "hash", Description = "The hash of the block used to fetch state from chain." } ), resolve: context => @@ -82,12 +85,13 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi } var address = context.GetArgument
("address"); + var accountAddress = context.GetArgument
("accountAddress"); var blockHashByteArray = context.GetArgument("hash"); var blockHash = blockHashByteArray is null ? blockChain.Tip.Hash : new BlockHash(blockHashByteArray); - var state = blockChain.GetStates(new[] { address }, blockHash)[0]; + var state = blockChain.GetState(address, accountAddress, blockHash); if (state is null) { @@ -204,13 +208,12 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi ? blockChain.Tip.Hash : new BlockHash(blockHashByteArray); Currency currency = new GoldCurrencyState( - (Dictionary)blockChain.GetState(GoldCurrencyState.Address) + (Dictionary)blockChain.GetWorldState(blockHash).GetLegacyState(GoldCurrencyState.Address) ).Currency; - return blockChain.GetBalance( + return blockChain.GetWorldState(blockHash).GetBalance( address, - currency, - blockHash + currency ).GetQuantityString(); } ); @@ -308,24 +311,22 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi BlockHash offset = blockChain.Tip.Hash; + IWorldState worldState = blockChain.GetWorldState(offset); #pragma warning disable S3247 - if (blockChain.GetStates(new[] { agentAddress }, offset)[0] is Dictionary agentDict) + if (worldState.GetAgentState(agentAddress) is { } agentState) #pragma warning restore S3247 { - AgentState agentState = new AgentState(agentDict); - Address deriveAddress = MonsterCollectionState.DeriveAddress(agentAddress, agentState.MonsterCollectionRound); + Address deriveAddress = + MonsterCollectionState.DeriveAddress(agentAddress, agentState.MonsterCollectionRound); Currency currency = new GoldCurrencyState( - (Dictionary)blockChain.GetStates(new[] { Addresses.GoldCurrency }, offset)[0] - ).Currency; + (Dictionary)worldState.GetLegacyState(Addresses.GoldCurrency)).Currency; - FungibleAssetValue balance = blockChain.GetBalance(agentAddress, currency, offset); - if (blockChain.GetStates(new[] { deriveAddress }, offset)[0] is Dictionary mcDict) + FungibleAssetValue balance = worldState.GetBalance(agentAddress, currency); + if (worldState.GetLegacyState(deriveAddress) is Dictionary mcDict) { var rewardSheet = new MonsterCollectionRewardSheet(); - var csv = blockChain.GetStates( - new[] { Addresses.GetSheetAddress() }, - offset - )[0].ToDotnetString(); + var csv = worldState.GetLegacyState( + Addresses.GetSheetAddress()).ToDotnetString(); rewardSheet.Set(csv); var monsterCollectionState = new MonsterCollectionState(mcDict); long tipIndex = blockChain.Tip.Index; @@ -371,7 +372,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi string invitationCode = context.GetArgument("invitationCode"); ActivationKey activationKey = ActivationKey.Decode(invitationCode); - if (blockChain.GetState(activationKey.PendingAddress) is Dictionary dictionary) + if (blockChain.GetWorldState().GetLegacyState(activationKey.PendingAddress) is Dictionary dictionary) { var pending = new PendingActivationState(dictionary); ActivateAccount action = activationKey.CreateActivateAccount(pending.Nonce); @@ -415,7 +416,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi { throw new ExecutionError("invitationCode format is invalid."); } - if (blockChain.GetState(activationKey.PendingAddress) is Dictionary dictionary) + if (blockChain.GetWorldState().GetLegacyState(activationKey.PendingAddress) is Dictionary dictionary) { var pending = new PendingActivationState(dictionary); return ByteUtil.Hex(pending.Nonce); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index d0c10c9b0..55c5f4720 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -9,7 +9,6 @@ using Libplanet.Net; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; @@ -27,6 +26,7 @@ using Libplanet.Store; using Libplanet.Types.Tx; using Microsoft.Extensions.Configuration; +using Nekoyume.Module; using Serilog; namespace NineChronicles.Headless.GraphTypes @@ -229,12 +229,6 @@ public StandaloneSubscription(StandaloneContext standaloneContext, IConfiguratio .Subscribe(RenderBlock); ActionRenderer actionRenderer = standaloneContext.NineChroniclesNodeService!.ActionRenderer; - actionRenderer.EveryRender() - .ObserveOn(NewThreadScheduler.Default) - .Subscribe(RenderMonsterCollectionStateSubject); - actionRenderer.EveryRender() - .ObserveOn(NewThreadScheduler.Default) - .Subscribe(RenderMonsterCollectionStateSubject); actionRenderer.EveryRender() .ObserveOn(NewThreadScheduler.Default) .Subscribe(RenderMonsterCollectionStateSubject); @@ -368,13 +362,11 @@ private void RenderBlock((Block OldTip, Block NewTip) pair) BlockChain blockChain = StandaloneContext.NineChroniclesNodeService.BlockChain; Currency currency = new GoldCurrencyState( - (Dictionary)blockChain.GetStates(new[] { Addresses.GoldCurrency }, _tipHeader.Hash)[0] + (Dictionary)blockChain.GetWorldState(_tipHeader.Hash).GetLegacyState(Addresses.GoldCurrency) ).Currency; var rewardSheet = new MonsterCollectionRewardSheet(); - var csv = blockChain.GetStates( - new[] { Addresses.GetSheetAddress() }, - _tipHeader.Hash - )[0].ToDotnetString(); + var csv = blockChain.GetWorldState(_tipHeader.Hash) + .GetLegacyState(Addresses.GetSheetAddress()).ToDotnetString(); rewardSheet.Set(csv); Log.Debug($"StandaloneSubscription.RenderBlock target addresses. (count: {StandaloneContext.AgentAddresses.Count})"); StandaloneContext.AgentAddresses @@ -409,15 +401,14 @@ private void RenderForAgent( ReplaySubject balanceSubject, MonsterCollectionRewardSheet rewardSheet) { - FungibleAssetValue agentBalance = blockChain.GetBalance(address, currency, tipHeader.Hash); + FungibleAssetValue agentBalance = blockChain.GetWorldState(tipHeader.Hash).GetBalance(address, currency); balanceSubject.OnNext(agentBalance.GetQuantityString(true)); - if (blockChain.GetStates(new[] { address }, tipHeader.Hash)[0] is Dictionary rawAgent) + if (blockChain.GetWorldState(tipHeader.Hash).GetAgentState(address) is { } agentState) { - AgentState agentState = new AgentState(rawAgent); Address deriveAddress = MonsterCollectionState.DeriveAddress(address, agentState.MonsterCollectionRound); if (agentState.avatarAddresses.Any() && - blockChain.GetStates(new[] { deriveAddress }, tipHeader.Hash)[0] is Dictionary collectDict) + blockChain.GetWorldState(tipHeader.Hash).GetLegacyState(deriveAddress) is Dictionary collectDict) { var monsterCollectionState = new MonsterCollectionState(collectDict); List rewards = monsterCollectionState.CalculateRewards( @@ -454,9 +445,8 @@ private void RenderMonsterCollectionStateSubject(ActionEvaluation eval) foreach (var (address, subjects) in StandaloneContext.AgentAddresses) { if (eval.Signer.Equals(address) && - service.BlockChain.GetStates(new[] { address }, _tipHeader?.Hash)[0] is Dictionary agentDict) + service.BlockChain.GetWorldState(_tipHeader?.Hash).GetAgentState(address) is { } agentState) { - var agentState = new AgentState(agentDict); Address deriveAddress = MonsterCollectionState.DeriveAddress(address, agentState.MonsterCollectionRound); var subject = subjects.stateSubject; if (service.BlockChain.GetAccountState(eval.OutputState).GetState(deriveAddress) is Dictionary state) diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index d088fb5af..d1b7b4de9 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -18,6 +18,7 @@ using Nekoyume.Model.Item; using Nekoyume.Model.Stake; using Nekoyume.Model.State; +using Nekoyume.Module; using Nekoyume.TableData; using Nekoyume.TableData.Crystal; using Nekoyume.TableData.Stake; @@ -43,8 +44,8 @@ public StateQuery() try { return new AvatarStateType.AvatarStateContext( - context.AccountState.GetAvatarState(address), - context.AccountState, + context.WorldState.GetAvatarState(address), + context.WorldState, context.BlockIndex, context.StateMemoryCache); } catch (InvalidAddressException) @@ -97,7 +98,7 @@ public StateQuery() resolve: context => { var index = context.GetArgument("index"); - if (context.Source.GetState(RankingState.Derive(index)) is { } state) + if (context.Source.WorldState.GetLegacyState(RankingState.Derive(index)) is { } state) { return new RankingMapState((Dictionary)state); } @@ -108,7 +109,7 @@ public StateQuery() name: "shop", description: "State for shop.", deprecationReason: "Shop is migrated to ShardedShop and not using now. Use shardedShop() instead.", - resolve: context => context.Source.GetState(Addresses.Shop) is { } state + resolve: context => context.Source.WorldState.GetLegacyState(Addresses.Shop) is { } state ? new ShopState((Dictionary)state) : null); Field( @@ -130,7 +131,7 @@ public StateQuery() var subType = context.GetArgument("itemSubType"); var nonce = context.GetArgument("nonce").ToString("X").ToLower(); - if (context.Source.GetState(ShardedShopStateV2.DeriveAddress(subType, nonce)) is { } state) + if (context.Source.WorldState.GetLegacyState(ShardedShopStateV2.DeriveAddress(subType, nonce)) is { } state) { return new ShardedShopStateV2((Dictionary)state); } @@ -150,20 +151,20 @@ public StateQuery() { var index = context.GetArgument("index"); var arenaAddress = WeeklyArenaState.DeriveAddress(index); - if (context.Source.GetState(arenaAddress) is { } state) + if (context.Source.WorldState.GetLegacyState(arenaAddress) is { } state) { var arenastate = new WeeklyArenaState((Dictionary)state); if (arenastate.OrderedArenaInfos.Count == 0) { var listAddress = arenaAddress.Derive("address_list"); - if (context.Source.GetState(listAddress) is List rawList) + if (context.Source.WorldState.GetLegacyState(listAddress) is List rawList) { var addressList = rawList.ToList(StateExtensions.ToAddress); var arenaInfos = new List(); foreach (var address in addressList) { var infoAddress = arenaAddress.Derive(address.ToByteArray()); - if (context.Source.GetState(infoAddress) is Dictionary rawInfo) + if (context.Source.WorldState.GetLegacyState(infoAddress) is Dictionary rawInfo) { var info = new ArenaInfo(rawInfo); arenaInfos.Add(info); @@ -213,8 +214,8 @@ public StateQuery() return ( address, - new ArenaInformation((List)context.Source.GetState(infoAddr)!), - new ArenaScore((List)context.Source.GetState(scoreAddr)!) + new ArenaInformation((List)context.Source.WorldState.GetLegacyState(infoAddr)!), + new ArenaScore((List)context.Source.WorldState.GetLegacyState(scoreAddr)!) ); } ); @@ -231,11 +232,11 @@ public StateQuery() resolve: context => { var address = context.GetArgument
("address"); - if (context.Source.GetState(address) is Dictionary state) + if (context.Source.WorldState.GetAgentState(address) is { } agentState) { return new AgentStateType.AgentStateContext( - new AgentState(state), - context.Source.AccountState, + agentState, + context.Source.WorldState, context.Source.BlockIndex, context.Source.StateMemoryCache ); @@ -248,12 +249,12 @@ public StateQuery() StakeStateType.StakeStateContext? GetStakeState(StateContext ctx, Address agentAddress) { var stakeStateAddress = StakeState.DeriveAddress(agentAddress); - if (ctx.AccountState.TryGetStakeStateV2(agentAddr: agentAddress, out StakeStateV2 stakeStateV2)) + if (ctx.WorldState.TryGetStakeStateV2(agentAddr: agentAddress, out StakeStateV2 stakeStateV2)) { return new StakeStateType.StakeStateContext( stakeStateV2, stakeStateAddress, - ctx.AccountState, + ctx.WorldState, ctx.BlockIndex, ctx.StateMemoryCache ); @@ -309,13 +310,12 @@ public StateQuery() resolve: context => { var agentAddress = context.GetArgument
("agentAddress"); - if (!(context.Source.GetState(agentAddress) is Dictionary value)) + if (!(context.Source.WorldState.GetAgentState(agentAddress) is { } agentState)) { return null; } - var agentState = new AgentState(value); var deriveAddress = MonsterCollectionState.DeriveAddress(agentAddress, agentState.MonsterCollectionRound); - if (context.Source.GetState(deriveAddress) is Dictionary state) + if (context.Source.WorldState.GetLegacyState(deriveAddress) is Dictionary state) { return new MonsterCollectionState(state); } @@ -330,13 +330,9 @@ public StateQuery() { var sheetAddress = Addresses.GetSheetAddress(); var rewardSheetAddress = Addresses.GetSheetAddress(); - IReadOnlyList values = context.Source.GetStates(new[] - { - sheetAddress, - rewardSheetAddress, - }); - if (values[0] is Text ss && - values[1] is Text srs) + IValue sheetValue = context.Source.WorldState.GetLegacyState(sheetAddress); + IValue rewardSheetValue = context.Source.WorldState.GetLegacyState(rewardSheetAddress); + if (sheetValue is Text ss && rewardSheetValue is Text srs) { var monsterCollectionSheet = new MonsterCollectionSheet(); monsterCollectionSheet.Set(ss); @@ -354,7 +350,8 @@ public StateQuery() description: "The latest stake rewards based on StakePolicySheet.", resolve: context => { - var stakePolicySheetStateValue = context.Source.GetState(Addresses.GetSheetAddress()); + var stakePolicySheetStateValue = + context.Source.WorldState.GetLegacyState(Addresses.GetSheetAddress()); var stakePolicySheet = new StakePolicySheet(); if (stakePolicySheetStateValue is not Text stakePolicySheetStateText) { @@ -363,13 +360,14 @@ public StateQuery() stakePolicySheet.Set(stakePolicySheetStateText); - IReadOnlyList values = context.Source.GetStates(new[] - { - Addresses.GetSheetAddress(stakePolicySheet["StakeRegularFixedRewardSheet"].Value), - Addresses.GetSheetAddress(stakePolicySheet["StakeRegularRewardSheet"].Value), - }); + IValue fixedRewardSheetValue = + context.Source.WorldState.GetLegacyState( + Addresses.GetSheetAddress(stakePolicySheet["StakeRegularFixedRewardSheet"].Value)); + IValue rewardSheetValue = + context.Source.WorldState.GetLegacyState( + Addresses.GetSheetAddress(stakePolicySheet["StakeRegularRewardSheet"].Value)); - if (!(values[0] is Text fsv && values[1] is Text sv)) + if (!(fixedRewardSheetValue is Text fsv && rewardSheetValue is Text sv)) { return null; } @@ -393,19 +391,18 @@ public StateQuery() if (context.Source.BlockIndex < StakeState.StakeRewardSheetV2Index) { stakeRegularRewardSheet = new StakeRegularRewardSheet(); - stakeRegularRewardSheet.Set(ClaimStakeReward8.V1.StakeRegularRewardSheetCsv); + //stakeRegularRewardSheet.Set(ClaimStakeReward8.V1.StakeRegularRewardSheetCsv); stakeRegularFixedRewardSheet = new StakeRegularFixedRewardSheet(); - stakeRegularFixedRewardSheet.Set(ClaimStakeReward8.V1.StakeRegularFixedRewardSheetCsv); + //stakeRegularFixedRewardSheet.Set(ClaimStakeReward8.V1.StakeRegularFixedRewardSheetCsv); } else { - IReadOnlyList values = context.Source.GetStates(new[] - { - Addresses.GetSheetAddress(), - Addresses.GetSheetAddress() - }); + IValue rewardSheetValue = context.Source.WorldState.GetLegacyState( + Addresses.GetSheetAddress()); + IValue fixedRewardSheetValue = context.Source.WorldState.GetLegacyState( + Addresses.GetSheetAddress()); - if (!(values[0] is Text sv && values[1] is Text fsv)) + if (!(rewardSheetValue is Text sv && fixedRewardSheetValue is Text fsv)) { return null; } @@ -424,7 +421,7 @@ public StateQuery() resolve: context => { var sheetAddress = Addresses.GetSheetAddress(); - IValue? sheetValue = context.Source.GetState(sheetAddress); + IValue? sheetValue = context.Source.WorldState.GetLegacyState(sheetAddress); if (sheetValue is Text sv) { var crystalMonsterCollectionMultiplierSheet = new CrystalMonsterCollectionMultiplierSheet(); @@ -447,8 +444,8 @@ public StateQuery() { var avatarAddress = context.GetArgument
("avatarAddress"); var address = avatarAddress.Derive("recipe_ids"); - IReadOnlyList values = context.Source.AccountState.GetStates(new[] { address }); - if (values[0] is List rawRecipeIds) + IValue value = context.Source.WorldState.GetLegacyState(address); + if (value is List rawRecipeIds) { return rawRecipeIds.ToList(StateExtensions.ToInteger); } @@ -469,8 +466,8 @@ public StateQuery() { var avatarAddress = context.GetArgument
("avatarAddress"); var address = avatarAddress.Derive("world_ids"); - IReadOnlyList values = context.Source.AccountState.GetStates(new[] { address }); - if (values[0] is List rawWorldIds) + IValue value = context.Source.WorldState.GetLegacyState(address); + if (value is List rawWorldIds) { return rawWorldIds.ToList(StateExtensions.ToInteger); } @@ -492,7 +489,7 @@ public StateQuery() resolve: context => { var raiderAddress = context.GetArgument
("raiderAddress"); - if (context.Source.GetState(raiderAddress) is List list) + if (context.Source.WorldState.GetLegacyState(raiderAddress) is List list) { return new RaiderState(list); } @@ -522,7 +519,7 @@ public StateQuery() var prev = context.GetArgument("prev"); var sheet = new WorldBossListSheet(); var address = Addresses.GetSheetAddress(); - if (context.Source.GetState(address) is Text text) + if (context.Source.WorldState.GetLegacyState(address) is Text text) { sheet.Set(text); } @@ -543,7 +540,7 @@ public StateQuery() resolve: context => { var bossAddress = context.GetArgument
("bossAddress"); - if (context.Source.GetState(bossAddress) is List list) + if (context.Source.WorldState.GetLegacyState(bossAddress) is List list) { return new WorldBossState(list); } @@ -562,7 +559,7 @@ public StateQuery() resolve: context => { var address = context.GetArgument
("worldBossKillRewardRecordAddress"); - if (context.Source.GetState(address) is List list) + if (context.Source.WorldState.GetLegacyState(address) is List list) { return new WorldBossKillRewardRecord(list); } @@ -586,7 +583,7 @@ public StateQuery() { var address = context.GetArgument
("address"); var currency = context.GetArgument("currency"); - return context.Source.GetBalance(address, currency); + return context.Source.WorldState.GetBalance(address, currency); } ); @@ -600,7 +597,7 @@ public StateQuery() resolve: context => { var address = context.GetArgument
("raiderListAddress"); - if (context.Source.GetState(address) is List list) + if (context.Source.WorldState.GetLegacyState(address) is List list) { return list.ToList(StateExtensions.ToAddress); } @@ -618,7 +615,7 @@ public StateQuery() { var avatarAddress = context.GetArgument
("avatarAddress"); var orderDigestListAddress = OrderDigestListState.DeriveAddress(avatarAddress); - if (context.Source.GetState(orderDigestListAddress) is Dictionary d) + if (context.Source.WorldState.GetLegacyState(orderDigestListAddress) is Dictionary d) { return new OrderDigestListState(d); } @@ -638,7 +635,7 @@ public StateQuery() Address? address = null; bool approved = false; int mead = 0; - if (context.Source.GetState(pledgeAddress) is List l) + if (context.Source.WorldState.GetLegacyState(pledgeAddress) is List l) { address = l[0].ToAddress(); approved = l[1].ToBoolean(); @@ -671,12 +668,12 @@ public StateQuery() var blockIndex = context.Source.BlockIndex; var currentAvatarAddr = context.GetArgument
("avatarAddress"); var filterBounds = context.GetArgument("filterBounds"); - var currentRoundData = context.Source.AccountState.GetSheet().GetRoundByBlockIndex(blockIndex); + var currentRoundData = context.Source.WorldState.GetSheet().GetRoundByBlockIndex(blockIndex); int playerScore = ArenaScore.ArenaScoreDefault; var cacheKey = $"{currentRoundData.ChampionshipId}_{currentRoundData.Round}"; List result = new(); var scoreAddr = ArenaScore.DeriveAddress(currentAvatarAddr, currentRoundData.ChampionshipId, currentRoundData.Round); - var scoreState = context.Source.GetState(scoreAddr); + var scoreState = context.Source.WorldState.GetLegacyState(scoreAddr); if (scoreState is List scores) { playerScore = (Integer)scores[1]; diff --git a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs index 9cfdf8051..2d16c20f4 100644 --- a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs @@ -10,6 +10,7 @@ using Libplanet.Explorer.GraphTypes; using Nekoyume; using Nekoyume.Model.Garages; +using Nekoyume.Module; using NineChronicles.Headless.GraphTypes.States; namespace NineChronicles.Headless.GraphTypes; @@ -66,7 +67,7 @@ private void RegisterGarages() throw new ExecutionError($"Invalid currency enum: {currencyEnum}"); } - var balance = context.Source.GetBalance(garageBalanceAddr, currency); + var balance = context.Source.WorldState.GetBalance(garageBalanceAddr, currency); garageBalances.Add(balance); } } @@ -79,7 +80,7 @@ private void RegisterGarages() throw new ExecutionError($"Invalid currency ticker: {currencyTicker}"); } - var balance = context.Source.GetBalance(garageBalanceAddr, currency); + var balance = context.Source.WorldState.GetBalance(garageBalanceAddr, currency); garageBalances.Add(balance); } } @@ -97,7 +98,8 @@ private void RegisterGarages() agentAddr, HashDigest.FromString(fungibleItemId))) .ToArray(); - fungibleItemGarages = context.Source.GetStates(fungibleItemGarageAddresses) + fungibleItemGarages = fungibleItemGarageAddresses + .Select(address => context.Source.WorldState.GetLegacyState(address)) .Select((value, i) => value is null or Null ? (fungibleItemIds[i], fungibleItemGarageAddresses[i], null) : (fungibleItemIds[i], fungibleItemGarageAddresses[i], new FungibleItemGarage(value))); diff --git a/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs b/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs index 1e203b6f7..f0b18afa7 100644 --- a/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AgentStateType.cs @@ -10,6 +10,7 @@ using Nekoyume.Helper; using Nekoyume.Model.Quest; using Nekoyume.Model.State; +using Nekoyume.Module; using NineChronicles.Headless.GraphTypes.States.Models; using static Lib9c.SerializeKeys; @@ -19,8 +20,8 @@ public class AgentStateType : ObjectGraphType { public class AgentStateContext : StateContext { - public AgentStateContext(AgentState agentState, IAccountState accountState, long blockIndex, StateMemoryCache stateMemoryCache) - : base(accountState, blockIndex, stateMemoryCache) + public AgentStateContext(AgentState agentState, IWorldState worldState, long blockIndex, StateMemoryCache stateMemoryCache) + : base(worldState, blockIndex, stateMemoryCache) { AgentState = agentState; } @@ -45,10 +46,10 @@ public AgentStateType() resolve: context => { IReadOnlyList
avatarAddresses = context.Source.GetAvatarAddresses(); - return context.Source.AccountState.GetAvatarStates(avatarAddresses).Select( + return avatarAddresses.Select(avatarAddress => context.Source.WorldState.GetAvatarState(avatarAddress)).Select( x => new AvatarStateType.AvatarStateContext( x, - context.Source.AccountState, + context.Source.WorldState, context.Source.BlockIndex, context.Source.StateMemoryCache)); }); @@ -58,10 +59,10 @@ public AgentStateType() resolve: context => { Currency currency = new GoldCurrencyState( - (Dictionary)context.Source.GetState(GoldCurrencyState.Address)! + (Dictionary)context.Source.WorldState.GetLegacyState(GoldCurrencyState.Address)! ).Currency; - return context.Source.GetBalance( + return context.Source.WorldState.GetBalance( context.Source.AgentAddress, currency ).GetQuantityString(true); @@ -80,7 +81,7 @@ public AgentStateType() context.Source.AgentAddress, context.Source.AgentState.MonsterCollectionRound ); - if (context.Source.GetState(monsterCollectionAddress) is { } state) + if (context.Source.WorldState.GetLegacyState(monsterCollectionAddress) is { } state) { return new MonsterCollectionState((Dictionary)state).Level; } @@ -93,43 +94,15 @@ public AgentStateType() resolve: context => { IReadOnlyList
avatarAddresses = context.Source.GetAvatarAddresses(); - var addresses = new Address[avatarAddresses.Count * 2]; - for (int i = 0; i < avatarAddresses.Count; i++) - { - addresses[i] = avatarAddresses[i].Derive(LegacyQuestListKey); - addresses[avatarAddresses.Count + i] = avatarAddresses[i]; - } - - IReadOnlyList values = context.Source.GetStates(addresses); - for (int i = 0; i < avatarAddresses.Count; i++) - { - if (values[i] is { } rawQuestList) - { - var questList = new QuestList((Dictionary)rawQuestList); - var traded = IsTradeQuestCompleted(questList); - if (traded) - { - return true; - } - } - else if (values[avatarAddresses.Count + i] is { } state) - { - var avatarState = new AvatarState((Dictionary)state); - var traded = IsTradeQuestCompleted(avatarState.questList); - if (traded) - { - return true; - } - } - } - - return false; + IEnumerable avatarStates = + avatarAddresses.Select(address => context.Source.WorldState.GetAvatarState(address)); + return avatarStates.Any(avatarState => IsTradeQuestCompleted(avatarState.questList)); } ); Field>( "crystal", description: "Current CRYSTAL.", - resolve: context => context.Source.GetBalance( + resolve: context => context.Source.WorldState.GetBalance( context.Source.AgentAddress, CrystalCalculator.CRYSTAL ).GetQuantityString(true)); @@ -142,7 +115,7 @@ public AgentStateType() Address? address = null; bool approved = false; int mead = 0; - if (context.Source.GetState(pledgeAddress) is List l) + if (context.Source.WorldState.GetLegacyState(pledgeAddress) is List l) { address = l[0].ToAddress(); approved = l[1].ToBoolean(); diff --git a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs index fd16acd87..87ffcaae4 100644 --- a/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/AvatarStateType.cs @@ -8,6 +8,7 @@ using Libplanet.Action.State; using Nekoyume.Action; using Nekoyume.Model.State; +using Nekoyume.Module; using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes.States.Models; using NineChronicles.Headless.GraphTypes.States.Models.World; @@ -21,8 +22,8 @@ public class AvatarStateType : ObjectGraphType { - if (!(context.Source.GetState(context.Source.AvatarState.agentAddress) is Dictionary dictionary)) + if (context.Source.WorldState.GetAgentState(context.Source.AvatarState.agentAddress) is not + { } agentState) { throw new InvalidOperationException(); } - var agentState = new AgentState(dictionary); return agentState.avatarAddresses .First(x => x.Value.Equals(context.Source.AvatarState.address)) .Key; @@ -118,11 +119,11 @@ public AvatarStateType() description: "Rune list of avatar", resolve: context => { - var runeSheet = context.Source.AccountState.GetSheet(); + var runeSheet = context.Source.WorldState.GetSheet(); var runeList = new List(); foreach (var rune in runeSheet) { - var runeState = context.Source.GetState( + var runeState = context.Source.WorldState.GetLegacyState( RuneState.DeriveAddress(context.Source.AvatarState.address, rune.Id) ); if (runeState is not null) @@ -144,7 +145,7 @@ public AvatarStateType() resolve: context => { var addresses = context.Source.AvatarState.combinationSlotAddresses; - return context.Source.AccountState.GetStates(addresses) + return context.Source.WorldState.GetLegacyStates(addresses) .OfType() .Select(x => new CombinationSlotState(x)); }); diff --git a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs index 9d82d3f4b..bea0c8f7a 100644 --- a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs @@ -1,21 +1,14 @@ -using System; using System.Collections.Generic; using Bencodex.Types; using GraphQL; using GraphQL.Types; -using Libplanet.Action; using Libplanet.Explorer.GraphTypes; using Libplanet.Action.State; using Libplanet.Crypto; using Nekoyume.Model.State; -using NineChronicles.Headless.GraphTypes.States.Models; -using NineChronicles.Headless.GraphTypes.States.Models.World; -using NineChronicles.Headless.GraphTypes.States.Models.Item; -using NineChronicles.Headless.GraphTypes.States.Models.Mail; -using NineChronicles.Headless.GraphTypes.States.Models.Quest; -using Nekoyume.Blockchain.Policy; using Nekoyume; using Nekoyume.Model.Stake; +using Nekoyume.Module; using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes.Abstractions; @@ -25,8 +18,8 @@ public class StakeStateType : ObjectGraphType { public class StakeStateContext : StateContext { - public StakeStateContext(StakeStateV2 stakeState, Address address, IAccountState accountState, long blockIndex, StateMemoryCache stateMemoryCache) - : base(accountState, blockIndex, stateMemoryCache) + public StakeStateContext(StakeStateV2 stakeState, Address address, IWorldState worldState, long blockIndex, StateMemoryCache stateMemoryCache) + : base(worldState, blockIndex, stateMemoryCache) { StakeState = stakeState; Address = address; @@ -45,9 +38,10 @@ public StakeStateType() Field>( "deposit", description: "The staked amount.", - resolve: context => context.Source.AccountState.GetBalance( + resolve: context => context.Source.WorldState.GetBalance( context.Source.Address, - new GoldCurrencyState((Dictionary)context.Source.GetState(GoldCurrencyState.Address)!).Currency) + new GoldCurrencyState( + (Dictionary)context.Source.WorldState.GetLegacyState(GoldCurrencyState.Address)!).Currency) .GetQuantityString(true)); Field>( "startedBlockIndex", @@ -79,7 +73,7 @@ public StakeStateType() return null; } - IReadOnlyList values = context.Source.GetStates(new[] + IReadOnlyList values = context.Source.WorldState.GetLegacyStates(new[] { Addresses.GetSheetAddress(contract.StakeRegularFixedRewardSheetTableName), Addresses.GetSheetAddress(contract.StakeRegularRewardSheetTableName), diff --git a/NineChronicles.Headless/GraphTypes/States/StateContext.cs b/NineChronicles.Headless/GraphTypes/States/StateContext.cs index 25853a11b..df6b63e22 100644 --- a/NineChronicles.Headless/GraphTypes/States/StateContext.cs +++ b/NineChronicles.Headless/GraphTypes/States/StateContext.cs @@ -12,18 +12,18 @@ namespace NineChronicles.Headless.GraphTypes.States public class StateContext { public StateContext( - IAccountState accountState, + IWorldState worldState, long blockIndex, StateMemoryCache stateMemoryCache) { - AccountState = accountState; + WorldState = worldState; BlockIndex = blockIndex; - CurrencyFactory = new CurrencyFactory(() => accountState); + CurrencyFactory = new CurrencyFactory(() => worldState); FungibleAssetValueFactory = new FungibleAssetValueFactory(CurrencyFactory); StateMemoryCache = stateMemoryCache; } - public IAccountState AccountState { get; } + public IWorldState WorldState { get; } public long BlockIndex { get; } @@ -31,12 +31,6 @@ public StateContext( public FungibleAssetValueFactory FungibleAssetValueFactory { get; } - public IValue? GetState(Address address) => AccountState.GetState(address); - - public IReadOnlyList GetStates(IReadOnlyList
addresses) => AccountState.GetStates(addresses); - - public FungibleAssetValue GetBalance(Address address, Currency currency) => AccountState.GetBalance(address, currency); - public StateMemoryCache StateMemoryCache { get; } } } diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 181f14e6e..96dbec06d 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -14,7 +14,6 @@ using Libplanet.Headless.Hosting; using Libplanet.Net; using Libplanet.Store; -using Libplanet.Types.Blocks; using Microsoft.Extensions.Hosting; using Nekoyume.Blockchain; using Nekoyume.Blockchain.Policy; @@ -293,7 +292,7 @@ internal void ConfigureContext(StandaloneContext standaloneContext) standaloneContext.Store = Store; standaloneContext.Swarm = Swarm; standaloneContext.CurrencyFactory = - new CurrencyFactory(() => standaloneContext.BlockChain.GetAccountState(standaloneContext.BlockChain.Tip.Hash)); + new CurrencyFactory(() => standaloneContext.BlockChain.GetWorldState(standaloneContext.BlockChain.Tip.Hash)); standaloneContext.FungibleAssetValueFactory = new FungibleAssetValueFactory(standaloneContext.CurrencyFactory); BootstrapEnded.WaitAsync().ContinueWith((task) => diff --git a/NineChronicles.Headless/Utils/CurrencyFactory.cs b/NineChronicles.Headless/Utils/CurrencyFactory.cs index 2c2945bf4..af92ef1cf 100644 --- a/NineChronicles.Headless/Utils/CurrencyFactory.cs +++ b/NineChronicles.Headless/Utils/CurrencyFactory.cs @@ -5,20 +5,21 @@ using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Model.State; +using Nekoyume.Module; using NineChronicles.Headless.GraphTypes; namespace NineChronicles.Headless.Utils; public class CurrencyFactory { - private readonly Func _accountStateGetter; + private readonly Func _worldStateGetter; private Currency? _ncg; public CurrencyFactory( - Func accountStateGetter, + Func worldStateGetter, Currency? ncg = null) { - _accountStateGetter = accountStateGetter; + _worldStateGetter = worldStateGetter; _ncg = ncg; } @@ -52,7 +53,7 @@ public bool TryGetCurrency(string ticker, out Currency currency) return _ncg; } - var value = _accountStateGetter().GetState(Addresses.GoldCurrency); + var value = _worldStateGetter().GetLegacyState(Addresses.GoldCurrency); if (value is Dictionary goldCurrencyDict) { var goldCurrency = new GoldCurrencyState(goldCurrencyDict); From 1abe2fd5fcbc5c53f5cf596ba4482e234d87a53d Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Thu, 28 Dec 2023 18:28:23 +0900 Subject: [PATCH 03/36] feat: add newly added graph types --- NineChronicles.Headless/GraphQLServiceExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NineChronicles.Headless/GraphQLServiceExtensions.cs b/NineChronicles.Headless/GraphQLServiceExtensions.cs index 4679ef4e7..b6c77733b 100644 --- a/NineChronicles.Headless/GraphQLServiceExtensions.cs +++ b/NineChronicles.Headless/GraphQLServiceExtensions.cs @@ -46,6 +46,8 @@ public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); return services; } From 5d6fcbe4ddf7e7b0c6f0754da738dd82f085cdca Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Fri, 5 Jan 2024 11:13:12 +0900 Subject: [PATCH 04/36] bump: lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index d61bea413..cf6ae5955 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit d61bea4133030986c07c942cfd34792f23c6f437 +Subproject commit cf6ae595581d805ac39869a48f29849be5b4807b From 9b751e5881efa68ecb88c99736829d67343dd2eb Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Fri, 5 Jan 2024 11:13:31 +0900 Subject: [PATCH 05/36] bump: NineChronicles.RPC.Shared --- NineChronicles.RPC.Shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index dea4eb519..8e1dc231f 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit dea4eb5193adf8228206172d214626a1e433e56f +Subproject commit 8e1dc231f6c79ab2a2443e010d524e2927dd766f From 3dd420181d634b1af9bd0a42e156cbdedbe05194 Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Fri, 5 Jan 2024 13:53:42 +0900 Subject: [PATCH 06/36] feat: adjust libplanet API changes --- .../Commands/ReplayCommand.Privates.cs | 26 +++++----- .../GraphTypes/ActionQueryTest.cs | 1 - NineChronicles.Headless/BlockChainService.cs | 48 +++++++++---------- .../GraphTypes/StandaloneQuery.cs | 10 ++-- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index 9c5aa894a..e5f91af7f 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -117,34 +117,34 @@ public IAccountState GetAccountState(HashDigest? hash) _source.GetAccountState(hash), _rocksDb); - public IAccountState GetAccountState(Address address, BlockHash? offset) + public IAccountState GetAccountState(BlockHash? offset, Address address) => new LocalCacheAccountState( - _source.GetAccountState(address, offset), + _source.GetAccountState(offset, address), _rocksDb); - public IValue? GetState(Address address, Address accountAddress, BlockHash? offset) - => GetAccountState(accountAddress, offset).GetState(address); + public IValue? GetState(BlockHash? offset, Address accountAddress, Address address) + => GetAccountState(offset, accountAddress).GetState(address); - public IValue? GetState(Address address, HashDigest? stateRootHash) + public IValue? GetState(HashDigest? stateRootHash, Address address) => GetAccountState(stateRootHash).GetState(address); - public FungibleAssetValue GetBalance(Address address, Currency currency, HashDigest? stateRootHash) + public FungibleAssetValue GetBalance(HashDigest? stateRootHash, Address address, Currency currency) => GetAccountState(stateRootHash).GetBalance(address, currency); - public FungibleAssetValue GetBalance(Address address, Currency currency, Address accountAddress, BlockHash? offset) - => GetAccountState(accountAddress, offset).GetBalance(address, currency); + public FungibleAssetValue GetBalance(BlockHash? offset, Address address, Currency currency) + => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetBalance(address, currency); - public FungibleAssetValue GetTotalSupply(Currency currency, HashDigest? stateRootHash) + public FungibleAssetValue GetTotalSupply(HashDigest? stateRootHash, Currency currency) => GetAccountState(stateRootHash).GetTotalSupply(currency); - public FungibleAssetValue GetTotalSupply(Currency currency, Address accountAddress, BlockHash? offset) - => GetAccountState(accountAddress, offset).GetTotalSupply(currency); + public FungibleAssetValue GetTotalSupply(BlockHash? offset, Currency currency) + => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetTotalSupply(currency); public ValidatorSet GetValidatorSet(HashDigest? stateRootHash) => GetAccountState(stateRootHash).GetValidatorSet(); - public ValidatorSet GetValidatorSet(Address accountAddress, BlockHash? offset) - => GetAccountState(accountAddress, offset).GetValidatorSet(); + public ValidatorSet GetValidatorSet(BlockHash? offset) + => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetValidatorSet(); } private sealed class LocalCacheWorldState : IWorldState diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index d21ccddd0..47ec4c484 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -1134,7 +1134,6 @@ public async Task LoadIntoMyGarages( var actualAction = Assert.IsType(actionBase); Assert.True(expectedAction.FungibleAssetValues?.SequenceEqual(actualAction.FungibleAssetValues) ?? actualAction.FungibleAssetValues is null); - Assert.Equal(expectedAction.InventoryAddr, actualAction.InventoryAddr); Assert.True(expectedAction.FungibleIdAndCounts?.SequenceEqual(actualAction.FungibleIdAndCounts) ?? actualAction.FungibleIdAndCounts is null); Assert.Equal(expectedAction.Memo, actualAction.Memo); diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 7e2a53f1a..2c1e2acab 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -120,29 +120,29 @@ public UnaryResult PutTransaction(byte[] txBytes) } } - public UnaryResult GetState(byte[] addressBytes, byte[] accountAddressBytes, byte[] blockHashBytes) + public UnaryResult GetState(byte[] blockHashBytes, byte[] accountAddressBytes, byte[] addressBytes) { - var address = new Address(addressBytes); - var accountAddress = new Address(accountAddressBytes); var hash = new BlockHash(blockHashBytes); - IValue state = _blockChain.GetState(address, accountAddress, hash); + var accountAddress = new Address(accountAddressBytes); + var address = new Address(addressBytes); + IValue state = _blockChain.GetState(hash, accountAddress, address); // FIXME: Null과 null 구분해서 반환해야 할 듯 byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); } - public UnaryResult GetStateBySrh(byte[] addressBytes, byte[] stateRootHashBytes) + public UnaryResult GetStateBySrh(byte[] stateRootHashBytes, byte[] addressBytes) { var stateRootHash = new HashDigest(stateRootHashBytes); var address = new Address(addressBytes); - IValue state = _blockChain.GetState(address, stateRootHash); + IValue state = _blockChain.GetState(stateRootHash, address); byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); } public async UnaryResult> GetAvatarStates( - IEnumerable addressBytesList, - byte[] blockHashBytes) + byte[] blockHashBytes, + IEnumerable addressBytesList) { var hash = new BlockHash(blockHashBytes); var worldState = _blockChain.GetWorldState(hash); @@ -160,8 +160,8 @@ public async UnaryResult> GetAvatarStates( } public async UnaryResult> GetAvatarStatesBySrh( - IEnumerable addressBytesList, - byte[] worldStateRootHashBytes) + byte[] worldStateRootHashBytes, + IEnumerable addressBytesList) { var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var stateRootHash = new HashDigest(worldStateRootHashBytes); @@ -181,16 +181,16 @@ public async UnaryResult> GetAvatarStatesBySrh( } public UnaryResult> GetStateBulk( - IEnumerable addressBytesList, + byte[] blockHashBytes, byte[] accountBytes, - byte[] blockHashBytes) + IEnumerable addressBytesList) { var hash = new BlockHash(blockHashBytes); var result = new Dictionary(); Address[] addresses = addressBytesList.Select(b => new Address(b)).ToArray(); var accountAddress = new Address(accountBytes); IReadOnlyList values = - addresses.Select(address => _blockChain.GetState(address, accountAddress, hash)).ToList(); + addresses.Select(address => _blockChain.GetState(hash, accountAddress, address)).ToList(); for (int i = 0; i < addresses.Length; i++) { result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); @@ -200,8 +200,8 @@ public UnaryResult> GetStateBulk( } public UnaryResult> GetStateBulkBySrh( - IEnumerable addressBytesList, - byte[] stateRootHashBytes) + byte[] stateRootHashBytes, + IEnumerable addressBytesList) { var stateRootHash = new HashDigest(stateRootHashBytes); var result = new Dictionary(); @@ -216,8 +216,8 @@ public UnaryResult> GetStateBulkBySrh( } public UnaryResult> GetSheets( - IEnumerable addressBytesList, - byte[] stateRootHashBytes) + byte[] stateRootHashBytes, + IEnumerable addressBytesList) { var started = DateTime.UtcNow; var sw = new Stopwatch(); @@ -259,16 +259,16 @@ public UnaryResult> GetSheets( } public UnaryResult GetBalance( - byte[] addressBytes, - byte[] currencyBytes, + byte[] blockHashBytes, byte[] accountBytes, - byte[] blockHashBytes) + byte[] addressBytes, + byte[] currencyBytes) { + var blockHash = new BlockHash(blockHashBytes); + var accountAddress = new Address(accountBytes); var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); - var accountAddress = new Address(accountBytes); - var blockHash = new BlockHash(blockHashBytes); FungibleAssetValue balance = _blockChain.GetWorldState(blockHash) .GetAccount(accountAddress).GetBalance(address, currency); byte[] encoded = _codec.Encode( @@ -283,10 +283,10 @@ public UnaryResult GetBalance( return new UnaryResult(encoded); } - public UnaryResult GetBalanceBySrh(byte[] addressBytes, byte[] currencyBytes, byte[] stateRootHashBytes) + public UnaryResult GetBalanceBySrh(byte[] stateRootHashBytes, byte[] addressBytes, byte[] currencyBytes) { - var address = new Address(addressBytes); var stateRootHash = new HashDigest(stateRootHashBytes); + var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain.GetAccountState(stateRootHash).GetBalance(address, currency); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index ca33c2acc..ff6cf2148 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -72,9 +72,9 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi Field( name: "state", arguments: new QueryArguments( - new QueryArgument> { Name = "address", Description = "The address of state to fetch from the account." }, + new QueryArgument { Name = "hash", Description = "The hash of the block used to fetch state from chain." }, new QueryArgument> { Name = "accountAddress", Description = "The address of account to fetch from the chain." }, - new QueryArgument { Name = "hash", Description = "The hash of the block used to fetch state from chain." } + new QueryArgument> { Name = "address", Description = "The address of state to fetch from the account." } ), resolve: context => { @@ -84,14 +84,14 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); } - var address = context.GetArgument
("address"); - var accountAddress = context.GetArgument
("accountAddress"); var blockHashByteArray = context.GetArgument("hash"); var blockHash = blockHashByteArray is null ? blockChain.Tip.Hash : new BlockHash(blockHashByteArray); + var accountAddress = context.GetArgument
("accountAddress"); + var address = context.GetArgument
("address"); - var state = blockChain.GetState(address, accountAddress, blockHash); + var state = blockChain.GetState(blockHash, accountAddress, address); if (state is null) { From 7335644b75ce2c3d1eccd009e538a1aa92ed7592 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 8 Jan 2024 10:07:18 +0900 Subject: [PATCH 07/36] Bump lib9c to a56d8ea52 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index cf6ae5955..a56d8ea52 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit cf6ae595581d805ac39869a48f29849be5b4807b +Subproject commit a56d8ea52e16a7785996c255454accaeae6a8f22 From a41a8fdf1b5dd423404e12efeef2af59550c39a1 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 3 Jan 2024 22:22:35 +0900 Subject: [PATCH 08/36] Bump fix --- NineChronicles.Headless.Tests/Common/MockAccount.cs | 12 ++++++++++++ NineChronicles.Headless/GraphQLServiceExtensions.cs | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.Tests/Common/MockAccount.cs b/NineChronicles.Headless.Tests/Common/MockAccount.cs index 668cc0c2a..587c8653f 100644 --- a/NineChronicles.Headless.Tests/Common/MockAccount.cs +++ b/NineChronicles.Headless.Tests/Common/MockAccount.cs @@ -57,6 +57,10 @@ public MockAccount( [Pure] public IAccount SetState(Address address, IValue state) => UpdateState(address, state); + /// + [Pure] + public IAccount RemoveState(Address address) => UpdateState(address); + /// [Pure] public FungibleAssetValue GetBalance(Address address, Currency currency) => @@ -193,6 +197,14 @@ private MockAccount UpdateState( MockTrie.Add(ToStateKey(address), value)), TotalUpdatedFungibleAssets); + [Pure] + private MockAccount UpdateState( + Address address) => + new MockAccount( + new MockAccountState( + MockTrie.Remove(ToStateKey(address))), + TotalUpdatedFungibleAssets); + [Pure] private MockAccount UpdateFungibleAssets( Address address, diff --git a/NineChronicles.Headless/GraphQLServiceExtensions.cs b/NineChronicles.Headless/GraphQLServiceExtensions.cs index b6c77733b..71dcc668a 100644 --- a/NineChronicles.Headless/GraphQLServiceExtensions.cs +++ b/NineChronicles.Headless/GraphQLServiceExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using System.Security.Cryptography; using GraphQL.Types; using Libplanet.Explorer.GraphTypes; using Libplanet.Explorer.Interfaces; @@ -34,9 +35,13 @@ public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -45,9 +50,9 @@ public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton>(); + services.TryAddSingleton>(); return services; } @@ -72,6 +77,7 @@ public static IServiceCollection AddLibplanetExplorer(this IServiceCollection se services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(_ => new StateQuery() { Name = "LibplanetStateQuery", From 849e79439d342b6a5587591004383645006e152b Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 8 Jan 2024 13:18:14 +0900 Subject: [PATCH 09/36] Linting --- NineChronicles.Headless.Tests/Common/KeyConverters.cs | 4 ++-- NineChronicles.Headless.Tests/Common/MockAccount.cs | 2 +- NineChronicles.Headless.Tests/Common/MockAccountState.cs | 2 +- NineChronicles.Headless.Tests/Common/MockWorld.cs | 2 +- NineChronicles.Headless.Tests/Common/MockWorldDelta.cs | 2 +- NineChronicles.Headless.Tests/Common/MockWorldState.cs | 2 +- NineChronicles.Headless/GraphTypes/ActionMutation.cs | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/NineChronicles.Headless.Tests/Common/KeyConverters.cs b/NineChronicles.Headless.Tests/Common/KeyConverters.cs index a1894fa22..43a64fd4f 100644 --- a/NineChronicles.Headless.Tests/Common/KeyConverters.cs +++ b/NineChronicles.Headless.Tests/Common/KeyConverters.cs @@ -1,10 +1,10 @@ -using Libplanet.Crypto; +using Libplanet.Crypto; using Libplanet.Store.Trie; using Libplanet.Types.Assets; namespace NineChronicles.Headless.Tests.Common { - + /// /// A rough replica of https://github.com/planetarium/libplanet/blob/main/Libplanet.Action/State/KeyConverters.cs /// except this has its constructors exposed as public for testing. diff --git a/NineChronicles.Headless.Tests/Common/MockAccount.cs b/NineChronicles.Headless.Tests/Common/MockAccount.cs index 587c8653f..ac2d3acdd 100644 --- a/NineChronicles.Headless.Tests/Common/MockAccount.cs +++ b/NineChronicles.Headless.Tests/Common/MockAccount.cs @@ -1,4 +1,4 @@ -namespace NineChronicles.Headless.Tests.Common +namespace NineChronicles.Headless.Tests.Common { using System; using System.Collections.Generic; diff --git a/NineChronicles.Headless.Tests/Common/MockAccountState.cs b/NineChronicles.Headless.Tests/Common/MockAccountState.cs index 4fd25d1a3..21a6c187f 100644 --- a/NineChronicles.Headless.Tests/Common/MockAccountState.cs +++ b/NineChronicles.Headless.Tests/Common/MockAccountState.cs @@ -104,7 +104,7 @@ public MockAccountState SubtractBalance(Address address, FungibleAssetValue amou public MockAccountState SubtractBalance(Address address, Currency currency, BigInteger rawAmount) => SubtractBalance((address, currency), rawAmount); - public MockAccountState SubtractBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) + public MockAccountState SubtractBalance((Address Address, Currency Currency) pair, BigInteger rawAmount) { var amount = GetBalance(pair.Address, pair.Currency).RawValue - rawAmount; return SetBalance(pair, amount); diff --git a/NineChronicles.Headless.Tests/Common/MockWorld.cs b/NineChronicles.Headless.Tests/Common/MockWorld.cs index 1ed529553..6e893a992 100644 --- a/NineChronicles.Headless.Tests/Common/MockWorld.cs +++ b/NineChronicles.Headless.Tests/Common/MockWorld.cs @@ -1,4 +1,4 @@ -using Libplanet.Store.Trie; +using Libplanet.Store.Trie; namespace NineChronicles.Headless.Tests.Common { diff --git a/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs b/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs index 7df6501ed..e354b0acd 100644 --- a/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs +++ b/NineChronicles.Headless.Tests/Common/MockWorldDelta.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; namespace NineChronicles.Headless.Tests.Common { diff --git a/NineChronicles.Headless.Tests/Common/MockWorldState.cs b/NineChronicles.Headless.Tests/Common/MockWorldState.cs index 0d3c2cbb9..73612a891 100644 --- a/NineChronicles.Headless.Tests/Common/MockWorldState.cs +++ b/NineChronicles.Headless.Tests/Common/MockWorldState.cs @@ -1,4 +1,4 @@ -using Libplanet.Store.Trie; +using Libplanet.Store.Trie; namespace NineChronicles.Headless.Tests.Common { diff --git a/NineChronicles.Headless/GraphTypes/ActionMutation.cs b/NineChronicles.Headless/GraphTypes/ActionMutation.cs index 782b98bcd..debfcedcd 100644 --- a/NineChronicles.Headless/GraphTypes/ActionMutation.cs +++ b/NineChronicles.Headless/GraphTypes/ActionMutation.cs @@ -466,10 +466,10 @@ public ActionMutation(NineChroniclesNodeService service) Address avatarAddress = context.GetArgument
("avatarAddress"); Address agentAddress = service.MinerPrivateKey.Address; - AgentState agentState = + AgentState agentState = service.BlockChain.GetWorldState().GetAgentState(agentAddress) ?? throw new InvalidOperationException($"Given agent of address {agentAddress} does not exist."); - + var action = new ClaimMonsterCollectionReward { avatarAddress = avatarAddress, From 874a52a2940269b884aa28aaebb2956acc545a8a Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 8 Jan 2024 15:03:09 +0900 Subject: [PATCH 10/36] Fix MockTrie --- .../Common/MockAccountState.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless.Tests/Common/MockAccountState.cs b/NineChronicles.Headless.Tests/Common/MockAccountState.cs index 21a6c187f..0523cbc94 100644 --- a/NineChronicles.Headless.Tests/Common/MockAccountState.cs +++ b/NineChronicles.Headless.Tests/Common/MockAccountState.cs @@ -35,7 +35,9 @@ public MockAccountState(IImmutableDictionary mockTrie) public IImmutableDictionary MockTrie { get; } /// - public IValue GetState(Address address) => MockTrie[ToStateKey(address)]; + public IValue? GetState(Address address) => MockTrie.TryGetValue(ToStateKey(address), out var v) + ? v + : null; /// public IReadOnlyList GetStates(IReadOnlyList
addresses) => @@ -44,7 +46,9 @@ public MockAccountState(IImmutableDictionary mockTrie) /// public FungibleAssetValue GetBalance(Address address, Currency currency) { - IValue? value = MockTrie[ToFungibleAssetKey(address, currency)]; + IValue? value = MockTrie.TryGetValue(ToFungibleAssetKey(address, currency), out var v) + ? v + : null; return value is Integer i ? FungibleAssetValue.FromRawValue(currency, i) : currency * 0; @@ -58,7 +62,9 @@ public FungibleAssetValue GetTotalSupply(Currency currency) throw TotalSupplyNotTrackableException.WithDefaultMessage(currency); } - IValue? value = MockTrie[ToTotalSupplyKey(currency)]; + IValue? value = MockTrie.TryGetValue(ToTotalSupplyKey(currency), out var v) + ? v + : null; return value is Integer i ? FungibleAssetValue.FromRawValue(currency, i) : currency * 0; @@ -67,7 +73,9 @@ public FungibleAssetValue GetTotalSupply(Currency currency) /// public ValidatorSet GetValidatorSet() { - IValue? value = MockTrie[ValidatorSetKey]; + IValue? value = MockTrie.TryGetValue(ValidatorSetKey, out var v) + ? v + : null; return value is List list ? new ValidatorSet(list) : new ValidatorSet(); From 10edc32c268095d23a749815d79936bd550a00bf Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 8 Jan 2024 15:52:56 +0900 Subject: [PATCH 11/36] Fix tests --- .../GraphTypes/StateQueryFields/Garages.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs index 2d16c20f4..70a2f016f 100644 --- a/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs +++ b/NineChronicles.Headless/GraphTypes/StateQueryFields/Garages.cs @@ -12,6 +12,7 @@ using Nekoyume.Model.Garages; using Nekoyume.Module; using NineChronicles.Headless.GraphTypes.States; +using System; namespace NineChronicles.Headless.GraphTypes; @@ -102,7 +103,8 @@ private void RegisterGarages() .Select(address => context.Source.WorldState.GetLegacyState(address)) .Select((value, i) => value is null or Null ? (fungibleItemIds[i], fungibleItemGarageAddresses[i], null) - : (fungibleItemIds[i], fungibleItemGarageAddresses[i], new FungibleItemGarage(value))); + : (fungibleItemIds[i], fungibleItemGarageAddresses[i], new FungibleItemGarage(value))) + .ToArray(); } return new GaragesType.Value( From 61a2d462a2ba9ed8a9dddcf091cbea4afb41fd4b Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 9 Jan 2024 10:57:38 +0900 Subject: [PATCH 12/36] Bump lib9c to 675aea0a --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index a56d8ea52..675aea0a0 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit a56d8ea52e16a7785996c255454accaeae6a8f22 +Subproject commit 675aea0a098f16ba50dfcd349cc4529eb095f60f From 31a8684b1159ffaa278430e306d1723df6d01dbf Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 9 Jan 2024 11:29:11 +0900 Subject: [PATCH 13/36] Bump rpc shared --- NineChronicles.RPC.Shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index 8e1dc231f..a30f8a4d6 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit 8e1dc231f6c79ab2a2443e010d524e2927dd766f +Subproject commit a30f8a4d6a89b83e1b4b13040c836fccbd152fc7 From c809956ab713cd1ba136d54bac87b7df6686af88 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 9 Jan 2024 11:38:24 +0900 Subject: [PATCH 14/36] Refactor BlockChainService --- NineChronicles.Headless/BlockChainService.cs | 80 +++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 2c1e2acab..f428f8688 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -120,27 +120,40 @@ public UnaryResult PutTransaction(byte[] txBytes) } } - public UnaryResult GetState(byte[] blockHashBytes, byte[] accountAddressBytes, byte[] addressBytes) + public UnaryResult GetStateByBlockHash( + byte[] blockHashBytes, + byte[] accountAddressBytes, + byte[] addressBytes) { var hash = new BlockHash(blockHashBytes); var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); - IValue state = _blockChain.GetState(hash, accountAddress, address); + IValue state = _blockChain + .GetWorldState(hash) + .GetAccount(accountAddress) + .GetState(address); // FIXME: Null과 null 구분해서 반환해야 할 듯 byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); } - public UnaryResult GetStateBySrh(byte[] stateRootHashBytes, byte[] addressBytes) + public UnaryResult GetStateByStateRootHash( + byte[] stateRootHashBytes, + byte[] accountAddressBytes, + byte[] addressBytes) { var stateRootHash = new HashDigest(stateRootHashBytes); + var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); - IValue state = _blockChain.GetState(stateRootHash, address); + IValue state = _blockChain + .GetWorldState(stateRootHash) + .GetAccount(accountAddress) + .GetState(address); byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); } - public async UnaryResult> GetAvatarStates( + public async UnaryResult> GetAvatarStatesByBlockHash( byte[] blockHashBytes, IEnumerable addressBytesList) { @@ -159,12 +172,12 @@ public async UnaryResult> GetAvatarStates( return result.ToDictionary(kv => kv.Key, kv => kv.Value); } - public async UnaryResult> GetAvatarStatesBySrh( - byte[] worldStateRootHashBytes, + public async UnaryResult> GetAvatarStatesByStateRootHash( + byte[] stateRootHashBytes, IEnumerable addressBytesList) { var addresses = addressBytesList.Select(a => new Address(a)).ToList(); - var stateRootHash = new HashDigest(worldStateRootHashBytes); + var stateRootHash = new HashDigest(stateRootHashBytes); var worldState = _blockChain.GetWorldState(stateRootHash); var result = new ConcurrentDictionary(); var taskList = addresses @@ -180,18 +193,18 @@ public async UnaryResult> GetAvatarStatesBySrh( return result.ToDictionary(kv => kv.Key, kv => kv.Value); } - public UnaryResult> GetStateBulk( + public UnaryResult> GetBulkStateByBlockHash( byte[] blockHashBytes, - byte[] accountBytes, + byte[] accountAddressBytes, IEnumerable addressBytesList) { - var hash = new BlockHash(blockHashBytes); + var blockHash = new BlockHash(blockHashBytes); + var accountAddress = new Address(accountAddressBytes); + List
addresses = addressBytesList.Select(b => new Address(b)).ToList(); + var result = new Dictionary(); - Address[] addresses = addressBytesList.Select(b => new Address(b)).ToArray(); - var accountAddress = new Address(accountBytes); - IReadOnlyList values = - addresses.Select(address => _blockChain.GetState(hash, accountAddress, address)).ToList(); - for (int i = 0; i < addresses.Length; i++) + IReadOnlyList values = _blockChain.GetWorldState(blockHash).GetAccount(accountAddress).GetStates(addresses); + for (int i = 0; i < addresses.Count; i++) { result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); } @@ -199,15 +212,18 @@ public UnaryResult> GetStateBulk( return new UnaryResult>(result); } - public UnaryResult> GetStateBulkBySrh( + public UnaryResult> GetBulkStateByStateRootHash( byte[] stateRootHashBytes, + byte[] accountAddressBytes, IEnumerable addressBytesList) { var stateRootHash = new HashDigest(stateRootHashBytes); + var accountAddress = new Address(accountAddressBytes); + List
addresses = addressBytesList.Select(b => new Address(b)).ToList(); + var result = new Dictionary(); - Address[] addresses = addressBytesList.Select(b => new Address(b)).ToArray(); - IReadOnlyList values = _blockChain.GetAccountState(stateRootHash).GetStates(addresses); - for (int i = 0; i < addresses.Length; i++) + IReadOnlyList values = _blockChain.GetWorldState(stateRootHash).GetAccount(accountAddress).GetStates(addresses); + for (int i = 0; i < addresses.Count; i++) { result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); } @@ -258,19 +274,21 @@ public UnaryResult> GetSheets( return new UnaryResult>(result); } - public UnaryResult GetBalance( + public UnaryResult GetBalanceByBlockHash( byte[] blockHashBytes, - byte[] accountBytes, + byte[] accountAddressBytes, byte[] addressBytes, byte[] currencyBytes) { var blockHash = new BlockHash(blockHashBytes); - var accountAddress = new Address(accountBytes); + var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); - FungibleAssetValue balance = _blockChain.GetWorldState(blockHash) - .GetAccount(accountAddress).GetBalance(address, currency); + FungibleAssetValue balance = _blockChain + .GetWorldState(blockHash) + .GetAccount(accountAddress) + .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( new IValue[] @@ -283,13 +301,21 @@ public UnaryResult GetBalance( return new UnaryResult(encoded); } - public UnaryResult GetBalanceBySrh(byte[] stateRootHashBytes, byte[] addressBytes, byte[] currencyBytes) + public UnaryResult GetBalanceByStateRootHash( + byte[] stateRootHashBytes, + byte[] accountAddressBytes, + byte[] addressBytes, + byte[] currencyBytes) { var stateRootHash = new HashDigest(stateRootHashBytes); + var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); - FungibleAssetValue balance = _blockChain.GetAccountState(stateRootHash).GetBalance(address, currency); + FungibleAssetValue balance = _blockChain + .GetWorldState(stateRootHash) + .GetAccount(accountAddress) + .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( new IValue[] From 9f2d3517c9dfa0c46a89a45ee39b5f3b602d4c73 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 9 Jan 2024 11:40:47 +0900 Subject: [PATCH 15/36] Additional API fixes --- NineChronicles.Headless/GraphTypes/StandaloneQuery.cs | 2 +- .../GraphTypes/StandaloneSubscription.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index ff6cf2148..62cbff1d9 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -91,7 +91,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi var accountAddress = context.GetArgument
("accountAddress"); var address = context.GetArgument
("address"); - var state = blockChain.GetState(blockHash, accountAddress, address); + var state = blockChain.GetWorldState(blockHash).GetAccount(accountAddress).GetState(address); if (state is null) { diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 55c5f4720..d5499463b 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -28,6 +28,7 @@ using Microsoft.Extensions.Configuration; using Nekoyume.Module; using Serilog; +using Libplanet.Action.State; namespace NineChronicles.Headless.GraphTypes { @@ -449,7 +450,10 @@ private void RenderMonsterCollectionStateSubject(ActionEvaluation eval) { Address deriveAddress = MonsterCollectionState.DeriveAddress(address, agentState.MonsterCollectionRound); var subject = subjects.stateSubject; - if (service.BlockChain.GetAccountState(eval.OutputState).GetState(deriveAddress) is Dictionary state) + if (service.BlockChain + .GetWorldState(eval.OutputState) + .GetAccount(ReservedAddresses.LegacyAccount) + .GetState(deriveAddress) is Dictionary state) { subject.OnNext(new MonsterCollectionState(state)); } From 0cabfa871737a3784a0e9d760c091de9a67afa4c Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 9 Jan 2024 16:09:50 +0900 Subject: [PATCH 16/36] Removed local cache for replays --- .../Commands/ReplayCommand.Privates.cs | 262 ------------------ .../Commands/ReplayCommand.cs | 11 +- 2 files changed, 2 insertions(+), 271 deletions(-) diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index e5f91af7f..2a5154b23 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -2,23 +2,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Security.Cryptography; -using Bencodex; -using Bencodex.Types; using Cocona; using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Action; using Libplanet.Action.State; -using Libplanet.Types.Assets; -using Libplanet.Types.Consensus; -using Libplanet.Types.Blocks; using Libplanet.Types.Tx; -using RocksDbSharp; using Serilog; -using Libplanet.Store.Trie; namespace NineChronicles.Headless.Executable.Commands { @@ -89,259 +80,6 @@ public Random(int seed) public int Seed { get; private set; } } - private sealed class LocalCacheBlockChainStates : IBlockChainStates - { - private readonly IBlockChainStates _source; - private readonly RocksDb _rocksDb; - - public LocalCacheBlockChainStates(IBlockChainStates source, string cacheDirectory) - { - _source = source; - var options = new DbOptions().SetCreateIfMissing(); - _rocksDb = RocksDb.Open(options, cacheDirectory); - } - public IWorldState GetWorldState(BlockHash? offset) - => new LocalCacheWorldState( - _source.GetWorldState(offset), - _source.GetAccountState, - _rocksDb); - - public IWorldState GetWorldState(HashDigest? hash) - => new LocalCacheWorldState( - _source.GetWorldState(hash), - _source.GetAccountState, - _rocksDb); - - public IAccountState GetAccountState(HashDigest? hash) - => new LocalCacheAccountState( - _source.GetAccountState(hash), - _rocksDb); - - public IAccountState GetAccountState(BlockHash? offset, Address address) - => new LocalCacheAccountState( - _source.GetAccountState(offset, address), - _rocksDb); - - public IValue? GetState(BlockHash? offset, Address accountAddress, Address address) - => GetAccountState(offset, accountAddress).GetState(address); - - public IValue? GetState(HashDigest? stateRootHash, Address address) - => GetAccountState(stateRootHash).GetState(address); - - public FungibleAssetValue GetBalance(HashDigest? stateRootHash, Address address, Currency currency) - => GetAccountState(stateRootHash).GetBalance(address, currency); - - public FungibleAssetValue GetBalance(BlockHash? offset, Address address, Currency currency) - => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetBalance(address, currency); - - public FungibleAssetValue GetTotalSupply(HashDigest? stateRootHash, Currency currency) - => GetAccountState(stateRootHash).GetTotalSupply(currency); - - public FungibleAssetValue GetTotalSupply(BlockHash? offset, Currency currency) - => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetTotalSupply(currency); - - public ValidatorSet GetValidatorSet(HashDigest? stateRootHash) - => GetAccountState(stateRootHash).GetValidatorSet(); - - public ValidatorSet GetValidatorSet(BlockHash? offset) - => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetValidatorSet(); - } - - private sealed class LocalCacheWorldState : IWorldState - { - private static readonly Codec Codec = new Codec(); - private readonly IWorldState _worldState; - private readonly Func?, IAccountState> _accountStateGetter; - private readonly RocksDb _rocksDb; - - public LocalCacheWorldState( - IWorldState worldState, - Func?, IAccountState> accountStateGetter, - RocksDb rocksDb) - { - _worldState = worldState; - _accountStateGetter = accountStateGetter; - _rocksDb = rocksDb; - } - - public ITrie Trie => _worldState.Trie; - - public bool Legacy => _worldState.Legacy; - - public IAccount GetAccount(Address address) - { - var key = WithStateRootHash(address.ToByteArray()); - try - { - return GetAccount(key); - } - catch (KeyNotFoundException) - { - var account = _worldState.GetAccount(address); - SetAccount(key, account); - return account; - } - } - - public IAccount GetAccount(byte[] key) - { - if (_rocksDb.Get(key) is not { } bytes) - { - throw new KeyNotFoundException(); - } - - return new Account(_accountStateGetter( - new HashDigest(((Binary)Codec.Decode(bytes)).ToImmutableArray()))); - } - - private void SetAccount(byte[] key, IAccount? account) - { - _rocksDb.Put(key, account is null ? new byte[] { 0x78 } : account.Trie.Hash.ToByteArray()); - } - - private byte[] WithStateRootHash(params byte[][] suffixes) - { - if (Trie.Hash is { } stateRootHash) - { - var stream = new MemoryStream(HashDigest.Size + suffixes.Sum(s => s.Length)); - stream.Write(stateRootHash.ToByteArray()); - foreach (var suffix in suffixes) - { - stream.Write(suffix); - } - - return stream.ToArray(); - } - throw new InvalidOperationException(); - } - } - - private sealed class LocalCacheAccountState : IAccountState - { - private static readonly Codec _codec = new Codec(); - private readonly IAccountState _accountState; - private readonly RocksDb _rocksDb; - - public LocalCacheAccountState( - IAccountState accountState, - RocksDb rocksDb) - { - _accountState = accountState; - _rocksDb = rocksDb; - } - - public ITrie Trie => _accountState.Trie; - - public IValue? GetState(Address address) - { - var key = WithStateRootHash(address.ToByteArray()); - try - { - return GetValue(key); - } - catch (KeyNotFoundException) - { - var state = _accountState.GetState(address); - SetValue(key, state); - return state; - } - } - - public IReadOnlyList GetStates(IReadOnlyList
addresses) - { - return addresses.Select(GetState).ToList(); - } - - public FungibleAssetValue GetBalance(Address address, Currency currency) - { - var key = WithStateRootHash(address.ToByteArray(), currency.Hash.ToByteArray()); - try - { - var state = GetValue(key); - if (state is not Integer integer) - { - throw new InvalidOperationException(); - } - - return FungibleAssetValue.FromRawValue(currency, integer); - } - catch (KeyNotFoundException) - { - var fav = _accountState.GetBalance(address, currency); - SetValue(key, (Integer)fav.RawValue); - return fav; - } - } - - public FungibleAssetValue GetTotalSupply(Currency currency) - { - var key = WithStateRootHash(currency.Hash.ToByteArray()); - try - { - var state = GetValue(key); - if (state is not Integer integer) - { - throw new InvalidOperationException(); - } - - return FungibleAssetValue.FromRawValue(currency, integer); - } - catch (KeyNotFoundException) - { - var fav = _accountState.GetTotalSupply(currency); - SetValue(key, (Integer)fav.RawValue); - return fav; - } - } - - public ValidatorSet GetValidatorSet() - { - var key = WithStateRootHash(new byte[] { 0x5f, 0x5f, 0x5f }); - try - { - var state = GetValue(key); - return state is not null ? new ValidatorSet(state) : new ValidatorSet(); - } - catch (KeyNotFoundException) - { - var validatorSet = _accountState.GetValidatorSet(); - SetValue(key, validatorSet.Bencoded); - return validatorSet; - } - } - - private IValue? GetValue(byte[] key) - { - if (_rocksDb.Get(key) is not { } bytes) - { - throw new KeyNotFoundException(); - } - - return bytes[0] == 'x' ? null : _codec.Decode(bytes); - } - - private void SetValue(byte[] key, IValue? value) - { - _rocksDb.Put(key, value is null ? new byte[] { 0x78 } : _codec.Encode(value)); - } - - private byte[] WithStateRootHash(params byte[][] suffixes) - { - if (Trie.Hash is { } stateRootHash) - { - var stream = new MemoryStream(HashDigest.Size + suffixes.Sum(s => s.Length)); - stream.Write(stateRootHash.ToByteArray()); - foreach (var suffix in suffixes) - { - stream.Write(suffix); - } - - return stream.ToArray(); - } - throw new InvalidOperationException(); - } - } - /// /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/ActionEvaluator.cs#L286. /// diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 0937a48eb..9e1db0b20 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -354,12 +354,7 @@ public int RemoteTx( [Option("tx", new[] { 't' }, Description = "The transaction id")] string transactionId, [Option("endpoint", new[] { 'e' }, Description = "GraphQL endpoint to get remote state")] - string endpoint, - [Option( - "cache-directory", - new [] { 'c' }, - Description = "A directory to store states, balances, etc as cache")] - string? cacheDirectory=null) + string endpoint) { var graphQlClient = new GraphQLHttpClient(new Uri(endpoint), new SystemTextJsonSerializer()); var transactionResponse = GetTransactionData(graphQlClient, transactionId); @@ -392,9 +387,7 @@ public int RemoteTx( var miner = new Address(minerValue); var explorerEndpoint = $"{endpoint}/explorer"; - var blockChainStates = new LocalCacheBlockChainStates( - new RemoteBlockChainStates(new Uri(explorerEndpoint)), - cacheDirectory ?? Path.Join(Path.GetTempPath(), "ncd-replay-remote-tx-cache")); + var blockChainStates = new RemoteBlockChainStates(new Uri(explorerEndpoint)); var previousBlockHash = BlockHash.FromString(previousBlockHashValue); var previousStates = new World(blockChainStates.GetWorldState(previousBlockHash)); From 59092d0ea7fa3a6382fe574a36f03760486bb742 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 10 Jan 2024 16:26:13 +0900 Subject: [PATCH 17/36] Bump lib9c to b4c1fda61 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 675aea0a0..b4c1fda61 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 675aea0a098f16ba50dfcd349cc4529eb095f60f +Subproject commit b4c1fda6183deb8879f1cc411371456628f50cce From ebedb0c22937c2f367c9df1d162f3ad9cda30cc2 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 10 Jan 2024 18:17:47 +0900 Subject: [PATCH 18/36] Bump fix --- NineChronicles.Headless/BlockChainService.cs | 18 ++++++++++++------ .../GraphTypes/StandaloneQuery.cs | 5 ++++- .../GraphTypes/StandaloneSubscription.cs | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index f428f8688..1401119d1 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -130,7 +130,7 @@ public UnaryResult GetStateByBlockHash( var address = new Address(addressBytes); IValue state = _blockChain .GetWorldState(hash) - .GetAccount(accountAddress) + .GetAccountState(accountAddress) .GetState(address); // FIXME: Null과 null 구분해서 반환해야 할 듯 byte[] encoded = _codec.Encode(state ?? Null.Value); @@ -147,7 +147,7 @@ public UnaryResult GetStateByStateRootHash( var address = new Address(addressBytes); IValue state = _blockChain .GetWorldState(stateRootHash) - .GetAccount(accountAddress) + .GetAccountState(accountAddress) .GetState(address); byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); @@ -203,7 +203,10 @@ public UnaryResult> GetBulkStateByBlockHash( List
addresses = addressBytesList.Select(b => new Address(b)).ToList(); var result = new Dictionary(); - IReadOnlyList values = _blockChain.GetWorldState(blockHash).GetAccount(accountAddress).GetStates(addresses); + IReadOnlyList values = _blockChain + .GetWorldState(blockHash) + .GetAccountState(accountAddress) + .GetStates(addresses); for (int i = 0; i < addresses.Count; i++) { result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); @@ -222,7 +225,10 @@ public UnaryResult> GetBulkStateByStateRootHash( List
addresses = addressBytesList.Select(b => new Address(b)).ToList(); var result = new Dictionary(); - IReadOnlyList values = _blockChain.GetWorldState(stateRootHash).GetAccount(accountAddress).GetStates(addresses); + IReadOnlyList values = _blockChain + .GetWorldState(stateRootHash) + .GetAccountState(accountAddress) + .GetStates(addresses); for (int i = 0; i < addresses.Count; i++) { result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); @@ -287,7 +293,7 @@ public UnaryResult GetBalanceByBlockHash( Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain .GetWorldState(blockHash) - .GetAccount(accountAddress) + .GetAccountState(accountAddress) .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( @@ -314,7 +320,7 @@ public UnaryResult GetBalanceByStateRootHash( Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain .GetWorldState(stateRootHash) - .GetAccount(accountAddress) + .GetAccountState(accountAddress) .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index 62cbff1d9..afd2f7471 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -91,7 +91,10 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi var accountAddress = context.GetArgument
("accountAddress"); var address = context.GetArgument
("address"); - var state = blockChain.GetWorldState(blockHash).GetAccount(accountAddress).GetState(address); + var state = blockChain + .GetWorldState(blockHash) + .GetAccountState(accountAddress) + .GetState(address); if (state is null) { diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index d5499463b..37d7a1883 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -452,7 +452,7 @@ private void RenderMonsterCollectionStateSubject(ActionEvaluation eval) var subject = subjects.stateSubject; if (service.BlockChain .GetWorldState(eval.OutputState) - .GetAccount(ReservedAddresses.LegacyAccount) + .GetAccountState(ReservedAddresses.LegacyAccount) .GetState(deriveAddress) is Dictionary state) { subject.OnNext(new MonsterCollectionState(state)); From 7f080c3fc23a93b297a42ec710813cf9d93b96f9 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 10 Jan 2024 19:19:02 +0900 Subject: [PATCH 19/36] Fix mocks --- .../Common/MockWorld.cs | 24 +++++++++++++++---- .../Common/MockWorldState.cs | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/NineChronicles.Headless.Tests/Common/MockWorld.cs b/NineChronicles.Headless.Tests/Common/MockWorld.cs index 6e893a992..c31e6f723 100644 --- a/NineChronicles.Headless.Tests/Common/MockWorld.cs +++ b/NineChronicles.Headless.Tests/Common/MockWorld.cs @@ -2,6 +2,7 @@ namespace NineChronicles.Headless.Tests.Common { + using System; using System.Diagnostics.Contracts; using Libplanet.Action.State; using Libplanet.Crypto; @@ -44,11 +45,26 @@ public MockWorld( [Pure] public IAccount GetAccount(Address address) { - return Delta.Accounts.TryGetValue(address, out IAccount? account) - ? account - : _baseState.GetAccount(address); + if (Delta.Accounts.TryGetValue(address, out IAccount? account)) + { + return account; + } + else + { + switch (_baseState.GetAccountState(address)) + { + case MockAccount mockAccount: + return mockAccount; + case MockAccountState mockAccountState: + return new MockAccount(mockAccountState); + default: + throw new InvalidOperationException(); + } + } } + public IAccountState GetAccountState(Address address) => GetAccount(address); + /// [Pure] public IWorld SetAccount(Address address, IAccount account) @@ -59,7 +75,7 @@ public IWorld SetAccount(Address address, IAccount account) return this; } - return new World( + return new MockWorld( this, Delta.SetAccount(address, account)); } diff --git a/NineChronicles.Headless.Tests/Common/MockWorldState.cs b/NineChronicles.Headless.Tests/Common/MockWorldState.cs index 73612a891..75713585c 100644 --- a/NineChronicles.Headless.Tests/Common/MockWorldState.cs +++ b/NineChronicles.Headless.Tests/Common/MockWorldState.cs @@ -27,7 +27,7 @@ public MockWorldState(IImmutableDictionary accounts) public IImmutableDictionary Accounts => _accounts; - public IAccount GetAccount(Address address) => _accounts.TryGetValue(address, out IAccount? account) + public IAccountState GetAccountState(Address address) => _accounts.TryGetValue(address, out IAccount? account) ? account : new MockAccount(new MockAccountState()); } From c5f305adfc3fde591b72b67bc52d890b77c536f0 Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Thu, 11 Jan 2024 17:54:02 +0900 Subject: [PATCH 20/36] bump: RPC.Shared --- NineChronicles.RPC.Shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index a30f8a4d6..bf2b9ce54 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit a30f8a4d6a89b83e1b4b13040c836fccbd152fc7 +Subproject commit bf2b9ce54b80d0d987a65a4f2abeb30d4d194e83 From bda532a1dd9f5758a4a93dee808dc47e6bf8314f Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Thu, 11 Jan 2024 18:00:15 +0900 Subject: [PATCH 21/36] bump: lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index b4c1fda61..7bddfe67a 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit b4c1fda6183deb8879f1cc411371456628f50cce +Subproject commit 7bddfe67a46ff95aeb2dd7fa8348a0ce98fa0988 From d0b4d61a7930b2f9c0f3e1af3dd771135a4188ef Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Thu, 11 Jan 2024 18:00:31 +0900 Subject: [PATCH 22/36] feat: fix BlockChainService Co-authored-by: Ko Chanhyuck --- NineChronicles.Headless/BlockChainService.cs | 38 ++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 1401119d1..b2910dc75 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Bencodex; using Bencodex.Types; +using Libplanet.Action.State; using Libplanet.Blockchain; using Libplanet.Common; using Libplanet.Crypto; @@ -21,10 +22,11 @@ using MagicOnion.Server; using Microsoft.Extensions.Caching.Memory; using Nekoyume; -using Nekoyume.Shared.Services; -using Serilog; +using Nekoyume.Model.State; using Nekoyume.Module; +using Nekoyume.Shared.Services; using Sentry; +using Serilog; using static NineChronicles.Headless.NCActionUtils; using NodeExceptionType = Libplanet.Headless.NodeExceptionType; using Transaction = Libplanet.Types.Tx.Transaction; @@ -153,6 +155,26 @@ public UnaryResult GetStateByStateRootHash( return new UnaryResult(encoded); } + public UnaryResult GetAgentStateByBlockHash( + byte[] blockHashBytes, + byte[] addressBytes) + { + var hash = new BlockHash(blockHashBytes); + var worldState = _blockChain.GetWorldState(hash); + var address = new Address(addressBytes); + return new UnaryResult(_codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + } + + public UnaryResult GetAgentStateByStateRootHash( + byte[] stateRootHashBytes, + byte[] addressBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var worldState = _blockChain.GetWorldState(stateRootHash); + var address = new Address(addressBytes); + return new UnaryResult(_codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + } + public async UnaryResult> GetAvatarStatesByBlockHash( byte[] blockHashBytes, IEnumerable addressBytesList) @@ -165,7 +187,7 @@ public async UnaryResult> GetAvatarStatesByBlockHash( { result.TryAdd( address.ToByteArray(), - new Codec().Encode(worldState.GetAvatarState(address).SerializeList())); + _codec.Encode(worldState.GetFullAvatarStateRaw(address))); })); await Task.WhenAll(taskList); @@ -185,7 +207,7 @@ public async UnaryResult> GetAvatarStatesByStateRootH { result.TryAdd( address.ToByteArray(), - new Codec().Encode(worldState.GetAvatarState(address).SerializeList())); + _codec.Encode(worldState.GetFullAvatarStateRaw(address))); })) .ToList(); @@ -282,18 +304,16 @@ public UnaryResult> GetSheets( public UnaryResult GetBalanceByBlockHash( byte[] blockHashBytes, - byte[] accountAddressBytes, byte[] addressBytes, byte[] currencyBytes) { var blockHash = new BlockHash(blockHashBytes); - var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain .GetWorldState(blockHash) - .GetAccountState(accountAddress) + .GetAccountState(ReservedAddresses.LegacyAccount) .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( @@ -309,18 +329,16 @@ public UnaryResult GetBalanceByBlockHash( public UnaryResult GetBalanceByStateRootHash( byte[] stateRootHashBytes, - byte[] accountAddressBytes, byte[] addressBytes, byte[] currencyBytes) { var stateRootHash = new HashDigest(stateRootHashBytes); - var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); FungibleAssetValue balance = _blockChain .GetWorldState(stateRootHash) - .GetAccountState(accountAddress) + .GetAccountState(ReservedAddresses.LegacyAccount) .GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( From 919613522a46090f21997a547b0989a5b4582ec6 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 15 Jan 2024 10:53:24 +0900 Subject: [PATCH 23/36] Bump lib9c to 66ea3fdd9 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 7bddfe67a..66ea3fdd9 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 7bddfe67a46ff95aeb2dd7fa8348a0ce98fa0988 +Subproject commit 66ea3fdd98f3cc318d632324559c2fda74762c7c From 21ef50028d9943b584a14a0b785670d87729d413 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 15 Jan 2024 10:53:43 +0900 Subject: [PATCH 24/36] Updated GraphQL API --- .../GraphQLServiceExtensions.cs | 2 +- .../GraphTypes/TxIdType.cs | 50 ------------------- 2 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 NineChronicles.Headless/GraphTypes/TxIdType.cs diff --git a/NineChronicles.Headless/GraphQLServiceExtensions.cs b/NineChronicles.Headless/GraphQLServiceExtensions.cs index 71dcc668a..d717a0153 100644 --- a/NineChronicles.Headless/GraphQLServiceExtensions.cs +++ b/NineChronicles.Headless/GraphQLServiceExtensions.cs @@ -41,7 +41,7 @@ public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/NineChronicles.Headless/GraphTypes/TxIdType.cs b/NineChronicles.Headless/GraphTypes/TxIdType.cs deleted file mode 100644 index ae88a8042..000000000 --- a/NineChronicles.Headless/GraphTypes/TxIdType.cs +++ /dev/null @@ -1,50 +0,0 @@ -using GraphQL.Language.AST; -using GraphQL.Types; -using Libplanet.Common; -using Libplanet.Types.Tx; -using System; - -namespace NineChronicles.Headless.GraphTypes -{ - public class TxIdType : StringGraphType - { - public TxIdType() - { - Name = "TxId"; - } - - public override object? Serialize(object? value) - { - if (value is TxId txId) - { - return txId.ToString(); - } - - return value; - } - - public override object? ParseValue(object? value) - { - switch (value) - { - case null: - return null; - case string hex: - return new TxId(ByteUtil.ParseHex(hex)); - default: - throw new ArgumentException( - $"Expected a hexadecimal string but {value}", nameof(value)); - } - } - - public override object? ParseLiteral(IValue value) - { - if (value is StringValue) - { - return ParseValue(value.Value); - } - - return null; - } - } -} From 47a4fb2a42efaf00dadc8c844124a97511b3778b Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 17 Jan 2024 19:57:12 +0900 Subject: [PATCH 25/36] bump: Lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 66ea3fdd9..dcdeef7f1 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 66ea3fdd98f3cc318d632324559c2fda74762c7c +Subproject commit dcdeef7f19796d4393627e20dbf3903d2dda05cb From 5cdb26dc95dda31352f663d0bab4eac6ff6a8b2a Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 17 Jan 2024 19:57:59 +0900 Subject: [PATCH 26/36] Match API of AEVs not to reveal stateRootHashBytes --- .../ForkableActionEvaluatorTest.cs | 16 +++++++--------- .../ForkableActionEvaluator.cs | 4 ++-- .../PluggedActionEvaluator.cs | 7 ++----- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs index 14e225647..fe68994d0 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs @@ -22,11 +22,11 @@ public void ForkEvaluation() ((101L, long.MaxValue), new PostActionEvaluator()), }, new SingleActionLoader(typeof(MockAction))); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(0), null, out _)).Action); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(99), null, out _)).Action); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(100), null, out _)).Action); - Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(101), null, out _)).Action); - Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(long.MaxValue), null, out _)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(0), null)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(99), null)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(100), null)).Action); + Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(101), null)).Action); + Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(long.MaxValue), null)).Action); } [Fact] @@ -63,9 +63,8 @@ class PostActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); public IReadOnlyList Evaluate( - IPreEvaluationBlock block, HashDigest? baseStateRootHash, out HashDigest stateRootHash) + IPreEvaluationBlock block, HashDigest? baseStateRootHash) { - stateRootHash = new HashDigest(); return new ICommittedActionEvaluation[] { new CommittedActionEvaluation( @@ -88,9 +87,8 @@ class PreActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); public IReadOnlyList Evaluate( - IPreEvaluationBlock block, HashDigest? baseStateRootHash, out HashDigest stateRootHash) + IPreEvaluationBlock block, HashDigest? baseStateRootHash) { - stateRootHash = new HashDigest(); return new ICommittedActionEvaluation[] { new CommittedActionEvaluation( diff --git a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs index 637110766..bf067c6ca 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs @@ -22,9 +22,9 @@ public IActionLoader ActionLoader } public IReadOnlyList Evaluate( - IPreEvaluationBlock block, HashDigest? baseStateRootHash, out HashDigest stateRootHash) + IPreEvaluationBlock block, HashDigest? baseStateRootHash) { var actionEvaluator = _router.GetEvaluator(block.Index); - return actionEvaluator.Evaluate(block, baseStateRootHash, out stateRootHash); + return actionEvaluator.Evaluate(block, baseStateRootHash); } } diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs index dc72b34df..9c70a1cb8 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs @@ -48,15 +48,12 @@ public static IPluginActionEvaluator CreateActionEvaluator(string pluginPath, st public IReadOnlyList Evaluate( IPreEvaluationBlock block, - HashDigest? baseStateRootHash, - out HashDigest stateRootHash) + HashDigest? baseStateRootHash) { var evaluations = _pluginActionEvaluator.Evaluate( PreEvaluationBlockMarshaller.Serialize(block), - baseStateRootHash is { } srh ? srh.ToByteArray() : null, - out var stateRootHashBytes) + baseStateRootHash is { } srh ? srh.ToByteArray() : null) .Select(eval => ActionEvaluationMarshaller.Deserialize(eval)).ToList().AsReadOnly(); - stateRootHash = new HashDigest(stateRootHashBytes); return evaluations; } } From 6ea12ca08a8190f605692670c249293a64331773 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 19 Jan 2024 14:06:52 +0900 Subject: [PATCH 27/36] ci: Add upgrade/libplanet-4.0 branch on workflow to push docker image --- .github/workflows/push_docker_image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/push_docker_image.yml b/.github/workflows/push_docker_image.yml index d603c9d2b..3d4620870 100644 --- a/.github/workflows/push_docker_image.yml +++ b/.github/workflows/push_docker_image.yml @@ -13,6 +13,7 @@ on: - release/* # This branch is for testing only. Use until the next(v200080) release. - test/action-evaluation-publisher-elapse-metric + - upgrade/libplanet-4.0 tags: - "*" workflow_dispatch: From 535fe8743ca11b54d58f4c75f184e9a6aa1c9405 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 23 Jan 2024 14:03:05 +0900 Subject: [PATCH 28/36] Bump lib9c to fb07bd824 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index dcdeef7f1..fb07bd824 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit dcdeef7f19796d4393627e20dbf3903d2dda05cb +Subproject commit fb07bd82437e2e112cc910651b55ec2acea92c2e From eddeb748f4ff3c46141f46f22c4ed8367d9d8db6 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 25 Jan 2024 10:18:14 +0900 Subject: [PATCH 29/36] bump: Bump Lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index fb07bd824..e311f6690 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit fb07bd82437e2e112cc910651b55ec2acea92c2e +Subproject commit e311f66901cf9269efe2405e0717ccf088cf64eb From e0ffd6b21ef1c88c86f555e0f71760989fa0a5d1 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 25 Jan 2024 10:18:35 +0900 Subject: [PATCH 30/36] bump: Bump NineChronicles.RPC.Shared --- NineChronicles.RPC.Shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index bf2b9ce54..7e44fbae3 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit bf2b9ce54b80d0d987a65a4f2abeb30d4d194e83 +Subproject commit 7e44fbae3c2b689ed4579b0c3b6037581ce3e88f From a44bb06ea7befecf6730e2d36d7d42935e4b38ef Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 25 Jan 2024 11:28:22 +0900 Subject: [PATCH 31/36] fix: Build fix from NineChronicles.RPC.Shared bump --- NineChronicles.Headless/BlockChainService.cs | 34 +++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index b2910dc75..7744ded12 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -155,24 +155,42 @@ public UnaryResult GetStateByStateRootHash( return new UnaryResult(encoded); } - public UnaryResult GetAgentStateByBlockHash( + public async UnaryResult> GetAgentStatesByBlockHash( byte[] blockHashBytes, - byte[] addressBytes) + IEnumerable addressBytesList) { var hash = new BlockHash(blockHashBytes); var worldState = _blockChain.GetWorldState(hash); - var address = new Address(addressBytes); - return new UnaryResult(_codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + var result = new ConcurrentDictionary(); + var addresses = addressBytesList.Select(a => new Address(a)).ToList(); + var taskList = addresses.Select(address => Task.Run(() => + { + result.TryAdd( + address.ToByteArray(), + _codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + })); + + await Task.WhenAll(taskList); + return result.ToDictionary(kv => kv.Key, kv => kv.Value); } - public UnaryResult GetAgentStateByStateRootHash( + public async UnaryResult> GetAgentStatesByStateRootHash( byte[] stateRootHashBytes, - byte[] addressBytes) + IEnumerable addressBytesList) { var stateRootHash = new HashDigest(stateRootHashBytes); var worldState = _blockChain.GetWorldState(stateRootHash); - var address = new Address(addressBytes); - return new UnaryResult(_codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + var result = new ConcurrentDictionary(); + var addresses = addressBytesList.Select(a => new Address(a)).ToList(); + var taskList = addresses.Select(address => Task.Run(() => + { + result.TryAdd( + address.ToByteArray(), + _codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + })); + + await Task.WhenAll(taskList); + return result.ToDictionary(kv => kv.Key, kv => kv.Value); } public async UnaryResult> GetAvatarStatesByBlockHash( From 1c3aec2e95038c952913159c18e8542d57b2b540 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 26 Jan 2024 17:23:13 +0900 Subject: [PATCH 32/36] fix: Fix NRE on byte encodings on BlockChainService --- NineChronicles.Headless/BlockChainService.cs | 28 ++++++++------------ 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 7744ded12..28835c8a8 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -165,9 +165,8 @@ public async UnaryResult> GetAgentStatesByBlockHash( var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var taskList = addresses.Select(address => Task.Run(() => { - result.TryAdd( - address.ToByteArray(), - _codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + var value = worldState.GetResolvedState(address, Addresses.Agent); + result.TryAdd(address.ToByteArray(), _codec.Encode(value ?? Null.Value)); })); await Task.WhenAll(taskList); @@ -184,9 +183,8 @@ public async UnaryResult> GetAgentStatesByStateRootHa var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var taskList = addresses.Select(address => Task.Run(() => { - result.TryAdd( - address.ToByteArray(), - _codec.Encode(worldState.GetResolvedState(address, Addresses.Agent))); + var value = worldState.GetResolvedState(address, Addresses.Agent); + result.TryAdd(address.ToByteArray(), _codec.Encode(value ?? Null.Value)); })); await Task.WhenAll(taskList); @@ -203,9 +201,8 @@ public async UnaryResult> GetAvatarStatesByBlockHash( var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var taskList = addresses.Select(address => Task.Run(() => { - result.TryAdd( - address.ToByteArray(), - _codec.Encode(worldState.GetFullAvatarStateRaw(address))); + var value = worldState.GetFullAvatarStateRaw(address); + result.TryAdd(address.ToByteArray(), _codec.Encode(value ?? Null.Value)); })); await Task.WhenAll(taskList); @@ -220,14 +217,11 @@ public async UnaryResult> GetAvatarStatesByStateRootH var stateRootHash = new HashDigest(stateRootHashBytes); var worldState = _blockChain.GetWorldState(stateRootHash); var result = new ConcurrentDictionary(); - var taskList = addresses - .Select(address => Task.Run(() => - { - result.TryAdd( - address.ToByteArray(), - _codec.Encode(worldState.GetFullAvatarStateRaw(address))); - })) - .ToList(); + var taskList = addresses.Select(address => Task.Run(() => + { + var value = worldState.GetFullAvatarStateRaw(address); + result.TryAdd(address.ToByteArray(), _codec.Encode(value ?? Null.Value)); + })); await Task.WhenAll(taskList); return result.ToDictionary(kv => kv.Key, kv => kv.Value); From 8552cee009130d02718f4f966bb9f6e42c2a0a68 Mon Sep 17 00:00:00 2001 From: Ko Chanhyuck Date: Fri, 26 Jan 2024 18:57:18 +0900 Subject: [PATCH 33/36] chore: use given address byte as key in GetAgentState --- NineChronicles.Headless/BlockChainService.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 28835c8a8..237c3e9f0 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -162,11 +162,10 @@ public async UnaryResult> GetAgentStatesByBlockHash( var hash = new BlockHash(blockHashBytes); var worldState = _blockChain.GetWorldState(hash); var result = new ConcurrentDictionary(); - var addresses = addressBytesList.Select(a => new Address(a)).ToList(); - var taskList = addresses.Select(address => Task.Run(() => + var taskList = addressBytesList.Select(addressByte => Task.Run(() => { - var value = worldState.GetResolvedState(address, Addresses.Agent); - result.TryAdd(address.ToByteArray(), _codec.Encode(value ?? Null.Value)); + var value = worldState.GetResolvedState(new Address(addressByte), Addresses.Agent); + result.TryAdd(addressByte, _codec.Encode(value ?? Null.Value)); })); await Task.WhenAll(taskList); @@ -180,11 +179,10 @@ public async UnaryResult> GetAgentStatesByStateRootHa var stateRootHash = new HashDigest(stateRootHashBytes); var worldState = _blockChain.GetWorldState(stateRootHash); var result = new ConcurrentDictionary(); - var addresses = addressBytesList.Select(a => new Address(a)).ToList(); - var taskList = addresses.Select(address => Task.Run(() => + var taskList = addressBytesList.Select(addressByte => Task.Run(() => { - var value = worldState.GetResolvedState(address, Addresses.Agent); - result.TryAdd(address.ToByteArray(), _codec.Encode(value ?? Null.Value)); + var value = worldState.GetResolvedState(new Address(addressByte), Addresses.Agent); + result.TryAdd(addressByte, _codec.Encode(value ?? Null.Value)); })); await Task.WhenAll(taskList); From dfa50a3e4e0c6a33d71b27ebcbd190ddf96df915 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 31 Jan 2024 11:17:20 +0900 Subject: [PATCH 34/36] Bump lib9c to 717f2cc8 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index e311f6690..717f2cc8c 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit e311f66901cf9269efe2405e0717ccf088cf64eb +Subproject commit 717f2cc8cb910ca11501ed4f264efcdb263bdf56 From af4a89f37e8bcb3a5e4d219781cf15749956a3bf Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 31 Jan 2024 11:41:50 +0900 Subject: [PATCH 35/36] Bump lib9c to 21a5f55b3 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 717f2cc8c..21a5f55b3 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 717f2cc8cb910ca11501ed4f264efcdb263bdf56 +Subproject commit 21a5f55b31a8bef7976bf1530565664dfc8abd27 From 953af4d85fd341fd144b0f6830a874e1e25e77af Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Thu, 1 Feb 2024 11:52:56 +0900 Subject: [PATCH 36/36] Bump lib9c to development (instead of upgrade/libplanet-4.0) --- .github/workflows/push_docker_image.yml | 1 - Lib9c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/push_docker_image.yml b/.github/workflows/push_docker_image.yml index 3d4620870..d603c9d2b 100644 --- a/.github/workflows/push_docker_image.yml +++ b/.github/workflows/push_docker_image.yml @@ -13,7 +13,6 @@ on: - release/* # This branch is for testing only. Use until the next(v200080) release. - test/action-evaluation-publisher-elapse-metric - - upgrade/libplanet-4.0 tags: - "*" workflow_dispatch: diff --git a/Lib9c b/Lib9c index 21a5f55b3..7575c9a65 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 21a5f55b31a8bef7976bf1530565664dfc8abd27 +Subproject commit 7575c9a657bcc3a43360de823568ee3b9f0219c9