From 3646f6473744647440b74d4eab1639f82ec5db78 Mon Sep 17 00:00:00 2001 From: Chanhyuck Ko Date: Tue, 2 Apr 2024 19:04:39 +0900 Subject: [PATCH] feat: prepare initial pos states --- .Lib9c.Tests/Action/DPoS/DPoSActionTest.cs | 92 ++++++++++++++++++ .../Action/NCActionEvaluation.cs | 3 +- Lib9c.Policy/Policy/BlockPolicySource.cs | 3 +- Lib9c.Policy/Policy/DebugPolicy.cs | 3 +- Lib9c.Renderers/Renderers/ActionRenderer.cs | 5 +- Lib9c.Utils/BlockHelper.cs | 15 ++- Lib9c/Action/DPoS/InitializeValidators.cs | 94 +++++++++++++++++++ Lib9c/Action/DPoS/PoSAction.cs | 14 ++- Lib9c/Action/DPoS/Sys/PromoteValidator.cs | 8 +- Lib9c/Action/InitializeStates.cs | 38 +++++++- 10 files changed, 253 insertions(+), 22 deletions(-) create mode 100644 .Lib9c.Tests/Action/DPoS/DPoSActionTest.cs create mode 100644 Lib9c/Action/DPoS/InitializeValidators.cs diff --git a/.Lib9c.Tests/Action/DPoS/DPoSActionTest.cs b/.Lib9c.Tests/Action/DPoS/DPoSActionTest.cs new file mode 100644 index 0000000000..35e12a4775 --- /dev/null +++ b/.Lib9c.Tests/Action/DPoS/DPoSActionTest.cs @@ -0,0 +1,92 @@ +namespace Lib9c.Tests.Action.DPoS +{ + using System.Linq; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Action.DPoS; + using Nekoyume.Action.DPoS.Control; + using Nekoyume.Action.DPoS.Misc; + using Nekoyume.Action.DPoS.Sys; + using Nekoyume.Module; + using Xunit; + + public class DPoSActionTest + { + [Fact] + public void Execute() + { + // Prepare initial state. + IWorld initialState = new World(new MockWorldState()); + const int count = 4; + var validatorKeys = Enumerable.Range(0, count).Select(_ => new PrivateKey().PublicKey).ToArray(); + initialState = validatorKeys.Aggregate( + initialState, + (current, key) => current.MintAsset( + new ActionContext(), + key.Address, + new FungibleAssetValue(Asset.GovernanceToken, 1, 0))); + foreach (var key in validatorKeys) + { + Assert.Equal(1, initialState.GetBalance(key.Address, Asset.GovernanceToken).MajorUnit); + Assert.Equal(0, initialState.GetBalance(key.Address, Asset.GovernanceToken).MinorUnit); + } + + // Stake 1 for each validator. + foreach (var key in validatorKeys) + { + initialState = new PromoteValidator( + key, + new FungibleAssetValue(Asset.GovernanceToken, 1, 0)).Execute( + new ActionContext + { + PreviousState = initialState, + Signer = key.Address, + }); + } + + Assert.Equal(0, ValidatorSetCtrl.FetchBondedValidatorSet(initialState).Item2.Count); + Assert.Equal(0, initialState.GetValidatorSet().TotalCount); + + // Execute the action. + initialState = new PoSAction().Execute( + new ActionContext + { + PreviousState = initialState, + LastCommit = null, + }); + + Assert.Equal(count, ValidatorSetCtrl.FetchBondedValidatorSet(initialState).Item2.Count); + Assert.Equal(count, initialState.GetValidatorSet().TotalCount); + Assert.Equal( + validatorKeys.ToHashSet(), + initialState.GetValidatorSet() + .Validators.Select(validator => validator.PublicKey) + .ToHashSet()); + + // TODO: Add gold distribution tests + initialState = new Undelegate( + validatorKeys[0].Address, + new FungibleAssetValue(Asset.Share, 1, 0)).Execute( + new ActionContext + { + PreviousState = initialState, + Signer = validatorKeys[0].Address, + }); + + Assert.Equal(count, ValidatorSetCtrl.FetchBondedValidatorSet(initialState).Item2.Count); + Assert.Equal(count, initialState.GetValidatorSet().TotalCount); + + // Execute the action. + initialState = new PoSAction().Execute( + new ActionContext + { + PreviousState = initialState, + LastCommit = null, + }); + + Assert.Equal(count - 1, ValidatorSetCtrl.FetchBondedValidatorSet(initialState).Item2.Count); + Assert.Equal(count - 1, initialState.GetValidatorSet().TotalCount); + } + } +} diff --git a/Lib9c.MessagePack/Action/NCActionEvaluation.cs b/Lib9c.MessagePack/Action/NCActionEvaluation.cs index f977a3489d..fb5005b911 100644 --- a/Lib9c.MessagePack/Action/NCActionEvaluation.cs +++ b/Lib9c.MessagePack/Action/NCActionEvaluation.cs @@ -9,6 +9,7 @@ using Libplanet.Common; using Libplanet.Types.Tx; using MessagePack; +using Nekoyume.Action.DPoS; namespace Nekoyume.Action { @@ -81,7 +82,7 @@ public ActionEvaluation ToActionEvaluation() { return new ActionEvaluation { - Action = Action is null ? new RewardGold() : Action, + Action = Action is null ? new PoSAction() : Action, Signer = Signer, BlockIndex = BlockIndex, OutputState = OutputState, diff --git a/Lib9c.Policy/Policy/BlockPolicySource.cs b/Lib9c.Policy/Policy/BlockPolicySource.cs index edd16ab940..8b8b4c2033 100644 --- a/Lib9c.Policy/Policy/BlockPolicySource.cs +++ b/Lib9c.Policy/Policy/BlockPolicySource.cs @@ -16,6 +16,7 @@ using Libplanet.Crypto; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; +using Nekoyume.Action.DPoS; #if UNITY_EDITOR || UNITY_STANDALONE using UniRx; @@ -130,7 +131,7 @@ internal IBlockPolicy GetPolicy( // FIXME: Slight inconsistency due to pre-existing delegate. return new BlockPolicy( - new RewardGold(), + new PoSAction(), blockInterval: BlockInterval, validateNextBlockTx: validateNextBlockTx, validateNextBlock: validateNextBlock, diff --git a/Lib9c.Policy/Policy/DebugPolicy.cs b/Lib9c.Policy/Policy/DebugPolicy.cs index 54985c7aec..c5924ab7a9 100644 --- a/Lib9c.Policy/Policy/DebugPolicy.cs +++ b/Lib9c.Policy/Policy/DebugPolicy.cs @@ -4,6 +4,7 @@ using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using Nekoyume.Action; +using Nekoyume.Action.DPoS; namespace Nekoyume.Blockchain.Policy { @@ -13,7 +14,7 @@ public DebugPolicy() { } - public IAction BlockAction { get; } = new RewardGold(); + public IAction BlockAction { get; } = new PoSAction(); public TxPolicyViolationException ValidateNextBlockTx( BlockChain blockChain, Transaction transaction) diff --git a/Lib9c.Renderers/Renderers/ActionRenderer.cs b/Lib9c.Renderers/Renderers/ActionRenderer.cs index d46df38df8..135b475002 100644 --- a/Lib9c.Renderers/Renderers/ActionRenderer.cs +++ b/Lib9c.Renderers/Renderers/ActionRenderer.cs @@ -8,6 +8,7 @@ using Serilog; using Bencodex.Types; using Libplanet.Common; +using Nekoyume.Action.DPoS; using Nekoyume.Action.Loader; #if UNITY_EDITOR || UNITY_STANDALONE using UniRx; @@ -37,7 +38,7 @@ public void RenderAction(IValue action, ICommittedActionContext context, HashDig ActionRenderSubject.OnNext(new ActionEvaluation { Action = context.BlockAction - ? new RewardGold() + ? new PoSAction() : (ActionBase)_actionLoader.LoadAction(context.BlockIndex, action), Signer = context.Signer, BlockIndex = context.BlockIndex, @@ -53,7 +54,7 @@ public void RenderActionError(IValue action, ICommittedActionContext context, Ex ActionRenderSubject.OnNext(new ActionEvaluation { Action = context.BlockAction - ? new RewardGold() + ? new PoSAction() : (ActionBase)_actionLoader.LoadAction(context.BlockIndex, action), Signer = context.Signer, BlockIndex = context.BlockIndex, diff --git a/Lib9c.Utils/BlockHelper.cs b/Lib9c.Utils/BlockHelper.cs index a28fa9c51b..cf73159b48 100644 --- a/Lib9c.Utils/BlockHelper.cs +++ b/Lib9c.Utils/BlockHelper.cs @@ -15,6 +15,7 @@ using Libplanet.Types.Consensus; using Libplanet.Types.Tx; using Nekoyume.Action; +using Nekoyume.Action.DPoS; using Nekoyume.Action.Loader; using Nekoyume.Blockchain.Policy; using Nekoyume.Model.State; @@ -38,8 +39,8 @@ public static Block ProposeGenesisBlock( DateTimeOffset? timestamp = null, IEnumerable? actionBases = null, Currency? goldCurrency = null, - ISet
? assetMinters = null - ) + ISet
? assetMinters = null, + Dictionary? initialFavs = null) { if (!tableSheets.TryGetValue(nameof(GameConfigSheet), out var csv)) { @@ -74,7 +75,8 @@ public static Block ProposeGenesisBlock( pendingActivationStates: pendingActivationStates, authorizedMinersState: authorizedMinersState, creditsState: credits is null ? null : new CreditsState(credits), - assetMinters: assetMinters + assetMinters: assetMinters, + initialFavs: initialFavs ); List actions = new List { @@ -84,11 +86,8 @@ public static Block ProposeGenesisBlock( { new Initialize( states: ImmutableDictionary.Create(), - validatorSet: new ValidatorSet( - initialValidators.Select(validator => - new Validator(validator.Key, validator.Value)).ToList() - ) - ), + validatorSet: new ValidatorSet()), + new InitializeValidators(initialValidators) }; if (!(actionBases is null)) { diff --git a/Lib9c/Action/DPoS/InitializeValidators.cs b/Lib9c/Action/DPoS/InitializeValidators.cs new file mode 100644 index 0000000000..263ff662b1 --- /dev/null +++ b/Lib9c/Action/DPoS/InitializeValidators.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Numerics; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Action.DPoS.Control; +using Nekoyume.Action.DPoS.Misc; +using Nekoyume.Action.DPoS.Util; +using Nekoyume.Module; +using Serilog; + +namespace Nekoyume.Action.DPoS +{ + [ActionType(ActionTypeValue)] + public sealed class InitializeValidators : ActionBase + { + private const string ActionTypeValue = "initialize_validators"; + + public InitializeValidators(Dictionary validators) + { + Validators = validators.ToImmutableDictionary(); + } + + public InitializeValidators() + { + } + + public ImmutableDictionary Validators { get; set; } + + public override IValue PlainValue { + get + { + var validators = Dictionary.Empty; +#pragma warning disable LAA1002 + foreach (var (validator, power) in Validators) + { + validators = validators.Add((Binary)validator.Serialize(), power); + } +#pragma warning restore LAA1002 + + return Dictionary.Empty + .Add("type_id", new Text(ActionTypeValue)) + .Add("validators", validators); + } + } + + public override void LoadPlainValue(IValue plainValue) + { + var dict = (Bencodex.Types.Dictionary)plainValue; + var validatorDict = (Dictionary)dict["validators"]; + Validators = validatorDict.Select( + pair => + { + var key = pair.Key.ToPublicKey(); + var power = (Integer)pair.Value; + return new KeyValuePair(key, power); + }).ToImmutableDictionary(); + } + + public override IWorld Execute(IActionContext context) + { + Log.Debug("InitializeValidators"); + context.UseGas(1); + var states = context.PreviousState; + + var nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); +#pragma warning disable LAA1002 + foreach(var (validator, power) in Validators) + { + var amount = new FungibleAssetValue(Asset.GovernanceToken, power, 0); + states = states.MintAsset( + context, + validator.Address, + amount); + states = ValidatorCtrl.Create( + states, + context, + validator.Address, + validator, + amount, + nativeTokens); + } +#pragma warning restore LAA1002 + + Log.Debug("InitializeValidators complete"); + return states; + } + } +} diff --git a/Lib9c/Action/DPoS/PoSAction.cs b/Lib9c/Action/DPoS/PoSAction.cs index e11f525a0f..a2506b189e 100644 --- a/Lib9c/Action/DPoS/PoSAction.cs +++ b/Lib9c/Action/DPoS/PoSAction.cs @@ -6,13 +6,14 @@ using Nekoyume.Action.DPoS.Misc; using Nekoyume.Action.DPoS.Model; using Nekoyume.Module; +using Serilog; namespace Nekoyume.Action.DPoS { /// /// A block action for DPoS that updates . /// - public sealed class PoSAction : IAction + public sealed class PoSAction : ActionBase { /// /// Creates a new instance of . @@ -22,17 +23,18 @@ public PoSAction() } /// - public IValue PlainValue => new Bencodex.Types.Boolean(true); + public override IValue PlainValue => new Bencodex.Types.Boolean(true); /// - public void LoadPlainValue(IValue plainValue) + public override void LoadPlainValue(IValue plainValue) { // Method intentionally left empty. } /// - public IWorld Execute(IActionContext context) + public override IWorld Execute(IActionContext context) { + Log.Debug("Running PoSAction"); IActionContext ctx = context; var states = ctx.PreviousState; @@ -53,6 +55,10 @@ public IWorld Execute(IActionContext context) var bondedSet = ValidatorSetCtrl.FetchBondedValidatorSet(states).Item2.Set; foreach (var validator in bondedSet) { + Log.Debug( + "Set Validator: {0} (Power: {1})", + validator.OperatorPublicKey, + validator.ConsensusToken.RawValue); states = states.SetValidator( new Libplanet.Types.Consensus.Validator( validator.OperatorPublicKey, diff --git a/Lib9c/Action/DPoS/Sys/PromoteValidator.cs b/Lib9c/Action/DPoS/Sys/PromoteValidator.cs index fdd3bfbc0c..90e00121ca 100644 --- a/Lib9c/Action/DPoS/Sys/PromoteValidator.cs +++ b/Lib9c/Action/DPoS/Sys/PromoteValidator.cs @@ -15,7 +15,7 @@ namespace Nekoyume.Action.DPoS.Sys /// A system action for DPoS that promotes non-validator node to a validator. /// [ActionType(ActionTypeValue)] - public sealed class PromoteValidator : IAction + public sealed class PromoteValidator : ActionBase { private const string ActionTypeValue = "promote_validator"; @@ -50,13 +50,13 @@ public PromoteValidator() public FungibleAssetValue Amount { get; set; } /// - public IValue PlainValue => Bencodex.Types.Dictionary.Empty + public override IValue PlainValue => Bencodex.Types.Dictionary.Empty .Add("type_id", new Text(ActionTypeValue)) .Add("validator", Validator.Serialize()) .Add("amount", Amount.Serialize()); /// - public void LoadPlainValue(IValue plainValue) + public override void LoadPlainValue(IValue plainValue) { var dict = (Bencodex.Types.Dictionary)plainValue; Validator = dict["validator"].ToPublicKey(); @@ -64,7 +64,7 @@ public void LoadPlainValue(IValue plainValue) } /// - public IWorld Execute(IActionContext context) + public override IWorld Execute(IActionContext context) { IActionContext ctx = context; if (!ctx.Signer.Equals(Validator.Address)) diff --git a/Lib9c/Action/InitializeStates.cs b/Lib9c/Action/InitializeStates.cs index 31f6f359f5..6965c97ee1 100644 --- a/Lib9c/Action/InitializeStates.cs +++ b/Lib9c/Action/InitializeStates.cs @@ -9,6 +9,7 @@ using Nekoyume.Model.State; using Nekoyume.Module; using Libplanet.Crypto; +using Libplanet.Types.Assets; namespace Nekoyume.Action { @@ -57,6 +58,8 @@ public class InitializeStates : GameAction, IInitializeStatesV1 public ISet
AssetMinters { get; set; } + public Dictionary InitialFavs { get; set; } + Dictionary IInitializeStatesV1.Ranking => Ranking; Dictionary IInitializeStatesV1.Shop => Shop; Dictionary IInitializeStatesV1.TableSheets => TableSheets; @@ -87,7 +90,8 @@ public InitializeStates( AdminState adminAddressState = null, AuthorizedMinersState authorizedMinersState = null, CreditsState creditsState = null, - ISet
assetMinters = null) + ISet
assetMinters = null, + Dictionary initialFavs = null) { Ranking = (Dictionary)rankingState.Serialize(); Shop = (Dictionary)shopState.Serialize(); @@ -114,6 +118,17 @@ public InitializeStates( { AssetMinters = assetMinters; } + + if (!(initialFavs is null)) + { +#pragma warning disable LAA1002 + InitialFavs = new Dictionary( + initialFavs.Select( + kv => new KeyValuePair( + (Binary)kv.Key.Serialize(), + kv.Value.Serialize()))); +#pragma warning restore LAA1002 + } } public override IWorld Execute(IActionContext context) @@ -192,6 +207,17 @@ public override IWorld Execute(IActionContext context) ); } + if (InitialFavs is { }) + { + foreach (var (address, fav) in InitialFavs) + { + states = states.MintAsset( + ctx, + new Address(address), + new FungibleAssetValue(fav)); + } + } + return states; } @@ -235,6 +261,11 @@ protected override IImmutableDictionary PlainValueInternal rv = rv.Add("asset_minters", new List(AssetMinters.Select(addr => addr.Serialize()))); } + if (!(InitialFavs is null)) + { + rv = rv.Add("initial_favs", InitialFavs); + } + return rv; } } @@ -274,6 +305,11 @@ protected override void LoadPlainValueInternal(IImmutableDictionary addr.ToAddress()).ToHashSet(); } + + if (plainValue.TryGetValue("initial_favs", out IValue intialFavs)) + { + InitialFavs = (Dictionary)intialFavs; + } } } }