NEM2 SDK for Rustを作り始めました


はじめに

NEMのことをもっと知っていろいろアプリを作成してみたいなーと思い、何をすれば知ることができるだろうと考えました。私はソフトウェアエンジニアなので、ライブラリを書けば仕組みや処理の中身まである程度理解できるのではないかと考えました。

ということで、最近のお気に入り言語であるRustでNEM2 SDKを作り始めましたので、その紹介です。

nem2-rs: https://github.com/hhatto/nem2-rs

対象のバージョンはFushicho.2です。

nem2-sdknem2-cli を大いに参考にさせていただきました。先人に感謝

NEMに関してはまだそれほど詳しくないのでその点はご容赦ください

使ってみる

まずはRustのパッケージマネージャであるcargoを使って、プロジェクトを作ります。

$ cargo new nem2sample
$ cd nem2sample
$ tree
├── Cargo.toml
└── src
    └── main.rs

main.rs にmain関数があるので、そこに処理を書いていきます。

cargo buildを実行すると ./target/debug/nem2sampleという実行ファイルが作成されるので、それを実行すればサンプルが動きます。

以下、いくつかnem2-rsを使ったサンプルを紹介します。

ブロック1の情報を参照する

お馴染みの http://localhost:3000/block/1 へのアクセスは以下のような形で実現できます。

use nem2::block;

fn main() {
    let client = block::BlockHttp::new("http://localhost:3000");

    let resp = client.get_block_by_height(1);
    match resp {
        Ok(r) => println!("{:?}", r),
        Err(e) => println!("error: {:?}", e),
    }
}
Object({"block": Object({"beneficiaryPublicKey": String("6BA786ED45A9068079279BA3F492A76156C4C1FE3619D054661D93188E0D6D2C"), "difficulty": String("100000000000000"), "feeMultiplier": Number(0), "height": String("1"), "network": Number(144), "previousBlockHash": String("0000000000000000000000000000000000000000000000000000000000000000"), "receiptsHash": String("66EED86BED1396B33632B855C5A6F9AE102691077EF7342DAC17CBD540EEA0C3"), "signature": String("7B5C6311869B762C0419BEDDF06763826C74B428A2C8CB250056EB3192D173EE43FA4659AFDC139C17674BB6C3E532881F76A609BE8645F13A055ACA28E13C01"), "signerPublicKey": String("6BA786ED45A9068079279BA3F492A76156C4C1FE3619D054661D93188E0D6D2C"), "stateHash": String("81CE146945A4052F4DD789CDC723E5067348D13F520B66506EFEF969F1BBF1CE"), "timestamp": String("0"), "transactionsHash": String("A0ADE8A0994E8C3B382112C33460407901E8321B46CD1DC367464464D4CB505E"), "type": Number(32835), "version": Number(1)}), "meta": Object({"generationHash": String("44E547B1C422784027AAF1AC66AAC5068F07E4394E8FEF69682403D9D9D0C945"), "hash": String("D8520B17CD629FFF00C1843C82E88BBD6FE71E184FABFDD27ECB43894CC0484D"), "numTransactions": Number(29), "stateHashSubCacheMerkleRoots": Array([]), "totalFee": String("0")})})

ただのJSONシリアライズされた値ですが、情報が取得できていることがわかります。

アカウント情報の参照

use nem2::account;

fn main() {
    let client = account::AccountHttp::new("http://localhost:3000");

    // プライベートキーかアドレスをキーにアカウント情報を返す
    let resp = client.get_account_info("SB2OK4V5ZYV5YZJHIISBMDGSLTRLKRBSSGMTYLMI");

    match resp {
        Ok(r) => println!("{:?}", r),
        Err(e) => println!("error: {:?}", e),
    }
}
Object({"account": Object({"accountType": Number(0), "activityBuckets": Array([Object({"beneficiaryCount": Number(0), "rawScore": String("3562500"), "startHeight": String("1433"), "totalFeesPaid": String("0")}), Object({"beneficiaryCount": Number(0), "rawScore": String("3750000"), "startHeight": String("1"), "totalFeesPaid": String("0")})]), "address": String("905B116E74CE8D84664ACEF5C673D5B36FFF09B06D6A00C2A9"), "addressHeight": String("1"), "importance": String("3562500"), "importanceHeight": String("1433"), "linkedAccountKey": String("0000000000000000000000000000000000000000000000000000000000000000"), "mosaics": Array([Object({"amount": String("449929999900000"), "id": String("0BAD69462B1E1544")}), Object({"amount": String("3750000"), "id": String("5C2A7B95AD6B685C")})]), "publicKey": String("5F23A4A0360405DCCA13E368761FF9E986B6B908AAA8E28CAC9724C13659B208"), "publicKeyHeight": String("839")})})

これもJSONシリアライズされたままの値ですが、情報が取得できていることがわかります。

転送トランザクションとトランザクション状態の確認

転送トランザクションを生成して状態を確認してみます。

use nem2::transaction;
use nem2::{TransferTransaction, Account};


fn main() {
    let client = transaction::TransactionHttp::new("http://localhost:3000");

    // アカウント読み出し用のプライベートキー
    let private_key = "AF7D4700649FAD70522F8BFCB09620701698C97AC3E4C3CF92C330DFDC7F6D80";
    let network_generation_hash = "44E547B1C422784027AAF1AC66AAC5068F07E4394E8FEF69682403D9D9D0C945";

    // 転送トランザクションを生成
    let transfer_tx = TransferTransaction::new();
    // プライベートキーを元にアカウント読み出し
    let account = Account::from_private_key(private_key);

    // 転送トランザクションを署名してSignedTransactionを生成
    let signed_tx = account.sign(&transfer_tx, network_generation_hash);

    // payloadを取り出してアナウンス
    let resp = client.clone().announce(signed_tx.payload.as_str());

    match resp {
        Ok(r) => println!("{:?}", r),
        Err(e) => println!("error: {:?}", e),
    }

    // SignedTransactionからハッシュを取り出して、そのハッシュをキーに状態を取得する
    let resp = client.get_transaction_status(signed_tx.hash.as_str());
    match resp {
        Ok(r) => println!("{:?}", r),
        Err(e) => println!("error: {:?}", e),
    }
}

これは動きません。実装が追いつきませんでした

nem2-cliで転送したトランザクションハッシュをキーにget_transaction_status()を呼び出すと、ステータス情報は取り出せることが確認できます。

Object({"deadline": String("117653668212"), "group": String("confirmed"), "hash": String("6D88D4E93032C17B8B7E2BF661D6AE45450D2B345317698436AA5E4A0215DAA3"), "height": String("872"), "status": String("Success")})

いくつかサンプルを紹介しました。

実装

基本的にデフォルトポート3000番のREST APIとの通信になるので、そこの中身が分かればなんとかなるだろうというところからスタートしました。ureqというRustのHTTPクライアントパッケージを使って、通信部分を実装しました。

エンドポイントに関しては、nem2-sdkのsrc/infrastructure/apisrc/infrastructure/xxHttp.tsあたりを見れば、ほぼHTTP通信の部分は当たりがつきます。OpenAPIも参考になると思います。

nem2-rsはまだnem2-sdkで言うところのモデル的な層やサービス的な層がなく、情報の取り回しがやりづらいです。今後の開発でこの辺りも整備していけたらなと考えています。

これを書いている時に気づいたのですが、SDK 開発 — NEM Developer Centerがかなり参考になるので、SDK開発に興味がある方は一読をオススメします。

おわりに

ここまでの実装を行っただけでもNEMの中身が少し分かってかなり勉強になりました。
リポジトリ作りたてで足りない機能だらけなので、まずははじめてのアプリケーションを作成 — NEM Developer Centernem2-rsで全て動かせるようにしたいです。

興味があれば使っていただければと。
GitHubスター、フィードバックもお待ちしています!!

では、良いクリスマスを