From d1d7004777096f6df1c6175f355225008e0a9fc8 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Tue, 21 May 2024 13:18:59 +0900 Subject: [PATCH 1/2] Implement patch table sync in block poller --- Mimir.Worker/BlockPoller.cs | 130 ++++++++++++++++++++++++++++++----- Mimir.Worker/Queries.graphql | 8 +++ 2 files changed, 121 insertions(+), 17 deletions(-) diff --git a/Mimir.Worker/BlockPoller.cs b/Mimir.Worker/BlockPoller.cs index 54d669af..76b5268a 100644 --- a/Mimir.Worker/BlockPoller.cs +++ b/Mimir.Worker/BlockPoller.cs @@ -1,16 +1,25 @@ +using Bencodex; using Bencodex.Types; using HeadlessGQL; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Types.Tx; using Mimir.Worker.Models; using Mimir.Worker.Scrapper; using Mimir.Worker.Services; using Nekoyume; +using Nekoyume.Action; +using Nekoyume.Model.State; +using Nekoyume.TableData; using StrawberryShake; namespace Mimir.Worker; -public class BlockPoller(IStateService stateService, IHeadlessGQLClient headlessGqlClient, MongoDbStore mongoDbStore) +public class BlockPoller( + IStateService stateService, + IHeadlessGQLClient headlessGqlClient, + MongoDbStore mongoDbStore +) { public async Task RunAsync(CancellationToken cancellationToken) { @@ -28,6 +37,7 @@ public async Task RunAsync(CancellationToken cancellationToken) await EveryAvatarAsync(processBlockIndex, stateGetter, cancellationToken); await BattleArenaAsync(processBlockIndex, stateGetter, cancellationToken); + await EveryPatchTableAsync(processBlockIndex, cancellationToken); await mongoDbStore.UpdateLatestBlockIndex(processBlockIndex); } } @@ -35,11 +45,13 @@ public async Task RunAsync(CancellationToken cancellationToken) private async Task EveryAvatarAsync( long processBlockIndex, StateGetter stateGetter, - CancellationToken cancellationToken) + CancellationToken cancellationToken + ) { var operationResult = await headlessGqlClient.GetTransactionSigners.ExecuteAsync( processBlockIndex, - cancellationToken); + cancellationToken + ); if (operationResult.Data is null) { HandleErrors(operationResult); @@ -60,25 +72,104 @@ private async Task EveryAvatarAsync( } var agentAddress = new Address(tx.Signer); - var avatarAddresses = Enumerable.Range(0, GameConfig.SlotCount) + var avatarAddresses = Enumerable + .Range(0, GameConfig.SlotCount) .Select(e => Addresses.GetAvatarAddress(agentAddress, e)); - var avatarDataArray = await Task.WhenAll(avatarAddresses.Select(stateGetter.GetAvatarData)); + var avatarDataArray = await Task.WhenAll( + avatarAddresses.Select(stateGetter.GetAvatarData) + ); await mongoDbStore.BulkUpsertAvatarDataAsync( - avatarDataArray - .Where(e => e is not null) - .OfType() - .ToList()); + avatarDataArray.Where(e => e is not null).OfType().ToList() + ); + } + } + + private async Task EveryPatchTableAsync( + long processBlockIndex, + CancellationToken cancellationToken + ) + { + var rawPatchTableTxsResp = await headlessGqlClient.GetPatchTableTransactions.ExecuteAsync( + processBlockIndex, + cancellationToken + ); + if (rawPatchTableTxsResp.Data is null) + { + HandleErrors(rawPatchTableTxsResp); + return; + } + + var patchTableTxs = rawPatchTableTxsResp + .Data?.Transaction?.NcTransactions.Where(raw => raw is not null) + .Select(raw => + TxMarshaler.DeserializeTransactionWithoutVerification( + Convert.FromBase64String(raw!.SerializedPayload) + ) + ) + .ToList(); + foreach (var patchTableTx in patchTableTxs) + { + var patchTableAction = (Dictionary)patchTableTx.Actions[0]; + var patchTableActionValues = (Dictionary)patchTableAction["values"]; + var tableName = ((Text)patchTableActionValues["table_name"]).ToDotnetString(); + + var sheetType = typeof(ISheet) + .Assembly.GetTypes() + .Where(type => + type.Namespace is { } @namespace + && @namespace.StartsWith($"{nameof(Nekoyume)}.{nameof(Nekoyume.TableData)}") + && !type.IsAbstract + && typeof(ISheet).IsAssignableFrom(type) + && type.Name == tableName + ) + .FirstOrDefault(); + + if (sheetType == null) + { + throw new TypeLoadException( + $"Unable to find a class type matching the table name '{tableName}' in the specified namespace." + ); + } + + var sheetAddress = Addresses.TableSheet.Derive(tableName); + var sheetState = await stateService.GetState(sheetAddress); + if (sheetState is not Text sheetValue) + { + throw new InvalidOperationException( + $"Expected sheet state to be of type 'Text'." + ); + } + + var sheetInstance = Activator.CreateInstance(sheetType); + if (sheetInstance is not ISheet sheet) + { + throw new InvalidCastException($"Type {sheetType.Name} cannot be cast to ISheet."); + } + + sheet.Set(sheetValue.Value); + + var sheetData = new TableSheetData( + sheetAddress, + tableName, + sheet, + sheetState.ToDotnetString(), + ByteUtil.Hex(new Codec().Encode(sheetState)) + ); + + await mongoDbStore.InsertTableSheets(sheetData); } } private async Task BattleArenaAsync( long processBlockIndex, StateGetter stateGetter, - CancellationToken cancellationToken) + CancellationToken cancellationToken + ) { var rawArenaTxsResp = await headlessGqlClient.GetBattleArenaTransactions.ExecuteAsync( processBlockIndex, - cancellationToken); + cancellationToken + ); if (rawArenaTxsResp.Data is null) { HandleErrors(rawArenaTxsResp); @@ -90,10 +181,13 @@ private async Task BattleArenaAsync( return; } - var arenaTxs = rawArenaTxsResp.Data.Transaction.NcTransactions - .Where(raw => raw is not null) - .Select(raw => TxMarshaler.DeserializeTransactionWithoutVerification( - Convert.FromBase64String(raw!.SerializedPayload))) + var arenaTxs = rawArenaTxsResp + .Data.Transaction.NcTransactions.Where(raw => raw is not null) + .Select(raw => + TxMarshaler.DeserializeTransactionWithoutVerification( + Convert.FromBase64String(raw!.SerializedPayload) + ) + ) .ToList(); foreach (var arenaTx in arenaTxs) { @@ -112,12 +206,14 @@ await mongoDbStore.BulkUpsertAvatarDataAsync( new[] { myAvatarData, enemyAvatarData } .Where(e => e is not null) .OfType() - .ToList()); + .ToList() + ); await mongoDbStore.BulkUpsertArenaDataAsync( new[] { myArenaData, enemyArenaData } .Where(e => e is not null) .OfType() - .ToList()); + .ToList() + ); } } diff --git a/Mimir.Worker/Queries.graphql b/Mimir.Worker/Queries.graphql index bdbfb73e..a896d86f 100644 --- a/Mimir.Worker/Queries.graphql +++ b/Mimir.Worker/Queries.graphql @@ -18,6 +18,14 @@ query GetBattleArenaTransactions($blockIndex: Long!) { } } +query GetPatchTableTransactions($blockIndex: Long!) { + transaction { + ncTransactions(startingBlockIndex: $blockIndex, limit: 1, actionType: "^patch_table_sheet") { + serializedPayload + } + } +} + query GetTransactionSigners($blockIndex: Long!) { transaction { ncTransactions(startingBlockIndex: $blockIndex, limit: 1, actionType: "^[a-zA-Z0-9]*$") { From 60474c1c80a8d3d2648b56b454c8a5f24a2b1311 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Tue, 21 May 2024 13:39:01 +0900 Subject: [PATCH 2/2] Refactor get sheet type --- Mimir.Worker/BlockPoller.cs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/Mimir.Worker/BlockPoller.cs b/Mimir.Worker/BlockPoller.cs index 76b5268a..48e25ef1 100644 --- a/Mimir.Worker/BlockPoller.cs +++ b/Mimir.Worker/BlockPoller.cs @@ -98,6 +98,14 @@ CancellationToken cancellationToken HandleErrors(rawPatchTableTxsResp); return; } + var sheetTypes = typeof(ISheet) + .Assembly.GetTypes() + .Where(type => + type.Namespace is { } @namespace + && @namespace.StartsWith($"{nameof(Nekoyume)}.{nameof(Nekoyume.TableData)}") + && !type.IsAbstract + && typeof(ISheet).IsAssignableFrom(type) + ); var patchTableTxs = rawPatchTableTxsResp .Data?.Transaction?.NcTransactions.Where(raw => raw is not null) @@ -113,16 +121,7 @@ CancellationToken cancellationToken var patchTableActionValues = (Dictionary)patchTableAction["values"]; var tableName = ((Text)patchTableActionValues["table_name"]).ToDotnetString(); - var sheetType = typeof(ISheet) - .Assembly.GetTypes() - .Where(type => - type.Namespace is { } @namespace - && @namespace.StartsWith($"{nameof(Nekoyume)}.{nameof(Nekoyume.TableData)}") - && !type.IsAbstract - && typeof(ISheet).IsAssignableFrom(type) - && type.Name == tableName - ) - .FirstOrDefault(); + var sheetType = sheetTypes.Where(type => type.Name == tableName).FirstOrDefault(); if (sheetType == null) { @@ -130,21 +129,17 @@ type.Namespace is { } @namespace $"Unable to find a class type matching the table name '{tableName}' in the specified namespace." ); } - - var sheetAddress = Addresses.TableSheet.Derive(tableName); - var sheetState = await stateService.GetState(sheetAddress); - if (sheetState is not Text sheetValue) - { - throw new InvalidOperationException( - $"Expected sheet state to be of type 'Text'." - ); - } - var sheetInstance = Activator.CreateInstance(sheetType); if (sheetInstance is not ISheet sheet) { throw new InvalidCastException($"Type {sheetType.Name} cannot be cast to ISheet."); } + var sheetAddress = Addresses.TableSheet.Derive(tableName); + var sheetState = await stateService.GetState(sheetAddress); + if (sheetState is not Text sheetValue) + { + throw new InvalidOperationException($"Expected sheet state to be of type 'Text'."); + } sheet.Set(sheetValue.Value);