前書き
今日は、株式会社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を作ることができるのでは?と思い今回の記事の作成に至りました。
ちなみにこれは以下の記事をさらに修正したバージョンになります。
TransferTransactionのスキーマを読み解こうその1 — Qiitaこれから各種トランザクションのスキーマを読み解いていきます。 このスキーマを解析することでSDKを作る上で非常に重要になるのかな?とは思いつつ、(間違っていてもいいんじゃ、失敗したらやり直せばええんじゃ) 進めていきます。… qiita.com
TransferTransactionのスキーマを読み解こうその2 — QiitaSend mosaics and messages between two accounts. qiita.com
TransferTransactionのスキーマを読み解こうその3 — Qiita該当ソースコードはこちら これがトランザクションのスキーマの全体像。 ここの@で始まるところは「属性」(アトリビュート)と言いまして、 トランザクションのスキーマに対して属性を付与することができます。… qiita.com
TransferTransactionのスキーマを読み解こうその4 — Qiitaここの部分を読み解く上で重要なキーワードが3つあります ・シリアライズ 複数の要素を一列に並べる操作や処理のこと。単にシリアライズといった場合には、 プログラムの実行状態や複雑なデータ構造などを… qiita.com
TransferTransactionのスキーマを読み解こうその5 — Qiitaスキーマは以下のようになっています。 recipient_address = UnresolvedAddress つまり recipient_address = binary_fixed(24): 24バイト message_size =… qiita.com
今回の対象コード
symbol/transfer.cats at dev · symbol/symbolThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below… github.com
# 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を参照ください。
データ構造アライメント - Wikipediaデータ構造アライメント(データこうぞうアライメント、 英語: data structure alignment)は、コンピュータのメモリ( 主記憶装置 )内のデータにアクセス(読み書き)する際に、メモリ上の位置の調整を行うことである。… ja.wikipedia.org
Transaction
さてデータ構造も分かったことですし、Transactionのコードを見ていきましょう。
symbol/transaction.cats at dev · symbol/symbolThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below… github.com
この箇所がトランザクションについてのコードです。
# 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(全体のサイズがどのくらいか?)
symbol/entity.cats at dev · symbol/symbolThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below… github.com
VerifiableEntity(署名内容)
symbol/entity.cats at dev · symbol/symbolThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below… github.com
EntityBody(署名者の公開鍵とversionとnetwork)
symbol/entity.cats at dev · symbol/symbolThis file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below… github.com
SizePrefixedEntityは4バイトあります。(uint32と言う型が定義されていると言うことはここには32bitのバイナリが入ると言うわけです。
さて次のパラメータに行きたいですが、8バイト区切りになっていません。そこで、次のVerifiableEntityの前に4バイト空白を作れば次のSignatureは8バイト区切りのポインタになると言うわけです。
イメージ図
次にEntityBodyには
signer_public_keyがPublicKeyとして32バイト穴埋め用のentity_body_reserved_1が4バイトバージョンが1バイトネットワークが1バイトとなります。つまり現状はこんな感じ
このようになるとオイオイ空白があるじゃねーかとなりますが、まぁ落ち着いてください。
あくまでもこれは3つのEntityを図式化しただけであって、まだTransactionのスキーマ定義は終了していねぇ(俺のバトルフェイズ)
type = TransactionType
fee = Amount
deadline = Timestamp
Transactionのスキーマにあったこの3つのパラメータこちらを調べていきます。
まずtypeとなっているところはTransactionの種類が入ります。Transactionはuint16で構成されるので2バイト
はい、きれいになりました。
次にfeeは手数料です。手数料はAmountと言う型になります。Amountはuint64なので8バイト
deadline(トランザクション承認までの締切)はuint64なので8バイトになります。
さてこれでTransactionのスキーマ定義ができました。次は TransferTransactionBodyのところにいきましょう。
TransferTransactionBody
symbol/transfer.cats at dev · symbol/symbolYou can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or… github.com
ここに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バイトのデータと言うことです)
図式にするとこんな感じ
次に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バイト分になります。
あとはモザイクの配列とメッセージの配列になります。
モザイクの配列はモザイクID(送信したいモザイクの種類)とAmount(送信するモザイクの数量)で構成されています。
これはそれぞれuint64で定義されているので、8バイトずつ定義されます。
これが送信するモザイクの種類分だけ付与されていきます。
この写真はあくまでも1種類のモザイクを送信するときの話です。
そして最後にメッセージとなります。
このメッセージは1文字1文字をバイナリ化して配列にします。Rustで言うところの文字列スライスと言う表現と同じです。
ここは1023文字分入れることができますが、配列の有効部分がメッセージとして表示されます。つまりこのメッセージは可変長の配列なのです。
あえて可変長を強調したメッセージにしました。
さてまとめますとこんな感じです。
catbuffer schemaTransferTransaction ・Symbolブロックチェーンのペイロードではわかりやすくするために8バイト区切りで表現されます。 (これが1バイト8ビットと似ているので、最初は非常に混乱しました。)… docs.google.com
このようにエクセルやスプレッドシートのような表計算ソフトを使って解析を試みると非常にわかりやすくなります。是非お試しください。🙇♂️