LoginSignup
14
6

Blockchain Symbol の転送トランザクションを発行する【ピアアナウンス】

Last updated at Posted at 2024-02-12

SSL パケット通信を使って API を介さずにネットワークにアナウンスすることも出来ます。

「Blockchain Symbol の転送トランザクションを発行する」をピアアナウンスに変更してみます。

証明書の作成

証明書は下記を参照して作成。
OpenSSL のバージョンは 3.1.4 で確認しています。

ピアノードにアナウンスするクラス

SymbolAnnouncer.ts
import fs from "fs";
import tls, { ConnectionOptions } from "tls";

enum PacketType {
  /** トランザクションアナウンス */
  PUSH_TRANSACTIONS = 0x009,
}

export class SymbolCatapult {
  private connectionOptions: ConnectionOptions;

  /**
   * コンストラクタ
   * @param nodeHost ノードホスト
   * @param nodePort ノードポート(デフォルト: 7900)
   */
  constructor(private nodeHost: string, private nodePort: number = 7900) {
    this.connectionOptions = {
      host: this.nodeHost,
      port: this.nodePort,
      timeout: 3000,
      cert: fs.readFileSync("cert/node.full.crt.pem"),
      key: fs.readFileSync("cert/node.key.pem"),
      rejectUnauthorized: false,
    };
  }

  /**
   * トランザクションアナウンス
   * @param payload トランザクションペイロード
   */
  public async announceTransaction(payload: Uint8Array): Promise<void> {
    await this.catapult(PacketType.PUSH_TRANSACTIONS, payload, false);
  }

  /**
   * カタパルトサーバへパケットを送信する
   * @param packetType パケットタイプ
   * @param payload ペイロード
   * @param isResponse レスポンス有無(デフォルト: true)
   * @returns レスポンスデータ
   */
  private async catapult(
    packetType: PacketType,
    payload?: Uint8Array,
    isResponse: boolean = true
  ): Promise<Uint8Array | undefined> {
    return new Promise<Uint8Array | undefined>((resolve, reject) => {
      const payloadSize = payload ? payload.length : 0;
      let responseSize = 8; // ヘッダ分のサイズを前もって付与
      let responseData: Uint8Array | undefined = undefined;

      let socket = tls.connect(this.connectionOptions, () => {
        // Symbolパケット生成
        const headerSize = 8;
        const packetSize = headerSize + payloadSize;
        const symbolPacketBuffer = new ArrayBuffer(packetSize);
        // Symbolヘッダー編集
        const symbolHeader = new DataView(symbolPacketBuffer);
        symbolHeader.setUint32(0, packetSize, true);
        symbolHeader.setUint32(4, packetType, true);

        if (payload) {
          // Symbolペイロード編集
          const symbolPayload = new Uint8Array(
            symbolPacketBuffer,
            headerSize,
            payloadSize
          );
          symbolPayload.set(payload);
        }

        // Symbolパケット送信
        socket.write(new Uint8Array(symbolPacketBuffer));
        console.debug(`packet type ${packetType} written`);

        if (!isResponse) {
          // ソケット切断
          socket.destroy();
        }
      });

      // SSL接続時
      socket.on("secureConnect", () => {
        const peerX509 = socket.getPeerX509Certificate();
        if (!peerX509) return;
        console.log(`${peerX509}`);
      });

      // データ受信
      socket.once("data", (data) => {
        // レスポンスデータ(ヘッダ)取得
        const nodeBufferView = Buffer.from(new Uint8Array(data).buffer);
        // レスポンスサイズチェック
        const responseDataSize = nodeBufferView.readUInt32LE(0);
        if (responseDataSize === 0) {
          socket.destroy();
          reject("empty data");
        }
        // レスポンスパケットタイプチェック
        const responsePacketType = nodeBufferView.readUInt32LE(4);
        if (responsePacketType !== packetType) {
          socket.destroy();
          reject(
            `mismatch packet type: expect: ${packetType} actual: ${responsePacketType}`
          );
        }

        // ヘッダが問題なければデータ部取得
        socket.on("data", (data) => {
          const tempResponseData = new Uint8Array(data);
          console.debug(tempResponseData);
          responseSize += tempResponseData.length;
          if (!responseData) {
            // 初回
            responseData = tempResponseData;
          } else {
            // 連結
            const merged = new Uint8Array(
              responseData.length + tempResponseData.length
            );
            merged.set(responseData);
            merged.set(tempResponseData, responseData.length);
            responseData = merged;
          }
          if (responseDataSize <= responseSize) {
            // 受信が終わったら終了
            socket.end();
          }
        });
      });

      socket.on("timeout", function () {
        socket.destroy();
        reject("timeout");
      });

      socket.on("error", function (error) {
        socket.destroy();
        reject(error);
      });

      socket.on("close", function () {
        resolve(responseData);
      });
    });
  }
}

アナウンス部分の変更

REST にアナウンス部分を下記に書き換えます。
送信するノードは、ノード間通信ポートが開いているノードであればどれでも良いです。
ソケット通信なのでhttp等の情報は不要です(httpと違ってトランスポート層なので...)。

/** ノードホスト */
const NODE = "symbol02.harvestasya.com";

// Peerノードにアナウンス
const symbolCatapult = new SymbolCatapult(NODE);
await symbolCatapult.announceTransaction(transferTx.serialize());
14
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
6