１台のサーバーでSymbolのTEST NET & MAIN NETを同時に動かす

# はじめに

この記事では、Symbolの `TEST NET` と `MAIN NET` を１台のサーバーでDockerを使って同時に動かす方法を紹介します。
あくまで自分の備忘録的なものなので、何かあっても自己責任でお願いします。
また、インフラは専門ではないので設定等においておかしな部分があればツッコミもお待ちしております。

# 実行環境

```bash
$ cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
```

```bash
$ node -v
v18.19.0

$ npm -v
10.2.3

$ symbol-bootstrap -v
symbol-bootstrap/1.1.10 linux-x64 node-v18.19.0

$ docker -v
Docker version 25.0.3, build 4debf41
```

※ Nodejsはnvmで入れています。
※ symbol-shoestringはまだフォルダ構成等把握できていないものが多すぎるので、ノードの設定ファイル生成にはsymbol-bootstrapを使用します。
※ サーバー側のファイアウォール等の設定は適時行ってください。

# 手順

１台のサーバーでsymbolの `TEST NET` と `MAIN NET` を同時に動かすには `symbol-bootstrap run -d`　等のコマンドは使用できません。（立ち上げたコンテナがシャットダウンされて別のコンテナが立ち上がるので）
ですので、 `docker compose` コマンドを使用して各ノードを立ち上げます。
それでは以下より手順を説明します。

## testnet用のフォルダとmainnet用のフォルダを作成します。

```bash
$ mkdir testnet
$ mkdir mainnet
```
## mainnet構築

まずは、簡単なmainnetの構築からです。
これより以降の作業は全てmainnetフォルダ内で行います。

### 各種設定ファイルの生成

```bash
$ symbol-bootstrap config -a dual -p mainnet -c mypreset.yml
$ symbol-bootstrap compose
```

myprese.ymlはご自身の設定を用いてください。

例）
```mypreset.yml
unfinalizedBlocksDuration: 10m
maxChainBytesPerSyncAttempt: 10MB
messageSynchronizationMaxResponseSize: 5MB
blockDisruptorMaxMemorySize: 1000MB
```

mainnetは以上です。（実は特に何もする必要はありません）

### スクリプトの配置

以下の３つのスクリプトをmainnetフォルダ直下に配置します。

```startNode.sh
#!/bin/bash

mkdir -p target/databases
mkdir -p target/databases/db
mkdir -p target/nodes/node/data
docker-compose -p mainnet -f target/docker/docker-compose.yml up --remove-orphans --detach
```

```stopNode.sh
#!/bin/bash

docker-compose -p mainnet -f target/docker/docker-compose.yml stop
```

```resetData.sh
#!/bin/bash

rm -fr target/nodes/node/data
rm -fr target/nodes/node/logs
rm -fr target/gateways/rest-gateway/logs
rm -fr target/databases/db/*
```

### 実行権限を付与

```bash
$ chmod +x startNode.sh
$ chmod +x stopNode.sh
$ chmod +x resetData.sh
```

### mainnetの起動

```bash
./startNode.sh
```

### コンテナ確認

コンテナが正常に起動していれば以下のように出力されているはずです。

```bash
$ docker ps
CONTAINER ID   IMAGE                                      COMMAND                  CREATED       STATUS       PORTS                                       NAMES
11a5eb582292   symbolplatform/symbol-server:gcc-1.0.3.6   "/bin/bash /symbol-c…"   8 hours ago   Up 8 hours   0.0.0.0:7900->7900/tcp, :::7900->7900/tcp   node
2a535fa6a215   symbolplatform/symbol-rest:2.4.3           "npm start --prefix …"   8 hours ago   Up 8 hours   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   rest-gateway
6ebfd91bf262   symbolplatform/symbol-server:gcc-1.0.3.6   "/bin/bash /symbol-c…"   8 hours ago   Up 8 hours                                               broker
9fcd0c76d07f   mongo:5.0.15                               "docker-entrypoint.s…"   8 hours ago   Up 8 hours   27017/tcp                                   db
```

起動したら `http://〇〇.〇〇〇.〇〇〇.〇〇:3000/node/health` などでノードの状態を確認してください。 

mainnetは以上です。

## testnet構築

testnetは少し複雑です。
これより以降の作業は全てtestnetフォルダ内で行います。

### 各種設定ファイルの生成

```bash
$ symbol-bootstrap config -a dual -p testnet -c mypreset.yml
$ symbol-bootstrap compose
```

myprese.ymlはご自身の設定を用いてください。

例）
```mypreset.yml
nodePort: 7901
unfinalizedBlocksDuration: 10m
maxChainBytesPerSyncAttempt: 10MB
messageSynchronizationMaxResponseSize: 5MB
blockDisruptorMaxMemorySize: 1000MB
```
:::note warn
testnetのmypreset.ymlでは`nodePort: 7901`は必ず入れてください
:::

### スクリプトの配置

以下の３つのスクリプトをtestnetフォルダ直下に配置します。

```startNode.sh
#!/bin/bash

mkdir -p target/databases
mkdir -p target/databases/db
mkdir -p target/nodes/node/data
docker-compose -p testnet -f target/docker/docker-compose.yml up --remove-orphans --detach
```

```stopNode.sh
#!/bin/bash

docker-compose -p testnet -f target/docker/docker-compose.yml stop
```

```resetData.sh
#!/bin/bash

rm -fr target/nodes/node/data
rm -fr target/nodes/node/logs
rm -fr target/gateways/rest-gateway/logs
rm -fr target/databases/db/*
```

### 実行権限を付与

```bash
$ chmod +x startNode.sh
$ chmod +x stopNode.sh
$ chmod +x resetData.sh
```

### 設定ファイルの書き換え

testnet用の以下の設定ファイルを書き換えていきます。

#### docker-compose.yml

`target/docker/docker-compose.yml`

※特にこだわりがなければ、docker-compose.ymlはコピペで大丈夫です。

変更点

- コンテナ名が被らないように修正
- portの修正
- ネットワークが被らないように修正

```docker-compose.yml
version: '2.4'
services:
    testnet-db:
        user: '1000:1000'
        environment:
            MONGO_INITDB_DATABASE: catapult
        container_name: testnet-db
        image: mongo:5.0.15
        command: mongod --dbpath=/dbdata --bind_ip=testnet-db --wiredTigerCacheSizeGB 2
        stop_signal: SIGINT
        working_dir: /docker-entrypoint-initdb.d
        volumes:
            - ./mongo:/docker-entrypoint-initdb.d:ro
            - ../databases/db:/dbdata:rw
    testnet-node:
        user: '1000:1000'
        container_name: testnet-node
        image: symbolplatform/symbol-server:gcc-1.0.3.6
        command: /bin/bash /symbol-commands/start.sh /usr/catapult ./data server broker testnet-node NORMAL true
        stop_signal: SIGINT
        working_dir: /symbol-workdir
        restart: on-failure:2
        ports:
            - '7901:7901'
        volumes:
            - ../nodes/node:/symbol-workdir:rw
            - ./server:/symbol-commands:ro
        depends_on:
            - testnet-db
            - testnet-broker
        networks:
            default:
                aliases:
                    - testnet-node
        hostname: testnet-node
    testnet-broker:
        user: '1000:1000'
        container_name: testnet-broker
        image: symbolplatform/symbol-server:gcc-1.0.3.6
        working_dir: /symbol-workdir
        command: /bin/bash /symbol-commands/start.sh /usr/catapult ./data broker server testnet-broker NORMAL
        stop_signal: SIGINT
        restart: on-failure:2
        volumes:
            - ../nodes/node:/symbol-workdir:rw
            - ./server:/symbol-commands:ro
        depends_on:
            - testnet-db
    testnet-rest-gateway:
        container_name: testnet-rest-gateway
        user: '1000:1000'
        environment:
            npm_config_cache: /symbol-workdir
        image: symbolplatform/symbol-rest:2.4.3
        command: npm start --prefix /app /symbol-workdir/rest.json
        stop_signal: SIGINT
        working_dir: /symbol-workdir
        ports:
            - '3002:3000'
        restart: on-failure:2
        volumes:
            - ../gateways/rest-gateway:/symbol-workdir:rw
        depends_on:
            - testnet-db
        networks:
            default:
                ipv4_address: 172.21.0.25
networks:
    default:
        ipam:
            config:
                -
                    subnet: 172.21.0.0/24

```

#### config-database.properties

`target/nodes/node/broker-config/resources/config-database.properties`

`target/nodes/node/server-config/resources/config-database.properties`

```diff
[database]

- databaseUri = mongodb://db:27017
+ databaseUri = mongodb://testnet-db:27017
databaseName = catapult
maxWriterThreads = 8
maxDropBatchSize = 10
writeTimeout = 10m
```

#### rest.json

`target/gateways/rest-gateway/rest.json`

```diff
{
  "network": {
    "name": "testnet",
    "description": "Symbol Sainet Network"
  },
  "port": 3000,
  "protocol": "HTTP",
  "sslKeyPath": "/symbol-workdir/restSSL.key",
  "sslCertificatePath": "/symbol-workdir/restSSL.crt",
  "crossDomain": {
    "allowedHosts": [
      "*"
    ],
    "allowedMethods": [
      "GET",
      "POST",
      "PUT",
      "OPTIONS"
    ]
  },
  "uncirculatingAccountPublicKeys": [],
  "extensions": [
    "accountLink",
    "aggregate",
    "lockHash",
    "lockSecret",
    "mosaic",
    "metadata",
    "multisig",
    "namespace",
    "receipts",
    "restrictions",
    "transfer"
  ],
  "db": {
-    "url": "mongodb://db:27017/",
+    "url": "mongodb://testnet-db:27017/",
    "name": "catapult",
    "pageSizeMin": 10,
    "pageSizeMax": 100,
    "maxConnectionAttempts": 15,
    "baseRetryDelay": 750,
    "connectionPoolSize": 10
  },
  "apiNode": {
-    "host": "node",
+   "host": "testnet-node",
-    "port": 7900,
+    "port": 7901,
    "tlsClientCertificatePath": "/symbol-workdir/api-node-config/cert/node.crt.pem",
    "tlsClientKeyPath": "/symbol-workdir/api-node-config/cert/node.key.pem",
    "tlsCaCertificatePath": "/symbol-workdir/api-node-config/cert/ca.cert.pem",
    "timeout": 1000,
    "networkPropertyFilePath": "/symbol-workdir/api-node-config/config-network.properties",
    "nodePropertyFilePath": "/symbol-workdir/api-node-config/config-node.properties",
    "inflationPropertyFilePath": "/symbol-workdir/api-node-config/config-inflation.properties"
  },
  "websocket": {
    "mq": {
-      "host": "broker",
+      "host": "testnet-broker",
      "port": 7902,
      "monitorInterval": 500,
      "connectTimeout": 10000,
      "monitorLoggingThrottle": 60000,
      "maxSubscriptions": 300
    },
    "allowOptionalAddress": true
  },
  "throttling": {
    "burst": 80,
    "rate": 60
  },
  "logging": {
    "console": {
      "formats": [
        "colorize",
        "simple"
      ],
      "level": "verbose",
      "handleExceptions": true
    },
    "file": {
      "formats": [
        "prettyPrint"
      ],
      "level": "verbose",
      "handleExceptions": true,
      "filename": "/symbol-workdir/logs/catapult-rest.log",
      "maxsize": 20971520,
      "maxFiles": 100
    }
  },
  "numBlocksTransactionFeeStats": 300,
  "deployment": {
    "deploymentTool": "symbol-bootstrap",
    "deploymentToolVersion": "1.1.10",
    "lastUpdatedDate": "2024-02-07"
  }
}
```

#### config-node.properties

`target/gateways/rest-gateway/api-node-config/config-node.properties`
`target/nodes/node/server-config/resources/config-node.properties`
`target/nodes/node/broker-config/resources/config-node.properties`

```diff
...省略

minPartnerNodeVersion = 1.0.2.0
maxPartnerNodeVersion = 1.0.255.255

# all hosts are trusted when list is empty
- trustedHosts = 127.0.0.1, 172.20.0.25
- localNetworks = 127.0.0.1, 172.20.0.25
+ trustedHosts = 127.0.0.1, 172.21.0.25
+ localNetworks = 127.0.0.1, 172.21.0.25
listenInterface = 0.0.0.0

...省略
```

### testnetの起動

```bash
./startNode.sh
```

### コンテナ確認

コンテナが正常に起動していれば以下のように出力されているはずです。
先ほど起動したmainnetのコンテナの他にtestnet-〇〇とついたコンテナが起動しているはずです。

```bash
$ docker ps
CONTAINER ID   IMAGE                                      COMMAND                  CREATED       STATUS       PORTS                                       NAMES
11a5eb582292   symbolplatform/symbol-server:gcc-1.0.3.6   "/bin/bash /symbol-c…"   8 hours ago   Up 8 hours   0.0.0.0:7900->7900/tcp, :::7900->7900/tcp   node
2a535fa6a215   symbolplatform/symbol-rest:2.4.3           "npm start --prefix …"   8 hours ago   Up 8 hours   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   rest-gateway
6ebfd91bf262   symbolplatform/symbol-server:gcc-1.0.3.6   "/bin/bash /symbol-c…"   8 hours ago   Up 8 hours                                               broker
9fcd0c76d07f   mongo:5.0.15                               "docker-entrypoint.s…"   8 hours ago   Up 8 hours   27017/tcp                                   db
202b8d270eed   symbolplatform/symbol-server:gcc-1.0.3.6   "/bin/bash /symbol-c…"   8 hours ago   Up 8 hours   0.0.0.0:7901->7900/tcp, :::7901->7900/tcp   testnet-node
360549e831d6   symbolplatform/symbol-server:gcc-1.0.3.6   "/bin/bash /symbol-c…"   8 hours ago   Up 8 hours                                               testnet-broker
06efe5e3fffa   symbolplatform/symbol-rest:2.4.3           "npm start --prefix …"   8 hours ago   Up 8 hours   0.0.0.0:3002->3000/tcp, :::3002->3000/tcp   testnet-rest-gateway
39b136897451   mongo:5.0.15                               "docker-entrypoint.s…"   8 hours ago   Up 8 hours   27017/tcp                                   testnet-db
```

起動したら `http://〇〇.〇〇〇.〇〇〇.〇〇:3002/node/health` などでノードの状態を確認してください。 

もし、`apiNode` や `db` が `down` している場合は各設定ファイルのコンテナ名やポート、ネットワーク設定を見直してください。

# 最後に

この記事では１台のサーバーで複数ノードを起動する方法をご紹介しましたが、そもそも複数ノードを１サーバで動かすメリットは多くないので（高性能マシンを除く）、あくまでも趣味の参考程度にしてください。

また、今回の方法でノードを起動すると `symbol-bootstrap start --upgrade -d`等のコマンドは使用できません。（エラーが発生します）
ですので、ノードのアップデート等があった場合は手動で設定ファイルを書き換えるか、一度フォルダを全て削除して最初からやり直すか、の２択になるかと思います（もし、他の方法で出来たら教えてください）