tsunagi-functionsでSymbolブロックチェーンのトランザクションを自由自在に作ろう！（Rust編）

この記事は、以下に紹介したtsunagi-functionsのRust実装版です。
なお、Rust化の実装についてはオープニングライン社にインターンされているヨシザワさんにご協力いただきました

https://qiita.com/nem_takanobu/items/4b5d67fe9eb80ae35433

トランザクション構築の内部処理やノードへの通知方法については以下の記事もご参考ください。

https://qiita.com/nem_takanobu/items/ec9c24e0dfd43d84e1d2

Symbolブロックチェーンをよく知らないという方はこちらで速習することができます。

https://github.com/xembook/quick_learning_symbol

# リポジトリ

##### tsunagi-functions for Rust
https://github.com/xembook/tsunagi-sdk/tree/main/rust

関数群の提供のため、sdkという名称は今後functionsに変更予定です。


# テスト

テストスクリプトを以下に置いています。実行することができればそのロジックをそのまま実装に利用することができます。

#### v_0_1_0_3_5.rs

https://github.com/xembook/tsunagi-sdk/blob/main/rust/tests/v_0_1_0_3_5.rs

```toml:Cargo.toml
[package]
authors = ["XEMBook", "Yoshizawa Shogo <xxxx@gmail.com>"]
description = "This is a bridge until a de facto standard SDK is available."

name = "tsunagi_sdk"
version = "0.1.0"
edition = "2021"
license = "MIT"
readme = "README.md"
keywords = ["Symbol"]

repository = "https://github.com/xembook/tsunagi-sdk"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
json = "0.12.4"
url = "2.3.1"
reqwest = { version = "0.11", features = ["blocking"] }
rustc-serialize = "0.3.24"
hex = "0.4.3"
sha3 = "0.10.6"
ed25519-dalek = "1"
base32 = "0.4.0"
```

```sh:テスト実行
cargo test
```

# 実装例

テストスクリプトから転送トランザクションの書き方を紹介します。

### 転送トランザクション

```rust
use tsunagi_sdk::v0_1::*; // tsunagi_sdkの関数群をインポート
use json::object;

fn main() {
    // network情報を定義
    let network = object!{
        version:1,
        network:"TESTNET",
        generationHash:"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836",
        currencyMosaicId:0x3A8416DB2D53B6C8u64,
        currencyNamespaceId:0xE74B99BA41F4AFEEu64,
        currencyDivisibility:6,
        epochAdjustment:1637848847,
        catjasonBase:"https://xembook.github.io/tsunagi-sdk/catjson/",
        wellknownNodes:[
            "https://sym-test.opening-line.jp:3001",
            "https://sym-test.opening-line.jp:3001",
            "https://sym-test.opening-line.jp:3001",
        ]
    };
    // 自分の秘密鍵
    let private_key: &str = "94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7";
    // トランザクションを定義
    let tx = object!{
        type:"TRANSFER",
        signer_public_key:"5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
        fee:25000u64,
        deadline:7200000u64,
        recipient_address:generate_address_id("TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"),
        mosaics:[
            {mosaic_id: 0x2A09B7F9097934C2u64, amount: 1u64},
            {mosaic_id: 0x3A8416DB2D53B6C8u64, amount: 100u64},
        ],
        message:"Hello Tsunagi(Catjson) SDK!",
    };
    // catjsonの取得
    let catjson = load_catjson(&tx, &network);
    // トランザクションレイアウトの取得
    let layout = load_layout(&tx, &catjson, false);
    // トランザクションの事前準備
    let mut prepared_tx = prepare_transaction(&tx, &layout, &network);
    // レイアウトの解析とトランザクションデータの注入
    let parsed_tx = parse_transaction(&mut prepared_tx, &layout, &catjson, &network);
    // トランザクションの構築
    let built_tx = build_transaction(&parsed_tx);
    // 署名
    let signature = sign_transaction(&built_tx, private_key, &network);
    // トランザクションの更新
    let built_tx = update_transaction(&built_tx, "signature", "value", &signature);

    //ペイロード出力
    let payload = hexlify_transaction(&built_tx.into(), 0);
    // payloadを任意の方法でJson形式でSymbolネットワークへ送信してください。
    // Send the payload to the Symbol network in Json format in any way you wish.
    
    assert_eq!(payload, 
        "dc000000000000001e1a289eef4550fe482ff5a073ba9b91bf38e8623e8767eb54eae5fd48dba354f662dce635ad299efb050cbf187c6b52674613d7e81bb58a4a662d2528d491005f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb0000000001985441a86100000000000000dd6d0000000000989df3aea3852feafc5fdfc2266eb84ed8a7fa242688a8b81c00020000000000c2347909f9b7092a0100000000000000c8b6532ddb16843a64000000000000000048656c6c6f205473756e616769284361746a736f6e292053444b21"
    );
}
```

### 通知

```rust
let json_request = format!(r#"{{"payload":"{}"}}"#, payload);
let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions")
    .set("Content-Type", "application/json")
    .send_string(&json_request);
println!("{}", json_request);
println!("{:?}", r);

```

### 連署

連署を必要とするトランザクションの場合は、tx1 に加えて以下のような定義と署名の追加が必要になります。

```rust
let cosignature1 = object!{
	version:0u64,
	signer_public_key:"6199BAE3B241DF60418E258D046C22C8C1A5DE2F4F325753554E7FD9C650AFEC",
	signature:"",
};

let cosignature2 = object!{
	version:0u64,
	signer_public_key:"886ADFBD4213576D63EA7E7A4BECE61C6933C27CD2FF36F85155C8FEBFB6EB4E",
	signature:"",
};

let agg_tx = object!{
	type:"AGGREGATE_COMPLETE",
	signer_public_key:"5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	fee:1000000u64,
	deadline:get_deadline(&get_network_info()),
	transactions:[tx1,tx2,tx3],
	cosignatures:[cosignature1,cosignature2]
};

let catjson = load_catjson(&agg_tx,&get_network_info());
let layout = load_layout(&agg_tx,&catjson,false); //isEmbedded false

let mut prepared_tx = prepare_transaction(&agg_tx,&layout,&get_network_info()); //TX事前準備
let parsed_tx   = parse_transaction(&mut prepared_tx,&layout,&catjson,&get_network_info()); //TX解析
let mut built_tx    = build_transaction(&parsed_tx); //TX構築

let signature = sign_transaction(&built_tx,PRIVATE_KEY,&get_network_info());
built_tx = update_transaction(&built_tx,"signature","value",&signature);

//トランザクションハッシュ作成
let tx_hash = hash_transaction(&agg_tx["signer_public_key"].to_string(),&signature.to_string(),&built_tx,&get_network_info());


//連署
prepared_tx["cosignatures"][0]["signature"] = cosign_transaction(&tx_hash,BOB_PRIVATE_KEY).into();
prepared_tx["cosignatures"][1]["signature"] = cosign_transaction(&tx_hash,CAROL_PRIVATE_KEY).into();

let cosignatures_layout = layout.iter().find(|&lf| lf["name"] == "cosignatures").unwrap().clone();
let parsed_cosignatures = parse_transaction(&mut prepared_tx,&vec![cosignatures_layout],&catjson,&get_network_info()); //構築
built_tx = update_transaction(&built_tx,"cosignatures","layout",&parsed_cosignatures[0]["layout"]);

let payload = hexlify_transaction(&built_tx.into(), 0);


```

他にもテストスクリプトに様々なトランザクションを記述しているのでぜひお試しください！