TransferTransactionのスキーマを学習しましょう!
💍

TransferTransactionのスキーマを学習しましょう!

タグ
Symbol
公開日
June 25, 2022

前書き

今日は、株式会社Opening Lineの松本です。

これまで使われていたTypescript版のSDK( https://github.com/symbol/symbol-sdk-typescript-javascript )(以下、旧SDK)が非推奨となり、今後は新しいJavascript版 (https://github.com/symbol/symbol/tree/dev/sdk/javascript )のSDK(以下、新SDK)を使用することが推奨されました。さて、この新SDKでは旧SDKに含まれていたREST APIの部分が省略され、トランザクションのペイロードの作成と署名の作成にフォーカスされたものになっております。トランザクションのペイロードはSymbolのスキーマ定義から作られることとなります。ということはですよ、Symbolのスキーマ定義が分かれば、色々な言語のSDKを作ることができるのでは?と思い今回の記事の作成に至りました。

ちなみにこれは以下の記事をさらに修正したバージョンになります。

今回の対象コード

# Send mosaics and messages between two accounts.
struct TransferTransaction
     TRANSACTION_VERSION = make_const(uint8, 1)
     TRANSACTION_TYPE = make_const(TransactionType, TRANSFER)

     inline Transaction
     inline TransferTransactionBody

このコードを解読していきます。

スキーマは基本的にこのパラメータが必要ですよと言う意味合いになって、パラメータなのかそうでないのか?と言う違いを見極めていく必要があります。

まず、最初にmake_constと定義されていると思います。

こちらはスキーマの値で使用する定数であって、そのスキーマの構造体自体に含まれず、他のパラメータに対して設定する値になります。

TRANSACTION_VERSION = make_const(uint8, 1)
TRANSACTION_TYPE = make_const(TransactionType, TRANSFER)

この二つはスキーマ定義には影響を与えますが、これ自体がスキーマのパラメータにならないと言うことです。

inline Transaction
inline TransferTransactionBody

次にこのTransferTransactionはTransactionとTransferTransactionBodyの二つのスキーマを合成することで完成します。

なので

TransactionとTransferTransactionBodyを把握すればOKとなるわけです。

と、その前に

Symbolのスキーマではポインタ(それぞれの変数の先頭のアドレスのこと)を見つけやすくするため、8バイトごとに整理しています。(ここ重要です、8バイトごとです。

公式のドキュメントとか見てもらえるとわかるのですが、

上の0, 1, 2, 3, 4, 5, 6, 7

となっているところがあるのがわかると思います。

基本的にこのような「区切り」をつけることによって「計算速度を高める」と言う目的があります。

詳細は以下のWikiを参照ください。

image

Transaction

さてデータ構造も分かったことですし、Transactionのコードを見ていきましょう。

この箇所がトランザクションについてのコードです。

# binary layout for a transaction
@size(size)
@initializes(version, TRANSACTION_VERSION)
@initializes(type,TRANSACTION_TYPE)
@discriminator(type)
@is_aligned
abstract struct Transaction
  inline SizePrefixedEntity
  inline VerifiableEntity
  inline EntityBody
# transaction type
# transaction fee
# transaction deadline
  type = TransactionType
  fee = Amount
  deadline = Timestamp

こんな感じになっています。@には属性値がいろいろ入ってきます。

例えば

・sizeはトランザクションのバイトサイズ・initializes(version, TRANSACTION_VERSION)にはトランザクションのバージョンが(今はSymbolのトランザクションのバージョンは1しかありません)・initializes(type, TRANSACTION_TYPE)にはトランザクションの種類が16進数で初期設定されます・discriminator(type)は先ほど設定したトランザクションの種類の16進数からどのタイプなのか判別します・is_alignedは先ほどのデータ構造アライメント(8バイト区切りにして、ポインタの場所を早期発見できるようにするもの)

これらが属性値となるわけです。

Transactionスキーマの中身

まずは最初の3つのエンティティから

SizePrefixedEntity(全体のサイズがどのくらいか?)

VerifiableEntity(署名内容)

EntityBody(署名者の公開鍵とversionとnetwork)

SizePrefixedEntityは4バイトあります。(uint32と言う型が定義されていると言うことはここには32bitのバイナリが入ると言うわけです。

さて次のパラメータに行きたいですが、8バイト区切りになっていません。そこで、次のVerifiableEntityの前に4バイト空白を作れば次のSignatureは8バイト区切りのポインタになると言うわけです。

イメージ図

image

次にEntityBodyには

signer_public_keyがPublicKeyとして32バイト穴埋め用のentity_body_reserved_1が4バイトバージョンが1バイトネットワークが1バイトとなります。つまり現状はこんな感じ

image

このようになるとオイオイ空白があるじゃねーかとなりますが、まぁ落ち着いてください。

あくまでもこれは3つのEntityを図式化しただけであって、まだTransactionのスキーマ定義は終了していねぇ(俺のバトルフェイズ)

type = TransactionType
fee = Amount
deadline = Timestamp

Transactionのスキーマにあったこの3つのパラメータこちらを調べていきます。

まずtypeとなっているところはTransactionの種類が入ります。Transactionはuint16で構成されるので2バイト

image

はい、きれいになりました。

次にfeeは手数料です。手数料はAmountと言う型になります。Amountはuint64なので8バイト

image

deadline(トランザクション承認までの締切)はuint64なので8バイトになります。

image

さてこれでTransactionのスキーマ定義ができました。次は TransferTransactionBodyのところにいきましょう。

TransferTransactionBody

ここにTransferTransactionBodyの内容があります。

# Shared content between TransferTransaction and EmbeddedTransferTransaction.inline struct TransferTransactionBody
  # recipient address
  recipient_address = UnresolvedAddress
  # size of attached message
  message_size = uint16
  # number of attached mosaics
  mosaics_count = uint8
  # reserved padding to align mosaics on 8-byte boundary
  transfer_transaction_body_reserved_1 = make_reserved(uint8, 0)
  # reserved padding to align mosaics on 8-byte boundary
  transfer_transaction_body_reserved_2 = make_reserved(uint32, 0)
  # attached mosaics
  @sort_key(mosaic_id)
  mosaics = array(UnresolvedMosaic, mosaics_count)
  # attached message
  message = array(uint8, message_size)

さて上から順に見ていきましょう。

recipient_addressは送信先(受け取りアドレス)になります。このUnresolvedAddressはbinary_fixed(24)と言う固定の長さのバイナリデータになります。つまりこれは24バイトのバイナリデータになります。(要するに24バイトのデータと言うことです)

図式にするとこんな感じ

image

次にmessage_sizeです。これは規定値で1023となっていますが、この数値はuint16つまり2バイトで表現されます。

次にmosaics_countですが、これは送信できるモザイクの個数(種類)を指定できます。uint8で1バイトになっています。ここで1バイトは256まで(0~255)表現できるので、一度のトランザクションで送信できるモザイクは256種類となるはずです。(今度検証します)

で次のパラメータに行く前に、空白を埋めます。

現在3バイト分確保していますので、あと5バイト分必要です。こうなるとuint40とか欲しいですが、そのような型情報はありません。uintで存在する数値は8, 16, 32, 64の4種類です。この4つをやりくりするしかありません。そして引き算はできないので足し算です。となると

transfer_transaction_body_reserved_1が1バイトtransfer_transaction_body_reserved_2が4バイト確保すれば5バイト分になります。

image

あとはモザイクの配列とメッセージの配列になります。

モザイクの配列はモザイクID(送信したいモザイクの種類)とAmount(送信するモザイクの数量)で構成されています。

これはそれぞれuint64で定義されているので、8バイトずつ定義されます。

これが送信するモザイクの種類分だけ付与されていきます。

image

この写真はあくまでも1種類のモザイクを送信するときの話です。

そして最後にメッセージとなります。

このメッセージは1文字1文字をバイナリ化して配列にします。Rustで言うところの文字列スライスと言う表現と同じです。

ここは1023文字分入れることができますが、配列の有効部分がメッセージとして表示されます。つまりこのメッセージは可変長の配列なのです。

image

あえて可変長を強調したメッセージにしました。

さてまとめますとこんな感じです。

このようにエクセルやスプレッドシートのような表計算ソフトを使って解析を試みると非常にわかりやすくなります。是非お試しください。🙇‍♂️