Symbolブロックチェーンを触ってみた

## はじめに
オープンソースの仮想通貨NEM（ネム）の新しいブロックチェーンプラットフォームSymbolが21年1月14日 にローンチされることが発表されたので、触ってみました。

ブロックチェーンブームは落ち着いて、現状は幻滅期と言われています。
（ガートナーのパイプ・サイクル参照）
そんな中で、プラットフォームの改善が進み導入サポート企業などが徐々に増えてきたことで、実用化に向けた動きは徐々に進み、わざわざブロックチェーンを使う価値が出てきつつあるようです。
![l_bit201912251428399374.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537403/7ae5f818-2f84-f3ea-9065-506fb71a3d3e.jpeg)

## Symbolとは？
ネム（NEM）の処理速度アップやセキュリティ強化を目的とした大型アップデートで新しく誕生するプラットフォームです。
実用化を強く意識していて、セキュリティ面がより強く採掘者（マイナー）が必要なパブリックチェーンと採掘者（マイナー）が不要なプライベートチェーンの互換性をもったハイブリッドチェーンと言われています。

[NEM Japan](https://japan.nem.io/)も設立されています！

#### 以下あたりが変わると言われています。
1. 安全性を重視しており、セキュリティが強化される
2. 処理速度が速くなる  
 1ブロックの生成にビットコインは10分かかりますが、1分足らずまで処理速度が高速化すると言われている。
3. アグリゲートトランザクションなどの搭載で実用性がアップする  
複数の取引処理を第三者の介入なしにひとまとめにして処理できる機能  

#### 実用性について 
アグリゲートトランザクションにより、ワインの生産から輸送プロセスのどの段階でも改ざんされないことを保証可能らしい。
実用性を意識した機能だとモザイク、ネームスペース、マルチシグなど多数あり、ビジネスシーンでの活用を意識している。  
高級ワイン偽装問題を抱えるワイン産業への導入シナリオを例として紹介しています。
参考 : [Eliminating fraud and ensuring the authenticity of fine wine throughout the supply chain](https://blog.nem.io/eliminating-fraud-and-ensuring-the-authenticity-of-fine-wine-throughout-the-supply-chain-using-symbol-blockchain/)
参考 : [Invest in your future](https://symbolplatform.com/developers)


## チュートリアルをやってみる
参考 : [Symbol Platform Getting Started](https://docs.symbolplatform.com/getting-started/index.html)
参考 : [Github](https://github.com/nemtech)
Node.js 12 LTS が必要なようなので、Docker環境で試しました。
（チュートリアル通りで動かない部分は微調整してます）

Dockerfileを作成して、起動

```shell
$ vi docker-compose.yml
version: '3'
services:
  app:
    # 起動イメージ Node.js v12
    image: node:12

    # 環境変数
    environment:
      - DEBUG=app:*

    tty: true

    # ホスト側の./srcをコンテナの/appにマウント
    volumes:
      - ./src:/app

    # 起動時のカレント
    working_dir: /app
$ mkdir src
$ docker-compose run --rm app /bin/bash
Creating network "nem-symbol-work_default" with the default driver
Creating nem-symbol-work_app_run ... done
root@8c52a1045a19:/app# node -v
v12.20.0
```

アカウントの作成

```shell
$ docker-compose run --rm app /bin/bash
Pulling app (node:12)...
12: Pulling from library/node
22f9b9782fc3: Already exists
2c75c9e56a8a: Already exists
b31d19b99daa: Already exists
e000cbf49b08: Already exists
892def1a6f88: Already exists
0581e893c9eb: Already exists
92b6633afaae: Already exists
dc11bf172033: Already exists
f09b45a84f38: Already exists
Digest: sha256:05b0f50fc60d07a18cbe399d3c9d6942b2bd1e0202070efec217c541130dfe52
Status: Downloaded newer image for node:12
Creating nem-symbol-work_app_run ... done

# latestバージョンがbugで起動しないため、1個前のバージョンを指定
# https://github.com/nemtech/symbol-cli
root@6183c122b89e:/app# npm install -g symbol-cli@0.21.x 
npm WARN deprecated @types/ora@3.2.0: This is a stub types definition. ora provides its own type definitions, so you do not need this installed.
npm WARN deprecated request-promise-native@1.0.9: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
npm WARN deprecated js-joda@1.11.0: This package is deprecated, please check/use @js-joda/core instead
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
/usr/local/bin/symbol-cli -> /usr/local/lib/node_modules/symbol-cli/bin/symbol-cli

> tiny-secp256k1@1.1.6 install /usr/local/lib/node_modules/symbol-cli/node_modules/tiny-secp256k1
> npm run build || echo "secp256k1 bindings compilation fail. Pure JS implementation will be used."

npm ERR! code EACCES
npm ERR! syscall scandir
npm ERR! path /root/.npm/_logs
npm ERR! errno -13
npm ERR! 
npm ERR! Your cache folder contains root-owned files, due to a bug in
npm ERR! previous versions of npm which has since been addressed.
npm ERR! 
npm ERR! To permanently fix this problem, please run:
npm ERR!   sudo chown -R 65534:0 "/root/.npm"
glob error [Error: EACCES: permission denied, scandir '/root/.npm/_logs'] {
  errno: -13,
  code: 'EACCES',
  syscall: 'scandir',
  path: '/root/.npm/_logs'
}

> tiny-secp256k1@1.1.6 build /usr/local/lib/node_modules/symbol-cli/node_modules/tiny-secp256k1
> node-gyp rebuild

gyp WARN EACCES current user ("nobody") does not have permission to access the dev dir "/root/.cache/node-gyp/12.20.0"
gyp WARN EACCES attempting to reinstall using temporary dev dir "/usr/local/lib/node_modules/symbol-cli/node_modules/tiny-secp256k1/.node-gyp"
gyp WARN install got an error, rolling back install
gyp WARN install got an error, rolling back install
gyp ERR! configure error 
gyp ERR! stack Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/symbol-cli/node_modules/tiny-secp256k1/.node-gyp'
gyp ERR! System Linux 5.4.39-linuxkit
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /usr/local/lib/node_modules/symbol-cli/node_modules/tiny-secp256k1
gyp ERR! node -v v12.20.0
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok 
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! tiny-secp256k1@1.1.6 build: `node-gyp rebuild`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the tiny-secp256k1@1.1.6 build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
secp256k1 bindings compilation fail. Pure JS implementation will be used.
npm WARN eslint-plugin-prettier@3.3.0 requires a peer of eslint@>=5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-plugin-prettier@3.3.0 requires a peer of prettier@>=1.13.0 but none is installed. You must install peer dependencies yourself.
npm WARN eslint-config-prettier@6.15.0 requires a peer of eslint@>=3.14.1 but none is installed. You must install peer dependencies yourself.
npm WARN symbol-sdk@0.20.8-alpha-202009251426 requires a peer of utf8@^2.1.2 but none is installed. You must install peer dependencies yourself.
npm WARN symbol-sdk@0.20.7 requires a peer of utf8@^2.1.2 but none is installed. You must install peer dependencies yourself.

+ symbol-cli@0.21.1
added 264 packages from 212 contributors in 88.396s

# ↑ 権限周りのエラーは出ているが動作確認に影響ないので進めてしまいます。

# プロファイルを作成
# test node は、こちらにリストアップされています。
#  http://explorer-0.10.0.x-01.symboldev.network/nodes
root@6183c122b89e:/app# symbol-cli account generate --namespace-id symbol.xym --divisibility 6
✔ Select the network type: › TEST_NET
✔ Do you want to save the account? … yes
✔ Select an import type: › PrivateKey
✔ Enter the Symbol node URL. (Example: http://localhost:3000): … http://api-01.ap-northeast-1.0.10.0.x.symboldev.network:3000/
✔ Enter a profile name: … test-user-92810178
✔ Enter your wallet password: … ********
✔ Do you want to set the account as the default profile? … yes

Account
┌───────────────┬──────────────────────────────────────────────────────────────────────┐
│ Property      │ Value                                                                │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Address       │ TBI...IZY       │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Public Key    │ 880D...999B     │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Private Key   │ 56A...FCCC      │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Password      │ e..                                                             │
└───────────────┴──────────────────────────────────────────────────────────────────────┘

SUCCESS Stored test-user-92810178 profile

```

## テスト通貨の取得
以下のページにアクセスして、上記で取得したAddressを入力して、通貨を取得します。
http://faucet-0.10.0.x-01.symboldev.network/
RecipientのAddress、XYM Amountに欲しい通貨量を指定して、「CLAIM!」をクリック
![スクリーンショット 2020-12-17 12.37.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537403/da22a5f1-f91b-374d-c0eb-ea72cd3625a6.png)
通貨がアカウントに反映されていることを確認

```shell
root@6183c122b89e:/app# symbol-cli account info --profile test-user-92810178
⠋ Processing
Account Information
┌───────────────────┬──────────────────────────────────────────────────────────────────┐
│ Property          │ Value                                                            │
├───────────────────┼──────────────────────────────────────────────────────────────────┤
│ Address           │ TBI...IZY                    │
├───────────────────┼──────────────────────────────────────────────────────────────────┤
│ Address Height    │ 231895                                                           │
├───────────────────┼──────────────────────────────────────────────────────────────────┤
│ Public Key        │ 0000000000000000000000000000000000000000000000000000000000000000 │
├───────────────────┼──────────────────────────────────────────────────────────────────┤
│ Public Key Height │ 0                                                                │
├───────────────────┼──────────────────────────────────────────────────────────────────┤
│ Importance        │ 0                                                                │
├───────────────────┼──────────────────────────────────────────────────────────────────┤
│ Importance Height │ 0                                                                │
└───────────────────┴──────────────────────────────────────────────────────────────────┘ 
Balance Information
┌──────────────────┬─────────────────┬─────────────────┬───────────────────┐
│ Mosaic Id        │ Relative Amount │ Absolute Amount │ Expiration Height │
├──────────────────┼─────────────────┼─────────────────┼───────────────────┤
│ 5B66E76BECAD0860 │ 10,000          │ 10000000000     │ Never             │
└──────────────────┴─────────────────┴─────────────────┴───────────────────┘ 
```

## 新しいプロジェクトを作成
TypeScript / JavaScript / Java のサンプルがあったのでTypeScriptを選択

```shell
root@6183c122b89e:/app# npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (app) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /app/package.json:

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) yes
root@6183c122b89e:/app# npm install symbol-sdk rxjs
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN app@1.0.0 No description
npm WARN app@1.0.0 No repository field.

+ rxjs@6.6.3
+ symbol-sdk@0.22.2
added 32 packages from 27 contributors and audited 32 packages in 201.071s

2 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

root@6183c122b89e:/app# npm install --global typescript
/usr/local/bin/tsc -> /usr/local/lib/node_modules/typescript/bin/tsc
/usr/local/bin/tsserver -> /usr/local/lib/node_modules/typescript/bin/tsserver
+ typescript@4.1.3
added 1 package from 1 contributor in 26.032s
root@6183c122b89e:/app# npm install --global ts-node
/usr/local/bin/ts-node -> /usr/local/lib/node_modules/ts-node/dist/bin.js
/usr/local/bin/ts-script -> /usr/local/lib/node_modules/ts-node/dist/bin-script-deprecated.js
/usr/local/bin/ts-node-script -> /usr/local/lib/node_modules/ts-node/dist/bin-script.js
/usr/local/bin/ts-node-transpile-only -> /usr/local/lib/node_modules/ts-node/dist/bin-transpile.js
npm WARN ts-node@9.1.1 requires a peer of typescript@>=2.7 but none is installed. You must install peer dependencies yourself.

+ ts-node@9.1.1
added 9 packages from 43 contributors in 4.362s
```

## ユースケース
アクター
- チケット販売者（先ほど作ったテストアカウントを使用）
- 購入者

要求
1. 各チケットと購入者を識別する。
2. チケットの転売防止。
3. 未認証のチケットとその複製の防止。

を実現するものとする。

###販売者を作成

```shell
root@6183c122b89e:/app# symbol-cli account generate --network TEST_NET --save --url http://api-01.ap-northeast-1.0.10.0.x.symboldev.network:3000 --profile customer --namespace-id symbol.xym --divisibility 6
✔ Select an import type: › PrivateKey
✔ Enter your wallet password: … ********
✔ Do you want to set the account as the default profile? … no

Account
┌───────────────┬──────────────────────────────────────────────────────────────────────┐
│ Property      │ Value                                                                │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Address       │ TDR...LYI    │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Public Key    │ EC...F9F     │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Private Key   │ E5...721     │
├───────────────┼──────────────────────────────────────────────────────────────────────┤
│ Password      │ N...a                                                             │
└───────────────┴──────────────────────────────────────────────────────────────────────┘

SUCCESS Stored customer profile
```

## チケットの作成
Symbolモザイクを使ってチケット相当のものを作成します。

- 主要項目  

|  プロパティ  |  値  | 説明 |
| ---- | ---- | ---- |
| Divisibility  |  0  |  モザイクが0.5ticketなど分割出来ないように0を指定 |
| Duration  |  1000  |  1000ブロック登録 |
| Amount | 99 | 販売するチケットの枚数 | 
| Supply mutable | True | 後から販売枚数を変更出来るかどうか | 
| Transferable | False | モザイクはモザイク作成者にしか送信出来ない（転売防止） | 

[詳細なpropertyはこちら](https://docs.symbolplatform.com/concepts/mosaic.html)

```shell
root@6183c122b89e:/app# symbol-cli transaction mosaic --amount 99 --divisibility 0 --duration 1000 --max-fee 2000000 --sync --profile test-user-92810178
✔ Enter your wallet password: … ********
✔ Do you want a non-expiring mosaic? … no
✔ Do you want this mosaic to have a mutable supply? … yes
✔ Do you want this mosaic to be transferable? … no
✔ Do you want this mosaic to be restrictable? … no
✔ Select the transaction announce mode: › normal
┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                          AGGREGATE_COMPLETE                                           │
├────────────────────────────────────┬──────────────────────────────────────────────────────────────────┤
│ Max fee:                           │ 2,000,000                                                        │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ Network type:                      │ TEST_NET                                                         │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ Deadline:                          │ 2020-12-17 07:30:11.906                                          │
├────────────────────────────────────┴──────────────────────────────────────────────────────────────────┤
│                             Inner transaction 1 of 2 - MOSAIC_DEFINITION                              │
├────────────────────────────────────┬──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 1 of 2] Mosaic Id:      │ 7A71117FB95A4C2A                                                 │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 1 of 2] Duration:       │ 1,000 blocks                                                     │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 1 of 2] Divisibility:   │ 0                                                                │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 1 of 2] Supply mutable: │ true                                                             │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 1 of 2] Transferable:   │ false                                                            │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 1 of 2] Restrictable:   │ false                                                            │
├────────────────────────────────────┴──────────────────────────────────────────────────────────────────┤
│                            Inner transaction 2 of 2 - MOSAIC_SUPPLY_CHANGE                            │
├────────────────────────────────────┬──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 2 of 2] Mosaic Id:      │ 7A71117FB95A4C2A                                                 │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 2 of 2] Direction:      │ Increase supply                                                  │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ [Inner tx. 2 of 2] Delta:          │ 99                                                               │
├────────────────────────────────────┴──────────────────────────────────────────────────────────────────┤
│                                           Signature details                                           │
├────────────────────────────────────┬──────────────────────────────────────────────────────────────────┤
│ Payload:                           │ 38...99 │
│                                    │ D6...CB │
│                                    │ 4D...64 │
│                                    │ 69...00 │
│                                    │ AD...2F │
│                                    │ 90...01 │
│                                    │ C3...7A │
│                                    │ E8...3D │
│                                    │ 0B...42 │
│                                    │ 2A...00 │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ Hash:                              │ 66...6F │
├────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ Signer:                            │ 88...9B │
└────────────────────────────────────┴──────────────────────────────────────────────────────────────────┘
✔ Do you want to announce this transaction? … no
```

## チケットの送信
トランザクションをネットワークへアナウンスします。

```shell
root@6183c122b89e:/app# npm install utf8
npm WARN app@1.0.0 No description
npm WARN app@1.0.0 No repository field.

+ utf8@3.0.0
added 1 package from 1 contributor and audited 33 packages in 4.498s

2 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

root@6183c122b89e:/app# vi FirstApplication.ts
/*
*
* Copyright 2018-present NEM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import {
    Account,
    Address,
    Deadline,
    Mosaic,
    MosaicId,
    NetworkType,
    PlainMessage,
    RepositoryFactoryHttp,
    TransferTransaction,
    UInt64,
} from 'symbol-sdk';

/* start block 01 */
// replace with mosaic id
const mosaicIdHex = '7A71117FB95A4C2A';
const mosaicId = new MosaicId(mosaicIdHex);
// replace with customer address
const rawAddress = 'TDR...LYI';
const recipientAddress = Address.createFromRawAddress(rawAddress);
// replace with network type
const networkType = NetworkType.TEST_NET;

const transferTransaction = TransferTransaction.create(
	Deadline.create(parseInt((Date.now()/1000).toString())),
        recipientAddress,
	[new Mosaic(mosaicId, UInt64.fromUint(1))],
        PlainMessage.create('enjoy your ticket'),
	networkType,
        UInt64.fromUint(2000000));
/* end block 01 */

/* start block 02 */
// replace with ticket vendor private key
const privateKey = 'E5C...721';
const account = Account.createFromPrivateKey(privateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
// http://api-01.ap-northeast-1.0.10.0.x.symboldev.network:3000/node/info から取得
const networkGenerationHash = '6C1...17CD';
const signedTransaction = account.sign(transferTransaction, networkGenerationHash);
/* end block 02 */

/* start block 03 */
// replace with node endpoint
const nodeUrl = 'http://api-01.ap-northeast-1.0.10.0.x.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);

const transactionHttp = repositoryFactory.createTransactionRepository();

transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));
/* end block 03 */

# 実行
root@6183c122b89e:/app# ts-node FirstApplication.ts 

# 結果確認
root@6183c122b89e:/app# symbol-cli account info --profile customer
```