SymbolブロックチェーンにPython SDKで入門してみる

以下の記事を見てSymbolに入門してみようと思いました。
シリーズで存在するので写経するのがオススメです。

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

個人的に待ちに待ったPythonのSDKです。
待ってたはずなのにリリース情報を完全に見逃してたので
雑多な内容になるとは思いますが、SDKを触ってみた内容をまとめていきます。
プログラム的なところについては元記事が詳しいので参照してみてください。

使用するPython SDK

https://pypi.org/project/symbol-sdk-core-python/

#### 検証環境
- macOS Catalina
- PyCharm CE
- python 3.7
- Python SDK

インストール

``` sh
pip install symbol-sdk-core-python
```

#### 全体イメージ
まず、ざっくりですがSymbol SDKを利用した場合の
単純な送金の流れは以下の図のイメージです。

1. アカウントの設定
2. トランザクションの作成
3. トランザクションへの署名
4. ネットワークへのアナウンス
![python symbol SDK(単純送金の場合).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153148/462ddd4c-6991-82d7-d1c0-8b91d7552fc7.jpeg)

アグリゲートトランザクションなどは赤枠の中が複雑になったりしますが
基本的には、SymbolのPython SDKでは必要なライブラリを読み込んで
トランザクションを作成して、ネットワークに通知すれば問題なさそうです。

他言語のSymbol ライブラリは触っていないのですが、概ね一緒だと思います。
想像以上に簡単にブロックチェーンにアクセスできそうです。




#### 各処理について
##### 接続先の設定
facadeはSymbolブロックチェーンにアクセスするための機能を提供してくれます。
初期設定として、引数にアクセスしたいネットワークに対応する文字列を渡します。

- public　　 　パブリックチェーンのメインネット用
- public_test　パブリックチェーンのテストネット用  
sym/Network.pyの中を覗いてみると、privateやprivate_testも存在したので  
おそらくプライベートチェーン用だと思いますが、詳しくは確認できていません。	

###### メインネット
``` python
from symbolchain.core.facade.SymFacade import SymFacade
# メインネット
facade = SymFacade('public')
```
###### テストネット
``` python
from symbolchain.core.facade.SymFacade import SymFacade
# テストネット
facade = SymFacade('public_test')
```


##### アカウントの設定
既存のアカウント(ウォレット)情報が存在するかどうかで変わってきます。
秘密鍵の処理部分が違うだけで、キーペアの作成などの処理は同じです。
キーペアには秘密鍵と公開鍵がセットされています。
###### 新規作成
``` python
from symbolchain.core.CryptoTypes import PrivateKey

# 秘密鍵の新規作成
private_key = PrivateKey.random()
# 秘密鍵からキーペアを作成
key_pair = KeyPair(private_key)
# 公開鍵からアドレスの作成
address = facade.network.public_key_to_address(key_pair.public_key)
```
###### 既存読み込み(秘密鍵)
一度アカウントを作った後はこっちを使うことが多いと思います。

``` python
from symbolchain.core.CryptoTypes import PrivateKey

# 秘密鍵を復元
# 復元する場合は引数に秘密鍵を渡せばOK
private_key = PrivateKey("78E2713910E90F772410669********2A1747648DEAC01B71AF49F05DA8CEC48")

# 秘密鍵からキーペアを作成
key_pair = KeyPair(private_key)
# 公開鍵からアドレスの作成
address = facade.network.public_key_to_address(key_pair.public_key)
```
###### 既存読み込み(ニーモニック)
SDK経由でニーモニックを作成・復元ができるのかは確認できていません。
ご存知の方がいればコメントで教えてください。

##### トランザクションの作成
転送を行うためのトランザクションを例にしていますが
トランザクション自体は、大きく分けて2種類あると思っています。

- 通常のトランザクション：単一のトランザクション 
- エンベデッドトランザクション：集約用のトランザクション 

![symbol_トランザクション の作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153148/dc4cb5d9-d84f-e9fa-e5fc-a7f902c99d1a.png)

確認した限りでは、エンベデッドトランザクション単体では
ネットワークにアナウンスできなかったので
通常のトランザクションでラップしてあげる必要がありそうでした。

例では、署名者自身に対してメッセージを送信しています。

``` python
# 有効期限の設定
deadline = (int((datetime.datetime.today()
         + datetime.timedelta(hours=2)).timestamp() - 1616694977) * 1000)

# トランザクションの作成
tx = facade.transaction_factory.create({
    'type': 'transfer'
    , 'signer_public_key': key_pair.public_key
    , 'fee': 1000000
    , 'deadline': deadline
    , 'recipient_address': address
    , 'message': bytes(1) + "Hello world".encode('utf8')
})
```
トランザクションにはオプションがいくつも存在するため
typeに合わせてオプションの設定をする必要があります。
上記の例ではmessageの部分以外は必要そうでした。

- type（トランザクションの種別）
transactionBuilderFactory.pyの中に
色々とパラメータがマッピングされてるので、興味があったら確認してみてください。
また、以下のリンクに定義されてる種別が設定可能なはずです。
https://docs.symbolplatform.com/ja/serialization/index.html

- signer_public_key（署名者の公開鍵を設定）
作成したトランザクションの検証に利用されます。
- fee（ネットワーク手数料）
少なすぎると取り込まれないので注意が必要です。

- deadline（トランザクションの有効期限）
ネメシスブロックからの経過時間で設定するので注意が必要です。

- recipient_address
トランザクション受信者のアドレスを設定します。
署名者のアドレスでもOKです。ここでは署名者のアドレスに対して送信を行います。

- mosaics
モザイクを送信する時は設定を行います。
xymもモザイクとして定義されているので、送金する場合はここで設定します。

- message
メッセージを送りたい場合は設定します。

##### トランザクションへの署名
Python SDKを利用した場合の署名は、facadeに対して
キーペアと署名したいトランザクションの情報を引数として渡せばOKです。

また、署名した情報をトランザクションに格納する場合も
facadeに対して、トランザクションの情報と署名情報を引数として渡せばOKです。
attach_signature()の中で、jsonで送信できる形に整形してくれているので
ネットワークにアナウンスする際、ペイロードにそのままセットできました。

``` python
# トランザクションに署名を行う
signature = facade.sign_transaction(key_pair, tx)
# トランザクションに署名を設定して、リクエストを作成する。
payload = facade.transaction_factory.attach_signature(tx, signature)
```

##### ネットワークへのアナウンス
ノードに対してjsonでリクエストを投げます。
ここでresponse statusがエラーになる場合は
httpリクエストが失敗しているので
payloadとかを見直すと良いと思います。

``` python
import http.client

# ネットワークへのアナウンス
url = 'ngl-dual-301.testnet.symboldev.network'

# リクエストの送信
conn = http.client.HTTPConnection(url, 3000)
conn.request("PUT"
             , "/transactions"
             , payload
             , {'Content-type': 'application/json'})

response = conn.getresponse()
print("response status :" + str(response.status) )
print(response.reason)

hash_tx = facade.hash_transaction(tx)
print('http://' + url + ':3000/transactionStatus/' + str(hash_tx))

```
最後のprint文で出力されるURLにアクセスすると
トランザクションの現在の情報が分かります。
httpのリクエストが成功していても、
ネットワークに取り込む処理がエラーになっている場合があります。
開発時は確認しておくのが良いと思います。

http://ngl-dual-301.testnet.symboldev.network:3000/transactionStatus/3C139CD6986C4D7C3FE4E08972B6F74C01094C777828BE3ACC020568E58918B7

``` json
{
"group": "confirmed",
"code": "Success",
"hash": "3C139CD6986C4D7C3FE4E08972B6F74C01094C777828BE3ACC020568E58918B7",
"deadline": "6096648000",
"height": "142572"
}
```

Symbolネットワーク取り込む際にエラーが出ている場合
"code"の部分にエラーステータスが表示されます。
以下の記事でまとめられているので参照してみてください。
原因がわかるかもしれません。

https://qiita.com/nem_takanobu/items/276509fec42076564e77

#### コード全文
``` python
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade
import datetime
import http.client

# Symbolのファサード作成（パブリックチェーンのテストネット用）
facade = SymFacade('public_test')

private_key = PrivateKey("78E2713910E90F772410669********2A1747648DEAC01B71AF49F05DA8CEC48")
# キーペアを作成
key_pair = KeyPair(private_key)
# アドレスの作成
address = facade.network.public_key_to_address(key_pair.public_key)

# 有効期限の設定
deadline = (int((datetime.datetime.today()
                 + datetime.timedelta(hours=2)).timestamp() - 1616694977) * 1000)

# トランザクションの作成
tx = facade.transaction_factory.create({
    'type': 'transfer'
    , 'signer_public_key': key_pair.public_key
    , 'fee': 1000000
    , 'deadline': deadline
    , 'recipient_address': address
    , 'message': bytes(1) + "Hello world".encode('utf8')
})

# トランザクションに署名を行う
signature = facade.sign_transaction(key_pair, tx)
# トランザクションに署名を設定して、リクエストを作成する。
payload = facade.transaction_factory.attach_signature(tx, signature)

# ネットワークへのアナウンス
url = 'ngl-dual-301.testnet.symboldev.network'

# リクエストの送信
conn = http.client.HTTPConnection(url, 3000)
conn.request("PUT"
             , "/transactions"
             , payload
             , {'Content-type': 'application/json'})

response = conn.getresponse()
print("response status :" + str(response.status) )
print(response.reason)

hash_tx = facade.hash_transaction(tx)
print('http://' + url + ':3000/transactionStatus/' + str(hash_tx))

```


#### まとめ
Symbol SDKを利用してみて
ライブラリの導入も簡単で、とても扱いやすかったです。
APIを利用してSymbolブロックチェーンにアクセスするのも簡単ですし
既存システムとの親和性も高そうだと思いました。

PythonやSymbolの技術記事などは、まだまだ数が多くないので
先人の記事を参考にしつつ、リファレンスなどを参照して、
少しずつですが、私もアウトプットできればと思います。

次はアグリゲート関連や
その他トランザクションのスキーマあたり中心に触ってみたいなと思います。