diff --git a/nekoyume/Assets/_Scripts/Blockchain/ActionRenderHandler.cs b/nekoyume/Assets/_Scripts/Blockchain/ActionRenderHandler.cs index e4d14f37e6..bcf33d070d 100644 --- a/nekoyume/Assets/_Scripts/Blockchain/ActionRenderHandler.cs +++ b/nekoyume/Assets/_Scripts/Blockchain/ActionRenderHandler.cs @@ -754,7 +754,7 @@ private void ResponseCreateAvatar( { await UpdateAgentStateAsync(eval); await UpdateAvatarState(eval, eval.Action.index); - await RxProps.SelectAvatarAsync(eval.Action.index); + await RxProps.SelectAvatarAsync(eval.Action.index, eval.OutputState); }).ToObservable() .ObserveOnMainThread() .Subscribe(x => @@ -2115,7 +2115,7 @@ private ActionEvaluation PrepareEventDungeonBattle( var task = UniTask.RunOnThreadPool(() => { UpdateCurrentAvatarStateAsync(eval).Forget(); - RxProps.EventDungeonInfo.UpdateAsync().Forget(); + RxProps.EventDungeonInfo.UpdateAsync(eval.OutputState).Forget(); _disposableForBattleEnd = null; Game.Game.instance.Stage.IsAvatarStateUpdatedAfterBattle = true; }, configureAwait: false); @@ -2602,12 +2602,12 @@ private static async UniTaskVoid ResponseJoinArenaAsync( eval.Action.round == currentRound.Round) { await UniTask.WhenAll( - RxProps.ArenaInfoTuple.UpdateAsync(), - RxProps.ArenaInformationOrderedWithScore.UpdateAsync()); + RxProps.ArenaInfoTuple.UpdateAsync(eval.OutputState), + RxProps.ArenaInformationOrderedWithScore.UpdateAsync(eval.OutputState)); } else { - await RxProps.ArenaInfoTuple.UpdateAsync(); + await RxProps.ArenaInfoTuple.UpdateAsync(eval.OutputState); } if (arenaJoin && arenaJoin.IsActive()) @@ -2646,8 +2646,8 @@ private async void ResponseBattleArenaAsync(ActionEvaluation eval) } // NOTE: Start cache some arena info which will be used after battle ends. - await UniTask.WhenAll(RxProps.ArenaInfoTuple.UpdateAsync(), - RxProps.ArenaInformationOrderedWithScore.UpdateAsync()); + await UniTask.WhenAll(RxProps.ArenaInfoTuple.UpdateAsync(eval.OutputState), + RxProps.ArenaInformationOrderedWithScore.UpdateAsync(eval.OutputState)); _disposableForBattleEnd?.Dispose(); _disposableForBattleEnd = Game.Game.instance.Arena.OnArenaEnd @@ -3165,6 +3165,7 @@ private void ManipulateState() { RxProps.SelectAvatarAsync( States.Instance.CurrentAvatarKey, + eval.OutputState, forceNewSelection: true).Forget(); NotificationSystem.Push( MailType.System, diff --git a/nekoyume/Assets/_Scripts/Blockchain/Agent.cs b/nekoyume/Assets/_Scripts/Blockchain/Agent.cs index db7d3da399..c64f9b1c2c 100644 --- a/nekoyume/Assets/_Scripts/Blockchain/Agent.cs +++ b/nekoyume/Assets/_Scripts/Blockchain/Agent.cs @@ -96,6 +96,8 @@ public class Agent : MonoBehaviour, IDisposable, IAgent public int AppProtocolVersion { get; private set; } public BlockHash BlockTipHash => blocks.Tip.Hash; + public HashDigest BlockTipStateRootHash => blocks.Tip.StateRootHash; + private readonly Subject<(NCTx tx, List actions)> _onMakeTransactionSubject = new Subject<(NCTx tx, List actions)>(); diff --git a/nekoyume/Assets/_Scripts/Blockchain/IAgent.cs b/nekoyume/Assets/_Scripts/Blockchain/IAgent.cs index 75ff90f3b3..c862c816f0 100644 --- a/nekoyume/Assets/_Scripts/Blockchain/IAgent.cs +++ b/nekoyume/Assets/_Scripts/Blockchain/IAgent.cs @@ -39,6 +39,8 @@ public interface IAgent BlockHash BlockTipHash { get; } + HashDigest BlockTipStateRootHash { get; } + IObservable<(Transaction tx, List actions)> OnMakeTransaction { get; } diff --git a/nekoyume/Assets/_Scripts/Blockchain/RPCAgent.cs b/nekoyume/Assets/_Scripts/Blockchain/RPCAgent.cs index 33f29c58bc..b1c6f464e5 100644 --- a/nekoyume/Assets/_Scripts/Blockchain/RPCAgent.cs +++ b/nekoyume/Assets/_Scripts/Blockchain/RPCAgent.cs @@ -97,6 +97,8 @@ public class RPCAgent : MonoBehaviour, IAgent, IActionEvaluationHubReceiver public BlockHash BlockTipHash { get; private set; } + public HashDigest BlockTipStateRootHash { get; private set; } + private readonly Subject<(NCTx tx, List actions)> _onMakeTransactionSubject = new Subject<(NCTx tx, List actions)>(); @@ -950,17 +952,21 @@ public void OnUnrender(byte[] evaluation) public void OnRenderBlock(byte[] oldTip, byte[] newTip) { - UniTask.RunOnThreadPool<(long, BlockHash)>(() => + UniTask.RunOnThreadPool<(long, BlockHash, HashDigest)>(() => { var dict = (Dictionary) _codec.Decode(newTip); var newTipBlock = BlockMarshaler.UnmarshalBlock(dict); - return (newTipBlock.Index, new BlockHash(newTipBlock.Hash.ToByteArray())); + return ( + newTipBlock.Index, + newTipBlock.Hash, + newTipBlock.StateRootHash); }).ToObservable().ObserveOnMainThread().Subscribe(tuple => { _blockHashCache.Add(tuple.Item1, tuple.Item2); BlockIndex = tuple.Item1; BlockIndexSubject.OnNext(BlockIndex); BlockTipHash = tuple.Item2; + BlockTipStateRootHash = tuple.Item3; BlockTipHashSubject.OnNext(BlockTipHash); _lastTipChangedAt = DateTimeOffset.UtcNow; diff --git a/nekoyume/Assets/_Scripts/Game/Game.cs b/nekoyume/Assets/_Scripts/Game/Game.cs index 60d6c51a6d..93780db38f 100644 --- a/nekoyume/Assets/_Scripts/Game/Game.cs +++ b/nekoyume/Assets/_Scripts/Game/Game.cs @@ -1314,7 +1314,7 @@ public static IDictionary GetTableCsvAssets() return container.tableCsvAssets.ToDictionary(asset => asset.name, asset => asset.text); } - private static async void EnterNext() + private async void EnterNext() { NcDebug.Log("[Game] EnterNext() invoked"); if (!GameConfig.IsEditor) @@ -1330,7 +1330,7 @@ private static async void EnterNext() var sw = new Stopwatch(); sw.Reset(); sw.Start(); - await RxProps.SelectAvatarAsync(slotIndex, true); + await RxProps.SelectAvatarAsync(slotIndex, Agent.BlockTipStateRootHash, true); sw.Stop(); NcDebug.Log("[Game] EnterNext()... SelectAvatarAsync() finished in" + $" {sw.ElapsedMilliseconds}ms.(elapsed)"); @@ -1362,7 +1362,7 @@ private static async void EnterNext() var sw = new Stopwatch(); sw.Reset(); sw.Start(); - await RxProps.SelectAvatarAsync(slotIndex, true); + await RxProps.SelectAvatarAsync(slotIndex, Agent.BlockTipStateRootHash, true); sw.Stop(); NcDebug.Log("[Game] EnterNext()... SelectAvatarAsync() finished in" + $" {sw.ElapsedMilliseconds}ms.(elapsed)"); diff --git a/nekoyume/Assets/_Scripts/State/AsyncUpdatableRxProp.cs b/nekoyume/Assets/_Scripts/State/AsyncUpdatableRxProp.cs index f41d0fd832..5f112d0176 100644 --- a/nekoyume/Assets/_Scripts/State/AsyncUpdatableRxProp.cs +++ b/nekoyume/Assets/_Scripts/State/AsyncUpdatableRxProp.cs @@ -1,23 +1,26 @@ -using System; +using System; using System.Threading.Tasks; using Cysharp.Threading.Tasks; namespace Nekoyume.State { + using Libplanet.Common; + using System.Security.Cryptography; using UniRx; public interface IReadOnlyAsyncUpdatableRxProp : IReadOnlyReactiveProperty { bool IsUpdating { get; } - UniTask UpdateAsync(bool forceNotify = false); + UniTask UpdateAsync(HashDigest stateRootHash, bool forceNotify = false); - IObservable UpdateAsObservable(bool forceNotify = false); + IObservable UpdateAsObservable(HashDigest stateRootHash, bool forceNotify = false); - IDisposable SubscribeWithUpdateOnce(Action onNext, bool forceNotify = false); + IDisposable SubscribeWithUpdateOnce(Action onNext, HashDigest stateRootHash, bool forceNotify = false); IDisposable SubscribeOnMainThreadWithUpdateOnce( Action onNext, + HashDigest stateRootHash, bool forceNotify = false); } @@ -30,27 +33,27 @@ public class AsyncUpdatableRxProp : ReactiveProperty, IAsyncUpdatableRxProp { - private readonly Func> _updateAsyncFunc; + private readonly Func, Task> _updateAsyncFunc; public bool IsUpdating { get; private set; } = false; - public AsyncUpdatableRxProp(Func> updateAsyncFunc) : + public AsyncUpdatableRxProp(Func, Task> updateAsyncFunc) : this(default, updateAsyncFunc) { } - public AsyncUpdatableRxProp(T defaultValue, Func> updateAsyncFunc) + public AsyncUpdatableRxProp(T defaultValue, Func, Task> updateAsyncFunc) { Value = defaultValue; _updateAsyncFunc = updateAsyncFunc ?? throw new ArgumentNullException(nameof(updateAsyncFunc)); } - public async UniTask UpdateAsync(bool forceNotify = false) + public async UniTask UpdateAsync(HashDigest stateRootHash, bool forceNotify = false) { IsUpdating = true; var t = await Task.Run(async () => - await _updateAsyncFunc(Value)); + await _updateAsyncFunc(Value, stateRootHash)); IsUpdating = false; if (forceNotify) { @@ -64,20 +67,21 @@ public async UniTask UpdateAsync(bool forceNotify = false) return t; } - public IObservable UpdateAsObservable(bool forceNotify = false) => - UpdateAsync(forceNotify).ToObservable(); + public IObservable UpdateAsObservable(HashDigest stateRootHash, bool forceNotify = false) => + UpdateAsync(stateRootHash, forceNotify).ToObservable(); - public IDisposable SubscribeWithUpdateOnce(Action onNext, bool forceNotify = false) + public IDisposable SubscribeWithUpdateOnce(Action onNext, HashDigest stateRootHash, bool forceNotify = false) { - UpdateAsync(forceNotify).Forget(); + UpdateAsync(stateRootHash, forceNotify).Forget(); return this.Subscribe(onNext); } public IDisposable SubscribeOnMainThreadWithUpdateOnce( Action onNext, + HashDigest stateRootHash, bool forceNotify = false) { - UpdateAsync(forceNotify).Forget(); + UpdateAsync(stateRootHash, forceNotify).Forget(); return this .SubscribeOnMainThread() .Subscribe(onNext); diff --git a/nekoyume/Assets/_Scripts/State/RxProps.Arena.cs b/nekoyume/Assets/_Scripts/State/RxProps.Arena.cs index f16fefec82..2e5c03faca 100644 --- a/nekoyume/Assets/_Scripts/State/RxProps.Arena.cs +++ b/nekoyume/Assets/_Scripts/State/RxProps.Arena.cs @@ -20,6 +20,8 @@ namespace Nekoyume.State { + using Libplanet.Common; + using System.Security.Cryptography; using UniRx; public static partial class RxProps @@ -143,7 +145,7 @@ private static void UpdateArenaTicketProgress(long blockIndex) private static async Task<(ArenaInformation current, ArenaInformation next)> UpdateArenaInfoTupleAsync( - (ArenaInformation current, ArenaInformation next) previous) + (ArenaInformation current, ArenaInformation next) previous, HashDigest stateRootHash) { var avatarAddress = _states.CurrentAvatarState?.address; if (!avatarAddress.HasValue) @@ -177,6 +179,7 @@ private static void UpdateArenaTicketProgress(long blockIndex) nextRoundData.Round) : default; var dict = await _agent.GetStateBulkAsync( + stateRootHash, ReservedAddresses.LegacyAccount, new[] { @@ -196,7 +199,8 @@ dict[nextArenaInfoAddress] is List nextList } private static async Task> - UpdateArenaInformationOrderedWithScoreAsync(List previous) + UpdateArenaInformationOrderedWithScoreAsync( + List previous, HashDigest stateRootHash) { var avatarAddress = _states.CurrentAvatarState?.address; List avatarAddrAndScoresWithRank = @@ -239,7 +243,7 @@ private static async Task> playerScoreAddr }; var stateBulk = - await agent.GetStateBulkAsync(ReservedAddresses.LegacyAccount, addrBulk); + await agent.GetStateBulkAsync(stateRootHash, ReservedAddresses.LegacyAccount, addrBulk); var purchasedCountDuringInterval = stateBulk[purchasedCountAddress] is Integer iValue ? (int)iValue : 0; diff --git a/nekoyume/Assets/_Scripts/State/RxProps.Event.cs b/nekoyume/Assets/_Scripts/State/RxProps.Event.cs index 036668bd86..94bfcddb93 100644 --- a/nekoyume/Assets/_Scripts/State/RxProps.Event.cs +++ b/nekoyume/Assets/_Scripts/State/RxProps.Event.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using Libplanet.Action.State; @@ -9,6 +9,8 @@ namespace Nekoyume.State { + using Libplanet.Common; + using System.Security.Cryptography; using UniRx; public static partial class RxProps @@ -111,7 +113,7 @@ private static void OnBlockIndexEvent(long blockIndex) private static void OnAvatarChangedEvent() { - _eventDungeonInfo.UpdateAsync().Forget(); + _eventDungeonInfo.UpdateAsync(_agent.BlockTipStateRootHash).Forget(); } private static void UpdateEventDungeonSheetData(long blockIndex) @@ -204,7 +206,7 @@ private static void UpdateEventDungeonRemainingTimeText(long blockIndex) } private static async Task - UpdateEventDungeonInfoAsync(EventDungeonInfo previous) + UpdateEventDungeonInfoAsync(EventDungeonInfo previous, HashDigest stateRootHash) { if (_eventDungeonInfoUpdatedBlockIndex == _agent.BlockIndex) { @@ -220,7 +222,7 @@ private static async Task var addr = Nekoyume.Model.Event.EventDungeonInfo.DeriveAddress( _currentAvatarAddr.Value, EventDungeonRow.Id); - return await _agent.GetStateAsync(ReservedAddresses.LegacyAccount, addr) + return await _agent.GetStateAsync(stateRootHash, ReservedAddresses.LegacyAccount, addr) is Bencodex.Types.List serialized ? new EventDungeonInfo(serialized) : null; diff --git a/nekoyume/Assets/_Scripts/State/RxProps.cs b/nekoyume/Assets/_Scripts/State/RxProps.cs index f833a368e5..6f2fcad824 100644 --- a/nekoyume/Assets/_Scripts/State/RxProps.cs +++ b/nekoyume/Assets/_Scripts/State/RxProps.cs @@ -13,6 +13,8 @@ namespace Nekoyume.State { + using Libplanet.Common; + using System.Security.Cryptography; using UniRx; public static partial class RxProps @@ -69,14 +71,15 @@ public static void Stop() public static async UniTask SelectAvatarAsync( int avatarIndexToSelect, + HashDigest stateRootHash, bool forceNewSelection = false) { await States.Instance.SelectAvatarAsync( avatarIndexToSelect, forceNewSelection: forceNewSelection); await UniTask.WhenAll( - ArenaInfoTuple.UpdateAsync(), - EventDungeonInfo.UpdateAsync(), + ArenaInfoTuple.UpdateAsync(stateRootHash), + EventDungeonInfo.UpdateAsync(stateRootHash), WorldBossStates.Set(States.Instance.CurrentAvatarState.address), UniTask.RunOnThreadPool(States.Instance.InitAvatarBalancesAsync).ToObservable().ObserveOnMainThread().ToUniTask(), States.Instance.InitItemSlotStates()); diff --git a/nekoyume/Assets/_Scripts/UI/Widget/ArenaBoard.cs b/nekoyume/Assets/_Scripts/UI/Widget/ArenaBoard.cs index 17334cb9c3..b3cc818356 100644 --- a/nekoyume/Assets/_Scripts/UI/Widget/ArenaBoard.cs +++ b/nekoyume/Assets/_Scripts/UI/Widget/ArenaBoard.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Cysharp.Threading.Tasks; @@ -64,8 +64,9 @@ public async UniTaskVoid ShowAsync(bool ignoreShowAnimation = false) loading.Show(LoadingScreen.LoadingType.Arena); var sw = new Stopwatch(); sw.Start(); - await UniTask.WhenAll(RxProps.ArenaInformationOrderedWithScore.UpdateAsync(), - RxProps.ArenaInfoTuple.UpdateAsync()); + await UniTask.WhenAll( + RxProps.ArenaInformationOrderedWithScore.UpdateAsync(Game.Game.instance.Agent.BlockTipStateRootHash), + RxProps.ArenaInfoTuple.UpdateAsync(Game.Game.instance.Agent.BlockTipStateRootHash)); loading.Close(); Show(RxProps.ArenaInformationOrderedWithScore.Value, ignoreShowAnimation); diff --git a/nekoyume/Assets/_Scripts/UI/Widget/ArenaJoin.cs b/nekoyume/Assets/_Scripts/UI/Widget/ArenaJoin.cs index ae6c460e18..817e9f785c 100644 --- a/nekoyume/Assets/_Scripts/UI/Widget/ArenaJoin.cs +++ b/nekoyume/Assets/_Scripts/UI/Widget/ArenaJoin.cs @@ -107,8 +107,9 @@ public async UniTaskVoid ShowAsync( sw.Start(); var loading = Find(); loading.Show(LoadingScreen.LoadingType.Arena); - await UniTask.WhenAll(RxProps.ArenaInfoTuple.UpdateAsync(), - RxProps.ArenaInformationOrderedWithScore.UpdateAsync()); + await UniTask.WhenAll( + RxProps.ArenaInfoTuple.UpdateAsync(Game.Game.instance.Agent.BlockTipStateRootHash), + RxProps.ArenaInformationOrderedWithScore.UpdateAsync(Game.Game.instance.Agent.BlockTipStateRootHash)); loading.Close(); sw.Stop(); NcDebug.Log($"[Arena] Loading Complete. {sw.Elapsed}"); diff --git a/nekoyume/Assets/_Scripts/UI/Widget/LoginDetail.cs b/nekoyume/Assets/_Scripts/UI/Widget/LoginDetail.cs index 69f78a0f76..b413c461cb 100644 --- a/nekoyume/Assets/_Scripts/UI/Widget/LoginDetail.cs +++ b/nekoyume/Assets/_Scripts/UI/Widget/LoginDetail.cs @@ -137,7 +137,7 @@ public async void LoginClick() var loadingScreen = Find(); loadingScreen.Show( LoadingScreen.LoadingType.Entering, L10nManager.Localize("UI_IN_MINING_A_BLOCK")); - await RxProps.SelectAvatarAsync(_selectedIndex); + await RxProps.SelectAvatarAsync(_selectedIndex, Game.Game.instance.Agent.BlockTipStateRootHash); loadingScreen.Close(); OnDidAvatarStateLoaded(States.Instance.CurrentAvatarState); } diff --git a/nekoyume/Assets/_Scripts/UI/Widget/Screen/RewardScreen.cs b/nekoyume/Assets/_Scripts/UI/Widget/Screen/RewardScreen.cs index 9ccb4be386..c24ce34bb2 100644 --- a/nekoyume/Assets/_Scripts/UI/Widget/Screen/RewardScreen.cs +++ b/nekoyume/Assets/_Scripts/UI/Widget/Screen/RewardScreen.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using Nekoyume.Game.Controller; using UnityEngine; diff --git a/nekoyume/Assets/_Scripts/UI/Widget/Synopsis.cs b/nekoyume/Assets/_Scripts/UI/Widget/Synopsis.cs index 27db300f18..4f9558a228 100644 --- a/nekoyume/Assets/_Scripts/UI/Widget/Synopsis.cs +++ b/nekoyume/Assets/_Scripts/UI/Widget/Synopsis.cs @@ -372,7 +372,7 @@ private async Task End() loadingScreen.Show( LoadingScreen.LoadingType.Entering, L10nManager.Localize("UI_LOADING_BOOTSTRAP_START")); - await RxProps.SelectAvatarAsync(slotIndex); + await RxProps.SelectAvatarAsync(slotIndex, Game.Game.instance.Agent.BlockTipStateRootHash); loadingScreen.Close(); Game.Event.OnRoomEnter.Invoke(false); Game.Event.OnUpdateAddresses.Invoke();