diff --git a/.gitallowed b/.gitallowed new file mode 100644 index 000000000..0bd84f123 --- /dev/null +++ b/.gitallowed @@ -0,0 +1,19 @@ +# For BUILD_GENESIS.md +e87a05d05506b73570e80f6e99beeceae6a9891333b2e8e8951197050fad96e2 + +# For ConversionCommandTest.cs +a1262b4f0911a9cec5e64344c0c9b50d64f8781ade0e09fa79faaa127ccdff89 + +# For ValidationCommandTest.cs +ab8d591ccdcce263c39eb1f353e44b64869f0afea2df643bf6839ebde650d244 +d6c3e0d525dac340a132ae05aaa9f3e278d61b70d2b71326570e64aee249e566 +761f68d68426549df5904395b5ca5bce64a3da759085d8565242db42a5a1b0b9 + +# For Fixtures.cs +b934cb79757b1dec9f89caa01c4b791a6de6937dbecdc102fbdca217156cc2f5 + +# For AddressQueryTest.cs +b8ce43967d7270348906c3b30efd41c30ab834ce07a36ee8ac5fd52cb7a3f579 + +# For StandaloneQueryTest.cs +9330b3287bd2bbc38770c69ae7cd380350c60a1dff9ec41254f3048d5b3eb01c diff --git a/Lib9c b/Lib9c index 8132d8023..fe86a78e2 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 8132d80236b7a586fb202d9be5101619917640a5 +Subproject commit fe86a78e29d7d15486779b133bea21ba0ec63f30 diff --git a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs index 85d414fb5..938e5e305 100644 --- a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs +++ b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs @@ -88,7 +88,7 @@ private class DummyAction : IAction { IValue IAction.PlainValue => Dictionary.Empty; - IAccountStateDelta IAction.Execute(IActionContext context) + IAccount IAction.Execute(IActionContext context) { return context.PreviousState; } diff --git a/Libplanet.Headless/ReducedStore.cs b/Libplanet.Headless/ReducedStore.cs index e55be9d65..389597d0b 100644 --- a/Libplanet.Headless/ReducedStore.cs +++ b/Libplanet.Headless/ReducedStore.cs @@ -113,7 +113,6 @@ public void PutTxExecution(TxSuccess txSuccess) txSuccess.BlockHash, txSuccess.TxId, updatedStates: txSuccess.UpdatedStates.ToImmutableDictionary(pair => pair.Key, _ => (IValue)Null.Value), - fungibleAssetsDelta: txSuccess.FungibleAssetsDelta, updatedFungibleAssets: txSuccess.UpdatedFungibleAssets ); InternalStore.PutTxExecution(reducedTxSuccess); diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs index 8721e29b7..a0745ed60 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ActionCommandTest.cs @@ -198,7 +198,9 @@ public void ClaimStakeReward(string addressString, int expectedCode) [InlineData(ClaimStakeReward6.ObsoleteBlockIndex, typeof(ClaimStakeReward6))] [InlineData(ClaimStakeReward6.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward7))] [InlineData(ClaimStakeReward7.ObsoleteBlockIndex, typeof(ClaimStakeReward7))] - [InlineData(ClaimStakeReward7.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward))] + [InlineData(ClaimStakeReward7.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward8))] + [InlineData(ClaimStakeReward8.ObsoleteBlockIndex, typeof(ClaimStakeReward8))] + [InlineData(ClaimStakeReward8.ObsoleteBlockIndex + 1, typeof(ClaimStakeReward))] [InlineData(long.MaxValue, typeof(ClaimStakeReward))] public void ClaimStakeRewardWithBlockIndex(long blockIndex, Type expectedActionType) { @@ -224,8 +226,8 @@ public void ClaimStakeRewardWithBlockIndex(long blockIndex, Type expectedActionT [Theory] [InlineData(0, 0, -1)] - [InlineData(1, 8, 0)] - [InlineData(9, 9, -1)] + [InlineData(1, 9, 0)] + [InlineData(10, 10, -1)] public void ClaimStakeRewardWithActionVersion( int actionVersionMin, int actionVersionMax, diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs index 893ddef93..17f04ca9a 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs @@ -255,7 +255,7 @@ public void PruneState(StoreType storeType) _command.PruneStates(storeType, _storePath); IStore outputStore = storeType.CreateStore(_storePath); var outputStateKeyValueStore = new RocksDBKeyValueStore(statesPath); - var outputStateStore = new TrieStateStore(outputStateKeyValueStore, true); + var outputStateStore = new TrieStateStore(outputStateKeyValueStore); int outputStatesCount = outputStateKeyValueStore.ListKeys().Count(); outputStore.Dispose(); outputStateStore.Dispose(); diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index 05046254b..9581815f3 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.Contracts; using System.IO; using System.Linq; -using System.Numerics; using System.Security.Cryptography; using Bencodex; using Bencodex.Types; @@ -13,481 +11,19 @@ using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Action; +using Libplanet.Action.State; using Libplanet.Types.Assets; using Libplanet.Types.Consensus; -using Libplanet.Action.State; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using RocksDbSharp; using Serilog; +using Libplanet.Store.Trie; namespace NineChronicles.Headless.Executable.Commands { public partial class ReplayCommand : CoconaLiteConsoleAppBase { - /// - /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/State/AccountStateDelta.cs. - /// - [Pure] - private sealed class AccountStateDelta : IAccountStateDelta - { - private readonly IAccountState _baseState; - - private AccountStateDelta(IAccountState baseState) - : this(baseState, new AccountDelta()) - { - } - - private AccountStateDelta(IAccountState baseState, IAccountDelta delta) - { - _baseState = baseState; - Delta = delta; - TotalUpdatedFungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; - } - - /// - public IAccountDelta Delta { get; private set; } - - /// - public IImmutableSet<(Address, Currency)> TotalUpdatedFungibleAssets => - TotalUpdatedFungibles.Keys.ToImmutableHashSet(); - - public IImmutableDictionary<(Address, Currency), BigInteger> TotalUpdatedFungibles { get; private set; } - - /// - [Pure] - public IValue? GetState(Address address) - { - IValue? state = GetStates(new[] { address })[0]; - return state; - } - - /// - [Pure] - public IReadOnlyList GetStates(IReadOnlyList
addresses) - { - int length = addresses.Count; - IValue?[] values = new IValue?[length]; - var notFoundIndices = new List(length); - for (int i = 0; i < length; i++) - { - Address address = addresses[i]; - if (Delta.States.TryGetValue(address, out IValue? updatedValue)) - { - values[i] = updatedValue; - } - else - { - notFoundIndices.Add(i); - } - } - - if (notFoundIndices.Count > 0) - { - IReadOnlyList restValues = _baseState.GetStates( - notFoundIndices.Select(index => addresses[index]).ToArray()); - foreach ((var v, var i) in notFoundIndices.Select((v, i) => (v, i))) - { - values[v] = restValues[i]; - } - } - - return values; - } - - /// - [Pure] - public IAccountStateDelta SetState(Address address, IValue state) => - UpdateStates(Delta.States.SetItem(address, state)); - - /// - [Pure] - public FungibleAssetValue GetBalance(Address address, Currency currency) => - GetBalance(address, currency, Delta.Fungibles); - - /// - [Pure] - public FungibleAssetValue GetTotalSupply(Currency currency) - { - if (!currency.TotalSupplyTrackable) - { - throw new TotalSupplyNotTrackableException( - $"Given currency {currency} is not trackable for its total supply", - currency); - } - - // Return dirty state if it exists. - if (Delta.TotalSupplies.TryGetValue(currency, out BigInteger totalSupplyValue)) - { - return FungibleAssetValue.FromRawValue(currency, totalSupplyValue); - } - - return _baseState.GetTotalSupply(currency); - } - - /// - [Pure] - public ValidatorSet GetValidatorSet() => - Delta.ValidatorSet ?? _baseState.GetValidatorSet(); - - /// - [Pure] - public IAccountStateDelta MintAsset( - IActionContext context, Address recipient, FungibleAssetValue value) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to mint has to be greater than zero." - ); - } - - Currency currency = value.Currency; - if (!currency.AllowsToMint(context.Signer)) - { - throw new CurrencyPermissionException( - $"The account {context.Signer} has no permission to mint currency {currency}.", - context.Signer, - currency - ); - } - - FungibleAssetValue balance = GetBalance(recipient, currency); - (Address, Currency) assetKey = (recipient, currency); - BigInteger rawBalance = (balance + value).RawValue; - - if (currency.TotalSupplyTrackable) - { - var currentTotalSupply = GetTotalSupply(currency); - if (currency.MaximumSupply < currentTotalSupply + value) - { - var msg = $"The amount {value} attempted to be minted added to the current" - + $" total supply of {currentTotalSupply} exceeds the" - + $" maximum allowed supply of {currency.MaximumSupply}."; - throw new SupplyOverflowException(msg, value); - } - - return UpdateFungibleAssets( - Delta.Fungibles.SetItem(assetKey, rawBalance), - TotalUpdatedFungibles.SetItem(assetKey, rawBalance), - Delta.TotalSupplies.SetItem(currency, (currentTotalSupply + value).RawValue) - ); - } - - return UpdateFungibleAssets( - Delta.Fungibles.SetItem(assetKey, rawBalance), - TotalUpdatedFungibles.SetItem(assetKey, rawBalance) - ); - } - - /// - [Pure] - public IAccountStateDelta TransferAsset( - IActionContext context, - Address sender, - Address recipient, - FungibleAssetValue value, - bool allowNegativeBalance = false) => context.BlockProtocolVersion > 0 - ? TransferAssetV1(sender, recipient, value, allowNegativeBalance) - : TransferAssetV0(sender, recipient, value, allowNegativeBalance); - - /// - [Pure] - public IAccountStateDelta BurnAsset( - IActionContext context, Address owner, FungibleAssetValue value) - { - string msg; - - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to burn has to be greater than zero." - ); - } - - Currency currency = value.Currency; - if (!currency.AllowsToMint(context.Signer)) - { - msg = $"The account {context.Signer} has no permission to burn assets of " + - $"the currency {currency}."; - throw new CurrencyPermissionException(msg, context.Signer, currency); - } - - FungibleAssetValue balance = GetBalance(owner, currency); - - if (balance < value) - { - msg = $"The account {owner}'s balance of {currency} is insufficient to burn: " + - $"{balance} < {value}."; - throw new InsufficientBalanceException(msg, owner, balance); - } - - (Address, Currency) assetKey = (owner, currency); - BigInteger rawBalance = (balance - value).RawValue; - if (currency.TotalSupplyTrackable) - { - return UpdateFungibleAssets( - Delta.Fungibles.SetItem(assetKey, rawBalance), - TotalUpdatedFungibles.SetItem(assetKey, rawBalance), - Delta.TotalSupplies.SetItem( - currency, - (GetTotalSupply(currency) - value).RawValue) - ); - } - - return UpdateFungibleAssets( - Delta.Fungibles.SetItem(assetKey, rawBalance), - TotalUpdatedFungibles.SetItem(assetKey, rawBalance) - ); - } - - /// - [Pure] - public IAccountStateDelta SetValidator(Validator validator) - { - return UpdateValidatorSet(GetValidatorSet().Update(validator)); - } - - /// - /// Creates a null state delta from given . - /// - /// The previous to use as - /// a basis. - /// A null state delta created from . - /// - internal static IAccountStateDelta Create(IAccountState previousState) => - new AccountStateDelta(previousState); - - /// - /// Creates a null state delta while inheriting s - /// total updated fungibles. - /// - /// The previous to use. - /// A null state delta that is of the same type as . - /// - /// Thrown if given - /// is not . - /// - /// - /// This inherits 's - /// . - /// - internal static IAccountStateDelta Flush(IAccountStateDelta stateDelta) => - stateDelta is AccountStateDelta impl - ? new AccountStateDelta(stateDelta) - { - TotalUpdatedFungibles = impl.TotalUpdatedFungibles, - } - : throw new ArgumentException( - $"Unknown type for {nameof(stateDelta)}: {stateDelta.GetType()}"); - - [Pure] - private FungibleAssetValue GetBalance( - Address address, - Currency currency, - IImmutableDictionary<(Address, Currency), BigInteger> balances) => - balances.TryGetValue((address, currency), out BigInteger balance) - ? FungibleAssetValue.FromRawValue(currency, balance) - : _baseState.GetBalance(address, currency); - - [Pure] - private AccountStateDelta UpdateStates( - IImmutableDictionary updatedStates) => - new AccountStateDelta( - _baseState, - new AccountDelta( - updatedStates, - Delta.Fungibles, - Delta.TotalSupplies, - Delta.ValidatorSet)) - { - TotalUpdatedFungibles = TotalUpdatedFungibles, - }; - - [Pure] - private AccountStateDelta UpdateFungibleAssets( - IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets, - IImmutableDictionary<(Address, Currency), BigInteger> totalUpdatedFungibles - ) => - UpdateFungibleAssets( - updatedFungibleAssets, - totalUpdatedFungibles, - Delta.TotalSupplies); - - [Pure] - private AccountStateDelta UpdateFungibleAssets( - IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets, - IImmutableDictionary<(Address, Currency), BigInteger> totalUpdatedFungibles, - IImmutableDictionary updatedTotalSupply - ) => - new AccountStateDelta( - _baseState, - new AccountDelta( - Delta.States, - updatedFungibleAssets, - updatedTotalSupply, - Delta.ValidatorSet)) - { - TotalUpdatedFungibles = totalUpdatedFungibles, - }; - - [Pure] - private AccountStateDelta UpdateValidatorSet( - ValidatorSet updatedValidatorSet) => - new AccountStateDelta( - _baseState, - new AccountDelta( - Delta.States, - Delta.Fungibles, - Delta.TotalSupplies, - updatedValidatorSet)) - { - TotalUpdatedFungibles = TotalUpdatedFungibles, - }; - - [Pure] - private IAccountStateDelta TransferAssetV0( - Address sender, - Address recipient, - FungibleAssetValue value, - bool allowNegativeBalance = false) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to transfer has to be greater than zero." - ); - } - - Currency currency = value.Currency; - FungibleAssetValue senderBalance = GetBalance(sender, currency); - FungibleAssetValue recipientBalance = GetBalance(recipient, currency); - - if (!allowNegativeBalance && senderBalance < value) - { - var msg = $"The account {sender}'s balance of {currency} is insufficient to " + - $"transfer: {senderBalance} < {value}."; - throw new InsufficientBalanceException(msg, sender, senderBalance); - } - - return UpdateFungibleAssets( - Delta.Fungibles - .SetItem((sender, currency), (senderBalance - value).RawValue) - .SetItem((recipient, currency), (recipientBalance + value).RawValue), - TotalUpdatedFungibles - .SetItem((sender, currency), (senderBalance - value).RawValue) - .SetItem((recipient, currency), (recipientBalance + value).RawValue) - ); - } - - [Pure] - private IAccountStateDelta TransferAssetV1( - Address sender, - Address recipient, - FungibleAssetValue value, - bool allowNegativeBalance = false) - { - if (value.Sign <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(value), - "The value to transfer has to be greater than zero." - ); - } - - Currency currency = value.Currency; - FungibleAssetValue senderBalance = GetBalance(sender, currency); - - if (!allowNegativeBalance && senderBalance < value) - { - var msg = $"The account {sender}'s balance of {currency} is insufficient to " + - $"transfer: {senderBalance} < {value}."; - throw new InsufficientBalanceException(msg, sender, senderBalance); - } - - (Address, Currency) senderAssetKey = (sender, currency); - BigInteger senderRawBalance = (senderBalance - value).RawValue; - - IImmutableDictionary<(Address, Currency), BigInteger> updatedFungibleAssets = - Delta.Fungibles.SetItem(senderAssetKey, senderRawBalance); - IImmutableDictionary<(Address, Currency), BigInteger> totalUpdatedFungibles = - TotalUpdatedFungibles.SetItem(senderAssetKey, senderRawBalance); - - FungibleAssetValue recipientBalance = GetBalance( - recipient, - currency, - updatedFungibleAssets); - (Address, Currency) recipientAssetKey = (recipient, currency); - BigInteger recipientRawBalance = (recipientBalance + value).RawValue; - - return UpdateFungibleAssets( - updatedFungibleAssets.SetItem(recipientAssetKey, recipientRawBalance), - totalUpdatedFungibles.SetItem(recipientAssetKey, recipientRawBalance) - ); - } - } - - /// - /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/State/AccountDelta.cs. - /// - private sealed class AccountDelta : IAccountDelta - { - internal AccountDelta() - { - States = ImmutableDictionary.Empty; - Fungibles = ImmutableDictionary<(Address, Currency), BigInteger>.Empty; - TotalSupplies = ImmutableDictionary.Empty; - ValidatorSet = null; - } - - internal AccountDelta( - IImmutableDictionary statesDelta, - IImmutableDictionary<(Address, Currency), BigInteger> fungiblesDelta, - IImmutableDictionary totalSuppliesDelta, - ValidatorSet? validatorSetDelta) - { - States = statesDelta; - Fungibles = fungiblesDelta; - TotalSupplies = totalSuppliesDelta; - ValidatorSet = validatorSetDelta; - } - - /// - public IImmutableSet
UpdatedAddresses => - StateUpdatedAddresses.Union(FungibleUpdatedAddresses); - - /// - public IImmutableSet
StateUpdatedAddresses => - States.Keys.ToImmutableHashSet(); - - /// - public IImmutableDictionary States { get; } - - /// - public IImmutableSet
FungibleUpdatedAddresses => - Fungibles.Keys.Select(pair => pair.Item1).ToImmutableHashSet(); - - /// - public IImmutableSet<(Address, Currency)> UpdatedFungibleAssets => - Fungibles.Keys.ToImmutableHashSet(); - - /// - public IImmutableDictionary<(Address, Currency), BigInteger> Fungibles { get; } - - /// - public IImmutableSet UpdatedTotalSupplyCurrencies => - TotalSupplies.Keys.ToImmutableHashSet(); - - /// - public IImmutableDictionary TotalSupplies { get; } - - /// - public ValidatorSet? ValidatorSet { get; } - } - /// /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/ActionContext.cs. /// @@ -501,7 +37,7 @@ public ActionContext( Address miner, long blockIndex, int blockProtocolVersion, - IAccountStateDelta previousState, + IAccount previousState, int randomSeed, bool rehearsal = false) { @@ -528,7 +64,7 @@ public ActionContext( public bool Rehearsal { get; } - public IAccountStateDelta PreviousState { get; } + public IAccount PreviousState { get; } public IRandom Random { get; } @@ -584,47 +120,54 @@ public LocalCacheBlockChainStates(IBlockChainStates source, string cacheDirector public IValue? GetState(Address address, BlockHash? offset) { - return GetBlockState(offset).GetState(address); + return GetAccountState(offset).GetState(address); } public IReadOnlyList GetStates(IReadOnlyList
addresses, BlockHash? offset) { - return GetBlockState(offset).GetStates(addresses); + return GetAccountState(offset).GetStates(addresses); } public FungibleAssetValue GetBalance(Address address, Currency currency, BlockHash? offset) { - return GetBlockState(offset).GetBalance(address, currency); + return GetAccountState(offset).GetBalance(address, currency); } public FungibleAssetValue GetTotalSupply(Currency currency, BlockHash? offset) { - return GetBlockState(offset).GetTotalSupply(currency); + return GetAccountState(offset).GetTotalSupply(currency); } public ValidatorSet GetValidatorSet(BlockHash? offset) { - return GetBlockState(offset).GetValidatorSet(); + return GetAccountState(offset).GetValidatorSet(); } - public IBlockState GetBlockState(BlockHash? offset) + public IAccountState GetAccountState(BlockHash? offset) { - return new LocalCacheBlockState(_rocksDb, _source.GetBlockState(offset)); + return new LocalCacheAccountState(_rocksDb, _source.GetAccountState, offset); } } - private sealed class LocalCacheBlockState : IBlockState + private sealed class LocalCacheAccountState : IAccountState { private static readonly Codec _codec = new Codec(); private readonly RocksDb _rocksDb; - private readonly IBlockState _sourceBlockState; + private readonly Func _sourceAccountStateGetter; + private readonly BlockHash? _offset; - public LocalCacheBlockState(RocksDb rocksDb, IBlockState sourceBlockState) + public LocalCacheAccountState( + RocksDb rocksDb, + Func sourceAccountStateGetter, + BlockHash? offset) { _rocksDb = rocksDb; - _sourceBlockState = sourceBlockState; + _sourceAccountStateGetter = sourceAccountStateGetter; + _offset = offset; } + public ITrie Trie => _sourceAccountStateGetter(_offset).Trie; + public IValue? GetState(Address address) { var key = WithBlockHash(address.ToByteArray()); @@ -634,7 +177,7 @@ public LocalCacheBlockState(RocksDb rocksDb, IBlockState sourceBlockState) } catch (KeyNotFoundException) { - var state = _sourceBlockState.GetState(address); + var state = _sourceAccountStateGetter(_offset).GetState(address); SetValue(key, state); return state; } @@ -660,7 +203,7 @@ public FungibleAssetValue GetBalance(Address address, Currency currency) } catch (KeyNotFoundException) { - var fav = _sourceBlockState.GetBalance(address, currency); + var fav = _sourceAccountStateGetter(_offset).GetBalance(address, currency); SetValue(key, (Integer)fav.RawValue); return fav; } @@ -681,7 +224,7 @@ public FungibleAssetValue GetTotalSupply(Currency currency) } catch (KeyNotFoundException) { - var fav = _sourceBlockState.GetTotalSupply(currency); + var fav = _sourceAccountStateGetter(_offset).GetTotalSupply(currency); SetValue(key, (Integer)fav.RawValue); return fav; } @@ -697,14 +240,12 @@ public ValidatorSet GetValidatorSet() } catch (KeyNotFoundException) { - var validatorSet = _sourceBlockState.GetValidatorSet(); + var validatorSet = _sourceAccountStateGetter(_offset).GetValidatorSet(); SetValue(key, validatorSet.Bencoded); return validatorSet; } } - public BlockHash? BlockHash => _sourceBlockState.BlockHash; - private IValue? GetValue(byte[] key) { if (_rocksDb.Get(key) is not { } bytes) @@ -722,7 +263,7 @@ private void SetValue(byte[] key, IValue? value) private byte[] WithBlockHash(params byte[][] suffixes) { - if (BlockHash is not { } blockHash) + if (_offset is not { } blockHash) { throw new InvalidOperationException(); } @@ -746,7 +287,7 @@ private static IEnumerable EvaluateActions( long blockIndex, int blockProtocolVersion, TxId? txid, - IAccountStateDelta previousStates, + IAccount previousStates, Address miner, Address signer, byte[] signature, @@ -754,7 +295,7 @@ private static IEnumerable EvaluateActions( ILogger? logger = null) { ActionContext CreateActionContext( - IAccountStateDelta prevState, + IAccount prevState, int randomSeed) { return new ActionContext( @@ -776,11 +317,11 @@ ActionContext CreateActionContext( byte[] preEvaluationHashBytes = preEvaluationHash.ToByteArray(); int seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, hashedSignature, signature, 0); - IAccountStateDelta states = previousStates; + IAccount states = previousStates; foreach (IAction action in actions) { Exception? exc = null; - IAccountStateDelta nextStates = states; + IAccount nextStates = states; ActionContext context = CreateActionContext(nextStates, seed); try diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 02a7f1218..19ebea278 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -100,8 +100,8 @@ public int Tx( } // Evaluate tx. - IAccountState previousBlockStates = blockChain.GetBlockState(previousBlock.Hash); - IAccountStateDelta previousStates = AccountStateDelta.Create(previousBlockStates); + IAccountState previousBlockStates = blockChain.GetAccountState(previousBlock.Hash); + IAccount previousStates = new Account(previousBlockStates); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( preEvaluationHash: targetBlock.PreEvaluationHash, @@ -398,8 +398,7 @@ public int RemoteTx( cacheDirectory ?? Path.Join(Path.GetTempPath(), "ncd-replay-remote-tx-cache")); var previousBlockHash = BlockHash.FromString(previousBlockHashValue); - var previousStates = - AccountStateDelta.Create(blockChainStates.GetBlockState(previousBlockHash)); + var previousStates = new Account(blockChainStates.GetAccountState(previousBlockHash)); var actions = transaction.Actions .Select(ToAction) diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 513a7e90b..235e7bbd8 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -461,7 +461,7 @@ private static ImmutableDictionary GetTotalDelta( return ImmutableDictionary.Empty; } - IAccountStateDelta lastStates = actionEvaluations[actionEvaluations.Count - 1].OutputState; + IAccount lastStates = actionEvaluations[actionEvaluations.Count - 1].OutputState; ImmutableDictionary totalDelta = stateUpdatedAddresses.ToImmutableDictionary( diff --git a/NineChronicles.Headless.Executable/Commands/TxCommand.cs b/NineChronicles.Headless.Executable/Commands/TxCommand.cs index 235d2a96c..88bef4faa 100644 --- a/NineChronicles.Headless.Executable/Commands/TxCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/TxCommand.cs @@ -76,6 +76,7 @@ public void Sign( nameof(ClaimStakeReward4) => new ClaimStakeReward4(), nameof(ClaimStakeReward5) => new ClaimStakeReward5(), nameof(ClaimStakeReward7) => new ClaimStakeReward7(), + nameof(ClaimStakeReward8) => new ClaimStakeReward8(), nameof(ClaimStakeReward) => new ClaimStakeReward(), nameof(TransferAsset) => new TransferAsset(), nameof(MigrateMonsterCollection) => new MigrateMonsterCollection(), diff --git a/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs b/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs index de3b21288..a33c0ecb2 100644 --- a/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs +++ b/NineChronicles.Headless.Tests/Common/Actions/EmptyAction.cs @@ -11,12 +11,12 @@ public void LoadPlainValue(IValue plainValue) { } - public IAccountStateDelta Execute(IActionContext context) + public IAccount Execute(IActionContext context) { return context.PreviousState; } - public void Render(IActionContext context, IAccountStateDelta nextStates) + public void Render(IActionContext context, IAccount nextStates) { } @@ -24,7 +24,7 @@ public void RenderError(IActionContext context, Exception exception) { } - public void Unrender(IActionContext context, IAccountStateDelta nextStates) + public void Unrender(IActionContext context, IAccount nextStates) { } diff --git a/NineChronicles.Headless.Tests/Common/Fixtures.cs b/NineChronicles.Headless.Tests/Common/Fixtures.cs index c53218f24..59cdc950b 100644 --- a/NineChronicles.Headless.Tests/Common/Fixtures.cs +++ b/NineChronicles.Headless.Tests/Common/Fixtures.cs @@ -53,8 +53,11 @@ public static ShopState ShopStateFX() { var row = TableSheetsFX.EquipmentItemSheet.OrderedList[index]; var equipment = ItemFactory.CreateItemUsable(row, Guid.Empty, 0); - var shopItem = new ShopItem(UserAddress, AvatarAddress, Guid.NewGuid(), index * CurrencyFX, equipment); - shopState.Register(shopItem); + if (equipment is ITradableItem tradableItem) + { + var shopItem = new ShopItem(UserAddress, AvatarAddress, Guid.NewGuid(), index * CurrencyFX, tradableItem); + shopState.Register(shopItem); + } } for (var i = 0; i < TableSheetsFX.CostumeItemSheet.OrderedList.Count; i++) diff --git a/NineChronicles.Headless.Tests/Common/MockState.cs b/NineChronicles.Headless.Tests/Common/MockState.cs index 8ad33c2d0..2ca86f1b0 100644 --- a/NineChronicles.Headless.Tests/Common/MockState.cs +++ b/NineChronicles.Headless.Tests/Common/MockState.cs @@ -6,6 +6,7 @@ using Bencodex.Types; using Libplanet.Action.State; using Libplanet.Crypto; +using Libplanet.Store.Trie; using Libplanet.Types.Assets; using Libplanet.Types.Consensus; @@ -85,6 +86,8 @@ private MockState( public ValidatorSet ValidatorSet => _validatorSet; + public ITrie Trie => throw new NotSupportedException(); + public IValue? GetState(Address address) => _states.TryGetValue(address, out IValue? value) ? value : null; diff --git a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs index 12b3ba9bb..ce2ad8c6b 100644 --- a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs +++ b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs @@ -92,7 +92,7 @@ public static StandaloneContext CreateStandaloneContext() stateStore, genesisBlock, actionEvaluator); - var currencyFactory = new CurrencyFactory(blockchain.GetBlockState); + var currencyFactory = new CurrencyFactory(() => blockchain.GetAccountState(blockchain.Tip.Hash)); var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return new StandaloneContext { @@ -133,7 +133,7 @@ PrivateKey minerPrivateKey actionEvaluator); var ncg = new GoldCurrencyState((Dictionary)blockchain.GetState(Addresses.GoldCurrency)) .Currency; - var currencyFactory = new CurrencyFactory(blockchain.GetBlockState, ncg); + var currencyFactory = new CurrencyFactory(() => blockchain.GetAccountState(blockchain.Tip.Hash), ncg); var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return new StandaloneContext { diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index b964d7416..8e720b2ce 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -1311,5 +1311,34 @@ private static IEnumerable GetMemberDataOfUnloadFromMyGarages() "memo", }; } + + [Fact] + public async Task AuraSummon() + { + var random = new Random(); + var avatarAddress = new PrivateKey().ToAddress(); + var groupId = random.Next(10001, 10002 + 1); + // FIXME: Change 10 to AuraSummon.SummonLimit + var summonCount = random.Next(1, 10 + 1); + + var query = $@"{{ + auraSummon( + avatarAddress: ""{avatarAddress}"", + groupId: {groupId}, + summonCount: {summonCount} + ) + }}"; + + var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["auraSummon"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var action = Assert.IsType(actionBase); + + Assert.Equal(avatarAddress, action.AvatarAddress); + Assert.Equal(groupId, action.GroupId); + Assert.Equal(summonCount, action.SummonCount); + } } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs index c632651a9..76f309a1f 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs @@ -75,7 +75,7 @@ public async Task SubscribeTx() { const string query = @" subscription { - tx (actionType: ""grinding"") { + tx (actionType: ""grinding2"") { transaction { id } txResult { blockIndex } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/ShopItemTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/ShopItemTypeTest.cs index 787794586..4ff857465 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/ShopItemTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/ShopItemTypeTest.cs @@ -44,7 +44,7 @@ Dictionary costumeDict if (costume is null) { shopItem = new ShopItem(Fixtures.UserAddress, Fixtures.AvatarAddress, - new Guid("d3d9ac06-eb91-4cc4-863a-5b4769ad633e"), 100 * Fixtures.CurrencyFX, itemUsable); + new Guid("d3d9ac06-eb91-4cc4-863a-5b4769ad633e"), 100 * Fixtures.CurrencyFX, (ITradableItem)itemUsable); } else { diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs index a08ff0969..d2f91c95d 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/StakeStateTypeTest.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; using System.Threading.Tasks; using GraphQL.Execution; +using Libplanet.Crypto; using Libplanet.Types.Assets; using Nekoyume; +using Nekoyume.Model.Stake; using Nekoyume.Model.State; +using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes.States; using NineChronicles.Headless.Tests.Common; using Xunit; @@ -15,7 +18,7 @@ public class StakeStateTypeTest { [Theory] [MemberData(nameof(Members))] - public async Task Query(StakeState stakeState, long deposit, long blockIndex, Dictionary expected) + public async Task Query(StakeStateV2 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 @@ -36,7 +39,12 @@ public async Task Query(StakeState stakeState, long deposit, long blockIndex, Di claimableBlockIndex }"; var queryResult = await ExecuteQueryAsync( - query, source: new StakeStateType.StakeStateContext(stakeState, mockState, blockIndex)); + query, + source: new StakeStateType.StakeStateContext( + stakeState, + stakeStateAddress, + mockState, + blockIndex)); var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; Assert.Equal(expected, data); } @@ -45,76 +53,83 @@ public async Task Query(StakeState stakeState, long deposit, long blockIndex, Di { new object[] { - new StakeState(Fixtures.StakeStateAddress, 0), + new StakeStateV2( + new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 0), + Fixtures.StakeStateAddress, 100, 0, new Dictionary { ["address"] = Fixtures.StakeStateAddress.ToString(), ["deposit"] = "100.00", - ["startedBlockIndex"] = 0, + ["startedBlockIndex"] = 0L, ["cancellableBlockIndex"] = StakeState.LockupInterval, - ["receivedBlockIndex"] = 0, - ["claimableBlockIndex"] = 0 + StakeState.RewardInterval, + ["receivedBlockIndex"] = 0L, + ["claimableBlockIndex"] = 0L + StakeState.RewardInterval, } }, new object[] { - new StakeState(Fixtures.StakeStateAddress, 100), + new StakeStateV2(new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 100), + Fixtures.StakeStateAddress, 100, 0, new Dictionary { ["address"] = Fixtures.StakeStateAddress.ToString(), ["deposit"] = "100.00", - ["startedBlockIndex"] = 100, + ["startedBlockIndex"] = 100L, ["cancellableBlockIndex"] = 100 + StakeState.LockupInterval, - ["receivedBlockIndex"] = 0, + ["receivedBlockIndex"] = 0L, ["claimableBlockIndex"] = 100 + StakeState.RewardInterval, } }, new object[] { - new StakeState(Fixtures.StakeStateAddress, 100), + new StakeStateV2(new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 100), + Fixtures.StakeStateAddress, 100, 0, new Dictionary { ["address"] = Fixtures.StakeStateAddress.ToString(), ["deposit"] = "100.00", - ["startedBlockIndex"] = 100, + ["startedBlockIndex"] = 100L, ["cancellableBlockIndex"] = StakeState.LockupInterval + 100, - ["receivedBlockIndex"] = 0, + ["receivedBlockIndex"] = 0L, ["claimableBlockIndex"] = StakeState.RewardInterval + 100, } }, new object[] { - new StakeState(Fixtures.StakeStateAddress, 10, 50412, 201610, new StakeState.StakeAchievements()), + new StakeStateV2( + new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 10, 50412), + Fixtures.StakeStateAddress, 100, 0, new Dictionary { ["address"] = Fixtures.StakeStateAddress.ToString(), ["deposit"] = "100.00", - ["startedBlockIndex"] = 10, + ["startedBlockIndex"] = 10L, ["cancellableBlockIndex"] = 201610L, - ["receivedBlockIndex"] = 50412, - ["claimableBlockIndex"] = 100812L, + ["receivedBlockIndex"] = 50412L, + ["claimableBlockIndex"] = 100810L, } }, new object[] { - new StakeState(Fixtures.StakeStateAddress, 10, 50412, 201610, new StakeState.StakeAchievements()), + new StakeStateV2(new Contract("StakeRegularFixedRewardSheet_V1", "StakeRegularRewardSheet_V1", 50400, 201600), 10, 50412), + Fixtures.StakeStateAddress, 100, ActionObsoleteConfig.V100290ObsoleteIndex, new Dictionary { ["address"] = Fixtures.StakeStateAddress.ToString(), ["deposit"] = "100.00", - ["startedBlockIndex"] = 10, + ["startedBlockIndex"] = 10L, ["cancellableBlockIndex"] = 201610L, - ["receivedBlockIndex"] = 50412, + ["receivedBlockIndex"] = 50412L, ["claimableBlockIndex"] = 100810L, } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index 652d3c940..5716a9479 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -343,7 +343,7 @@ public async Task TransactionResultIsSuccess() private Task ExecuteAsync(string query) { - var currencyFactory = new CurrencyFactory(_blockChain.GetBlockState); + var currencyFactory = new CurrencyFactory(() => _blockChain.GetAccountState(_blockChain.Tip.Hash)); var fungibleAssetValueFactory = new FungibleAssetValueFactory(currencyFactory); return GraphQLTestUtils.ExecuteQueryAsync(query, standaloneContext: new StandaloneContext { diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index 050924f90..6f6ac1117 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -125,7 +125,7 @@ public UnaryResult GetState(byte[] addressBytes, byte[] blockHashBytes) public async UnaryResult> GetAvatarStates(IEnumerable addressBytesList, byte[] blockHashBytes) { var hash = new BlockHash(blockHashBytes); - var accountState = _blockChain.GetBlockState(hash); + var accountState = _blockChain.GetAccountState(hash); var result = new ConcurrentDictionary(); var addresses = addressBytesList.Select(a => new Address(a)).ToList(); var rawAvatarStates = accountState.GetRawAvatarStates(addresses); diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 070458f12..a3e583954 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -223,7 +223,7 @@ public ActionQuery(StandaloneContext standaloneContext) @namespace.StartsWith($"{nameof(Nekoyume)}.{nameof(Nekoyume.TableData)}") && !type.IsAbstract && typeof(ISheet).IsAssignableFrom(type) && - type.Name == tableName); + tableName.Split('_').First() == type.Name); } catch (Exception) { @@ -542,6 +542,7 @@ public ActionQuery(StandaloneContext standaloneContext) RegisterCombinationConsumable(); RegisterMead(); RegisterGarages(); + RegisterSummon(); Field>( name: "craftQuery", diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/Summon.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Summon.cs new file mode 100644 index 000000000..6391f7eaf --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/Summon.cs @@ -0,0 +1,43 @@ +using GraphQL; +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Nekoyume.Action; + +namespace NineChronicles.Headless.GraphTypes; + +public partial class ActionQuery +{ + private void RegisterSummon() + { + Field>( + "auraSummon", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "avatarAddress", + Description = "Avatar address to get summoned items" + }, + new QueryArgument> + { + Name = "groupId", + Description = "Summon group id" + }, + new QueryArgument> + { + Name = "summonCount", + Description = "Count to summon. Must between 1 and 10." + } + ), + resolve: context => + { + var avatarAddr = context.GetArgument
("avatarAddress"); + var groupId = context.GetArgument("groupId"); + var summonCount = context.GetArgument("summonCount"); + + ActionBase action = new AuraSummon(avatarAddr, groupId, summonCount); + return Encode(context, action); + } + ); + } +} diff --git a/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs b/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs index a3f91ac4b..ef6745ea3 100644 --- a/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs +++ b/NineChronicles.Headless/GraphTypes/CurrencyEnumType.cs @@ -9,6 +9,7 @@ public enum CurrencyEnum CRYSTAL, NCG, GARAGE, + MEAD } public class CurrencyEnumType : EnumerationGraphType diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index 87667f91f..a5d9688d5 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -52,7 +52,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi } return new StateContext( - chain.GetBlockState(blockHash), + chain.GetAccountState(blockHash), blockHash switch { BlockHash bh => chain[bh].Index, @@ -84,6 +84,11 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi var state = blockChain.GetStates(new[] { address }, blockHash)[0]; + if (state is null) + { + return null; + } + return new Codec().Encode(state); } ); @@ -116,39 +121,32 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi var recipient = context.GetArgument("recipient"); IEnumerable txs = digest.TxIds - .Select(b => new TxId(b.ToBuilder().ToArray())) + .Select(bytes => new TxId(bytes)) .Select(store.GetTransaction); - var filteredTransactions = txs.Where(tx => - tx.Actions!.Count == 1 && - ToAction(tx.Actions.First()) is ITransferAsset transferAsset && - (!recipient.HasValue || transferAsset.Recipient == recipient) && - transferAsset.Amount.Currency.Ticker == "NCG" && - store.GetTxExecution(blockHash, tx.Id) is TxSuccess); - - TransferNCGHistory ToTransferNCGHistory(TxSuccess txSuccess, string? memo) + + var pairs = txs + .Where(tx => + tx.Actions!.Count == 1 && + store.GetTxExecution(blockHash, tx.Id) is TxSuccess) + .Select(tx => (tx.Id, ToAction(tx.Actions.First()))) + .Where(pair => + pair.Item2 is ITransferAsset transferAssset && + transferAssset.Amount.Currency.Ticker == "NCG") + .Select(pair => (pair.Item1, (ITransferAsset)pair.Item2)) + .Where(pair => (!(recipient is { } r) || pair.Item2.Recipient == r)); + + TransferNCGHistory ToTransferNCGHistory((TxId TxId, ITransferAsset Transfer) pair) { - var rawTransferNcgHistories = txSuccess.FungibleAssetsDelta - .Where(pair => pair.Value.Values.Any(fav => fav.Currency.Ticker == "NCG")) - .Select(pair => - (pair.Key, pair.Value.Values.First(fav => fav.Currency.Ticker == "NCG"))) - .ToArray(); - var ((senderAddress, _), (recipientAddress, amount)) = - rawTransferNcgHistories[0].Item2.RawValue > rawTransferNcgHistories[1].Item2.RawValue - ? (rawTransferNcgHistories[1], rawTransferNcgHistories[0]) - : (rawTransferNcgHistories[0], rawTransferNcgHistories[1]); return new TransferNCGHistory( - txSuccess.BlockHash, - txSuccess.TxId, - senderAddress, - recipientAddress, - amount, - memo); + blockHash, + pair.TxId, + pair.Transfer.Sender, + pair.Transfer.Recipient, + pair.Transfer.Amount, + pair.Transfer.Memo); } - var histories = filteredTransactions.Select(tx => - ToTransferNCGHistory((TxSuccess)store.GetTxExecution(blockHash, tx.Id), - ((ITransferAsset)ToAction(tx.Actions!.Single())).Memo)); - + var histories = pairs.Select(ToTransferNCGHistory); return histories; }); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 4b699c65e..4fdf34bac 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -329,21 +329,17 @@ private IObservable SubscribeTx(IResolveFieldContext context) txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), null, - null, success.UpdatedStates .Select(kv => new KeyValuePair( kv.Key, kv.Value)) .ToImmutableDictionary(), - success.FungibleAssetsDelta, success.UpdatedFungibleAssets), TxFailure failure => new TxResult( TxStatus.FAILURE, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), failure.ExceptionName, - failure.ExceptionMetadata, - null, null, null), _ => null diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index ed61ab8ba..8ce7407b8 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -13,9 +13,11 @@ using Nekoyume.Extensions; using Nekoyume.Model.Arena; using Nekoyume.Model.Item; +using Nekoyume.Model.Stake; using Nekoyume.Model.State; using Nekoyume.TableData; using Nekoyume.TableData.Crystal; +using Nekoyume.TableData.Stake; using NineChronicles.Headless.GraphTypes.Abstractions; using NineChronicles.Headless.GraphTypes.States; using NineChronicles.Headless.GraphTypes.States.Models; @@ -239,10 +241,12 @@ public StateQuery() StakeStateType.StakeStateContext? GetStakeState(StateContext ctx, Address agentAddress) { - if (ctx.GetState(StakeState.DeriveAddress(agentAddress)) is Dictionary state) + var stakeStateAddress = StakeState.DeriveAddress(agentAddress); + if (ctx.AccountState.TryGetStakeStateV2(agentAddr: agentAddress, out StakeStateV2 stakeStateV2)) { return new StakeStateType.StakeStateContext( - new StakeState(state), + stakeStateV2, + stakeStateAddress, ctx.AccountState, ctx.BlockIndex ); @@ -252,7 +256,7 @@ public StateQuery() } Field( - name: nameof(StakeState), + name: "stakeState", description: "State for staking.", arguments: new QueryArguments(new QueryArgument> { @@ -338,8 +342,42 @@ public StateQuery() } ); + Field( + "latestStakeRewards", + description: "The latest stake rewards based on StakePolicySheet.", + resolve: context => + { + var stakePolicySheetStateValue = context.Source.GetState(Addresses.GetSheetAddress()); + var stakePolicySheet = new StakePolicySheet(); + if (stakePolicySheetStateValue is not Text stakePolicySheetStateText) + { + return null; + } + + stakePolicySheet.Set(stakePolicySheetStateText); + + IReadOnlyList values = context.Source.GetStates(new[] + { + Addresses.GetSheetAddress(stakePolicySheet["StakeRegularFixedRewardSheet"].Value), + Addresses.GetSheetAddress(stakePolicySheet["StakeRegularRewardSheet"].Value), + }); + + if (!(values[0] is Text fsv && values[1] is Text sv)) + { + return null; + } + + var stakeRegularFixedRewardSheet = new StakeRegularFixedRewardSheet(); + var stakeRegularRewardSheet = new StakeRegularRewardSheet(); + stakeRegularFixedRewardSheet.Set(fsv); + stakeRegularRewardSheet.Set(sv); + + return (stakeRegularRewardSheet, stakeRegularFixedRewardSheet); + } + ); Field( "stakeRewards", + deprecationReason: "Since stake3, claim_stake_reward9 actions, each stakers have their own contracts.", resolve: context => { StakeRegularRewardSheet stakeRegularRewardSheet; @@ -348,9 +386,9 @@ public StateQuery() if (context.Source.BlockIndex < StakeState.StakeRewardSheetV2Index) { stakeRegularRewardSheet = new StakeRegularRewardSheet(); - stakeRegularRewardSheet.Set(ClaimStakeReward.V1.StakeRegularRewardSheetCsv); + stakeRegularRewardSheet.Set(ClaimStakeReward8.V1.StakeRegularRewardSheetCsv); stakeRegularFixedRewardSheet = new StakeRegularFixedRewardSheet(); - stakeRegularFixedRewardSheet.Set(ClaimStakeReward.V1.StakeRegularFixedRewardSheetCsv); + stakeRegularFixedRewardSheet.Set(ClaimStakeReward8.V1.StakeRegularFixedRewardSheetCsv); } else { diff --git a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs index d7a4ee811..50deeb03d 100644 --- a/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/StakeStateType.cs @@ -1,9 +1,12 @@ using System; +using System.Collections.Generic; using Bencodex.Types; +using GraphQL; using GraphQL.Types; using Libplanet.Action; using Libplanet.Explorer.GraphTypes; using Libplanet.Action.State; +using Libplanet.Crypto; using Nekoyume.Model.State; using NineChronicles.Headless.GraphTypes.States.Models; using NineChronicles.Headless.GraphTypes.States.Models.World; @@ -12,6 +15,9 @@ using NineChronicles.Headless.GraphTypes.States.Models.Quest; using Nekoyume.Blockchain.Policy; using Nekoyume; +using Nekoyume.Model.Stake; +using Nekoyume.TableData; +using NineChronicles.Headless.GraphTypes.Abstractions; namespace NineChronicles.Headless.GraphTypes.States { @@ -19,49 +25,79 @@ public class StakeStateType : ObjectGraphType { public class StakeStateContext : StateContext { - public StakeStateContext(StakeState stakeState, IAccountState accountState, long blockIndex) + public StakeStateContext(StakeStateV2 stakeState, Address address, IAccountState accountState, long blockIndex) : base(accountState, blockIndex) { StakeState = stakeState; + Address = address; } - public StakeState StakeState { get; } + public StakeStateV2 StakeState { get; } + public Address Address { get; } } public StakeStateType() { Field>( - nameof(StakeState.address), + "address", description: "The address of current state.", - resolve: context => context.Source.StakeState.address); + resolve: context => context.Source.Address); Field>( "deposit", description: "The staked amount.", resolve: context => context.Source.AccountState.GetBalance( - context.Source.StakeState.address, + context.Source.Address, new GoldCurrencyState((Dictionary)context.Source.GetState(GoldCurrencyState.Address)!).Currency) .GetQuantityString(true)); - Field>( - nameof(StakeState.StartedBlockIndex), + Field>( + "startedBlockIndex", description: "The block index the user started to stake.", resolve: context => context.Source.StakeState.StartedBlockIndex); - Field>( - nameof(StakeState.ReceivedBlockIndex), + Field>( + "receivedBlockIndex", description: "The block index the user received rewards.", resolve: context => context.Source.StakeState.ReceivedBlockIndex); Field>( - nameof(StakeState.CancellableBlockIndex), + "cancellableBlockIndex", description: "The block index the user can cancel the staking.", resolve: context => context.Source.StakeState.CancellableBlockIndex); Field>( "claimableBlockIndex", description: "The block index the user can claim rewards.", - resolve: context => context.Source.StakeState.GetClaimableBlockIndex( - context.Source.BlockIndex)); - Field>( + resolve: context => context.Source.StakeState.ClaimableBlockIndex); + Field( nameof(StakeState.Achievements), description: "The staking achievements.", - resolve: context => context.Source.StakeState.Achievements); + deprecationReason: "Since StakeStateV2, the achievement became removed.", + resolve: _ => null); + Field>( + "stakeRewards", + resolve: context => + { + if (context.Source.StakeState.Contract is not { } contract) + { + return null; + } + + IReadOnlyList values = context.Source.GetStates(new[] + { + Addresses.GetSheetAddress(contract.StakeRegularFixedRewardSheetTableName), + Addresses.GetSheetAddress(contract.StakeRegularRewardSheetTableName), + }); + + if (!(values[0] is Text fsv && values[1] is Text sv)) + { + throw new ExecutionError("Could not found stake rewards sheets"); + } + + StakeRegularFixedRewardSheet stakeRegularFixedRewardSheet = new StakeRegularFixedRewardSheet(); + StakeRegularRewardSheet stakeRegularRewardSheet = new StakeRegularRewardSheet(); + stakeRegularFixedRewardSheet.Set(fsv); + stakeRegularRewardSheet.Set(sv); + + return (stakeRegularRewardSheet, stakeRegularFixedRewardSheet); + } + ); } } } diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index fda11309a..5be72bb01 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -6,6 +6,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.RegularExpressions; using Bencodex; using Bencodex.Json; using GraphQL; @@ -75,7 +76,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) new QueryArgument> { Name = "limit", Description = "number of block to query." }, new QueryArgument> - { Name = "actionType", Description = "filter tx by having actions' type" } + { Name = "actionType", Description = "filter tx by having actions' type. It is regular expression." } ), resolve: context => { @@ -99,7 +100,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) return false; } - return typeId == actionType; + return Regex.IsMatch(typeId, actionType); })); return transactions; @@ -207,8 +208,8 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) { return blockChain.GetStagedTransactionIds().Contains(txId) - ? new TxResult(TxStatus.STAGING, null, null, null, null, null, null, null) - : new TxResult(TxStatus.INVALID, null, null, null, null, null, null, null); + ? new TxResult(TxStatus.STAGING, null, null, null, null, null) + : new TxResult(TxStatus.INVALID, null, null, null, null, null); } try @@ -222,21 +223,17 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), null, - null, txSuccess.UpdatedStates .Select(kv => new KeyValuePair( kv.Key, kv.Value)) .ToImmutableDictionary(), - txSuccess.FungibleAssetsDelta, txSuccess.UpdatedFungibleAssets), TxFailure txFailure => new TxResult( TxStatus.FAILURE, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), txFailure.ExceptionName, - txFailure.ExceptionMetadata, - null, null, null), _ => throw new NotImplementedException( @@ -245,7 +242,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) } catch (Exception) { - return new TxResult(TxStatus.INVALID, null, null, null, null, null, null, null); + return new TxResult(TxStatus.INVALID, null, null, null, null, null); } } ); diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 5b2f22d65..155068c0e 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -278,7 +278,8 @@ internal void ConfigureContext(StandaloneContext standaloneContext) standaloneContext.Store = Store; standaloneContext.Swarm = Swarm; standaloneContext.CurrencyFactory = - new CurrencyFactory(standaloneContext.BlockChain.GetBlockState); + new CurrencyFactory( + () => standaloneContext.BlockChain.GetAccountState(standaloneContext.BlockChain.Tip.Hash)); standaloneContext.FungibleAssetValueFactory = new FungibleAssetValueFactory(standaloneContext.CurrencyFactory); BootstrapEnded.WaitAsync().ContinueWith((task) => diff --git a/NineChronicles.Headless/Utils/CurrencyFactory.cs b/NineChronicles.Headless/Utils/CurrencyFactory.cs index 3bba7b27c..2c2945bf4 100644 --- a/NineChronicles.Headless/Utils/CurrencyFactory.cs +++ b/NineChronicles.Headless/Utils/CurrencyFactory.cs @@ -32,6 +32,7 @@ public bool TryGetCurrency(string ticker, out Currency currency) var result = ticker switch { "NCG" => GetNCG(), + "MEAD" => Currencies.Mead, _ => Currencies.GetMinterlessCurrency(ticker), }; if (result is null)