Blockchain
仮想通貨
NEM
catapult
暗号通貨

bridgeネットワーク上にCatapultネットワーク作ってみた(フルバージョン)

2018.11.08 BisonFinalized (pub) version-0.2.0.2 に伴い記事を一部修正しています
NemesisBlock生成時に使うcatapult.tools.nemgenの引数、オプションを変えました。

概要

Dockerのbridgeネットワークを作り、その中に図で示すようなネットワークを作ることを目標とする。

20181023_creating_my_catapult_network-1.png

catapultがビルドされた状態から始めるためのイメージは 44uk さんのものを基にしたものを使わせていただいています。

[Github : 44uk/catapult-server-docker]
https://github.com/44uk/catapult-server-docker

上記のDockerファイルの 98-113 行を消したものを使いました。(ビルド直後の状態にしたかったので)

なお、構成としては tech-bureau/catapult-service-bootstrap と同じで、こっちのほうが断然使い勝手が良いので、ネットワークが出来た状態から何かしたい場合にはそちらを使うのをおすすめする。

[Github : tech-bureau/catapult-service-bootstrap]
https://github.com/tech-bureau/catapult-service-bootstrap

また、今回はエラー吐かせながらやったために無駄に長い。ビルドした状態からノードを立ち上げたいだけであれば、 planet★箒星 さんの記事を読んで進めるのをおすすめする。

[nem catapult.server 実行]
https://hackmd.io/s/BkpxBlsRz

[Qiitaの方にも色々書いてくれてます]
https://qiita.com/planethouki

1.新規ネットワーク生成

まず、今回使う為のネットワークを作る。

$ sudo docker network create my_catapult_network

確認

$ sudo docker network ls
NETWORK ID          NAME                                        DRIVER              SCOPE
1337cecfc3a4        bridge                                      bridge              local
65f46a9eacca        host                                        host                local
9fae70d430b5        my_catapult_network                         bridge              local
2f1080053878        none                                        null                local

Driver bridgeのネットワークが出来た。ここに各コンテナを所属させていく。

2.ノード用コンテナの生成

peerノードもAPIノードも元は同じなので、コンテナを先に全て作ってしまう。

for-peer1-node
$ sudo docker run -i -t --net=my_catapult_network --name cat-peer-1 test-catapult /bin/bash
for-peer2-node
$ sudo docker run -i -t --net=my_catapult_network --name cat-peer-2 test-catapult /bin/bash
for-api-node
$ sudo docker run -i -t --net=my_catapult_network --name cat-api test-catapult /bin/bash

全てのノードが新規に作成したネットワークに参加していることを確認します。

$ sudo docker network inspect my_catapult_network
[
    {
        "Name": "my_catapult_network",
 <省略>
        "Containers": {
            "0d611f1d43cd094ddd2f56c60fb86856bfdd2a548473e54cf473ed3480fa4c2c": {
                "Name": "cat-api",
                "EndpointID": "6cd7db2469bb4774bcb018879680cc20e63d85d055702aa3214d5f7235210b8d",
                "MacAddress": "02:42:ac:14:00:04",
                "IPv4Address": "172.20.0.4/16",
                "IPv6Address": ""
            },
            "2f935d0a6388b1d4aab58f9b1269f22c7d1eaa8596b3ac343531734474ce594c": {
                "Name": "cat-peer-2",
                "EndpointID": "e637218fba42f4e3a6fe3346ccf0443b52098b4e74a2af9612d32c75b59e3850",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/16",
                "IPv6Address": ""
            },
            "54a75d8123dc3450b615b550af3d431007735adc696a7eccd8c0fdddc1f5e8ab": {
                "Name": "cat-peer-1",
                "EndpointID": "215a21c2d98e5abebd4a23223ea02731d716e932978323ace9069b153761e19d",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/16",
                "IPv6Address": ""
            }
        },
<省略>

ノード用のコンテナが生成されてアドレスが割り振られました。

3.KeyPair/Addressの生成

色々なところで使うことになるので、先に秘密鍵、公開鍵、アドレスのセットを用意しておく。

とりあえず、peer-1用のコンテナに入り、最初の場所を確認しましょう。

peer1:~/_build$ pwd
/tmp/catapult-server/_build

鍵ペアとアドレスのランダム生成は_build/binディレクトリ内にあるcatapult.tools.addressを実行するだけで済む。

peer1:~/_build$ ./bin/catapult.tools.address -g 10 -n mijin-test
Address Tool
Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
catapult version: 0.1.0.2 dc1be9f [master]

Address Tool Initializing Logging...
loading configuration from "../resources/config-logging.properties"

--- generating 10 keys ---
           private key: 116FE8C4901885065C19B49AB8FA901E907141D380F572F027DA470BD82A2C14
            public key: 5096DB3B7D25F49F0D3D1DE73E4C39B7FD556ABA4B1E4D39332C3BC99B97C06D
  address (mijin-test): SDZIX4TG7CYGTFPZZOMVMVMOLNUKULIQCLCSBBLB

           private key: 70D1774C675DFF45819F4A913742D9E567D4024CD5A3455D279E49792DFCE5E4
            public key: F59240141852736BC2A23832833EB2114CE037B0066217E09207DDFEA0E08478
  address (mijin-test): SA3DUQXABBXTNFH5SNPHUQM64TQNWIFEG4ZZRUYJ

           private key: ADA44598D1809AEC7ECE07FCA45EB3F5128AC8C5E0C9172E53E875888C447984
            public key: A62C50240CF8905D759CEF941CB9D2C3F2F00C5D2E6CF72A612B54C7AAAB7E26
  address (mijin-test): SBJLWLE7ZKMBJC7K76KS24WYC26JVTLUWM27USNS

           private key: 2D8C4D365D6C347B533FEDE8AC6D256F839879D5EFB123251B49F61149216908
            public key: AFDCFE113E7820C97C5715DE95F0C43F810643CEFBCB979C78E5F863DBC5372F
  address (mijin-test): SBDVJLQWMGJFAKJ56PJAQCRJI3CHK5XYVLN2YHLC

           private key: E383335EE7BB765DBE453FE509735D1D5C45C64E0E3950AD1156728203146476
            public key: 09AD9530E6B611E609679ABC544C6E5B76F5E0B3A1C956F2E95988B72DD624A5
  address (mijin-test): SCSVJOFEQMEPWRPBZ6CTLS7UQKD53TQHYKJS7IBY

           private key: E04D404AF24AF83FBAF354A99BF628B3794F6372226C7EC95CC14F4423A7ECE1
            public key: 0245B4DDC15136611E4AA2BAC7DE8F826A38A9D448E0D3C7F07BD2DBE614CA8C
  address (mijin-test): SB3M5DT2GAKQL4JHNQBWCCGA3B5DYZVKX6FSIJVT

           private key: 0D0A06A4EDC28E12683DAD3994404CAD162F6BEE1399511ED57A5F1BB0681C53
            public key: D47F30791EDDBDE77CCE413159FFE8E99146383CBE3CD7A450871939DB751B69
  address (mijin-test): SACVFLZDUFWGF5XF5GRU4SAE7RJFRTLHPC36ZEZE

           private key: E2DA6A70F76D72744D8A93C5A38EA85DC56A889488DC6C8464AB392E812EF5BC
            public key: 77825D19F50D389FDD67E78443CF9354F5CA88C2C40A87664918D3C28673013A
  address (mijin-test): SCHRC7P5XLH6QJJZDYD4TB77FVXDG63PW7YDPQOV

           private key: 53CE6D6D001665FFCC1B56261F757A2BC034F905C0E5958D0D40104A1D20FD08
            public key: 550BEC4CFA7159E986CDB5684025D79FF030B93EBDC290F89FAC61152C5ECAE2
  address (mijin-test): SBVL2ODBQBI5B6ILHICUWKKW22KELQDP576DFJFA

           private key: DF7A6F65FECDF059D1828AC031B67DD81FC49F52FF6AEB8DE9856008BD7CEF7D
            public key: FADD0425ED62B3792983D225AEDF2F3F8565353B93E64B336BFC9443583423E6
  address (mijin-test): SDHZLOWIKOS563XMF6XGOKDHHBPQGXH7KRCI2BD6

これらは度々使うので何処かに控えておく。

4.peerノードの設定

ここからはpeerノードの設定をしながら起動していく。

4-1.config読み込み

とりあえず何も考えずに_build/binディレクトリ内にあるcatapult.serverを実行してみる。catapult.serverはサーバーの実行コマンド。

peer1:~/_build$ cd bin

peer1:~/bin$ ./catapult.server

Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
catapult version: 0.1.0.2 dc1be9f [master]
loading resources from "../resources"
[2018-10-23 04:42:14.700000] [0x00007f6942870740] [fatal]   aborting load due to missing configuration file: "../resources/config-network.properties"
unhandled exception while loading configuration!
/tmp/catapult-server/src/catapult/config/ConfigurationFileLoader.h(38): Throw in function TConfiguration catapult::config::LoadConfiguration(const boost::filesystem::path&, TConfigurationLoader) [with TConfigurationLoader = catapult::config::LoadIniConfiguration(const boost::filesystem::path&) [with TConfiguration = catapult::model::BlockChainConfiguration]::<lambda(const auto:2&)>; TConfiguration = catapult::model::BlockChainConfiguration]
Dynamic exception type: boost::exception_detail::clone_impl<catapult::catapult_error<std::runtime_error> >
std::exception::what: aborting load due to missing configuration file

早速エラーが出た。エラーログを読んでみる。文字を追っていくと、なんだか良くわからないけど、最初にloading resources from "../resources"が見える。つまり、_build/resourcesから何かを読み込もうとしている。そして、LoadConfigurationの実行でエラーを吐いているpathという文字も見える。一番最後にはaborting load due to missing configuration fileとある。ここでやっと、どうやら_build/resourcesの中を読み込んだけどconfigファイルを見つけられなかったようだと分かる。

試しに中身を見てみると、configファイルらしきものがない。

peer1:~/bin$ ls ../resources
CMakeFiles  CTestTestfile.cmake  Makefile  cmake_install.cmake

ということで、resourcesの中身を_buildの上にあるresourcesディレクトリから全部引っ張ってくる。

peer1:~/bin$ cp ../../resources/* ../resources

peer1:~/bin$ ls ../resources

CMakeFiles           Makefile                    config-harvesting.properties  config-network.properties  config-task.properties      peers-api.json
CMakeLists.txt       cmake_install.cmake         config-logging.properties     config-node.properties     config-timesync.properties  peers-p2p.json
CTestTestfile.cmake  config-database.properties  config-messaging.properties   config-pt.properties       config-user.properties

これで良いはず。もう一回catapult.serverを実行してみる。

peer1:~/bin$ ./catapult.server
Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
catapult version: 0.1.0.2 dc1be9f [master]
loading resources from "../resources"
loading configuration from "../resources/config-network.properties"
loading configuration from "../resources/config-user.properties"
loading configuration from "../resources/config-logging.properties"
loading configuration from "../resources/config-node.properties"
2018-10-23 05:06:23.481354 0x00007fae5cdcc740: <warning> (io::FileLock.cpp@74) LockOpen failed: 2
2018-10-23 05:06:23.481450 0x00007fae5cdcc740: <fatal> (server::ServerMain.cpp@91) could not acquire instance lock "../data/file.lock"

loading configuration from "../resources/config-network.properties"が見える。どうやら読み込んでくれたようだ。別のエラー吐いてるけどな。

[ configの読み込み場所について ]
configファイルの読み込場所は、何も設定しなければ../resourcesを探しに行く。なので、実はbinディレクトリに入らず、_buildディレクトリ上で./bin/catapult.serverを実行すると、_buildディレクトリの上のディレクトリにある/tmp/catapult-server/resourcesを読み込みに行くので、エラーを吐かない。

もし任意のパスにあるresourcesディレクトリからconfigファイルを読ませたい場合は、catapult.serverに実行時引数としてパスを渡してやれば良い。
例えば、/home/resourcesの中にconfigファイルが詰まっているとするなら、

~/bin$ ./catapult.server /home

としてやれば良い。一度適当なパスでcatapult.serverを実行してみれば分かると思う。

では、次のエラーを読んでみる。

4-2.ファイルロック

4-1に戻ってエラーを見てみる。

2018-10-23 05:06:23.481354 0x00007fae5cdcc740: <warning> (io::FileLock.cpp@74) LockOpen failed: 2
2018-10-23 05:06:23.481450 0x00007fae5cdcc740: <fatal> (server::ServerMain.cpp@91) could not acquire instance lock "../data/file.lock"

could not acquire instance lock "../data/file.lock"とある。インスタンス(サーバー)を起動しようと思ったけどlockが取れませんでしたということらしい。

とりあえずdataディレクトリがない。作らないといけない。file.lockは多分起動時に生成されるやつだろう。

peer1:~/bin$ mkdir ../data

これで再びcatapult.server実行してみる。

peer1:~/bin$ ./catapult.server

Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
catapult version: 0.1.0.2 dc1be9f [master]
loading resources from "../resources"
loading configuration from "../resources/config-network.properties"
loading configuration from "../resources/config-user.properties"
loading configuration from "../resources/config-logging.properties"
loading configuration from "../resources/config-node.properties"
2018-10-23 05:32:17.255633 0x00007f91b6fb6740: <info> (server::ServerMain.cpp@61) booting local node with public key 43EEB17F0BAB10DD51AB70983C25200A1742D31B3B7B54C38C34D7B827B26EED
<省略>

なんか動いた。またすぐエラー吐いて止まったけど。dataディレクトリを作るだけでよかったみたい。

4-3.Nemesisブロックの生成

ログを見てみると、スレッドプール作ったり、extensionの読み込みをしたり、pluginの読み込みをしていることが分かる。ここは正常に動いているようだが、その次で止まった。エラーログを見てみる。

2018-10-23 05:32:17.604834 0x00007f91b6fb6740: <error> (io::RawFile.cpp@193) couldn't open the file ../data/00000/00001.dat (invalid)

ということで、先程作ったdataディレクトリに00000/00001.datが無いよということらしい。

ここで、何も分からなければ詰むかも知れないが、この手のファイルはブロックデータが入るファイルだ。NIS1やビットコインコアとかでもそうだったはず。

なので、Nemesisブロックを生成しないといけない。NemesisブロックはBitcoinで言うところのGenesisブロックで、ブロックチェーンの中で一番最初に生成されるブロック。

この生成にはbinディレクトリ内にあるcatapult.tools.nemgenを使う。これにNemesisブロック生成用のconfigファイルを渡してあげればその設定に沿ってNemesisブロックデータを生成してくれる。configファイルは/tmp/catapult-server/tools/nemgen/resources/mijin-test.propertiesのを拝借する。

まずは、mijin-test.propertiesをコピーする。(resourcesディレクトリに入れる)

peer1:~/bin$ cp /tmp/catapult-server/tools/nemgen/resources/mijin-test.properties ../resources

では、mijin-test.propertiesの中身をいくつか抜粋してみてみる。

[nemesis]

[nemesis]

networkIdentifier = mijin-test
nemesisGenerationHash = 57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6
nemesisSignerPrivateKey = C06B2CC5D7B66900B2493CF68BE10B7AA8690D973B7F0B65D0DAE4F7AA464716

Nemesisブロックの情報を指定する。Nemesisブロックといっても、扱いとしてはあくまでブロックなので、Nemesisブロックに続くブロックと同様GenerationHash署名者の署名が必要になる。それを指定している。これらは後に触るconfig-network.propertiesに影響してくる。とりあえず、最初に生成した鍵ペアのものに書き換える。

[nemesis]

networkIdentifier = mijin-test
nemesisGenerationHash = 57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6
nemesisSignerPrivateKey = 116FE8C4901885065C19B49AB8FA901E907141D380F572F027DA470BD82A2C14

これでNemesisブロックの署名者の秘密鍵が変わりました。この秘密鍵に対する公開鍵はこのcatapultネットワークに参加する全てのノードに共有されないといけないので、控えをなくさないでください。NemesisBlockのGenerationHashは何でも良いけどこれもネットワーク全体で共有されるもの。こっちは書き換えていない。

[output]

[output]

cppFile = ../tests/test/core/mocks/MockMemoryBasedStorage_data.h
binDirectory = ../seed/mijin-test

catapult.tools.nemgenので生成されるファイルの出力先を指定する。

cppFile
test時に使うMockMemoryBasedStorage_data.hを生成先。
空欄であれば生成しない。
binDirectory
nemesisブロックのバイナリファイルの生成先する。

cppFileは私が見た限りだと、テストに使うモックストレージの為のNemesisブロックデータを含むヘッダファイルを生成する模様(テスト時に使う擬似ストレージをメモリ上に作るためのもの)。なので今回は空欄で構わないはず。結果、cppFileのパスを消して下記のようになります。

[output]
cppFile =
binDirectory = ../seed/mijin-test

ということは、binディレクトリ上でcatapult.tools.nemgenを実行すると、Nemesisブロックが記録されたバイナリファイルが/tmp/catapult-server/_build/seed/mijin-testに生成されるということになるはずなので、ディレクトリを生成しておきます。

peer1:~/bin$ mkdir -p ../seed/mijin-test

初期分配アドレス

[output]以降は、初期分配モザイクとそのアドレスになっている。nem:xemeur:euroをNemesisブロックで生成していることが分かる。xemがないと困るので、初期分配アドレスのいくつかを、事前に生成したアドレスに書き換えておく。

config-network.properties

ブロックチェーンの設定はconfig-network.propertiesの書き換えで行う。この設定は、NemesisBlockを生成する時に変更をかける必要がある、

中身を見たら、一番最初の[network]にNemesisProperties(mijin-test.properties)と似た項目があることが分かる。違いは、privateKeyがpublicKeyになっていることだ。このpublicKeyを、NemesisブロックのprivateKeyに対応するものに変えてあげる。

peer1:~/bin$ vi ../resources/config-network.properties

[network]

identifier = mijin-test
publicKey = B4F12E7C9F6946091E2CB8B6D3A12B50D17CCBBF646386EA27CE2946A7423DCF
generationHash = 57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6

これが

[network]

identifier = mijin-test
publicKey = 5096DB3B7D25F49F0D3D1DE73E4C39B7FD556ABA4B1E4D39332C3BC99B97C06D
generationHash = 57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6

こうなる。


これでNemesisブロック生成の準備が出来たので、catapult.tools.nemgenを実行する。

peer1:~/bin$ ./catapult.tools.nemgen -r .. -p ../resources/mijin-test.properties

[catapult.tools.nemgen のオプション]

-r : config(.properties)ファイルが詰まったresourcesディレクトリのパス(デフォルトは..)

-p : NemesisPropertiesFile(今回だとmijin-test.propertiesファイル)のパス

その他のオプションは-hのヘルプを参照

ここで、エラーログが吐き出されるはずです。hashes.dat has invalid size (0)だそう。 そもそもそんなファイル作ってないけどな。 とりあえず中身を見てみます。

peer1:~/bin$ ls ../seed/mijin-test          
00000

00000というディレクトリが生成されている。

peer1:~/bin$ ls ../seed/mijin-test/00000
00001.dat  hashes.dat

00001.dathashes.datが生成されている。

peer1:~/bin$ xxd ../seed/mijin-test/00000/00001.dat
00000000: 7d10 0000 6229 8db4 bbcb df40 e1e9 0902  }...b).....@....
00000010: 4e1f d91f 37d3 bd97 5136 4548 10ad 61a1  N...7...Q6EH..a.
00000020: 65e6 2a7b 6e58 548a c095 89d8 c672 b97f  e.*{nXT......r..
00000030: cf89 6018 a98b 63cd 7732 5d2d a8c0 5918  ..`...c.w2]-..Y.
00000040: 2222 8107 5096 db3b 7d25 f49f 0d3d 1de7  ""..P..;}%...=..
00000050: 3e4c 39b7 fd55 6aba 4b1e 4d39 332c 3bc9  >L9..Uj.K.M93,;.
<省略>

Nemesisブロックのデータは作られている模様(ちゃんと読んでないけど)。

peer1:~/bin$ xxd ../seed/mijin-test/00000/hashes.dat

空のファイル。 まぁ俺作ってないしな

で、調べてみたところ、どうも00000に入るhashes.datは64byte分のデータが中に書かれていないと読み込み時にエラーを吐くらしい。とりあえず64byte分のデータを入れてみる。

peer1:~/bin$ echo -n 0000000000000000000000000000000000000000000000000000000000000000 > ../seed/mijin-test/00000/hashes.dat

これでもう一度catapult.tools.nemgenをする。

peer1:~/bin$ ./catapult.tools.nemgen ../resources/mijin-test.properties
Nemesis Block Generator Tool
Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
catapult version: 0.1.0.2 dc1be9f [master]

Nemesis Block Generator Tool Initializing Logging...
loading configuration from "../resources/config-logging.properties"

2018-10-24 00:30:39.738714 0x00007f4b597cc740: <info> (nemgen::main.cpp@60) loading nemesis configuration from "../resources/mijin-test.properties"
2018-10-24 00:30:39.745981 0x00007f4b597cc740: <info> (nemgen::main.cpp@352) creating binary storage seed in ../seed/mijin-test
2018-10-24 00:30:39.746229 0x00007f4b597cc740: <info> (nemgen::main.cpp@343) nemesis block hash: 9050E352BB5BF321522A7067B78C87C3C3CD5C7C7FBC1C96FAE82F6D73974452

生成できた。hashes.datの中身を見てみる。

peer1:~/bin$ xxd ../seed/mijin-test/00000/hashes.dat
00000000: 3030 3030 3030 3030 3030 3030 3030 3030  0000000000000000
00000010: 3030 3030 3030 3030 3030 3030 3030 3030  0000000000000000
00000020: 9050 e352 bb5b f321 522a 7067 b78c 87c3  .P.R.[.!R*pg....
00000030: c3cd 5c7c 7fbc 1c96 fae8 2f6d 7397 4452  ..\|....../ms.DR

後半32byteがNemesisBlockHashに置き換わっていることがわかったが、コレで正解なのかは現時点では分からない。

とりあえずseedディレクトリ内に出来たNemesisブロックのデータをdataディレクトリ内に移動する。

peer1:~/bin$ cp ../seed/mijin-test/00000/* ../data/00000

peer1:~/bin$ ls ../data/00000。
00001.dat  hashes.dat

これでNemesisブロックを生成できた。ふたたびcatapult.serverを実行してみる。

peer1:~/bin$ ./catapult.server
Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
catapult version: 0.1.0.2 dc1be9f [master]
loading resources from "../resources"
loading configuration from "../resources/config-network.properties"
loading configuration from "../resources/config-user.properties"
<省略>

動いた。エラーを吐いているが、packetsocketの文字を見れば分かるとおり、今はまだ通信の設定を何もしていないからエラーを吐いたのだと分かる。停止はCtr-cでインタラプト送ってやれば止まる。

ここから、やっとノードの設定に入る。

4-4.peer1ノードの設定

4-4-1.config-node.properties

resourcesディレクトリ内にあるconfig-node.propertiesをいじる。

まずは[node]を見てみる。

peer1:~/bin$ cat ../resources/config-node.properties

[node]

port = 7900
apiPort = 7901
shouldAllowAddressReuse = false
shouldUseSingleThreadPool = false
shouldUseCacheDatabaseStorage = false

port,apiPortはこのままで良いので別に触らなくて良いだろう。

次に[localnode]を見てみる。

[localnode]

host =
friendlyName =
version = 0
roles = Peer

最後のrolesでpeerノードなのか、apiノードなのか、役割を切り替えられる。peer1はpeerノードにしたいのでこれもこのままで良いだろう。friendlyNameだけ指定しておく。

[localnode]

host =

friendlyName = Peer1
version = 0
roles = Peer

これでOK。

4-4-2.config-user.properties

次に、config-user.propertiesをいじる。まずは中身を見てみる。

peer1:~/bin$ cat ../resources/config-user.properties

[account]

# keys should look like 3485d98efd7eb07adafcfd1a157d89de2796a95e780813c0258af3f5f84ed8cb
bootKey = DF7A6F65FECDF059D1828AC031B67DD81FC49F52FF6AEB8DE9856008BD7CEF7D

[storage]

dataDirectory = ../data
pluginsDirectory = .

bootKeyとは、このノードに結びつく公開鍵に対する秘密鍵だ。全てのノード(正確にはそのインスタンス)は自分自身の公開鍵を持つ。ノードはその公開鍵でお互いを認識する。また、今回は使わないがcatapultノードにはSecurityModeというモードが存在し、このモードがonになっているとき、ノードは通信するパケットに署名して、確かに自分が送ったものだと証明できる。また、相手から送られてきたパケットに署名がない、相手の公開鍵で署名が検証できないときにはそのパケットを受け付けないという設定が出来る。

20181023_creating_my_catapult_network-2.png

このbootKeyに対応する公開鍵は、ノードが他のノードに接続する時に交換し合う。(チャレンジを送って、署名・検証をし合う)

20181023_creating_my_catapult_network-3.png

ちなみに、SecurityModeのON,OFFはconfig-node.propertiesで行う。outgoingSecurityModeincomingSecurityModesの項目を変える。

とにかく、bootKeyを事前に作った使ってないアドレスの秘密鍵に変える。

4-4-3.peers-p2p.json

ノードが最初にネットワークにつながるとき、そもそも何処に繋げばよいのかノードは知らない。知るわけがない。一度ネットワークにつながれば、お互いが知っているノードのIPアドレスを交換し合うことで接続候補ノードを増やしていけるが、最初だけは教えてあげないといけない。それはpeers-p2p.jsonに記載してあげることで教えてあげられる。

peer1:~/bin$ cat ../resources/peers-p2p.json
{
  "_info": "this file contains a list of all trusted peers and can be shared",
  "knownPeers": [
    {
      "publicKey": "0000000000000000000000000000000000000000000000000000000000000000",
      "endpoint": {
        "host": "127.0.0.1",
        "port": 7900
      },
      "metadata": {
        "name": "peernode",
        "roles": "Peer"
      }
    }
  ]
}

ここで、前項でも言ったが相手のノードを公開鍵でもって認識するので(IPアドレスもだけど)、あいての公開鍵を知る必要がある。これがわからないと接続時のチャレンジが失敗して接続が切られる。peer2で使う予定の秘密鍵に対する公開鍵をセットしておく。また、IPアドレスはDockerコンテナに割り当てられたアドレスを入れておく。

peer1:~/bin$ cat ../resources/peers-p2p.json
{
  "_info": "this file contains a list of all trusted peers and can be shared",
  "knownPeers": [
    {
      "publicKey": "550BEC4CFA7159E986CDB5684025D79FF030B93EBDC290F89FAC61152C5ECAE2",
      "endpoint": {
        "host": "172.20.0.3",
        "port": 7900
      },
      "metadata": {
        "name": "peernode",
        "roles": "Peer"
      }
    }
  ]
}

ちなみに、peers-api.jsonというjsonファイルもあるが、こっちはAPIノード用のようで、peerノードはコレを読み込まない。apiノードの設定の時に使う。

4-4-4.config-harvesting.properties

peer1には、ハーベスティングもさせたいので、設定をしておく。当然、xemが必要になるので、Nemesisブロックで初期分配を割り当てたアドレスの秘密鍵を使う。

peer1:~/bin$ cat ../resources/config-harvesting.properties
[harvesting]

# keys should look like 3485d98efd7eb07adafcfd1a157d89de2796a95e780813c0258af3f5f84ed8cb
harvestKey =
isAutoHarvestingEnabled = false
maxUnlockedAccounts = 5

これが

peer1:~/bin$ cat ../resources/config-harvesting.properties
[harvesting]

# keys should look like 3485d98efd7eb07adafcfd1a157d89de2796a95e780813c0258af3f5f84ed8cb
harvestKey = 70D1774C675DFF45819F4A913742D9E567D4024CD5A3455D279E49792DFCE5E4
isAutoHarvestingEnabled = true
maxUnlockedAccounts = 5

こうなる。

これで、やっとpeer1の設定が終わった。次はpeer2だ。

4-5.peer2ノードの設定

peer2もpeerノードなので、peer1のときとやることは変わらない。bootKeypeer1で設定したものに合わせることなど、秘密鍵、公開鍵の設定、peer1のOPアドレス、公開鍵を教えてあげることが異なるところだ。やり方は全く同じ。Nemesisブロックのデータはpeer1のものを送ってあげれば楽だし、ハーベスティングの設定はしなくて良い。

ざっと設定を載せます。

config-node.properties

[localnode]

host =
friendlyName = Peer2
version = 0
roles = Peer

config-user.properties

[account]

# keys should look like 3485d98efd7eb07adafcfd1a157d89de2796a95e780813c0258af3f5f84ed8cb
bootKey = 53CE6D6D001665FFCC1B56261F757A2BC034F905C0E5958D0D40104A1D20FD08

[storage]

dataDirectory = ../data
pluginsDirectory = .

peers-p2p.json

{
  "_info": "this file contains a list of all trusted peers and can be shared",
  "knownPeers": [
    {
      "publicKey": "FADD0425ED62B3792983D225AEDF2F3F8565353B93E64B336BFC9443583423E6",
      "endpoint": {
        "host": "172.20.0.2",
        "port": 7900
      },
      "metadata": {
        "name": "peernode",
        "roles": "Peer"
      }
    }
  ]
}

dataディレクトリの中身、上記で変更した以外のresourcesディレクトリの中にある.propertiesファイルの中身はpeer1とおなじ。

では、ここでpeer1peer2をそれぞれ動かしてみましょう。接続ができ、暫く待って、peer1がハーベスティングに成功して、それをpeer2が受け取れば成功です。ネットワークを作ることが出来ました。ハーベスティングの成功時には明らかにログの出力が変わってharvestingの文字が出るので分かるでしょう。

Successfully harvested block at 3 with signer F59240141852736BC2A23832833EB2114CE037B0066217E09207DDFEA0E08478

こんなログが出ます。

失敗する人は、Dockerコンテナに割り当てられたIPアドレス、ポート、相手の公開鍵が相手のbookKeyに対応しているかを確認しましょう。


ここで、一回hashes.datの中身を見てみる。ブロックが一つ進んだが、どうなるだろうか。

peer1:~/bin$ xxd ../data/00000/hashes.dat
00000000: 3030 3030 3030 3030 3030 3030 3030 3030  0000000000000000
00000010: 3030 3030 3030 3030 3030 3030 3030 3030  0000000000000000
00000020: 9050 e352 bb5b f321 522a 7067 b78c 87c3  .P.R.[.!R*pg....
00000030: c3cd 5c7c 7fbc 1c96 fae8 2f6d 7397 4452  ..\|....../ms.DR
00000040: b2a8 9493 7a2a eb63 8291 b412 4136 f8c0  ....z*.c....A6..
00000050: 5ce8 899d 297f 67a2 dd55 8ff2 60d7 8438  \...).g..U..`..8

新しいブロックのhashが追加されている。結局一番最初の32byteは何なんだろうか。


4-6.APIノードの設定

最後にAPIノード。Nemesisブロックのデータはpeer2と同じ状態にする。

で、こうなる。

config-user.properties

[account]

# keys should look like 3485d98efd7eb07adafcfd1a157d89de2796a95e780813c0258af3f5f84ed8cb
bootKey = E2DA6A70F76D72744D8A93C5A38EA85DC56A889488DC6C8464AB392E812EF5BC

[storage]

dataDirectory = ../data
pluginsDirectory = .

次に、apiノードが最初の状態で知っているノードの情報はpeers-api.jsonに書く。peerノードの情報を追記する。

peers-api.json

{
  "_info": "this file contains a list of api peers",
  "knownPeers": [
    {
      "publicKey": "FADD0425ED62B3792983D225AEDF2F3F8565353B93E64B336BFC9443583423E6",
      "endpoint": {
        "host": "172.20.0.2",
        "port": 7900
      },
      "metadata": {
        "name": "peernode1",
        "roles": "Peer"
      }
    },
    {
      "publicKey": "550BEC4CFA7159E986CDB5684025D79FF030B93EBDC290F89FAC61152C5ECAE2",
      "endpoint": {
        "host": "172.20.0.3",
        "port": 7900
      },
      "metadata": {
        "name": "peernode2",
        "roles": "Peer"
      }
    }
  ]
}

これでapiノードが立ち上がった時に、peerノードに接続をしに行くようになる。

config-node.propertiesについては、役割をpeerからapiに変えてやる必要がある。

まずは[localnode]の設定。rolesApiにしてやる。ついでにfriendlyNameも付ける。

[localnode]

host =
friendlyName = Api
version = 0
roles = Api

次に[extensions]を変える。# api extensionsにあるものを全てtrueに、# p2p extensionsにあるものを全てfalseにする。

[extensions]

# api extensions
#   (in order for precomputation to work in all cases when enabled, `addressextraction` must be registered first
#    because it precomputes addresses of rolled-back transactions)
extension.addressextraction = true
extension.mongo = true
extension.partialtransaction = true
extension.zeromq = true

# p2p extensions
extension.eventsource = false
extension.harvesting = false
extension.syncsource = false

これで設定はOK。

次に、MongoDBを入れる。# api extensionstrueにしたものの中に、mongoがある。APIノードにはmongoDBが必要。

api:~/bin$ apt install mongodb

api:~/bin$ service mongodb start

これで完了。動けばOK、もし、何かしらのエラーを吐いてその中に[connection timeout calling ismaster on '127.0.0.1:27017']があれば、mongoDBとの接続がうまくいってないせいで動いてないのだと思われる。

mongoDB廻りは各自調べてね。(私がやった時はsystemctlが使えなかった)

なお、一度ランタイムエラーで止まってしまうと、/tmp/catapult-server/_build/dataの中にfile.lockが残ってしまって次回起動時に起動しなくなる。サーバーを起動する前にfile.lockを消そう。(エラーを見ればfile.lockが取れなかったから起動できないと出るはず)

5.restサーバーの準備

httpで通信するためにcatapult_restサーバーをapiノードの動いているコンテナに仕込んで動かします。

rest(api):/$ cd /tmp

// nodejs入れる(バージョンは8 or 9)
rest:/tmp$ git clone https://github.com/creationix/nvm.git /tmp/.nvm

rest:/tmp$ source /tmp/.nvm/nvm.sh

rest:/nvm$ nvm install v8.12.0

rest:/nvm$ nvm use v8.12.0

rest:/nvm$ node -v
v8.12.0

rest:/nvm$ cd /tmp

// yarnを使えるようにする
rest:/tmp$ apt install apt-transport-https

rest:/tmp$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -

rest:/tmp$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

rest:/tmp$ apt-get update && apt-get install --no-install-recommends yarn

rest:/tmp$ yarn -v
1.10.1

// restサーバーを入れる
rest:/tmp$ git clone https://github.com/nemtech/catapult-rest.git

rest:/tmp$ cd catapult-rest

rest:/catapult-rest$ ./yarn_setup.sh

rest:/catapult-rest$ cd rest

rest:/rest$ yarn build

正直Nodejs廻りは良く分からない。バージョン8か9のnodejs,yarnが入っていてビルドできればOKだと思う。

restサーバーの設定を行います。apiノードと通信をするので、apiノードのIPアドレス、bootKeyに対する公開鍵をセットしてください。下記のようになります。

/home/catapult-rest/rest/resources/rest.json

{
  "network": {
    "name": "mijinTest",
    "description": "catapult development network"
  },

  "port": 3000,
  "crossDomainHttpMethods": ["GET", "POST", "PUT", "OPTIONS"],
  "clientPrivateKey": "0D0A06A4EDC28E12683DAD3994404CAD162F6BEE1399511ED57A5F1BB0681C53",
  "extensions": ["aggregate", "lock", "multisig", "namespace", "transfer"],

  "db": {
    "url": "mongodb://172.20.0.4:27017/",
    "name": "catapult",
    "pageSizeMin": 10,
    "pageSizeMax": 100,
    "pageSizeStep": 25,
    "maxConnectionAttempts": 5,
    "baseRetryDelay": 500
  },

  "apiNode": {
    "host": "172.20.0.4",
    "port": 7901,
    "publicKey": "77825D19F50D389FDD67E78443CF9354F5CA88C2C40A87664918D3C28673013A",
    "timeout": 1000
  }
<省略>

要するに、restサーバーも一つのノードとして振る舞うので、自身のbootKeyにあたるものと、通信相手の公開鍵を知っておく必要があるということだと思います。

実行は下記の通り。実際にはapiノードも動かさないといけないので、どちらかをバックグラウンドで実行することになる。

rest:/rest$ cd _build

rest:_build$ node index.js

これでrestサーバの準備もOK。後はクライアントを作るだけ。

5.クライアント

ここできたらクライアントになるコンテナを生成して通信を試みます。

host$ sudo docker run -i -t --net=my_catapult_network --name cat-client ubuntu /bin/bash

全てのサーバを起動し、Restサーバに向けてリクエストを送ってみます。

client:/$ curl http://172.20.0.4:3000/block/3
{"meta":{"hash":"C814FEDF49AA2E53E6BCB3190D790B573FF74AAE25C5FF7A8FAC89D02B58EF0F","generationHash":"175B7A8463765189464108B3140936258AAA3D38EE095A2539F91F1ED19C4E7B","totalFee":[0,0],"numTransactions":0},"block":{"signature":"C95F18C42C6F1D5121E26E0211D808B7DB0E11737DAB2123F777BB231BABEF1EB3D54987FC2C5CCD5D09634744CFF27C63E42FCF8DB301FB13FD8345EDCDF40F","signer":"F59240141852736BC2A23832833EB2114CE037B0066217E09207DDFEA0E08478","version":36867,"type":33091,"height":[3,0],"timestamp":[3584828787,18],"difficulty":[3913347072,22118],"previousBlockHash":"B2A894937A2AEB638291B4124136F8C05CE8899D297F67A2DD558FF260D78438","blockTransactionsHash":"0000000000000000000000000000000000000000000000000000000000000000"}}

はい。無事ハーベスディングされたブロックが取れました。

さいごに / よく分かってないところ

  • apiノードを止めてもrestサーバは動くので独立している。mongoDBからデータを取っているから問題なく動くとして、apiノードと通信し合う必要性は?(webソケットの為?)
  • 結局、hahses.datを作るときの最初の64byteのデータに何を入れるのが正解なのか。
  • apiノードがmongoDBにブロック情報を貯めているのであれば、ファイルストレージ(datファイル)の方は不要では...?(mongoDBに何を貯めているのかも分からない)

きれいにまとめてどっかに書く。間違っているところがあればご指摘下さい。

これでオレオレネットワーク作れるね!