11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

mijinを使った簡単なユーザーアクティビティログの実装例

Posted at

mijinとは?

mijinはテックビューロが開発したプライベートブロックチェーン製品です。
その中でmijin Catapult(v2)の説明に限定して説明したいと思います。
パブリックブロックチェーンであるSymbol とは同じCatapultコアエンジンであり、Symbolと同等の機能があります。
他のブロックチェーン製品との大きな違いとしては、スマートコントラクトと呼ばれる機能がないですが、その代わりにスマートコントラクトに匹敵する複雑なトランザクション群を簡単に作れるアグリゲートトランザクション機能がコアレベルで提供されているのが特徴です。

アグリゲートトランザクション機能を使うと、例えばAさんとBさんがそれぞれ持つ二つのトークンを交換したい場合、片方ずつ送るのではなく、当人同士で一度で実現することができます。
図の例ですと、Aさんがトークンをやり取りするアグリゲートトランザクションを作成、署名し、Bさんがこのアグリゲートトランザクションに署名することでトランザクション成立し、交換処理が行われます。

image.png

その他機能については、公式サイトで確認することができますが、詳しく知りたい方がおられましたら、お気軽にお問合せください。

技術的な資料に興味がありましたら、以下のデプロイメントガイドを参照頂けたらと思います。

プライベートブロックチェーンを使うことのメリット

そもそもブロックチェーンを利用すると言っても、データベース製品が複数あるようにブロックチェーンにも多数の製品があり、アクセス方法が違います。
弊社製品のmijinというブロックチェーンは、 RESTful API でアクセスできるようになっており、Web開発に慣れているエンジニアであれば容易に開発することができると思います。

なぜパブリックブロックチェーンではなく、プライベートブロックチェーンを利用する必要があるの?と思われることも多いですが、ここではプライベートブロックチェーンならではの大きなメリットを3つ説明します。

  • 手数料
  • トランザクション量の制御
  • 柔軟なカスタム設定

手数料

通常、ブロックチェーンの世界では、トランザクション発行(DBでいうレコードインサート)にトランザクション手数料という支払いが必須であり、増減する手数料を計算してトランザクションを発行する必要があります。
プライベートチェーンでは手数料0に設定することもできますし、手数料ありで構築した場合でも発行に使用する手数料通貨を全額管理でき、手数料の増減に悩まされる必要もありません。
そのため、とくにIoTやWebサービスのトレーサビリティログやアクティビティログなどをブロックチェーンで管理したい場合、この手数料がネックになることが多く、ブロックチェーンの不変性を利用してログだけを保存したいといったケースにおいては、パブリックブロックチェーンを使うことより費用面や管理コストが効果的です。

トランザクション量の制御

パブリックチェーンは誰でも手数料を支払えばトランザクション発行できますが、プライベートチェーンでは手数料に使う通貨を持っているのはプライベートチェーン管理者のみですので不要なトランザクションはなく、不要なデータが突然増加しストレージが圧迫することもありません。

柔軟なカスタム設定

パブリックブロックチェーンでは複数のユーザーが立ち上げることからある程度安定したノードにするために、比較的上限が抑えた設定となっています。
プライベートブロックチェーンはチェーン管理者が全サーバのスペックを選定することができるため、上限を緩和した設定にすることができます。
例えばAWS Marketplaceで提供しているmijinでは、Symbolの標準設定からいくつか任意で変更が可能になっています。

  • 手数料あり/なし
  • ブロック生成間隔(10秒から60秒)
  • ネットワーク(mijin mijin_test)
  • キャッシュサイズ
  • ファイナライズタイプ(決定的、確率的)

どのようにmijinをサービスに組み込むかを検討する

長々とmijinのご紹介させて頂きましたが、ここからが今回の本題です。
今回、弊社にはCOMSAというNFTサービスがあるため、このサービスを使って実際にmijinにデータを保存する仕組みを検討していきます。

検討項目 どうするか なぜ?
どのようなデータを保存するか ユーザーアクティビティログ(ニックネーム変更) ユーザー履歴をブロックチェーン上に保存することで不変性を実現し、履歴から追うことを可能にする
mijinは公開するか YES アクティビティログを公開することで誰でもそのユーザーを検証できる。その代わりセキュリティ強化のため手数料ありモードを有効にする。
mijinをどこに配置するか サービス外に置く 手数料ありで公開設定するため、とくにサービス内で管理する必要がないため
サービスからどのようにmijinにアクセスするか sdkを使ってインタネット経由でアクセス サービスとmijinは切り離して考えることで分離できる
mijinのユーザーアカウント管理など サービス内部のIDから生成 ブロックチェーン特有の署名をユーザーにさせるわけでなく、あくまで識別としてアカウントを作成しログを保存すればいい

mijinの立ち上げと既存システムとの構成検討

mijinの立ち上げ方法は、現在二つのパターンを用意しています。

  • AWS Marketplaceを使用した立ち上げ
  • テックビューロ社による代行構築

今回は、AWS Marketplaceを使ってmijinを立ち上げ、サービス利用する流れを説明したいと思います。
AWS Marketplaceを使ったmijin構築方法については、以下に記載していますのでここでは立ち上げ方法は割愛します。
https://aws.mijin.io/deployment/ja/deploy/aws/index.html

AWS Marketplaceを使ったmijinは、構成をカスタムできるため、以下のような図の構成としました。
あくまで既存サービス内に配置ではなく、別の領域に置くことで「組み込む」というイメージより外部APIを導入したというイメージが強いと思います。

image.png

mijinへのログ保存方法

mijinの転送トランザクションには、メッセージと呼ばれる領域があります。
このメッセージ領域は1024byteまで書き込みができるため、ここにログ内容を記載します。

今回例ですので、ニックネーム変更ログを残すために、以下のような内容を誰でも参照できるような形でメッセージに入れます。
メッセージは暗号化して入れることもでき、特定のアカウントのみしか参照できないようにすることもできます。

{"activity": "change_nickname", "old":"old nickname","new":"new nickname", "timestamp": {UTC format time}}

まずは簡単に書き込みの実装をする

ここでは試しにトランザクション発行をしてみます。
今現状、簡単に実装できるのは、mijin対応するためにforkしたjavascript/typescript版のsymbol-sdkを使用します。

symbol公式のsymbol-sdkでは、mijinが対応してないことに注意してください。

nodejs(14又は16)を使用してsdkをインストールし、まずは簡単に実装例を示します。

sdkをインストールします。

yarn add @tech-bureau/symbol-sdk
 # または
 npm i @tech-bureau/symbol-sdk
import {
  Account,
  Address,
  AggregateTransaction,
  Deadline,
  EmptyMessage,
  NetworkType,
  PlainMessage,
  RepositoryFactoryHttp,
  TransferTransaction,
} from '@tech-bureau/symbol-sdk'
import { firstValueFrom } from 'rxjs'

type ActivityMessage = {
  activity: 'change_nickname'
  old: string
  new: string
  timestamp: string
}

const main = async () => {
  // mijinに記録する履歴メッセージ
  const activity = 'change_nickname'
  const oldNickName = 'nem'
  const newNickName = 'symbol'
  const timestamp = '2021-03-16 00:06:25'
  const activityMessage: ActivityMessage = {
    activity,
    old: oldNickName,
    new: newNickName,
    timestamp,
  }

  // ----------------------------------------------------------------------------------
  // mijin設定
  // mijinのrest endpointを指定
  const nodeUrl = 'http://localhost:3000'
  // mijinのネットワークを指定
  //  NetworkType.MIJIN mijin本番
  // NetworkType.MIJIN_TEST mijinテスト
  const networkType = NetworkType.MIJIN
  // mijinのブロック1のネットワークフィンガープリント
  const generationHash = '8F2A1E5AF45AA9DAE26F180660472D6C3F12C07888C5BA19B27FC0CFC21636AD'
  // mijinの稼働日(固定値)
  const epochAdjustment = 1560294000
  // 手数料率(なしなら0、ありなら100 固定値)
  const minFeeMultiplier = 100

  // 手数料を負担する署名アカウントの秘密鍵を指定
  const signerAccountPrivateKey = ''
  const signerAccount = Account.createFromPrivateKey(signerAccountPrivateKey, networkType)

  // userアカウントの秘密鍵を指定
  const userPrivateKey = ''
  const userAccount = Account.createFromPrivateKey(userPrivateKey, networkType)
  console.log(`signer address: ${signerAccount.address.plain()}`)
  console.log(`user address: ${userAccount.address.plain()}`)


  // log記録用アドレスを指定
  const logThreadAddress = ''

  // ----------------------------------------------------------------------------------

  const repository = new RepositoryFactoryHttp(nodeUrl, { networkType, generationHash, epochAdjustment })

  // 数量0のダミートランザクションを作成
  const createDummyTransferTransaction = async (toAddress: string, message: string) => {
    const deadline = Deadline.create(epochAdjustment, 6)
    const sendRawAddress = Address.createFromRawAddress(toAddress)
    const convertedMessage = message ? PlainMessage.create(message) : EmptyMessage
    const currency = await firstValueFrom(repository.getCurrencies())
    const transferTx = TransferTransaction.create(
      deadline,
      sendRawAddress,
      [currency.currency.createRelative(0)],
      convertedMessage,
      networkType
    ).setMaxFee(minFeeMultiplier)
    return transferTx as TransferTransaction
  }

  // アグリゲートの手数料を負担するためのダミートランザクションを作成
  const initDummyTransferTransaction = await createDummyTransferTransaction(signerAccount.address.plain(), '')
  const logDummyTransferTransaction = await createDummyTransferTransaction(
    logThreadAddress,
    JSON.stringify(activityMessage)
  )
  const aggregateCompleteTransaction = AggregateTransaction.createComplete(
    Deadline.create(epochAdjustment, 6),
    [
      initDummyTransferTransaction.toAggregate(signerAccount.publicAccount),
      logDummyTransferTransaction.toAggregate(userAccount.publicAccount),
    ],
    networkType,
    []
  ).setMaxFeeForAggregate(minFeeMultiplier, 1)

  // signerとuserが署名
  const signedTransaction = signerAccount.signTransactionWithCosignatories(
    aggregateCompleteTransaction,
    [userAccount],
    generationHash
  )

  // トランザクションアナウンス
  const transactionHttp = repository.createTransactionRepository()
  await firstValueFrom(transactionHttp.announce(signedTransaction))
  console.log(`announce transaction hash: ${signedTransaction.hash}`)
}

;(async () => {
  await main().catch((err) => console.error(err))
})()

実際の構成

mijinへの繋ぎ方を確認できたことで実際のサービスへの導入を進めていきます。
COMSAのバックエンドはサーバレス構成を基本としており、AWSのいくつかのサービスを利用してmijinのエンドポイントに対してトランザクション実行を行うことにしました。
本来、SQSからLambdaに渡してそのままmijinへトランザクションを投げることもできますが、COMSAでは全てAWS Stepfunctionsを使ったワークフローを使用しています。

理由は、

  • トランザクションがなかなか承認されない可能性があった場合再送するケースがある(※1)
  • トランザクションの承認チェックし、ワークフロー内で正常性確認を行える
  • 再送する場合、途中のワークフローから再開できる
  • 費用対効果が高い(EC2やECSを起動するよりはるかに安い)

といったブロックチェーンならではの問題をワークフローを用いることで対応しています。

※1 mijinではトランザクション承認順序がトランザクション受付順になっており、他にユーザーがいないため中々起きない問題ですが、通常パブリックチェーンは手数料を多く払うトランザクションを優先するため中々承認されないといったケースがあります。

image.png

導入工数

参考までに、ユーザーアクティビティログをCOMSAへ導入するまでかかった工数を示します。
mijinへの接続と、AWS使用サービス(SQS,lambda,StepFunctions)には慣れている前提の工数になります。

フェーズ 内容 工数
仕様策定 どのようにmijinをサービスに組み込むかを検討する 3時間
インフラ策定 mijinの立ち上げと既存システムとの構成検討 30分
実装 mijinへのログ保存方法 2時間
実装 AWS Stepfunctionsを使ったワークフロー導入 2時間
実装 COMSA上で変更履歴 表示画面作成(デザイン含む) 8時間
テスト 開発環境での確認 8時間
本番 リリース 30分

合計 24時間 約3人日

mijinへの接続に慣れていなかったとしても、 Webエンジニアであれば RESTful API へのアクセスによるトランザクション発行方法は、1日もあればすんなり習得できると思います。

AWSサービスに関しては、全く慣れていない場合はそれなりの習得工数かかるかもしれませんが、おそらく数日で取得可能です。

アクティビティログ一覧画面

ユーザーアカウントのニックネームリンク にて、アクティビティログ一覧を表示する様にしています。

image.png

ちょっとだけ独り言

今回一番時間がかかったのは、[COMSA上で変更履歴 表示画面作成(デザイン含む)] でした。

デザイン調整も、ビジネスロジックも書けるフロントエンジニア、絶賛募集中。

実現できたこと

  • ユーザーアクティビティログをブロックチェーン上に保存することで不変性を実現し、履歴から追うことを可能にする
  • ユーザーアクティビティログを公開することで、誰でもそのユーザーを検証できる

最後に

既存サービスのユーザーアクティビティログを、プライベートブロックチェーン mijin へ簡単に保存する手法の解説を行いました。
より詳しい情報は以下mijin公式サイトをご覧下さい

mijin 公式サイト
https://mijin.io/

テックビューロではNFTマーケットプレイスの運営も行っております。

オールオンチェーン NFTマーケットプレイス COMSA
https://comsa.io/

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?