アナウンサーなSymbolピア

## はじめに

「Symbol ピアノードと Python で会話する」の続きのようなものです。

https://qiita.com/ccHarvestasya/items/d38ee4a2ab5e343c2f20

ピアに対してトランザクションをアナウンスします。 rest でいうところの`/transactions`です。`/transactions/partial`、`/transactions/cosignature`は、別なのでまたの機会に…

## リクエスト

パケットタイプは Push_Transactions である 9 を使用します。
速習 Symbol でトランザクションをアナウンスして返ってくるメッセージにある、あの 9 です。

```
> TransactionAnnounceResponse {message: 'packet 9 was pushed to the network via /transactions'}
```

「Symbol ピアノードと Python で会話する」で Node_Discovery_Pull_Ping(0x111)を送信した際は Symbol ヘッダーのみでしたが、今回は送信するトランザクションデータがあるので、これを Symbol ヘッダーの後ろにくっつけて送信することになります。

| 項目           | 内容                                           |
| -------------- | ------------------------------------------------ |
| 送信サイズ     | ヘッダサイズ(8)+トランザクションペイロードサイズ |
| パケットタイプ | 9                                                |
| ペイロード     | トランザクションペイロード                       |

## コード

```python
import os
import ssl
import json
import socket
import datetime
from pathlib import Path
from binascii import unhexlify
from symbolchain.BufferWriter import BufferWriter
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.symbol.KeyPair import KeyPair
from symbolchain.CryptoTypes import PrivateKey

NODE_HOST = "symbol02.harvestasya.com"
NODE_PORT = 7900
CERTIFICATE_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) + "/cert"
PUSH_TRANSACTIONS = 9
EPOCH_ADJUSTMENT = 1667250467
MOSAIC_ID = 0x72C0212E67A08BCE


def create_transaction():
    facade = SymbolFacade("testnet")

    # 秘密鍵を復元
    private_key = PrivateKey(
        unhexlify("秘密鍵を入力")
    )
    # 秘密鍵からキーペアを作成
    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())
        - EPOCH_ADJUSTMENT
    ) * 1000

    # トランザクションの作成
    tx = facade.transaction_factory.create(
        {
            "type": "transfer_transaction_v1",
            "signer_public_key": key_pair.public_key,
            "fee": int(1 * 1000000),
            "deadline": deadline,
            "recipient_address": address,
            "mosaics": [{"mosaic_id": MOSAIC_ID, "amount": int(1 * 1000000)}],
            "message": bytes(1) + "Hello world!!".encode("utf8"),
        }
    )

    # トランザクションに署名を行う
    signature = facade.sign_transaction(key_pair, tx)
    # ペイロード作成
    payload = facade.transaction_factory.attach_signature(tx, signature)

    # ペイロードを返す
    return payload


def peer_request(payload_data):
    # ペイロードサイズ
    payload_bytes = unhexlify(payload_data)
    payload_len = len(payload_bytes)

    certificate_directory = Path(CERTIFICATE_DIRECTORY)
    timeout = 5
    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE
    ssl_context.load_cert_chain(
        certificate_directory / "node.full.crt.pem",
        keyfile=certificate_directory / "node.key.pem",
    )

    try:
        with socket.create_connection((NODE_HOST, NODE_PORT), timeout) as sock:
            with ssl_context.wrap_socket(sock) as ssock:
                packet_type = PUSH_TRANSACTIONS  # パケットタイプ
                # 送信データ作成
                buffers = BufferWriter()
                header_len = 8
                buffers.write_int(header_len + payload_len, 4)
                buffers.write_int(packet_type, 4)
                buffers.write_hex_string(payload_data)
                # データ送信
                ssock.send(buffers.buffer)
    except socket.timeout as ex:
        raise ConnectionRefusedError from ex


def main():
    # トランザクション作成
    payload_json = create_transaction()
    # ペイロードのみにする
    payload_data = json.loads(payload_json)["payload"]
    # ピアへアナウンス
    peer_request(payload_data)


if __name__ == "__main__":
    main()
```

`create_transaction()`は、(面倒だったので)SDK を使用してトランザクションを作成しています。そして、出来たトランザクション Json のペイロード部分のみをピアに送信しています。

Push_Transactions 送信後のレスポンスはなさそうです。

## さいごに

これでまたピア活用の幅が広がりましたね。

ただ、送信後のレスポンスがないので、送信前後でトランザクションの確認を行うロジックは必要だと思います。