SymbolピアノードとPythonで会話する・貳

## はじめに

さて、前回は Symbol ヘッダーのみ送信して、返事をもらう形でした。

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

今回は、ペイロードを付けて送るタイプである、ブロック情報の取得をしてみようかと思います。

## ブロック単体をリクエスト

ブロック単体のリクエストは、いつも通りの Symbol ヘッダーにブロック高をペイロード(8 バイト)として追加します。全体の送信サイズが変わるのでヘッダーサイズは更に 8 バイト増加します。
パケットタイプは 4 です。

- ヘッダーサイズ 4 バイト
- パケットタイプ 4 バイト
- ブロック高 8 バイト

コードを簡単に書くとこんな感じ。
ノード情報と違ってデシリアイズがあるので、取り扱いが楽です。

```python
import os
import ssl
import socket
from pathlib import Path
from symbolchain.BufferReader import BufferReader
from symbolchain.BufferWriter import BufferWriter
from symbolchain.sc import BlockFactory

NODE_HOST = "symbol02.harvestasya.com"
NODE_PORT = 7900
CERTIFICATE_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) + "/cert"
PULL_BLOCK = 4


def request_block_to_peer(block_height):
    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 = PULL_BLOCK  # パケットタイプ
                # 送信データ作成
                buffers = BufferWriter()
                header_len = 8
                buffers.write_int(header_len + 8, 4)
                buffers.write_int(packet_type, 4)
                buffers.write_int(block_height, 8)  # ブロック高
                # データ送信
                ssock.send(buffers.buffer)
                # レスポンス空チェック
                read_buffer = ssock.read()
                if 0 == len(read_buffer):
                    raise ConnectionRefusedError(f"{NODE_HOST} から受信したデータが空っぽですよ！！")
                # レスポンスデータ読み込み
                size = BufferReader(read_buffer).read_int(4)
                while len(read_buffer) < size:
                    read_buffer += ssock.read()
                # レスポンスヘッダーチェック
                reader = BufferReader(read_buffer)
                size = reader.read_int(4)
                actual_packet_type = reader.read_int(4)
                if packet_type != actual_packet_type:
                    raise ConnectionRefusedError(
                        f"戻りのパケットタイプは、 {packet_type} を期待したけど {actual_packet_type} でした。ダメです！"
                    )

                reader.offset = 8  # ヘッダー分進める

                offset = reader.offset  # オフセット退避
                block_size = reader.read_int(4)  # ブロックサイズ
                reader.offset = offset  # オフセット戻し
                block_data = reader.read_bytes(block_size)
                block = BlockFactory.deserialize(block_data)
                print(block)
    except socket.timeout as ex:
        raise ConnectionRefusedError from ex


def main():
    request_block_to_peer(1070355)


main()
```

## 複数のブロックをリクエスト

複数のブロックをリクエストする場合は、以下の様になります。
ブロック高に更に要求数と受信最大サイズを指定します。
パケットタイプは 8 です。

- ヘッダーサイズ 4 バイト
- パケットタイプ 4 バイト
- ブロック高 8 バイト
- 要求数 4 バイト
- 受信最大サイズ 4 バイト

コードはこんな感じ。
ループで最後まで読み切れば良いかと思います。

```python
import os
import ssl
import socket
from pathlib import Path
from symbolchain.BufferReader import BufferReader
from symbolchain.BufferWriter import BufferWriter
from symbolchain.sc import BlockFactory

NODE_HOST = "symbol02.harvestasya.com"
NODE_PORT = 7900
CERTIFICATE_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) + "/cert"
PULL_BLOCKS = 8


def request_blocks_to_peer(block_height, request_count, max_size):
    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 = PULL_BLOCKS  # パケットタイプ
                # 送信データ作成
                buffers = BufferWriter()
                header_len = 8
                buffers.write_int(header_len + 16, 4)
                buffers.write_int(packet_type, 4)
                buffers.write_int(block_height, 8)  # ブロック高
                buffers.write_int(request_count, 4)  # 要求数
                buffers.write_int(max_size, 4)  # 最大サイズ
                # データ送信
                ssock.send(buffers.buffer)
                # レスポンス空チェック
                read_buffer = ssock.read()
                if 0 == len(read_buffer):
                    raise ConnectionRefusedError(f"{NODE_HOST} から受信したデータが空っぽですよ！！")
                # レスポンスデータ読み込み
                size = BufferReader(read_buffer).read_int(4)
                while len(read_buffer) < size:
                    read_buffer += ssock.read()
                # レスポンスヘッダーチェック
                reader = BufferReader(read_buffer)
                size = reader.read_int(4)
                actual_packet_type = reader.read_int(4)
                if packet_type != actual_packet_type:
                    raise ConnectionRefusedError(
                        f"戻りのパケットタイプは、 {packet_type} を期待したけど {actual_packet_type} でした。ダメです！"
                    )

                reader.offset = 8  # ヘッダー分進める

                while reader.offset < len(read_buffer):
                    offset = reader.offset  # オフセット退避
                    block_size = reader.read_int(4)  # ブロックサイズ
                    reader.offset = offset  # オフセット戻し
                    block_data = reader.read_bytes(block_size)
                    block = BlockFactory.deserialize(block_data)
                    print(block)
    except socket.timeout as ex:
        raise ConnectionRefusedError from ex


def main():
    request_blocks_to_peer(1070355, 5, 4096)


main()
```

## さいごに

ブロック情報が取得出来るようになったので、承認済トランザクションとかアカウント情報とかも間接的に取れるようになりますね。
ただ、一見さんモードでピアに読みに行くので連続でリクエストできないという欠点があるので、ノード持ちの人以外は実用的ではないかなと。