Code Monkey home page Code Monkey logo

ninechronicles.dataprovider's Introduction

NineChronicles DataProvider

  • NineChronicles.DataProvider is an off-chain service that stores NineChronicles game action data to a database mainly for game analysis.
  • Currently, this service only supports MySQL database.

Table of Contents

Pre-requisite

Run

  • Before running the program, please refer to the option values in the latest official 9c-launcher-config.json and fill out the variables in appsettings.json.
  • In appsettings.json, AppProtocolVersionToken, StorePath, PeerStrings, MySqlConnectionString properties MUST be filled to run the program.
  • To setup the NineChronicles mainnet blockchain store to use in the StorePath, download and extract the latest mainnet snapshot to a desired location.
$ dotnet run --project ./NineChronicles.DataProvider.Executable/ -- 

Development Guide

  • This section lays out the steps in how to log a new action in the database.
  • The TransferAsset action is used as an example in this guide.

1. Setup Database

dotnet ef database update -- [Connection String]
- Connection String example: "server=localhost;database=data_provider;port=3306;uid=root;pwd=root;"

2. Create Model

namespace NineChronicles.DataProvider.Store.Models
{
    using System;
    using System.ComponentModel.DataAnnotations;

    public class TransferAssetModel
    {
        [Key]
        public string? TxId { get; set; }

        public long BlockIndex { get; set; }

        public string? Sender { get; set; }

        public string? Recipient { get; set; }

        public decimal Amount { get; set; }

        public DateOnly Date { get; set; }

        public DateTimeOffset TimeStamp { get; set; }
    }
}

// Table for storing TransferAsset actions
public DbSet<TransferAssetModel> TransferAssets => Set<TransferAssetModel>();

2. Create Store Method

public void StoreTransferAsset(TransferAssetModel model)
{
    using NineChroniclesContext ctx = _dbContextFactory.CreateDbContext();
    TransferAssetModel? prevModel =
        ctx.TransferAssets.FirstOrDefault(r => r.TxId == model.TxId);
    if (prevModel is null)
    {
        ctx.TransferAssets.Add(model);
    }
    else
    {
        prevModel.BlockIndex = model.BlockIndex;
        prevModel.Sender = model.Sender;
        prevModel.Recipient = model.Recipient;
        prevModel.Amount = model..Amount;
        prevModel.Date = model.Date;
        prevModel.TimeStamp = model.TimeStamp;
        ctx.TransferAssets.Update(prevModel);
    }

    ctx.SaveChanges();
}

3. (Optional) Add Data Getter

In some cases, you need to handle state to get data to make model.
To do this easily, you can make your own data getter inside NineChronicles.DataProvider/DataRendering/.

4. Render & Store Action Data

_actionRenderer.EveryRender<TransferAsset>()
    .Subscribe(ev =>
    {
        try
        {
            if (ev.Exception is null && ev.Action is { } transferAsset)
            {
                var model = new TransferAssetModel()
                {
                    TxId = transferAsset.TxId,
                    BlockIndex = transferAsset.BlockIndex,
                    Sender = transferAsset.Sender,
                    Recipient = transferAsset.Recipient,
                    Amount = Convert.ToDecimal(transferAsset.Amount.GetQuantityString()),
                    Date = DateOnly.FromDateTime(_blockTimeOffset.DateTime),
                    TimeStamp = _blockTimeOffset,
                };
                MySqlStore.StoreTransferAsset(model);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

5. Add Database Migration

dotnet ef migrations add AddTransferAsset -- [Connection String]
- Connection String example: "server=localhost;database=data_provider;port=3306;uid=root;pwd=root;"

Current Table Descriptions

Migrating Past Chain Data to MySQL Database

Migrate All Action Data

  • This command migrates all action data based on DataRendering to the designated MySQL database.
  • Options such as offset and limit are provided to specify which block data to migrate.
  • IMPORTANT) This migration tool requires you to have the necessary state data in the blocks you want to migrate (If your chain store lacks the state data, this tool will not work).
Usage: NineChronicles.DataProvider.Executable mysql-migration [--store-path <String>] [--mysql-server <String>] [--mysql-port <UInt32>] [--mysql-username <String>] [--mysql-password <String>] [--mysql-database <String>] [--offset <I
nt32>] [--limit <Int32>] [--help]

Migrate action data in rocksdb store to mysql db.

Options:
  -o, --store-path <String>    Rocksdb path to migrate. (Required)
  --mysql-server <String>      A hostname of MySQL server. (Required)
  --mysql-port <UInt32>        A port of MySQL server. (Required)
  --mysql-username <String>    The name of MySQL user. (Required)
  --mysql-password <String>    The password of MySQL user. (Required)
  --mysql-database <String>    The name of MySQL database to use. (Required)
  --offset <Int32>             offset of block index (no entry will migrate from the genesis block).
  --limit <Int32>              limit of block count (no entry will migrate to the chain tip).
  -h, --help                   Show help message

Migrate Battle Arena Ranking Data

  • This command calculates the battle arena ranking of all participants at a specific block index (migration-block-index) and inserts the data to a designated mysql database.
Usage: NineChronicles.DataProvider.Executable battle-arena-ranking-migration [--store-path <String>] [--mysql-server <String>] [--mysql-port <UInt32>] [--mysql-username <String>] [--mysql-password <String>] [--mysql-database <Stri
ng>] [--migration-block-index <Int64>] [--help]

Migrate battle arena ranking data at a specific block index to a mysql database.

Options:
  -o, --store-path <String>          RocksDB store path to migrate. (Required)
  --mysql-server <String>            Hostname of MySQL server. (Required)
  --mysql-port <UInt32>              Port of MySQL server. (Required)
  --mysql-username <String>          Name of MySQL user. (Required)
  --mysql-password <String>          Password of MySQL user. (Required)
  --mysql-database <String>          Name of MySQL database. (Required)
  --migration-block-index <Int64>    Block index to migrate.
  -h, --help                         Show help message

Migrate User Staking Data

  • This command calculates the NCG staking amount of all participants at a specific block index (migration-block-index) and inserts the data to a designated mysql database.
  • Currently, the slack-token and slack-channel options for sending the data in csv format are required, however, these will be changed to optional in the near future.
Usage: NineChronicles.DataProvider.Executable user-staking-migration [--store-path <String>] [--mysql-server <String>] [--mysql-port <UInt32>] [--mysql-username <String>] [--mysql-password <String>] [--mysql-database <String>] [--
slack-token <String>] [--slack-channel <String>] [--migration-block-index <Int64>] [--help]

Migrate staking amounts of users at a specific block index to a mysql database.

Options:
  -o, --store-path <String>          Rocksdb store path to migrate. (Required)
  --mysql-server <String>            Hostname of MySQL server. (Required)
  --mysql-port <UInt32>              Port of MySQL server. (Required)
  --mysql-username <String>          Name of MySQL user. (Required)
  --mysql-password <String>          Password of MySQL user. (Required)
  --mysql-database <String>          Name of MySQL database to use. (Required)
  --slack-token <String>             slack token to send the migration data. (Required)
  --slack-channel <String>           slack channel that receives the migration data. (Required)
  --migration-block-index <Int64>    Block index to migrate.
  -h, --help                         Show help message

ninechronicles.dataprovider's People

Contributors

akamig avatar area363 avatar atralupus avatar boscohyun avatar dahlia avatar greymistcube avatar hskim881028 avatar ipdae avatar limebell avatar longfin avatar moreal avatar onedgelee avatar planet-submodule-updater avatar riemannulus avatar sky1045 avatar sonohoshi avatar tkiapril avatar tyrosine1153 avatar u-lis avatar unengine avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ninechronicles.dataprovider's Issues

Change block rendering default value to true

Currently, users need to pass in NC_Render value as true to insert block data into their db. This can be rather confusing so changing this value to true can help minimize the confusion when new users set up the service.

Add Rune System Data

Deadline: 12/14/2022

  • A. Snapshot table with Rune Level up status by Avatar Address and Agent Address (e.g. UserRunes)

  • B.Log table with Rune Level up attempt and result by Avatar Address and Agent Address (e.g. RuneEnhancements)

  • C. Log table with Runes acquired by Avatar Address and Agent Address (e.g. RunesAcquired)

Double check about stat query

https://planetariumhq.slack.com/archives/C03P5080KJL/p1660285618583749

# 일별 DAU 및 스테이지 플레이 액션 카운트 조회 쿼리 (Play&Sweep)
select DATE_FORMAT(`Timestamp`, '%Y-%m-%d'), COUNT(DISTINCT AgentAddress), count(*) from 
(select AgentAddress, `Timestamp` FROM HackAndSlashes WHERE `Timestamp` > '2022-05-14 00:00:00'
union
select AgentAddress, `Timestamp`FROM HackAndSlashSweeps WHERE `Timestamp` > '2022-05-14 00:00:00')
as a GROUP by DATE_FORMAT(`Timestamp`, '%Y-%m-%d')

# 일별 아레나 수행 계정 및 횟수 조회 쿼리
select DATE_FORMAT(`Timestamp`, '%Y-%m-%d'), COUNT(DISTINCT AgentAddress), count(*) from BattleArenas where `Timestamp` > '2022-05-14 00:00:00' GROUP by DATE_FORMAT(`Timestamp`, '%Y-%m-%d')  

# 일별 강화 수행 계정 및 횟수 조회 쿼리
select DATE_FORMAT(`Timestamp`, '%Y-%m-%d'), COUNT(DISTINCT AgentAddress), count(*) from ItemEnhancements where `Timestamp` > '2022-05-14 00:00:00' GROUP by DATE_FORMAT(`Timestamp`, '%Y-%m-%d')

#일간 거래량/거래금액/거래인원 집계 쿼리 (품목 통합)
select DATE_FORMAT(`Timestamp`, '%Y-%m-%d') as date, COUNT(OrderId), sum(price), COUNT(DISTINCT SellerAvatarAddress, BuyerAvatarAddress)  from (
(select OrderId , SellerAvatarAddress , BuyerAvatarAddress , Price, Timestamp from ShopHistoryConsumables WHERE `Timestamp` > '2022-05-14 00:00:00')
Union all 
(select OrderId , SellerAvatarAddress , BuyerAvatarAddress , Price, Timestamp  from ShopHistoryCostumes WHERE `Timestamp` > '2022-05-14 00:00:00')
Union all 
(select OrderId , SellerAvatarAddress , BuyerAvatarAddress , Price, Timestamp  from ShopHistoryEquipments WHERE `Timestamp` > '2022-05-14 00:00:00')
Union all 
(select OrderId , SellerAvatarAddress , BuyerAvatarAddress , Price, Timestamp  from ShopHistoryMaterials WHERE `Timestamp` > '2022-05-14 00:00:00')
) as a group by DATE_FORMAT(`Timestamp`, '%Y-%m-%d') ORDER BY DATE

# 일별 아레나 수행 계정 및 횟수 조회 쿼리
select DATE_FORMAT(`Timestamp`, '%Y-%m-%d'), COUNT(DISTINCT AgentAddress), count(*) from CombinationEquipments where `Timestamp` > '2022-05-14 00:00:00' GROUP by DATE_FORMAT(`Timestamp`, '%Y-%m-%d')  

# 일별 강화 수행 계정 및 횟수 조회 쿼리
select DATE_FORMAT(`Timestamp`, '%Y-%m-%d'), COUNT(DISTINCT AgentAddress), count(*) from CombinationConsumables where `Timestamp` > '2022-05-14 00:00:00' GROUP by DATE_FORMAT(`Timestamp`, '%Y-%m-%d')

Update GetAvatarState api

Since the AvatarState is seperate in lib9c, the related API needs to be updated(GetAvatarStateV2).

Ranking number should be provided when using `StageRanking`.

// Query
query {
  stageRanking(limit: 100) {
    ranking
    avatarAddress
    clearedStageId
    name
  }
}

// Data
{
  "data": {
    "stageRanking": [
      {
        "ranking": 1,
        "avatarAddress": "...",
        "clearedStageId": 10000020,
        "name": "..."
      },
      {
        "ranking": 2,
        "avatarAddress": "...",
        "clearedStageId": 10000020,
        "name": "..."
      },
      {
        "ranking": 3,
        "avatarAddress": "...",
        "clearedStageId": 10000019,
        "name": "..."
      },
      {
        "ranking": 4,
        "avatarAddress": "...",
        "clearedStageId": 10000019,
        "name": "..."
      },
...
}

Desired query/data is like this.

Migrate missing block data to DB

Missing Block Range:

  • 5048300 ~ 5048900
  • 5050000 ~ 5051000

Data type:

  • block
  • tx
  • CombinationEquipment
  • CombinationConsumable
  • ItemEnhancement
  • HasSweep
  • Raider
  • Shop Histories

Subscribe to rendered actions during preloading stage

Currently, RenderSubscriber only has access to actions that occur after the preloading stage. This means that all other actions executed during the preloading stage would not be available to store in the database.

Getting ranking record with `AvatarAddress`.

query {
  stageRanking(avatarAddress: "...") {
    avatarAddress
    clearedStageId
    name
  }
}

Here is a desired format.

Currently, we should search all data to find certain user's record.

RDS Connection Failures

Unhandled exception. MySqlConnector.MySqlException (0x80004005): Host '18.191.112.51' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'
   at MySqlConnector.Core.ServerSession.ConnectAsync(ConnectionSettings cs, MySqlConnection connection, Int32 startTickCount, ILoadBalancer loadBalancer, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 441
   at MySqlConnector.Core.ConnectionPool.ConnectSessionAsync(MySqlConnection connection, String logMessage, Int32 startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ConnectionPool.cs:line 363
   at MySqlConnector.Core.ConnectionPool.GetSessionAsync(MySqlConnection connection, Int32 startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ConnectionPool.cs:line 94
   at MySqlConnector.Core.ConnectionPool.GetSessionAsync(MySqlConnection connection, Int32 startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ConnectionPool.cs:line 124
   at MySqlConnector.MySqlConnection.CreateSessionAsync(ConnectionPool pool, Int32 startTickCount, Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 915
   at MySqlConnector.MySqlConnection.OpenAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlConnection.cs:line 406
   at MySqlConnector.MySqlConnection.Open() in /_/src/MySqlConnector/MySqlConnection.cs:line 369
   at Microsoft.EntityFrameworkCore.ServerVersion.AutoDetect(String connectionString)
   at NineChronicles.DataProvider.Executable.Program.<>c__DisplayClass0_0.<Main>b__4(DbContextOptionsBuilder options) in /app/NineChronicles.DataProvider.Executable/Program.cs:line 107
   at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass12_0`2.<AddDbContextFactory>b__0(IServiceProvider p, DbContextOptionsBuilder b)
   at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.CreateDbContextOptions[TContext](IServiceProvider applicationServiceProvider, Action`2 optionsAction)
   at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass17_0`1.<AddCoreServices>b__0(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at NineChronicles.DataProvider.Executable.Program.Main() in /app/NineChronicles.DataProvider.Executable/Program.cs:line 125
   at NineChronicles.DataProvider.Executable.Program.<Main>()

It has been temporarily resolved by mysqladmin flush-hosts as guided, but we need to investigate root cause.

Slow action execution

History

8/23: 다른 RPC 노드 대비로 2배 이상 느린 상황
8/29: 추가 조사된 것 없음, 데이터 확인 후 이슈화

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.