From 91c72717d13fd6d0a85085e0b4fd9218ae891f99 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Thu, 2 Jan 2025 05:23:42 +0900 Subject: [PATCH 01/26] Remove moreal from auto_assign reviewers --- .github/auto_assign.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml index 904b5c412..fc9178169 100644 --- a/.github/auto_assign.yml +++ b/.github/auto_assign.yml @@ -4,4 +4,3 @@ addAssignees: author reviewers: - ipdae - area363 - - moreal From 5c9bbbeb579ef744bdbcc98fdb082adb35651a9c Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 9 Jan 2025 11:21:16 +0900 Subject: [PATCH 02/26] fix lint action error --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2a4f80500..689202973 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: dotnet format run: dotnet format --exclude Lib9c --exclude NineChronicles.RPC.Shared -v=d --no-restore --verify-no-changes validate-appsettings-json: From 054e0c8b1f31e8b627317e464383f11b80add224 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Mon, 13 Jan 2025 13:23:34 +0900 Subject: [PATCH 03/26] Update default max gas price --- NineChronicles.Headless/GraphTypes/StandaloneQuery.cs | 2 +- NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index c9ed20236..60ec53ef0 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -571,7 +571,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IKeyStore keyStore, new QueryArgument { Name = "maxGasPrice", - DefaultValue = 1 * Currencies.Mead + DefaultValue = 0.00001 * Currencies.Mead } ), resolve: context => diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index 3e8a1a19a..d651348f6 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -169,7 +169,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) genesisHash: blockChain.Genesis.Hash, actions: new TxActionList(new[] { action.PlainValue }), gasLimit: action is ITransferAsset or ITransferAssets ? RequestPledge.DefaultRefillMead : 1L, - maxGasPrice: 1 * Currencies.Mead + maxGasPrice: 0.00001 * Currencies.Mead ), new TxSigningMetadata(publicKey: publicKey, nonce: nonce) ); From 6e96ad2a2229513ae82258cc50b4535bb16c4783 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 16 Jan 2025 17:57:54 +0900 Subject: [PATCH 04/26] Filter for reduce broadcast action --- .../ActionEvaluationPublisher.cs | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 98000be39..eb20fb2d5 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -32,6 +32,13 @@ using Microsoft.Extensions.Options; using Nekoyume; using Nekoyume.Action; +using Nekoyume.Action.AdventureBoss; +using Nekoyume.Action.Coupons; +using Nekoyume.Action.CustomEquipmentCraft; +using Nekoyume.Action.Garages; +using Nekoyume.Action.Guild; +using Nekoyume.Action.Guild.Migration; +using Nekoyume.Action.ValidatorDelegation; using Nekoyume.Shared.Hubs; using Serilog; @@ -388,6 +395,7 @@ private sealed class Client : IAsyncDisposable private readonly IBlockChainStates _blockChainStates; private readonly RpcContext _context; private readonly Address _clientAddress; + private readonly List
_avatarAddresses; private IDisposable? _blockSubscribe; private IDisposable? _actionEveryRenderSubscribe; @@ -410,6 +418,8 @@ private Client( _clientAddress = clientAddress; _context = context; TargetAddresses = ImmutableHashSet
.Empty; + _avatarAddresses = Enumerable.Range(0, GameConfig.SlotCount) + .Select(index => Addresses.GetAvatarAddress(_clientAddress, index)).ToList(); } public static async Task CreateAsync( @@ -456,6 +466,7 @@ await _hub.BroadcastRenderBlockAsync( _actionEveryRenderSubscribe = actionRenderer.EveryRender() .SubscribeOn(NewThreadScheduler.Default) + .Where(FilterEvaluation) .ObserveOn(NewThreadScheduler.Default) .Subscribe( async ev => @@ -581,6 +592,117 @@ public async ValueTask DisposeAsync() _nodeStatusSubscribe?.Dispose(); await _hub.DisposeAsync(); } + + /// + /// Evaluates whether a given action should be filtered based on the signer and action type. + /// + /// The evaluation object containing the action and signer information. + /// + /// Returns true if the action passes the filter criteria; otherwise, false. + /// + private bool FilterEvaluation(ActionEvaluation eval) + { + var actionBase = eval.Action; + var isSigner = eval.Signer.Equals(_clientAddress); + + switch (actionBase) + { + // Actions that require the signer to be the client + case ApprovePledge + or ExploreAdventureBoss + or SweepAdventureBoss + or UnlockFloor + or AuraSummon + or BattleArena + or ChargeActionPoint + or ClaimGifts + or ClaimRaidReward + or ClaimStakeReward + or ClaimWordBossKillReward + or CombinationConsumable + or CombinationEquipment + or CostumeSummon + or CreateAvatar + or CustomEquipmentCraft + or DailyReward + or EndPledge + or EventConsumableItemCrafts + or EventDungeonBattle + or EventMaterialItemCrafts + or Grinding + or ClaimReward + or HackAndSlash + or HackAndSlashRandomBuff + or HackAndSlashSweep + or ItemEnhancement + or JoinArena + or PetEnhancement + or Raid + or RapidCombination + or RuneEnhancement + or RuneSummon + or UnlockCombinationSlot + or UnlockEquipmentRecipe + or UnlockRuneSlot + or UnlockWorld: + return isSigner; + + // Actions that are always allowed + case ClaimAdventureBossReward or Wanted or BuyProduct or CancelProductRegistration: + return true; + + // Actions with specific conditions + case ClaimItems claimItems: + return isSigner || claimItems.ClaimData.Any(c => _avatarAddresses.Contains(c.address)); + + case UnloadFromMyGarages unloadFromMyGarages: + return IsUnloadFromMyGaragesValid(unloadFromMyGarages, _avatarAddresses, _clientAddress); + + case RequestPledge requestPledge: + return isSigner || requestPledge.AgentAddress.Equals(_clientAddress); + + case TransferAsset transferAsset: + return isSigner || IsRecipientValid(transferAsset.Recipient, _avatarAddresses, _clientAddress); + + case TransferAssets transferAssets: + return isSigner || transferAssets.Recipients.Any(r => IsRecipientValid(r.recipient, _avatarAddresses, _clientAddress)); + + default: + return true; + } + } + + /// + /// Validates if the 'UnloadFromMyGarages' action is valid based on recipient and asset values. + /// + /// The action to validate. + /// List of avatar addresses to check against. + /// The client's address for validation. + /// + /// Returns true if the action is valid; otherwise, false. + /// + private bool IsUnloadFromMyGaragesValid(UnloadFromMyGarages unloadFromMyGarages, List
avatarAddresses, Address clientAddress) + { + return avatarAddresses.Contains(unloadFromMyGarages.RecipientAvatarAddr) || + (unloadFromMyGarages.FungibleAssetValues != null && + unloadFromMyGarages.FungibleAssetValues.Any(e => + e.balanceAddr.Equals(clientAddress) || avatarAddresses.Contains(e.balanceAddr))); + } + + /// + /// Checks if a recipient address is valid by comparing it to the client and avatar addresses. + /// + /// The recipient address to validate. + /// List of avatar addresses to check against. + /// The client's address for validation. + /// + /// Returns true if the recipient is valid; otherwise, false. + /// + private bool IsRecipientValid(Address recipient, List
avatarAddresses, Address clientAddress) + { + return recipient.Equals(clientAddress) || avatarAddresses.Contains(recipient); + } + } } } From cb76c89cec5b148862a3082e2a799b72e607e2ae Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 8 Jan 2025 19:32:09 +0900 Subject: [PATCH 05/26] Introduce ISwarmRepository --- NineChronicles.Headless/GraphQLService.cs | 2 ++ .../Repositories/Swarm/ISwarmRepository.cs | 9 +++++++++ .../Repositories/Swarm/SwarmRepository.cs | 12 ++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 NineChronicles.Headless/Repositories/Swarm/ISwarmRepository.cs create mode 100644 NineChronicles.Headless/Repositories/Swarm/SwarmRepository.cs diff --git a/NineChronicles.Headless/GraphQLService.cs b/NineChronicles.Headless/GraphQLService.cs index 74cbbdfe5..f15e9629d 100644 --- a/NineChronicles.Headless/GraphQLService.cs +++ b/NineChronicles.Headless/GraphQLService.cs @@ -20,6 +20,7 @@ using NineChronicles.Headless.Properties; using NineChronicles.Headless.Repositories.BlockChain; using NineChronicles.Headless.Repositories.StateTrie; +using NineChronicles.Headless.Repositories.Swarm; using NineChronicles.Headless.Repositories.Transaction; using NineChronicles.Headless.Repositories.WorldState; using Serilog; @@ -167,6 +168,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddHealthChecks(); diff --git a/NineChronicles.Headless/Repositories/Swarm/ISwarmRepository.cs b/NineChronicles.Headless/Repositories/Swarm/ISwarmRepository.cs new file mode 100644 index 000000000..041dc0eff --- /dev/null +++ b/NineChronicles.Headless/Repositories/Swarm/ISwarmRepository.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace NineChronicles.Headless.Repositories.Swarm +{ + public interface ISwarmRepository + { + void BroadcastTxs(IEnumerable txs); + } +} diff --git a/NineChronicles.Headless/Repositories/Swarm/SwarmRepository.cs b/NineChronicles.Headless/Repositories/Swarm/SwarmRepository.cs new file mode 100644 index 000000000..13bb6028f --- /dev/null +++ b/NineChronicles.Headless/Repositories/Swarm/SwarmRepository.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace NineChronicles.Headless.Repositories.Swarm +{ + public class SwarmRepository(Libplanet.Net.Swarm swarm) : ISwarmRepository + { + public void BroadcastTxs(IEnumerable txs) + { + swarm.BroadcastTxs(txs); + } + } +} From 2ac1741bdea04572978506134f08da4688df490b Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 8 Jan 2025 19:33:37 +0900 Subject: [PATCH 06/26] Use Repository pattern for Test --- NineChronicles.Headless/BlockChainService.cs | 62 ++++++++++--------- .../BlockChain/BlockChainRepository.cs | 40 ++++++++++++ .../BlockChain/IBlockChainRepository.cs | 11 ++++ 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 7553cc7b4..1cf773994 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -10,11 +10,9 @@ using Bencodex; using Bencodex.Types; using Libplanet.Action.State; -using Libplanet.Blockchain; using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Headless.Hosting; -using Libplanet.Net; using Libplanet.Types.Assets; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; @@ -22,9 +20,12 @@ using MagicOnion.Server; using Microsoft.Extensions.Caching.Memory; using Nekoyume; -using Nekoyume.Model.State; using Nekoyume.Module; using Nekoyume.Shared.Services; +using NineChronicles.Headless.Repositories.BlockChain; +using NineChronicles.Headless.Repositories.Swarm; +using NineChronicles.Headless.Repositories.Transaction; +using NineChronicles.Headless.Repositories.WorldState; using Serilog; using static NineChronicles.Headless.NCActionUtils; using NodeExceptionType = Libplanet.Headless.NodeExceptionType; @@ -34,8 +35,10 @@ namespace NineChronicles.Headless { public class BlockChainService : ServiceBase, IBlockChainService { - private BlockChain _blockChain; - private Swarm _swarm; + private IBlockChainRepository _blockChainRepository; + private ITransactionRepository _transactionRepository; + private IWorldStateRepository _worldStateRepository; + private ISwarmRepository _swarmRepository; private RpcContext _context; private Codec _codec; private LibplanetNodeServiceProperties _libplanetNodeServiceProperties; @@ -43,16 +46,20 @@ public class BlockChainService : ServiceBase, IBlockChainSer private MemoryCache _memoryCache; public BlockChainService( - BlockChain blockChain, - Swarm swarm, + IBlockChainRepository blockChainRepository, + ITransactionRepository transactionRepository, + IWorldStateRepository worldStateRepository, + ISwarmRepository swarmRepository, RpcContext context, LibplanetNodeServiceProperties libplanetNodeServiceProperties, ActionEvaluationPublisher actionEvaluationPublisher, StateMemoryCache cache ) { - _blockChain = blockChain; - _swarm = swarm; + _blockChainRepository = blockChainRepository; + _transactionRepository = transactionRepository; + _worldStateRepository = worldStateRepository; + _swarmRepository = swarmRepository; _context = context; _codec = new Codec(); _libplanetNodeServiceProperties = libplanetNodeServiceProperties; @@ -75,14 +82,13 @@ public UnaryResult PutTransaction(byte[] txBytes) try { Log.Debug("PutTransaction: (nonce: {nonce}, id: {id})", tx.Nonce, tx.Id); - Log.Debug("StagedTransactions: {txIds}", string.Join(", ", _blockChain.GetStagedTransactionIds())); + Log.Debug("StagedTransactions: {txIds}", string.Join(", ", _blockChainRepository.GetStagedTransactionIds())); #pragma warning disable CS8632 - Exception? validationExc = _blockChain.Policy.ValidateNextBlockTx(_blockChain, tx); + Exception? validationExc = _blockChainRepository.ValidateNextBlockTx(tx); #pragma warning restore CS8632 if (validationExc is null) { - _blockChain.StageTransaction(tx); - _swarm.BroadcastTxs(new[] { tx }); + _swarmRepository.BroadcastTxs(new[] { tx }); } else { @@ -112,7 +118,7 @@ public UnaryResult GetStateByBlockHash( var hash = new BlockHash(blockHashBytes); var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); - IValue state = _blockChain + IValue state = _worldStateRepository .GetWorldState(hash) .GetAccountState(accountAddress) .GetState(address); @@ -129,7 +135,7 @@ public UnaryResult GetStateByStateRootHash( var stateRootHash = new HashDigest(stateRootHashBytes); var accountAddress = new Address(accountAddressBytes); var address = new Address(addressBytes); - IValue state = _blockChain + IValue state = _worldStateRepository .GetWorldState(stateRootHash) .GetAccountState(accountAddress) .GetState(address); @@ -142,7 +148,7 @@ public async UnaryResult> GetAgentStatesByBlockHash( IEnumerable addressBytesList) { var hash = new BlockHash(blockHashBytes); - var worldState = _blockChain.GetWorldState(hash); + var worldState = _worldStateRepository.GetWorldState(hash); var result = new ConcurrentDictionary(); var taskList = addressBytesList.Select(addressByte => Task.Run(() => { @@ -159,7 +165,7 @@ public async UnaryResult> GetAgentStatesByStateRootHa IEnumerable addressBytesList) { var stateRootHash = new HashDigest(stateRootHashBytes); - var worldState = _blockChain.GetWorldState(stateRootHash); + var worldState = _worldStateRepository.GetWorldState(stateRootHash); var result = new ConcurrentDictionary(); var taskList = addressBytesList.Select(addressByte => Task.Run(() => { @@ -176,7 +182,7 @@ public async UnaryResult> GetAvatarStatesByBlockHash( IEnumerable addressBytesList) { var hash = new BlockHash(blockHashBytes); - var worldState = _blockChain.GetWorldState(hash); + var worldState = _worldStateRepository.GetWorldState(hash); var result = new ConcurrentDictionary(); var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var taskList = addresses.Select(address => Task.Run(() => @@ -195,7 +201,7 @@ public async UnaryResult> GetAvatarStatesByStateRootH { var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var stateRootHash = new HashDigest(stateRootHashBytes); - var worldState = _blockChain.GetWorldState(stateRootHash); + var worldState = _worldStateRepository.GetWorldState(stateRootHash); var result = new ConcurrentDictionary(); var taskList = addresses.Select(address => Task.Run(() => { @@ -217,7 +223,7 @@ public UnaryResult> GetBulkStateByBlockHash( List
addresses = addressBytesList.Select(b => new Address(b)).ToList(); var result = new Dictionary(); - IReadOnlyList values = _blockChain + IReadOnlyList values = _worldStateRepository .GetWorldState(blockHash) .GetAccountState(accountAddress) .GetStates(addresses); @@ -239,7 +245,7 @@ public UnaryResult> GetBulkStateByStateRootHash( List
addresses = addressBytesList.Select(b => new Address(b)).ToList(); var result = new Dictionary(); - IReadOnlyList values = _blockChain + IReadOnlyList values = _worldStateRepository .GetWorldState(stateRootHash) .GetAccountState(accountAddress) .GetStates(addresses); @@ -278,7 +284,7 @@ public UnaryResult> GetSheets( if (addresses.Any()) { var stateRootHash = new HashDigest(stateRootHashBytes); - IReadOnlyList values = _blockChain.GetWorldState(stateRootHash).GetLegacyStates(addresses); + IReadOnlyList values = _worldStateRepository.GetWorldState(stateRootHash).GetLegacyStates(addresses); sw.Stop(); Log.Information("[GetSheets]Get sheet from state: {Count}, Elapsed: {Elapsed}", addresses.Count, sw.Elapsed); sw.Restart(); @@ -303,7 +309,7 @@ public UnaryResult GetBalanceByBlockHash( var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); - FungibleAssetValue balance = _blockChain + FungibleAssetValue balance = _worldStateRepository .GetWorldState(blockHash) .GetBalance(address, currency); byte[] encoded = _codec.Encode( @@ -327,7 +333,7 @@ public UnaryResult GetBalanceByStateRootHash( var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); - FungibleAssetValue balance = _blockChain + FungibleAssetValue balance = _worldStateRepository .GetWorldState(stateRootHash) .GetBalance(address, currency); byte[] encoded = _codec.Encode( @@ -344,7 +350,7 @@ public UnaryResult GetBalanceByStateRootHash( public UnaryResult GetTip() { - Bencodex.Types.Dictionary headerDict = _blockChain.Tip.MarshalBlock(); + Bencodex.Types.Dictionary headerDict = _blockChainRepository.Tip.MarshalBlock(); byte[] headerBytes = _codec.Encode(headerDict); return new UnaryResult(headerBytes); } @@ -353,7 +359,7 @@ public UnaryResult GetBlockHash(long blockIndex) { try { - return new UnaryResult(_codec.Encode(_blockChain[blockIndex].Hash.Bencoded)); + return new UnaryResult(_codec.Encode(_blockChainRepository.GetBlock(blockIndex).Hash.Bencoded)); } catch (ArgumentOutOfRangeException) { @@ -364,7 +370,7 @@ public UnaryResult GetBlockHash(long blockIndex) public UnaryResult GetNextTxNonce(byte[] addressBytes) { var address = new Address(addressBytes); - var nonce = _blockChain.GetNextTxNonce(address); + var nonce = _transactionRepository.GetNextTxNonce(address); Log.Debug("GetNextTxNonce: {nonce}", nonce); return new UnaryResult(nonce); } @@ -389,7 +395,7 @@ public UnaryResult SetAddressesToSubscribe(byte[] addressBytes, IEnumerabl public UnaryResult IsTransactionStaged(byte[] txidBytes) { var id = new TxId(txidBytes); - var isStaged = _blockChain.GetStagedTransactionIds().Contains(id); + var isStaged = _blockChainRepository.GetStagedTransactionIds().Contains(id); Log.Debug( "Transaction {id} is {1}.", id, diff --git a/NineChronicles.Headless/Repositories/BlockChain/BlockChainRepository.cs b/NineChronicles.Headless/Repositories/BlockChain/BlockChainRepository.cs index dcf0b5486..87a2c61fd 100644 --- a/NineChronicles.Headless/Repositories/BlockChain/BlockChainRepository.cs +++ b/NineChronicles.Headless/Repositories/BlockChain/BlockChainRepository.cs @@ -1,3 +1,11 @@ +using System; +using System.Collections.Immutable; +using System.Security.Cryptography; +using Libplanet.Action.State; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Tx; + namespace NineChronicles.Headless.Repositories.BlockChain; using System.Collections.Generic; @@ -70,6 +78,38 @@ public IEnumerable IterateBlocksDescending(long offset) } } + public bool StageTransaction(Libplanet.Types.Tx.Transaction tx) + { + return _blockChain.StageTransaction(tx); + } + + public Exception? ValidateNextBlockTx(Libplanet.Types.Tx.Transaction tx) + { + return _blockChain.Policy.ValidateNextBlockTx(_blockChain, tx); + } + + public IImmutableSet GetStagedTransactionIds() + { + return _blockChain.GetStagedTransactionIds(); + } + + public IWorldState GetWorldState(HashDigest stateRootHash) + { + return _blockChain.GetWorldState(stateRootHash); + } + + public IWorldState GetWorldState(BlockHash blockHash) + { + return _blockChain.GetWorldState(blockHash); + } + + public long GetNextTxNonce(Address address) + { + return _blockChain.GetNextTxNonce(address); + } + + public LibplanetBlock Tip => _blockChain.Tip; + private Block FetchTip() { return Convert(_blockChain.Tip); diff --git a/NineChronicles.Headless/Repositories/BlockChain/IBlockChainRepository.cs b/NineChronicles.Headless/Repositories/BlockChain/IBlockChainRepository.cs index 57fa71abf..c87798213 100644 --- a/NineChronicles.Headless/Repositories/BlockChain/IBlockChainRepository.cs +++ b/NineChronicles.Headless/Repositories/BlockChain/IBlockChainRepository.cs @@ -1,5 +1,12 @@ +using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Security.Cryptography; +using Libplanet.Action.State; +using Libplanet.Common; +using Libplanet.Crypto; using Libplanet.Types.Blocks; +using Libplanet.Types.Tx; namespace NineChronicles.Headless.Repositories.BlockChain; @@ -11,4 +18,8 @@ public interface IBlockChainRepository Block GetBlock(long index); Block GetBlock(BlockHash blockHash); IEnumerable IterateBlocksDescending(long offset); + bool StageTransaction(Libplanet.Types.Tx.Transaction tx); + Exception? ValidateNextBlockTx(Libplanet.Types.Tx.Transaction tx); + IImmutableSet GetStagedTransactionIds(); + Libplanet.Types.Blocks.Block Tip { get; } } From 0198a4e2d8d01d1c1a577d9dd84c033b0baa734e Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 8 Jan 2025 19:34:02 +0900 Subject: [PATCH 07/26] PutTransaction return tx stage result --- .../BlockChainServiceTest.cs | 80 ++++++++++++++++++- NineChronicles.Headless/BlockChainService.cs | 4 +- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless.Tests/BlockChainServiceTest.cs b/NineChronicles.Headless.Tests/BlockChainServiceTest.cs index 6accec549..892a87ec7 100644 --- a/NineChronicles.Headless.Tests/BlockChainServiceTest.cs +++ b/NineChronicles.Headless.Tests/BlockChainServiceTest.cs @@ -1,13 +1,87 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Bencodex.Types; +using Lib9c.Renderers; +using Libplanet.Blockchain; +using Libplanet.Blockchain.Policies; +using Libplanet.Crypto; +using Libplanet.Headless.Hosting; +using Libplanet.Mocks; +using Libplanet.Types.Tx; +using Moq; +using Nekoyume.Action; +using NineChronicles.Headless.Repositories.BlockChain; +using NineChronicles.Headless.Repositories.Swarm; +using NineChronicles.Headless.Repositories.Transaction; +using NineChronicles.Headless.Repositories.WorldState; using Xunit; namespace NineChronicles.Headless.Tests { public class BlockChainServiceTest { - // FIXME 의미 있는 테스트를 추가해야 합니다. - [Fact] - public void Constructor() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PutTransaction(bool stageResult) { + // Arrange + var mockBlockChain = new Mock(); + var mockTransaction = new Mock(); + var mockWorld = new Mock(); + var mockSwarm = new Mock(); + var mockRpcContext = new Mock(); + var mockProperties = new Mock(); + var mockCache = new Mock(); + var mockPolicy = new Mock(); + + // Mocking dependencies + mockPolicy + .Setup(bc => bc.ValidateNextBlockTx(It.IsAny(), It.IsAny())) + .Returns((TxPolicyViolationException?) null); + mockBlockChain + .Setup(bc => bc.StageTransaction(It.IsAny())) + .Returns(stageResult); + mockBlockChain + .Setup(bc => bc.GetStagedTransactionIds()) + .Returns(ImmutableHashSet.Empty); + + var service = new BlockChainService( + mockBlockChain.Object, + mockTransaction.Object, + mockWorld.Object, + mockSwarm.Object, + mockRpcContext.Object, + mockProperties.Object, + new ActionEvaluationPublisher( + new BlockRenderer(), + new ActionRenderer(), + new ExceptionRenderer(), + new NodeStatusRenderer(), + new MockBlockChainStates(), + "", + 0, + new RpcContext(), + new StateMemoryCache() + ), + mockCache.Object); + + var tx = Transaction.Create(0, new PrivateKey(), null, new List + { + new DailyReward + { + avatarAddress = new Address(), + }.PlainValue, + }); // Create a valid transaction + + // Act + var result = await service.PutTransaction(tx.Serialize()); + + // Assert + Assert.Equal(stageResult, result); + mockBlockChain.Verify(bc => bc.StageTransaction(It.IsAny()), Times.Once); + mockSwarm.Verify(s => s.BroadcastTxs(It.IsAny()), Times.Once); } } } diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 1cf773994..fbfbf6ba5 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -78,6 +78,7 @@ public UnaryResult PutTransaction(byte[] txBytes) ? $"{action}" : "NoAction"; var txId = tx.Id.ToString(); + var stage = true; try { @@ -88,6 +89,7 @@ public UnaryResult PutTransaction(byte[] txBytes) #pragma warning restore CS8632 if (validationExc is null) { + stage = _blockChainRepository.StageTransaction(tx); _swarmRepository.BroadcastTxs(new[] { tx }); } else @@ -95,7 +97,7 @@ public UnaryResult PutTransaction(byte[] txBytes) Log.Debug("Skip StageTransaction({TxId}) reason: {Msg}", tx.Id, validationExc.Message); } - return new UnaryResult(true); + return new UnaryResult(stage); } catch (InvalidTxException ite) { From ed2b5f9021ff47b9e56a56332cba3b3dbbf0d906 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 16 Jan 2025 20:24:19 +0900 Subject: [PATCH 08/26] Fix format --- NineChronicles.Headless.Tests/BlockChainServiceTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless.Tests/BlockChainServiceTest.cs b/NineChronicles.Headless.Tests/BlockChainServiceTest.cs index 892a87ec7..358e17300 100644 --- a/NineChronicles.Headless.Tests/BlockChainServiceTest.cs +++ b/NineChronicles.Headless.Tests/BlockChainServiceTest.cs @@ -39,7 +39,7 @@ public async Task PutTransaction(bool stageResult) // Mocking dependencies mockPolicy .Setup(bc => bc.ValidateNextBlockTx(It.IsAny(), It.IsAny())) - .Returns((TxPolicyViolationException?) null); + .Returns((TxPolicyViolationException?)null); mockBlockChain .Setup(bc => bc.StageTransaction(It.IsAny())) .Returns(stageResult); From 17c73ccefa20fd55eeed0b622409d9809e5be045 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 31 Dec 2024 01:41:23 +0900 Subject: [PATCH 09/26] fix: Build fix from Lib9c bump --- .../Commands/ActionCommandTest.cs | 3 +- .../Commands/TxCommandTest.cs | 4 +- .../Commands/ActionCommand.cs | 3 +- .../GraphTypes/ActionQueryTest.cs | 8 ++-- .../GraphTypes/ActionQuery.cs | 47 +++++-------------- .../GraphTypes/StateQuery.cs | 4 +- 6 files changed, 26 insertions(+), 43 deletions(-) diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs index 18616edb2..76343096e 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs @@ -70,7 +70,8 @@ public void TransferAsset( public void Stake() { var filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - var resultCode = _command.Stake(1, filePath); + var resultCode = _command.Stake( + 1, new Address("0xab1dce17dCE1Db1424BB833Af6cC087cd4F5CB6d"), filePath); Assert.Equal(0, resultCode); var rawAction = Convert.FromBase64String(File.ReadAllText(filePath)); var decoded = (List)_codec.Decode(rawAction); diff --git a/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs index ab3a1f487..d732a0ea9 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs @@ -21,6 +21,7 @@ public class TxCommandTest private readonly StringIOConsole _console; private readonly TxCommand _command; private readonly PrivateKey _privateKey; + private readonly Address _avatarAddress; private readonly BlockHash _blockHash; public TxCommandTest() @@ -28,6 +29,7 @@ public TxCommandTest() _console = new StringIOConsole(); _command = new TxCommand(_console); _privateKey = new PrivateKey(); + _avatarAddress = new PrivateKey().Address; _blockHash = BlockHash.FromHashDigest(default); } @@ -54,7 +56,7 @@ public void Sign_Stake(bool gas) { var filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); var actionCommand = new ActionCommand(_console); - actionCommand.Stake(1, filePath); + actionCommand.Stake(1, _avatarAddress, filePath); Assert_Tx(1, filePath, gas); } diff --git a/NineChronicles.Headless.Executable/Commands/ActionCommand.cs b/NineChronicles.Headless.Executable/Commands/ActionCommand.cs index ca0752db6..cc401f8eb 100644 --- a/NineChronicles.Headless.Executable/Commands/ActionCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ActionCommand.cs @@ -138,13 +138,14 @@ public int TransferAsset( [Command(Description = "Create Stake action.")] public int Stake( long amount, + Address avatarAddress, [Argument("PATH", Description = "A file path of base64 encoded action.")] string? filePath = null ) { try { - Nekoyume.Action.Stake action = new Stake(amount); + Nekoyume.Action.Stake action = new Stake(amount, avatarAddress); byte[] raw = Codec.Encode(new List( new[] { diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 1b01ffb76..f5f866e1c 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -69,16 +69,16 @@ public ActionQueryTest() [Theory] [ClassData(typeof(StakeFixture))] - public async Task Stake(BigInteger amount) + public async Task Stake(BigInteger amount, Address avatarAddress) { string query = $@" {{ - stake(amount: {amount}) + stake(amount: {amount}, avatarAddress: {avatarAddress}) }}"; var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; - ActionBase action = new Stake(amount); + ActionBase action = new Stake(amount, avatarAddress); var expected = new Dictionary() { ["stake"] = ByteUtil.Hex(_codec.Encode(action.PlainValue)), @@ -134,10 +134,12 @@ private class StakeFixture : IEnumerable new object[] { new BigInteger(1), + new Address("0xD84F1893A1912DEC1834A31a43f5619e0b2D5915") }, new object[] { new BigInteger(100), + new Address("0x35FdEee2fABE6aa916a36620E104a3E9433E4698") }, }; diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 9e2803422..c6b43ef95 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -27,14 +27,20 @@ public ActionQuery(StandaloneContext standaloneContext) Field( name: "stake", - arguments: new QueryArguments(new QueryArgument - { - Name = "amount", - Description = "An amount to stake.", - }), + arguments: new QueryArguments( + new QueryArgument + { + Name = "amount", + Description = "An amount to stake.", + }, + new QueryArgument> + { + Name = "avatarAddress", + Description = "Address of avatar.", + }), resolve: context => Encode( context, - new Stake(context.GetArgument("amount")))); + new Stake(context.GetArgument("amount"), context.GetArgument
("avatarAddress")))); Field( name: "claimStakeReward", @@ -568,35 +574,6 @@ public ActionQuery(StandaloneContext standaloneContext) context, new MigrateDelegationHeight(context.GetArgument("amount")))); - Field( - name: "migratePlanetariumGuild", - resolve: context => Encode( - context, - new MigratePlanetariumGuild())); - - Field( - name: "fixToRefundFromNonValidator", - arguments: new QueryArguments( - new QueryArgument>>> - { - Description = "List of addresses to refund", - Name = "addresses", - }, - new QueryArgument>>> - { - Description = "List of amounts to refund", - Name = "amounts", - }), - resolve: context => - { - var addresses = context.GetArgument>("addresses"); - var amounts = context.GetArgument>("amounts"); - var targets = addresses.Zip(amounts, (address, amount) => (address, amount)); - return Encode( - context, - new FixToRefundFromNonValidator(targets)); - }); - RegisterHackAndSlash(); RegisterHackAndSlashSweep(); RegisterDailyReward(); diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index 302c9a817..a101aaeef 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -766,7 +766,7 @@ public StateQuery() var agentAddress = new AgentAddress(context.GetArgument
("agentAddress")); var validatorAddress = context.GetArgument
("validatorAddress"); var repository = new ValidatorRepository(new World(context.Source.WorldState), new HallowActionContext { }); - var delegatee = repository.GetValidatorDelegatee(validatorAddress); + var delegatee = repository.GetDelegatee(validatorAddress); var share = repository.GetBond(delegatee, agentAddress).Share; return share.ToString(); @@ -787,7 +787,7 @@ public StateQuery() { var validatorAddress = context.GetArgument
("validatorAddress"); var repository = new ValidatorRepository(new World(context.Source.WorldState), new HallowActionContext { }); - var delegatee = repository.GetValidatorDelegatee(validatorAddress); + var delegatee = repository.GetDelegatee(validatorAddress); return ValidatorType.FromDelegatee(delegatee); } ); From 5871f8253bc6db96329dae6f06f986f527fb9196 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 31 Dec 2024 02:35:16 +0900 Subject: [PATCH 10/26] feat: Introduce BlockChainService methods for reward claim --- NineChronicles.Headless/BlockChainService.cs | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index fbfbf6ba5..18b4f6002 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -20,6 +20,9 @@ using MagicOnion.Server; using Microsoft.Extensions.Caching.Memory; using Nekoyume; +using Nekoyume.Delegation; +using Nekoyume.Model.Guild; +using Nekoyume.Model.State; using Nekoyume.Module; using Nekoyume.Shared.Services; using NineChronicles.Headless.Repositories.BlockChain; @@ -350,6 +353,54 @@ public UnaryResult GetBalanceByStateRootHash( return new UnaryResult(encoded); } + public UnaryResult GetUnbondClaimableHeightByBlockHash( + byte[] blockHashBytes, byte[] addressBytes) + { + var blockHash = new BlockHash(blockHashBytes); + var address = new Address(addressBytes); + var worldState = _worldStateRepository.GetWorldState(blockHash); + var result = GetUnbondClaimableHeight(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + + public UnaryResult GetUnbondClaimableHeightByStateRootHash( + byte[] stateRootHashBytes, byte[] addressBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var address = new Address(addressBytes); + var worldState = _worldStateRepository.GetWorldState(stateRootHash); + var result = GetUnbondClaimableHeight(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + + public UnaryResult GetClaimableRewardsByBlockHash( + byte[] blockHashBytes, byte[] addressBytes) + { + var blockHash = new BlockHash(blockHashBytes); + var address = new Address(addressBytes); + var worldState = _worldStateRepository.GetWorldState(blockHash); + var result = GetClaimableRewards(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + + public UnaryResult GetClaimableRewardsByStateRootHash( + byte[] stateRootHashBytes, byte[] addressBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var address = new Address(addressBytes); + var worldState = _worldStateRepository.GetWorldState(stateRootHash); + var result = GetClaimableRewards(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + public UnaryResult GetTip() { Bencodex.Types.Dictionary headerDict = _blockChainRepository.Tip.MarshalBlock(); @@ -438,6 +489,40 @@ public UnaryResult RemoveClient(byte[] addressBytes) return new UnaryResult(true); } + private static IValue GetUnbondClaimableHeight(IWorldState worldState, Address address) + { + var repository = new GuildRepository(new World(worldState), new HallowActionContext { }); + var guildAddress = repository.GetGuildParticipant(address).GuildAddress; + var validatorAddress = repository.GetGuild(guildAddress).ValidatorAddress; + var guildDelegatee = repository.GetDelegatee(validatorAddress); + var unbondLockIn = repository.GetUnbondLockIn(guildDelegatee, address); + var unbondClaimableHeight = unbondLockIn.LowestExpireHeight; + + return new Integer(unbondClaimableHeight); + } + + private static IValue GetClaimableRewards(IWorldState worldState, Address address) + { + var repository = new GuildRepository(new World(worldState), new HallowActionContext { }); + var guildAddress = repository.GetGuildParticipant(address).GuildAddress; + var validatorAddress = repository.GetGuild(guildAddress).ValidatorAddress; + var guildDelegatee = repository.GetDelegatee(validatorAddress); + var bond = repository.GetBond(guildDelegatee, address); + var share = bond.Share; + + if (share.IsZero + || bond.LastDistributeHeight is null + || repository.GetCurrentRewardBase(guildDelegatee) is not RewardBase rewardBase) + { + return Null.Value; + } + + var lastRewardBase = repository.GetRewardBase(guildDelegatee, bond.LastDistributeHeight.Value); + var rewards = guildDelegatee.CalculateRewards(share, rewardBase, lastRewardBase); + + return new List(rewards.Select(r => r.Serialize())); + } + // Returning value is a list of [ Avatar, Inventory, QuestList, WorldInformation ] private static IValue GetFullAvatarStateRaw(IWorldState worldState, Address address) { From d354255f584283c264135ef483631f88432a13ac Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 31 Dec 2024 02:51:19 +0900 Subject: [PATCH 11/26] test: Fix tests --- NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs | 2 +- .../GraphTypes/StandaloneQueryTest.ActionTxQuery.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index f5f866e1c..3a95ee5f9 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -73,7 +73,7 @@ public async Task Stake(BigInteger amount, Address avatarAddress) { string query = $@" {{ - stake(amount: {amount}, avatarAddress: {avatarAddress}) + stake(amount: {amount}, avatarAddress: ""{avatarAddress.ToString()}"") }}"; var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs index 51d9a84e4..ef0d95cab 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs @@ -19,10 +19,11 @@ public async Task ActionTxQuery_CreateTransaction() { var publicKey = new PrivateKey().PublicKey; long nonce = 0; + var avatarAddress = new PrivateKey().Address; var result = await ExecuteQueryAsync($@" query {{ actionTxQuery(publicKey: ""{publicKey.ToString()}"", nonce: {nonce}) {{ - stake(amount: 100) + stake(amount: 100, avatarAddress: ""{avatarAddress.ToString()}"") }} }}"); Assert.Null(result.Errors); @@ -49,10 +50,11 @@ public async Task ActionTxQuery_CreateTransaction_With_Timestamp(string timestam { var publicKey = new PrivateKey().PublicKey; long nonce = 0; + var avatarAddress = new PrivateKey().Address; var result = await ExecuteQueryAsync($@" query {{ actionTxQuery(publicKey: ""{publicKey.ToString()}"", nonce: {nonce}, timestamp: ""{timestamp}"") {{ - stake(amount: 100) + stake(amount: 100, avatarAddress: ""{avatarAddress.ToString()}"") }} }}"); Assert.Null(result.Errors); From 69fcd441b0f2c47b1eccd64959ddd08237abccab Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 3 Jan 2025 16:55:53 +0900 Subject: [PATCH 12/26] fix: Fix return type of RPC queries --- NineChronicles.Headless/BlockChainService.cs | 68 +++++++++++++------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 18b4f6002..ea79a4599 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -20,6 +20,7 @@ using MagicOnion.Server; using Microsoft.Extensions.Caching.Memory; using Nekoyume; +using Nekoyume.Action; using Nekoyume.Delegation; using Nekoyume.Model.Guild; using Nekoyume.Model.State; @@ -492,35 +493,58 @@ public UnaryResult RemoveClient(byte[] addressBytes) private static IValue GetUnbondClaimableHeight(IWorldState worldState, Address address) { var repository = new GuildRepository(new World(worldState), new HallowActionContext { }); - var guildAddress = repository.GetGuildParticipant(address).GuildAddress; - var validatorAddress = repository.GetGuild(guildAddress).ValidatorAddress; - var guildDelegatee = repository.GetDelegatee(validatorAddress); - var unbondLockIn = repository.GetUnbondLockIn(guildDelegatee, address); - var unbondClaimableHeight = unbondLockIn.LowestExpireHeight; - - return new Integer(unbondClaimableHeight); + try + { + var guildAddress = repository.GetGuildParticipant(address).GuildAddress; + var validatorAddress = repository.GetGuild(guildAddress).ValidatorAddress; + var guildDelegatee = repository.GetDelegatee(validatorAddress); + var unbondLockIn = repository.GetUnbondLockIn(guildDelegatee, address); + var unbondClaimableHeight = unbondLockIn.LowestExpireHeight; + return new Integer(unbondClaimableHeight); + } + catch (FailedLoadStateException) + { + return new Integer(-1L); + } + catch (Exception e) + { + Log.Error(e, "Failed to get unbond claimable height. {e}", e); + return null; + } } private static IValue GetClaimableRewards(IWorldState worldState, Address address) { - var repository = new GuildRepository(new World(worldState), new HallowActionContext { }); - var guildAddress = repository.GetGuildParticipant(address).GuildAddress; - var validatorAddress = repository.GetGuild(guildAddress).ValidatorAddress; - var guildDelegatee = repository.GetDelegatee(validatorAddress); - var bond = repository.GetBond(guildDelegatee, address); - var share = bond.Share; - - if (share.IsZero - || bond.LastDistributeHeight is null - || repository.GetCurrentRewardBase(guildDelegatee) is not RewardBase rewardBase) + try { - return Null.Value; - } + var repository = new GuildRepository(new World(worldState), new HallowActionContext { }); + var guildAddress = repository.GetGuildParticipant(address).GuildAddress; + var validatorAddress = repository.GetGuild(guildAddress).ValidatorAddress; + var guildDelegatee = repository.GetDelegatee(validatorAddress); + var bond = repository.GetBond(guildDelegatee, address); + var share = bond.Share; + + if (share.IsZero + || bond.LastDistributeHeight is null + || repository.GetCurrentRewardBase(guildDelegatee) is not RewardBase rewardBase) + { + return List.Empty; + } - var lastRewardBase = repository.GetRewardBase(guildDelegatee, bond.LastDistributeHeight.Value); - var rewards = guildDelegatee.CalculateRewards(share, rewardBase, lastRewardBase); + var lastRewardBase = repository.GetRewardBase(guildDelegatee, bond.LastDistributeHeight.Value); + var rewards = guildDelegatee.CalculateRewards(share, rewardBase, lastRewardBase); - return new List(rewards.Select(r => r.Serialize())); + return new List(rewards.Select(r => r.Serialize())); + } + catch (FailedLoadStateException) + { + return List.Empty; + } + catch (Exception e) + { + Log.Error(e, "Failed to get claimable rewards. {e}", e); + return null; + } } // Returning value is a list of [ Avatar, Inventory, QuestList, WorldInformation ] From c0dd2ad9b71c7924336338efa6b454c9e482b2bb Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 15 Jan 2025 01:49:14 +0900 Subject: [PATCH 13/26] feat: Add RPC queries for modified staking --- NineChronicles.Headless/BlockChainService.cs | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index ea79a4599..144f8087c 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 Humanizer.Bytes; using Libplanet.Action.State; using Libplanet.Common; using Libplanet.Crypto; @@ -25,6 +26,7 @@ using Nekoyume.Model.Guild; using Nekoyume.Model.State; using Nekoyume.Module; +using Nekoyume.Module.Guild; using Nekoyume.Shared.Services; using NineChronicles.Headless.Repositories.BlockChain; using NineChronicles.Headless.Repositories.Swarm; @@ -402,6 +404,54 @@ public UnaryResult GetClaimableRewardsByStateRootHash( return new UnaryResult(encoded); } + public UnaryResult GetDelegationInfoByBlockHash( + byte[] blockHashBytes, byte[] addressBytes) + { + var blockHash = new BlockHash(blockHashBytes); + var address = new Address(addressBytes); + var worldState = _blockChain.GetWorldState(blockHash); + var result = GetDelegationInfo(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + + public UnaryResult GetDelegationInfoByStateRootHash( + byte[] stateRootHashBytes, byte[] addressBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var address = new Address(addressBytes); + var worldState = _blockChain.GetWorldState(stateRootHash); + var result = GetDelegationInfo(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + + public UnaryResult GetStakedByBlockHash( + byte[] blockHashBytes, byte[] addressBytes) + { + var blockHash = new BlockHash(blockHashBytes); + var address = new Address(addressBytes); + var worldState = _blockChain.GetWorldState(blockHash); + var result = GetStaked(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + + public UnaryResult GetStakedByStateRootHash( + byte[] stateRootHashBytes, byte[] addressBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var address = new Address(addressBytes); + var worldState = _blockChain.GetWorldState(stateRootHash); + var result = GetStaked(worldState, address); + + byte[] encoded = _codec.Encode(result); + return new UnaryResult(encoded); + } + public UnaryResult GetTip() { Bencodex.Types.Dictionary headerDict = _blockChainRepository.Tip.MarshalBlock(); @@ -547,6 +597,30 @@ private static IValue GetClaimableRewards(IWorldState worldState, Address addres } } + private static IValue GetDelegationInfo(IWorldState worldState, Address address) + { + try + { + var delegationInfo = new World(worldState).GetDelegationInfo(address); + return List.Empty + .Add(delegationInfo.Share) + .Add(delegationInfo.TotalShare) + .Add(delegationInfo.TotalDelegated.Serialize()); + } + catch (InvalidOperationException) + { + return List.Empty; + } + catch (Exception e) + { + Log.Error(e, "Failed to get delegation info. {e}", e); + return null; + } + } + + private static IValue GetStaked(IWorldState worldState, Address address) + => new World(worldState).GetStaked(address).Serialize(); + // Returning value is a list of [ Avatar, Inventory, QuestList, WorldInformation ] private static IValue GetFullAvatarStateRaw(IWorldState worldState, Address address) { From e66525571431fcda4c3b199463fefabac6997f2e Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 02:57:15 +0900 Subject: [PATCH 14/26] feat: Update GQL query for stake --- .../GraphTypes/StateQuery.cs | 1 + .../GraphTypes/States/StakeStateType.cs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index a101aaeef..f30b87d1d 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -258,6 +258,7 @@ public StateQuery() if (ctx.WorldState.TryGetStakeState(agentAddr: agentAddress, out StakeState stakeStateV2)) { return new StakeStateType.StakeStateContext( + agentAddress, stakeStateV2, stakeStateAddress, ctx.WorldState, diff --git a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs index bae74bac2..804bc48b1 100644 --- a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs @@ -11,6 +11,7 @@ using Nekoyume.Module; using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes.Abstractions; +using Nekoyume.Module.Guild; namespace NineChronicles.Headless.GraphTypes.States { @@ -18,13 +19,21 @@ public class StakeStateType : ObjectGraphType { public class StakeStateContext : StateContext { - public StakeStateContext(StakeState stakeState, Address address, IWorldState worldState, long blockIndex, StateMemoryCache stateMemoryCache) + public StakeStateContext( + Address agentAddress, + StakeState stakeState, + Address address, + IWorldState worldState, + long blockIndex, + StateMemoryCache stateMemoryCache) : base(worldState, blockIndex, stateMemoryCache) { + AgentAddress = agentAddress; StakeState = stakeState; Address = address; } + public Address AgentAddress { get; } public StakeState StakeState { get; } public Address Address { get; } } @@ -38,11 +47,8 @@ public StakeStateType() Field>( "deposit", description: "The staked amount.", - resolve: context => context.Source.WorldState.GetBalance( - context.Source.Address, - new GoldCurrencyState( - (Dictionary)context.Source.WorldState.GetLegacyState(GoldCurrencyState.Address)!).Currency) - .GetQuantityString(true)); + resolve: context => new World(context.Source.WorldState).GetStaked( + context.Source.Address).GetQuantityString(true)); Field>( "startedBlockIndex", description: "The block index the user started to stake.", From aea0711e613115101adeabc2a506dc6d5b09363a Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 03:01:19 +0900 Subject: [PATCH 15/26] chore: Build fix from rebasing --- NineChronicles.Headless/BlockChainService.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 144f8087c..cb5d2d436 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -32,6 +32,7 @@ using NineChronicles.Headless.Repositories.Swarm; using NineChronicles.Headless.Repositories.Transaction; using NineChronicles.Headless.Repositories.WorldState; +using NineChronicles.Headless.Utils; using Serilog; using static NineChronicles.Headless.NCActionUtils; using NodeExceptionType = Libplanet.Headless.NodeExceptionType; @@ -409,7 +410,7 @@ public UnaryResult GetDelegationInfoByBlockHash( { var blockHash = new BlockHash(blockHashBytes); var address = new Address(addressBytes); - var worldState = _blockChain.GetWorldState(blockHash); + var worldState = _worldStateRepository.GetWorldState(blockHash); var result = GetDelegationInfo(worldState, address); byte[] encoded = _codec.Encode(result); @@ -421,7 +422,7 @@ public UnaryResult GetDelegationInfoByStateRootHash( { var stateRootHash = new HashDigest(stateRootHashBytes); var address = new Address(addressBytes); - var worldState = _blockChain.GetWorldState(stateRootHash); + var worldState = _worldStateRepository.GetWorldState(stateRootHash); var result = GetDelegationInfo(worldState, address); byte[] encoded = _codec.Encode(result); @@ -433,7 +434,7 @@ public UnaryResult GetStakedByBlockHash( { var blockHash = new BlockHash(blockHashBytes); var address = new Address(addressBytes); - var worldState = _blockChain.GetWorldState(blockHash); + var worldState = _worldStateRepository.GetWorldState(blockHash); var result = GetStaked(worldState, address); byte[] encoded = _codec.Encode(result); @@ -445,7 +446,7 @@ public UnaryResult GetStakedByStateRootHash( { var stateRootHash = new HashDigest(stateRootHashBytes); var address = new Address(addressBytes); - var worldState = _blockChain.GetWorldState(stateRootHash); + var worldState = _worldStateRepository.GetWorldState(stateRootHash); var result = GetStaked(worldState, address); byte[] encoded = _codec.Encode(result); From 552408d69fdb7de29af19799a71c328a143b5265 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 03:04:34 +0900 Subject: [PATCH 16/26] test: Test build fix --- .../GraphTypes/States/Models/StakeStateTypeTest.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs index 0b50e926d..5542570cf 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs @@ -21,7 +21,13 @@ public class StakeStateTypeTest { [Theory] [MemberData(nameof(Members))] - public async Task Query(StakeState stakeState, Address stakeStateAddress, long deposit, long blockIndex, Dictionary expected) + public async Task Query( + Address agentAddress, + StakeState stakeState, + Address stakeStateAddress, + long deposit, + long blockIndex, + Dictionary expected) { #pragma warning disable CS0618 // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 @@ -48,6 +54,7 @@ public async Task Query(StakeState stakeState, Address stakeStateAddress, long d var queryResult = await ExecuteQueryAsync( query, source: new StakeStateType.StakeStateContext( + agentAddress, stakeState, stakeStateAddress, new World(mockWorldState), @@ -60,6 +67,7 @@ public async Task Query(StakeState stakeState, Address stakeStateAddress, long d { new object[] { + Fixtures.UserAddress, new StakeState( new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 0), Fixtures.StakeStateAddress, From 30a1dedd8aa5d4a6f3a8546a002e55cd0680caf4 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 03:17:21 +0900 Subject: [PATCH 17/26] fix: Fix GQL query for stake deposit --- .../GraphTypes/States/Models/StakeStateTypeTest.cs | 7 ++++++- .../GraphTypes/States/StakeStateType.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs index 5542570cf..647b7ae31 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Threading.Tasks; using GraphQL.Execution; +using Lib9c; using Libplanet.Action.State; using Libplanet.Crypto; using Libplanet.Mocks; @@ -40,7 +41,7 @@ public async Task Query( ReservedAddresses.LegacyAccount, new Account(mockWorldState.GetAccountState(ReservedAddresses.LegacyAccount)) .SetState(GoldCurrencyState.Address, new GoldCurrencyState(goldCurrency).Serialize())) - .SetBalance(Fixtures.StakeStateAddress, goldCurrency, (goldCurrency * deposit).RawValue); + .SetBalance(Fixtures.StakeStateAddress, Currencies.GuildGold, (Currencies.GuildGold * deposit).RawValue); const string query = @" { @@ -85,6 +86,7 @@ public async Task Query( }, new object[] { + Fixtures.UserAddress, new StakeState(new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 100), Fixtures.StakeStateAddress, 100, @@ -101,6 +103,7 @@ public async Task Query( }, new object[] { + Fixtures.UserAddress, new StakeState(new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 100), Fixtures.StakeStateAddress, 100, @@ -117,6 +120,7 @@ public async Task Query( }, new object[] { + Fixtures.UserAddress, new StakeState( new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 10, 50412), Fixtures.StakeStateAddress, @@ -134,6 +138,7 @@ public async Task Query( }, new object[] { + Fixtures.UserAddress, new StakeState(new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 10, 50412), Fixtures.StakeStateAddress, 100, diff --git a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs index 804bc48b1..ce3b0c514 100644 --- a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs @@ -48,7 +48,7 @@ public StakeStateType() "deposit", description: "The staked amount.", resolve: context => new World(context.Source.WorldState).GetStaked( - context.Source.Address).GetQuantityString(true)); + context.Source.AgentAddress).GetQuantityString(true)); Field>( "startedBlockIndex", description: "The block index the user started to stake.", From 6a6618c37c31814c921064470247c1175f51efcc Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 12:05:05 +0900 Subject: [PATCH 18/26] bump: Lib9c, RPC.Shared --- Lib9c | 2 +- NineChronicles.RPC.Shared | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib9c b/Lib9c index e639502af..38e3660e2 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit e639502afb22df4d230d52002b519b3f8ef16b4f +Subproject commit 38e3660e288c0cc7dfaf8bc96014c23821604ca9 diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index 95700cbb0..af0bc9e64 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit 95700cbb0ee0b518bfae2f31385ab24c2d53ce7b +Subproject commit af0bc9e6429b28c11359a3e8d28234961764425b From 0f0bad1575587581311fdeb15ae4019bfb08763d Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 12:11:03 +0900 Subject: [PATCH 19/26] chore: Lint test code --- .../GraphTypes/States/Models/StakeStateTypeTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs index 647b7ae31..717e9d353 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs @@ -24,7 +24,7 @@ public class StakeStateTypeTest [MemberData(nameof(Members))] public async Task Query( Address agentAddress, - StakeState stakeState, + StakeState stakeState, Address stakeStateAddress, long deposit, long blockIndex, From 53d057b620e46a6aab875488866339d8b890e91b Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 12:17:40 +0900 Subject: [PATCH 20/26] chore: Build fix from broken dependencies --- ...t.Extensions.PluggedActionEvaluator.csproj | 2 +- .../PluggedActionEvaluator.cs | 2 +- NineChronicles.Headless.Executable.sln | 40 +++++++++---------- .../Action/ActionContext.cs | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj b/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj index c09aef0f8..e856dbcce 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj +++ b/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj @@ -7,8 +7,8 @@ + - diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs index 9c70a1cb8..1bcafdeec 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs @@ -1,10 +1,10 @@ using System.Reflection; using System.Security.Cryptography; +using Lib9c.ActionEvaluatorCommonComponents; using Lib9c.Plugin.Shared; using Libplanet.Action; using Libplanet.Action.Loader; using Libplanet.Common; -using Libplanet.Extensions.ActionEvaluatorCommonComponents; using Libplanet.Store.Trie; using Libplanet.Types.Blocks; diff --git a/NineChronicles.Headless.Executable.sln b/NineChronicles.Headless.Executable.sln index b379469bc..fbb377c6b 100644 --- a/NineChronicles.Headless.Executable.sln +++ b/NineChronicles.Headless.Executable.sln @@ -44,14 +44,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.Forkab EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.ForkableActionEvaluator.Tests", "Libplanet.Extensions.ForkableActionEvaluator.Tests\Libplanet.Extensions.ForkableActionEvaluator.Tests.csproj", "{490D20B6-FC0C-4459-8412-17777DB95931}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.ActionEvaluatorCommonComponents", "Lib9c\.Libplanet.Extensions.ActionEvaluatorCommonComponents\Libplanet.Extensions.ActionEvaluatorCommonComponents.csproj", "{A6922395-36E5-4B0A-BEBD-9BCE34D08722}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NineChronicles.Headless.AccessControlCenter", "NineChronicles.Headless.AccessControlCenter\NineChronicles.Headless.AccessControlCenter.csproj", "{162C0F4B-A1D9-4132-BC34-31F1247BC26B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.PluggedActionEvaluator", "Libplanet.Extensions.PluggedActionEvaluator\Libplanet.Extensions.PluggedActionEvaluator.csproj", "{DE91C36D-3999-47B6-A0BD-848C8EBA2A76}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.Plugin.Shared", "Lib9c\.Lib9c.Plugin.Shared\Lib9c.Plugin.Shared.csproj", "{3D32DA34-E619-429F-8421-848FF4F14417}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.ActionEvaluatorCommonComponents", "Lib9c\.Lib9c.ActionEvaluatorCommonComponents\Lib9c.ActionEvaluatorCommonComponents.csproj", "{0F29AB64-E2A9-4B32-96E9-291D47294A94}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -389,24 +389,6 @@ Global {490D20B6-FC0C-4459-8412-17777DB95931}.Release|x64.Build.0 = Release|Any CPU {490D20B6-FC0C-4459-8412-17777DB95931}.Release|x86.ActiveCfg = Release|Any CPU {490D20B6-FC0C-4459-8412-17777DB95931}.Release|x86.Build.0 = Release|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Debug|x64.ActiveCfg = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Debug|x64.Build.0 = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Debug|x86.ActiveCfg = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Debug|x86.Build.0 = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.DevEx|Any CPU.Build.0 = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.DevEx|x64.ActiveCfg = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.DevEx|x64.Build.0 = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.DevEx|x86.ActiveCfg = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.DevEx|x86.Build.0 = Debug|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|Any CPU.Build.0 = Release|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x64.ActiveCfg = Release|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x64.Build.0 = Release|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x86.ActiveCfg = Release|Any CPU - {A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x86.Build.0 = Release|Any CPU {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|Any CPU.Build.0 = Debug|Any CPU {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -461,6 +443,24 @@ Global {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x64.Build.0 = Release|Any CPU {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x86.ActiveCfg = Release|Any CPU {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x86.Build.0 = Release|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Debug|x64.Build.0 = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Debug|x86.Build.0 = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.DevEx|Any CPU.Build.0 = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.DevEx|x64.ActiveCfg = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.DevEx|x64.Build.0 = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.DevEx|x86.ActiveCfg = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.DevEx|x86.Build.0 = Debug|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Release|Any CPU.Build.0 = Release|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Release|x64.ActiveCfg = Release|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Release|x64.Build.0 = Release|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Release|x86.ActiveCfg = Release|Any CPU + {0F29AB64-E2A9-4B32-96E9-291D47294A94}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NineChronicles.Headless.Tests/Action/ActionContext.cs b/NineChronicles.Headless.Tests/Action/ActionContext.cs index fbcdd7c0a..7901ef437 100644 --- a/NineChronicles.Headless.Tests/Action/ActionContext.cs +++ b/NineChronicles.Headless.Tests/Action/ActionContext.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; +using Lib9c.ActionEvaluatorCommonComponents; using Libplanet.Action; using Libplanet.Action.State; using Libplanet.Crypto; -using Libplanet.Extensions.ActionEvaluatorCommonComponents; using Libplanet.Types.Assets; using Libplanet.Types.Blocks; using Libplanet.Types.Evidence; From 15bf230d7b5b16e8e4f6f3ef1ebc3a4ece6c9a90 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Fri, 17 Jan 2025 12:27:23 +0900 Subject: [PATCH 21/26] chore: Build fix from DevEx changes --- NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs index 712daa8b4..f17b28035 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.DevEx.cs @@ -90,7 +90,6 @@ private void RegisterFieldsForDevEx() var (exception, result) = CreateOrReplaceAvatarFactory.TryGetByBlockIndex( - chain.Tip.Index, avatarIndex: avatarIndex, name: name, hair: context.GetArgument("hair"), From b59d864795c26de111673237fb867da4e941f40a Mon Sep 17 00:00:00 2001 From: s2quake Date: Mon, 13 Jan 2025 11:40:10 +0900 Subject: [PATCH 22/26] feat: Add state queries --- .../GraphTypes/DelegatorTypeTest.cs | 45 ++++++++++++ .../GraphTypes/DelegatorType.cs | 30 ++++++++ .../GraphTypes/GuildType.cs | 38 ++++++++++ .../GraphTypes/StateQuery.cs | 71 +++++++++++++++++-- 4 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs create mode 100644 NineChronicles.Headless/GraphTypes/DelegatorType.cs create mode 100644 NineChronicles.Headless/GraphTypes/GuildType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs new file mode 100644 index 000000000..486303f27 --- /dev/null +++ b/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Numerics; +using System.Threading.Tasks; +using GraphQL.Execution; +using Lib9c; +using Libplanet.Types.Tx; +using Nekoyume.ValidatorDelegation; +using NineChronicles.Headless.GraphTypes; +using Xunit; +using Xunit.Abstractions; + +namespace NineChronicles.Headless.Tests.GraphTypes +{ + public class DelegatorTypeTest + { + [Fact] + public async Task ExecuteQuery() + { + var lastDistributeHeight = 1L; + var share = new BigInteger(1000000000000000000) * 10; + var fav = Currencies.GuildGold * 10; + + var result = await GraphQLTestUtils.ExecuteQueryAsync( + "{ lastDistributeHeight share fav { currency quantity } }", + source: new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + } + ); + + // Then + var data = (Dictionary)((ExecutionNode)result.Data!).ToValue()!; + + Assert.Equal(lastDistributeHeight, data["lastDistributeHeight"]); + Assert.Equal(share.ToString("N0"), data["share"]); + + var favResult = (Dictionary)data["fav"]; + Assert.Equal(fav.Currency.Ticker, favResult["currency"]); + Assert.Equal(fav.GetQuantityString(minorUnit: true), favResult["quantity"]); + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/DelegatorType.cs b/NineChronicles.Headless/GraphTypes/DelegatorType.cs new file mode 100644 index 000000000..e66514911 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/DelegatorType.cs @@ -0,0 +1,30 @@ +using System.Numerics; +using GraphQL.Types; +using Libplanet.Types.Assets; + +namespace NineChronicles.Headless.GraphTypes; + +public class DelegatorType : ObjectGraphType +{ + public long LastDistributeHeight { get; set; } + + public BigInteger Share { get; set; } + + public FungibleAssetValue Fav { get; set; } + + public DelegatorType() + { + Field>( + nameof(LastDistributeHeight), + description: "LastDistributeHeight of delegator", + resolve: context => context.Source.LastDistributeHeight); + Field>( + nameof(Share), + description: "Share of delegator", + resolve: context => context.Source.Share.ToString("N0")); + Field>( + nameof(Fav), + description: "Delegated FAV calculated based on Share value", + resolve: context => context.Source.Fav); + } +} diff --git a/NineChronicles.Headless/GraphTypes/GuildType.cs b/NineChronicles.Headless/GraphTypes/GuildType.cs new file mode 100644 index 000000000..fdfb46f8b --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/GuildType.cs @@ -0,0 +1,38 @@ +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Nekoyume.Model.Guild; + +namespace NineChronicles.Headless.GraphTypes; + +public class GuildType : ObjectGraphType +{ + public Address Address { get; set; } + + public Address ValidatorAddress { get; set; } + + public Address GuildMasterAddress { get; set; } + + public GuildType() + { + Field>( + nameof(Address), + description: "Address of the guild", + resolve: context => context.Source.Address); + Field>( + nameof(ValidatorAddress), + description: "Validator address of the guild", + resolve: context => context.Source.ValidatorAddress); + Field>( + nameof(GuildMasterAddress), + description: "Guild master address of the guild", + resolve: context => context.Source.GuildMasterAddress); + } + + public static GuildType FromDelegatee(Guild guild) => new GuildType + { + Address = guild.Address, + ValidatorAddress = guild.ValidatorAddress, + GuildMasterAddress = guild.GuildMasterAddress, + }; +} diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index f30b87d1d..f7faff214 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -34,6 +34,7 @@ using Nekoyume.Module.Guild; using Nekoyume.TypedAddress; using Nekoyume.ValidatorDelegation; +using Nekoyume.Module.ValidatorDelegation; namespace NineChronicles.Headless.GraphTypes { @@ -722,7 +723,7 @@ public StateQuery() } ); - Field( + Field( name: "guild", description: "State for guild.", arguments: new QueryArguments( @@ -735,15 +736,14 @@ public StateQuery() resolve: context => { var agentAddress = new AgentAddress(context.GetArgument
("agentAddress")); - if (!(context.Source.WorldState.GetAgentState(agentAddress) is { } agentState)) + var repository = new GuildRepository(new World(context.Source.WorldState), new HallowActionContext { }); + if (repository.GetJoinedGuild(agentAddress) is { } guildAddress) { - return null; + var guild = repository.GetGuild(guildAddress); + return GuildType.FromDelegatee(guild); } - var repository = new GuildRepository(new World(context.Source.WorldState), new HallowActionContext { }); - var joinedGuild = (Address?)repository.GetJoinedGuild(agentAddress); - - return joinedGuild; + return null; } ); @@ -792,6 +792,63 @@ public StateQuery() return ValidatorType.FromDelegatee(delegatee); } ); + + Field( + name: "delegator", + description: "State for delegator.", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "address", + Description = "Agent or Validator address." + } + ), + resolve: context => + { + var address = context.GetArgument
("address"); + var agentAddress = new AgentAddress(address); + var guildRepository = new GuildRepository( + new World(context.Source.WorldState), new HallowActionContext { }); + if (guildRepository.TryGetGuildParticipant(agentAddress, out var guildParticipant)) + { + var guild = guildRepository.GetGuild(guildParticipant.GuildAddress); + var guildDelegatee = guildRepository.GetDelegatee(guild.ValidatorAddress); + var bond = guildRepository.GetBond(guildDelegatee, guildParticipant.Address); + var totalDelegated = guildDelegatee.Metadata.TotalDelegatedFAV; + var totalShare = guildDelegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalDelegated).DivRem(totalShare).Quotient; + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + var validatorAddress = address; + var validatorRepository = new ValidatorRepository( + new World(context.Source.WorldState), new HallowActionContext { }); + if (validatorRepository.TryGetDelegatee(validatorAddress, out var validatorDelegatee)) + { + var bond = validatorRepository.GetBond(validatorDelegatee, address); + var totalDelegated = validatorDelegatee.Metadata.TotalDelegatedFAV; + var totalShare = validatorDelegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalDelegated).DivRem(totalShare).Quotient; + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + return null; + } + ); } public static List GetRuneOptions( From 9d755607eb67016cb4284ace18f222c8df8f0ddf Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Fri, 17 Jan 2025 13:37:15 +0900 Subject: [PATCH 23/26] Introduce FixedFungibleAssetValueInputType --- .../FixedFungibleAssetValueInputType.cs | 68 +++++++++++++++++++ .../GraphTypes/FungibleAssetValueInputType.cs | 1 + 2 files changed, 69 insertions(+) create mode 100644 NineChronicles.Headless/GraphTypes/FixedFungibleAssetValueInputType.cs diff --git a/NineChronicles.Headless/GraphTypes/FixedFungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/FixedFungibleAssetValueInputType.cs new file mode 100644 index 000000000..6cd77c715 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/FixedFungibleAssetValueInputType.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Numerics; +using GraphQL.Language.AST; +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Libplanet.Explorer.GraphTypes; + +namespace NineChronicles.Headless.GraphTypes +{ + public class FixedFungibleAssetValueInputType : InputObjectGraphType + { + public FixedFungibleAssetValueInputType() + { + Field>("quantity"); + Field>("ticker"); + Field>("decimalPlaces"); + Field>>("minters"); + } + + public override object ParseDictionary(IDictionary value) + { + IImmutableSet
? minters = null; + if (value.ContainsKey("minters")) + { + var rawMinters = (object[])value["minters"]!; + if (rawMinters.Any()) + { + minters = ImmutableHashSet
.Empty; + foreach (var rawMinter in rawMinters) + { + minters = minters.Add((Address)rawMinter); + } + } + } +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + var currency = Currency.Legacy((string)value["ticker"]!, (byte)value["decimalPlaces"]!, minters: minters); + return FungibleAssetValue.Parse(currency, (string)value["quantity"]!); +#pragma warning restore CS0618 + } + + public override IValue? ToAST(object value) + { + if (value is FungibleAssetValue fav) + { + IValue mintersValue = new NullValue(); + if (fav.Currency.Minters is not null) + { + var minters = new List(); + minters.AddRange(fav.Currency.Minters.Select(minter => new StringValue(minter.ToString()))); + mintersValue = new ListValue(minters); + } + return new ObjectValue(new List + { + new("quantity", new StringValue(fav.GetQuantityString())), + new("ticker", new StringValue(fav.Currency.Ticker)), + new("decimalPlaces", new IntValue(fav.Currency.DecimalPlaces)), + new("minters", mintersValue), + }); + } + + return null; + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/FungibleAssetValueInputType.cs b/NineChronicles.Headless/GraphTypes/FungibleAssetValueInputType.cs index 32c2438af..a5e50e9d4 100644 --- a/NineChronicles.Headless/GraphTypes/FungibleAssetValueInputType.cs +++ b/NineChronicles.Headless/GraphTypes/FungibleAssetValueInputType.cs @@ -14,6 +14,7 @@ public class FungibleAssetValueInputType : InputObjectGraphType>("quantity"); Field>("ticker"); Field>("decimalPlaces"); From 68ee54d156bd33923b073f27a9d5f811e4178ab3 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Fri, 17 Jan 2025 13:37:32 +0900 Subject: [PATCH 24/26] Update gas fee --- .../GraphTypes/SignScenarioTest.cs | 6 +++--- .../StandaloneQueryTest.ActionTxQuery.cs | 14 +++++++++----- .../GraphTypes/StandaloneQuery.cs | 4 ++-- .../GraphTypes/TransactionHeadlessQuery.cs | 6 +++--- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/SignScenarioTest.cs b/NineChronicles.Headless.Tests/GraphTypes/SignScenarioTest.cs index 08ee85c08..99ee72cd4 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/SignScenarioTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/SignScenarioTest.cs @@ -168,7 +168,7 @@ public async Task SignTransaction_CreatePledge() Assert.Equal(sender, action.AgentAddresses.Single().Item1); Assert.Equal(MeadConfig.PatronAddress, action.PatronAddress); Assert.Equal(1, signedTx.GasLimit); - Assert.Equal(1 * Currencies.Mead, signedTx.MaxGasPrice); + Assert.Equal(FungibleAssetValue.FromRawValue(Currencies.Mead, 10000000000000), signedTx.MaxGasPrice); await StageTransaction(signedTx, hex); } @@ -206,7 +206,7 @@ private async Task GetAction(string actionName, string queryArgs) // Create unsigned Transaction. var unsignedQuery = gas ? $@"query {{ - unsignedTransaction(publicKey: ""{hexedPublicKey}"", plainValue: ""{plainValue}"", nonce: {nonce}, maxGasPrice: {{ quantity: 1, decimalPlaces: 18, ticker: ""Mead"" }}) + unsignedTransaction(publicKey: ""{hexedPublicKey}"", plainValue: ""{plainValue}"", nonce: {nonce}, maxGasPrice: {{ quantity: ""0.00001"", decimalPlaces: 18, ticker: ""Mead"" }}) }}" : $@"query {{ unsignedTransaction(publicKey: ""{hexedPublicKey}"", plainValue: ""{plainValue}"", nonce: {nonce}) }}"; @@ -249,7 +249,7 @@ private async Task GetAction(string actionName, string queryArgs) Assert.Single(unsignedTx.Actions); Assert.Single(signedTx.Actions!); Assert.Equal(expectedGasLimit, signedTx.GasLimit); - Assert.Equal(1 * Currencies.Mead, signedTx.MaxGasPrice); + Assert.Equal(FungibleAssetValue.FromRawValue(Currencies.Mead, 10000000000000), signedTx.MaxGasPrice); return (signedTx, hex); } diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs index 51d9a84e4..3baeb05cc 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs @@ -5,10 +5,12 @@ using Lib9c; using Libplanet.Common; using Libplanet.Crypto; +using Libplanet.Types.Assets; using Libplanet.Types.Tx; using Nekoyume.Action; using Nekoyume.Action.Loader; using Xunit; +using BigInteger = System.Numerics.BigInteger; namespace NineChronicles.Headless.Tests.GraphTypes { @@ -35,7 +37,7 @@ public async Task ActionTxQuery_CreateTransaction() Assert.Equal(publicKey.Address, tx.Signer); Assert.Equal(0, tx.Nonce); Assert.Equal(1, tx.GasLimit); - Assert.Equal(1 * Currencies.Mead, tx.MaxGasPrice); + Assert.Equal(FungibleAssetValue.FromRawValue(Currencies.Mead, 10000000000000), tx.MaxGasPrice); var rawAction = Assert.Single(tx.Actions); var action = new NCActionLoader().LoadAction(0, rawAction); Assert.IsType(action); @@ -63,15 +65,17 @@ public async Task ActionTxQuery_CreateTransaction_With_Timestamp(string timestam Assert.Equal(DateTimeOffset.Parse(timestamp), tx.Timestamp); } - [Fact] - public async Task ActionTxQuery_With_Gas() + [Theory] + [InlineData("1", 1000000000000000000)] + [InlineData("0.00001", 10000000000000)] + public async Task ActionTxQuery_With_Gas(string quantity, BigInteger expected) { var publicKey = new PrivateKey().PublicKey; var address = new PrivateKey().Address; long nonce = 0; var result = await ExecuteQueryAsync($@" query {{ - actionTxQuery(publicKey: ""{publicKey.ToString()}"", nonce: {nonce}, maxGasPrice: {{ quantity: 1, decimalPlaces: 18, ticker: ""Mead"" }}) {{ + actionTxQuery(publicKey: ""{publicKey.ToString()}"", nonce: {nonce}, maxGasPrice: {{ quantity: ""{quantity}"", decimalPlaces: 18, ticker: ""Mead"" }}) {{ requestPledge(agentAddress: ""{address}"") }} }}"); @@ -85,7 +89,7 @@ public async Task ActionTxQuery_With_Gas() Assert.Equal(0, tx.Nonce); Assert.IsType(tx.Timestamp); Assert.Equal(1, tx.GasLimit); - Assert.Equal(1 * Currencies.Mead, tx.MaxGasPrice); + Assert.Equal(FungibleAssetValue.FromRawValue(Currencies.Mead, expected), tx.MaxGasPrice); var rawAction = Assert.Single(tx.Actions); var action = Assert.IsType(new NCActionLoader().LoadAction(0, rawAction)); Assert.Equal(address, action.AgentAddress); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index 60ec53ef0..a55e34336 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -568,10 +568,10 @@ public StandaloneQuery(StandaloneContext standaloneContext, IKeyStore keyStore, Name = "timestamp", Description = "The time this transaction is created.", }, - new QueryArgument + new QueryArgument { Name = "maxGasPrice", - DefaultValue = 0.00001 * Currencies.Mead + DefaultValue = FungibleAssetValue.Parse(Currencies.Mead, "0.00001"), } ), resolve: context => diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index d651348f6..af68f37f8 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -169,7 +169,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) genesisHash: blockChain.Genesis.Hash, actions: new TxActionList(new[] { action.PlainValue }), gasLimit: action is ITransferAsset or ITransferAssets ? RequestPledge.DefaultRefillMead : 1L, - maxGasPrice: 0.00001 * Currencies.Mead + maxGasPrice: FungibleAssetValue.Parse(Currencies.Mead, "0.00001") ), new TxSigningMetadata(publicKey: publicKey, nonce: nonce) ); @@ -253,10 +253,10 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) Name = "nonce", Description = "The nonce for Transaction.", }, - new QueryArgument + new QueryArgument { Name = "maxGasPrice", - DefaultValue = 1 * Currencies.Mead + DefaultValue = FungibleAssetValue.Parse(Currencies.Mead, "0.00001"), } ), resolve: context => From 18c4908744e5afa2916fe8eff1f88d1bb1832f61 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Fri, 17 Jan 2025 13:46:36 +0900 Subject: [PATCH 25/26] Avoid broken test --- .../GraphTypes/StandaloneQueryTest.ActionTxQuery.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs index 3baeb05cc..55d716d97 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.ActionTxQuery.cs @@ -66,9 +66,9 @@ public async Task ActionTxQuery_CreateTransaction_With_Timestamp(string timestam } [Theory] - [InlineData("1", 1000000000000000000)] - [InlineData("0.00001", 10000000000000)] - public async Task ActionTxQuery_With_Gas(string quantity, BigInteger expected) + [InlineData("1")] + [InlineData("0.00001")] + public async Task ActionTxQuery_With_Gas(string quantity) { var publicKey = new PrivateKey().PublicKey; var address = new PrivateKey().Address; @@ -89,7 +89,7 @@ public async Task ActionTxQuery_With_Gas(string quantity, BigInteger expected) Assert.Equal(0, tx.Nonce); Assert.IsType(tx.Timestamp); Assert.Equal(1, tx.GasLimit); - Assert.Equal(FungibleAssetValue.FromRawValue(Currencies.Mead, expected), tx.MaxGasPrice); + Assert.Equal(FungibleAssetValue.Parse(Currencies.Mead, quantity), tx.MaxGasPrice); var rawAction = Assert.Single(tx.Actions); var action = Assert.IsType(new NCActionLoader().LoadAction(0, rawAction)); Assert.Equal(address, action.AgentAddress); From a449d47a96bdca41825c25e5457fa59261ebc8fd Mon Sep 17 00:00:00 2001 From: Atralupus Date: Fri, 17 Jan 2025 15:19:07 +0900 Subject: [PATCH 26/26] bump lib9c 1.22.0 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 38e3660e2..48d4ee3d5 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 38e3660e288c0cc7dfaf8bc96014c23821604ca9 +Subproject commit 48d4ee3d57cea89c7aea9102a7995fa929a83df7