Windows と TypeScript ではじめる Symbol 通信 12【メタデータ】


すこし面倒ですが、メタデータも取得することが出来ます。

## ノード情報の取得

### ノードへリクエスト

ブロック高を取得するパケットタイプは、`0x444`です。

```typescript:Catapult.ts
/** パケットタイプ */
private PacketType = {
  PULL_BLOCK: 0x0_04,
  CHAIN_STATISTICS: 0x0_05,
  PULL_BLOCKS: 0x0_08,
  PUSH_TRANSACTIONS: 0x0_09,
  PUSH_PARTIAL_TRANSACTIONS: 0x1_00,
  NODE_DISCOVERY_PULL_PING: 0x1_11,
  TIME_SYNC_NETWORK_TIME: 0x1_20,
  FINALIZATION_STATISTICS: 0x1_32,
  UNLOCKED_ACCOUNTS: 0x3_04,
  METADATA_INFOS: 0x4_44,
}

async getMetadataInfos(payloads: Uint8Array[]) {
  const metadataEntries: MetadataEntry[] = []
  try {
    const payload = Buffer.concat(payloads)
    const socketData = await this.request(
      this.PacketType.METADATA_INFOS,
      payload
    )
    if (socketData) {
      let offset = 0
      do {
        const metadata = MetadataEntry.deserialize(
          socketData.slice(offset)
        ) as MetadataEntry
        metadataEntries.push(metadata)
        offset += metadata.metadataSize
      } while (offset < socketData.length)
    }
  } catch (e) {
    if (e instanceof Error) console.error(e.message)
    else console.error(e)
  } finally {
    this.close()
  }
  return metadataEntries
}
```

### レスポンスデータの解析

シリアライズされたデータが返ってくるので、デシリアライズします。
型は表示しやすいように全て`string`にしています。

```typescript:src/models/MetadataEntry.ts
import { PacketBuffer } from '../PacketBuffer.js'

export class MetadataEntry {
  constructor(
    public metadataSize: number,
    public size: number,
    public version: number,
    public compositeHash: string,
    public sourceAddress: string,
    public targetAddress: string,
    public scopedMetadataKey: string,
    public targetId: string,
    public metadataType: number,
    public valueSize: number,
    public value: string
  ) {}

  static deserialize(payload: Uint8Array) {
    const packetBuf = new PacketBuffer(Buffer.from(payload))
    const metadataSize = packetBuf.readUInt32LE()
    const size = packetBuf.readUInt32LE()
    const compositeHash = packetBuf.readHexString(32).toUpperCase()
    const version = packetBuf.readUInt16LE()
    const sourceAddress = packetBuf.readHexString(24).toUpperCase()
    const targetAddress = packetBuf.readHexString(24).toUpperCase()
    const scopedMetadataKey = packetBuf
      .readBigUInt64LE()
      .toString(16)
      .toUpperCase()
    const targetId = packetBuf
      .readBigUInt64LE()
      .toString(16)
      .toUpperCase()
      .padStart(16, '0')
    const metadataType = packetBuf.readUInt8()
    const valueSize = packetBuf.readUInt16LE()
    const value = packetBuf.readHexString(valueSize).toUpperCase()
    return new MetadataEntry(
      metadataSize,
      size,
      version,
      compositeHash,
      sourceAddress,
      targetAddress,
      scopedMetadataKey,
      targetId,
      metadataType,
      valueSize,
      value
    )
  }
}
```

## メインの作成

実行するコードを作成します。

ノードに問い合わせる際のメタデータのキーはコンポジットハッシュです。
メタデータの作成者、記録者、メタデータキーから算出する必要があります。

```typescript:src/mainMetadata.ts
import { sha3_256 } from '@noble/hashes/sha3'
import { utils } from 'symbol-sdk'
import { Address } from 'symbol-sdk/symbol'
import { Catapult } from './Catapult.js'

const calculateCompositeHash = (
  type: Uint8Array,
  sourceAddress: Address,
  targetAddress: Address,
  targetId: undefined | string,
  key: Uint8Array
) => {
  const hasher = sha3_256.create()
  hasher.update(sourceAddress.bytes)
  hasher.update(targetAddress.bytes)
  hasher.update(key.reverse())
  hasher.update(utils.hexToUint8(targetId || '0000000000000000').reverse())
  hasher.update(type)
  return hasher.digest()
}

// ターゲットと作成者アドレスの設定
const targetAddress = new Address('TBZN46UIU5BFLJI46VB4JTHHCE5EN2RFLR7NX3A') // メタデータ記録先アドレス
const sourceAddress = new Address('TBZN46UIU5BFLJI46VB4JTHHCE5EN2RFLR7NX3A') // メタデータ作成者アドレス
// メタデータキーの設定
const metadataKey = utils.hexToUint8('CF1ED85F57869D66')
// コンポジットハッシュ算出
const compositeHash = calculateCompositeHash(
  utils.hexToUint8('00'),
  sourceAddress,
  targetAddress,
  '0000000000000000',
  metadataKey
)

const catapult = new Catapult(
  'cert/ca.crt.pem',
  'cert/node.crt.pem',
  'cert/node.key.pem',
  'localhost'
)

const metadataEntries = await catapult.getMetadataInfos([compositeHash])
if (metadataEntries) {
  for (const metadataEntrie of metadataEntries) {
    console.log(JSON.stringify(metadataEntrie, null, 2))
  }
}
```

### 実行

```posh
yarn tsx .\src\mainMetadata.ts
```

実行すると以下の様に結果が出力されます。

```plaintext
socket connected.
{
  "metadataSize": 114,
  "size": 74,
  "version": 1,
  "compositeHash": "C8D8CEA0C98E7570D6136AEA41B89FE41EE41A4307309BEBA9F072A6F92BA657",
  "sourceAddress": "9872DE7A88A74255A51CF543C4CCE7113A46EA255C7EDBEC",
  "targetAddress": "9872DE7A88A74255A51CF543C4CCE7113A46EA255C7EDBEC",
  "scopedMetadataKey": "CF1ED85F57869D66",
  "targetId": "0000000000000000",
  "metadataType": 0,
  "valueSize": 5,
  "value": "7465737431"
}
socket close: 1092
```