NEMへの技術的なズームイン
この記事はSybol/nemの技術者であるBahaさんの記事「A technical zoom-in to NEM」を機械翻訳したものです。
この記事では、NEM NISコードの技術的な理解を深め、読者がより簡単に研究を進めるための手掛かりを提供することを目的としています。
技術スタック
Java 8 (開発ブランチではJava 11)
Spring Framework (v4.3.30)
Jetty Web サーバー (v9.4.43)
Hibernate ORM (v4.3.11)
H2 データベース (v1.4.200)
Flyway(データベース移行用
Maven (依存関係とビルド管理)
高層ビュー
モジュール
NEMのコードベースはいくつかのモジュール化されたパッケージで構成されており、それぞれがプラットフォーム全体の中で特定の機能を果たしています。ここでは、主要なモジュールのいくつかを簡単に紹介します。
Core
このモジュール内のパッケージはorg.nem.core.で始まります。これらのパッケージは以下の機能を提供します。
暗号化および直列化の基本メソッド
タイマーと非同期クラス/ヘルパー
トランザクション、ステート、ノードのためのデータ型
時間プロバイダと同期
共通ユーティリティ(コレクション、エンコーダ)
Peer
このモジュール内のパッケージはorg.nem.peerで始まります。このモジュールは、以下の基本メソッドを提供します。
同期、ネットワーク状態
ノードへの接続
ノード認証
レピュテーション管理アルゴリズムの実装(EigenTrust)
Deploy
このモジュール内のパッケージは org.nem.deploy で始まります。
主な機能は、ロギング、ブートストラップ、シリアライゼーション、プロパティファイルの処理、サーバのインスタンス化です。
NIS
(NEM Infrastructure Server) NISはNEMプラットフォームのコアコンポーネントで、ブロックチェーンの整合性とセキュリティを維持する役割を担っています。トランザクションの検証、新しいブロックの作成、コア、デプロイ、ピアモジュールに応じたネットワーク全体の状態の管理といったタスクを処理します。このJavaパッケージは、NEMノードを起動するために必要なすべてを提供します。
ソースからのビルド
git clone https://github.com/NemProject/nem.git
cd nem
mvn clean package -DskipTests
NISのブートストラップ
NEMブロックチェーンでは、CommonStarterクラス(main)がNEM Infrastructure Server(NIS)ノードをブートストラップするためのエントリポイントになります。
NISノードをブートストラップするために、CommonStarterクラスは以下のタスクを実行します。
・ApplicationContextのインスタンスを作成し、コンフィギュレーションをロードします。
NisAppConfigクラスがBean(Spring IOCで管理されるオブジェクト)のインスタンスを作成する。
データソース
DBの移行(フライウェイ)
ブロックチェーンオブジェクト
キャッシュ
ハーベスタ
NisMain (*重要: PostConstructで実行されるNisMain.initメソッドをご覧ください)
その他
・httpサーバ用のNemServerBootstrapperのインスタンスを作成する。
・ウェブソケットサーバー用のNemWebsockServerBootstrapperをインスタンス化する。
ここでは、NISノードを起動する方法を説明します。
java -Xms6G -Xmx6G -cp classpath_libs org.nem.deploy.CommonStarter
理想的には、本番環境でNISノードを動作させるためには、最低6GBのRAMが必要です。
コンフィギュレーション
NEMのコードベース内には、いくつかの設定ファイルがあります。
config(.*).properties
db.properties
logalpha.properties
設定を上書きするためには、そのファイルがクラスパスに配置されている必要があります。
config(.*).properties file
以下は、config.propertiesの設定階層です。
NisConfigurationPolicy
NisConfiguration
CommonConfiguration
merge:
config-default.properties
config.properties
config-user.properties
階層内では、config.propertiesはconfig-default.propertiesを上書きし、config-user.propertiesが定義されていれば、config.propertiesの値を上書きすることになります。
以下は、設定可能な値の一覧です。
nem.folder = %h/nem
nem.protocol = http
nem.host = 127.0.0.1
nem.httpPort = 7890
nem.httpsPort = 7891
nem.websocketPort = 7778
nem.network.nemesisFilePath:
nis.bootKey:
nis.bootName:
...
db.properties file
このファイルには、データベース接続の設定が含まれています。通常、このファイルの値は更新する必要はありません。
以下は、デフォルトの設定ファイルの内容です。
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:${nem.folder}/nis/data/nis5_${nem.network};DB_CLOSE_DELAY=-1
jdbc.username=
jdbc.password=
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.show_sql=false
hibernate.use_sql_comments=false
hibernate.jdbc.batch_size=20
hibernate.query.startup_check=false
flyway.locations=db/h2
logalpha.properties
NEMはjava.util.loggingを使用しています。デフォルトのロギングレベルはINFOです。
デフォルトのロギングレベルを変更する必要がある場合は、以下に示すレベルのいずれかを使用することができます。
OFF
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
ALL
以下は、デフォルトの設定ファイルです。
handlers = java.util.logging.ConsoleHandler, java.util.logging.FileHandler
.level = INFO
java.util.logging.ConsoleHandler.formatter = org.nem.deploy.ColorConsoleFormatter
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.FileHandler.formatter = org.nem.deploy.NemFormatter
java.util.logging.FileHandler.limit = 100000000
java.util.logging.FileHandler.count = 100
java.util.logging.FileHandler.pattern = ${nem.folder}/nis/logs/nis-%g.log
java.util.logging.SimpleFormatter.format = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %5$s%6$s (%2$s)%n
REST API
REST APIコントローラは、org.nem.nis.controllerパッケージの下に存在します。コントローラクラスの一部を紹介します。
AccountController
AccountInfoController
AccountTransfersController
BlockController
ChainController
ここでは、コントローラの1つである AccountTransfersController にスポットを当ててみましょう。
これはSpring Web Frameworkのアノテーションで、クラスがRESTfulなWebサービスを提供するコントローラであることを示し、クラス内のメソッドがWebリクエストを処理してレスポンスボディを返すことを意味します。
NEM APIのアノテーション。
AuthenticatedApi : ノードのチャレンジパラメータ(ランダムな64バイトのペイロード)を期待し、チャレンジデータを秘密鍵で署名し、呼び出し側が検証できるように署名がレスポンスに添付されます。
P2PApi : ピアから呼び出せることを示すマーカアノテーション
ClientApi : クライアントが呼び出し可能なAPIであることを示すマーカアノテーション
PublicAp i: 公開APIを表すマーカーアノテーション(全てGETメソッドベースであるべき)、全てのPublicApiはClientApiでもある
TrustedAp i: ローカルからの呼び出しを許可する(リモートからの呼び出しを防ぐ)
これらのアノテーションを検索することで、コード内の関連するAPIエンドポイントを見つけることができます。
P2Pエンドポイントについては org.nem.core.node.NisPeerId クラスを、クライアントAPIエンドポイントについては org.nem.core.connect.client.NisApiId を参照してください。
ピア・トゥー・ピアのコミュニケーションはどのように行われるのですか?
ノードが起動すると、ピアネットワークブート中に、peer-config_[mainnet|testnet].json ファイルから初期(事前に信頼された)ピアリストがロードされます。
ブート中に、ピアノードをリフレッシュするために REFRESH という名前で作成されたスケジュールタスクがあります (PeerNetworkScheduler.addTasks にあります)。
NodeRefresher (スケジュールされたタスク REFRESH によって起動) は、node/info および /node/peer-list/active への認証された呼び出しを行うことによってピアリストを更新し、ピア (PreTrustAwareNodeSelector によって選択) 上のローカルキャッシュに情報を保存します。
ピアは @P2PApi アノテーションされた API エンドポイントを呼び出し、チャレンジを送信して返された署名(期待するノードによって署名されているかどうか)を検証することで主に認証されます。
仲間はどのように仲間を信頼するのか?EigenTrustアルゴリズム、Node Experience
起動中に、以下のような複雑な遅延戦略で作成されたスケジュールタスクupdateNodeExperiencesが存在します。
このタスクが起動すると、NodeExperiencesUpdaterは(PreTrustAwareNodeSelectorで選択した)ピアの/node/experiencesに認証されたコールを行い、NodeExperiencesPair(これはノードの信頼度計算に使われる)をローカルキャッシュに格納します。
NodeExperienceは、あるノードが他のノードと交わした経験を表し、successfulCalls、failedCalls、lastUpdateTimeフィールドから構成される。
そして、この情報はトラストスコアの計算に使用される。
Eigentrust(++) Algorithm
EigenTrust++は、ピアツーピア(P2P)ネットワークにおける評価システムの精度と効率を向上させるために開発された、評価管理アルゴリズムである。ピアの集団行動を利用して、個々のピアの信頼性を評価するという考えに基づいています。
"ノードの評価システムを持つことで、ノードは他のノードの信頼値に応じて通信相手を選択することができる。また、通信相手の選択はノードの誠実さにのみ依存し、重要性には依存しないので、これはネットワークの負荷のバランスを取るのに役立つはずです。"
https://nemproject.github.io/nem-docs/pages/Whitepapers/NEM_techRef.pdf
EigenTrust++アルゴリズムは、ローカルおよびグローバル・レピュテーション情報の組み合わせを使用して、ノードのレピュテーションを計算します。ローカル・レピュテーションはノードの近隣との直接的な相互作用に基づき、グローバル・レピュテーションはネットワーク全体の集合的な振る舞いに基づいています。
EigenTrust アルゴリズムでは、悪意のあるノードが共謀して、正直なノードには低い信頼値を、不正なノードには高い信頼値を報告する可能性があります。このリスクを軽減するために、NEMはEigenTrust++アルゴリズムを使用しています。このアルゴリズムは、ノードの信頼性を、そのノードが他のノードと共有した経験に基づいて追加的に計算し、使用します。
NEMのコードではEigenTrustPlusPlusクラスがEigenTrustクラスを継承しています。
ブロック同期
すでに述べたように、定期的なタスクは、PeerNetworkSchedulerクラスで作成されるスケジュールタスクで処理されます。
スケジュールされたタスクの1つは、SYNCという名前のPeerNetwork.synchronize()で、3秒ごとに実行されるようスケジュールされています。
このスケジュールタスクは、以下の画像の呼び出し階層にあるように、最終的にBlockChain.synchronizeNodeを実行します。
ブロック同期ロジックは、次の図のようになります。
注意点としては、同期中に BlockChainConfigurationBuilder.blockChainRewriteLimit (default 360) を超えると、同期が成功しません(その場合、一から同期をする必要があります)。
Broadcasting data
ブロードキャストされるデータ(Transaction(s)、Block(s))はBroadcastBufferにenqueueされる。BROADCAST BUFFERED ENTITIESという名前のスケジュールされたタスクは、1秒ごとにバッファリングされたエンティティをピアにプッシュします。
このプロセスでは、PushService.pushTransaction()およびPushService.pushBlock()メソッドが使用されます。
時刻同期
NEMブロックチェーンは、TIME SYNCHRONIZATIONという名前のスケジュールされたタスクによってトリガーされる、カスタムの時間同期ロジックを使用しています。最初は1分ごとに同期し、時間が経つにつれて、成熟した後は9時間ごとに同期するようになります。
以下の画像で時間同期ロジックを説明しています(詳しくは https://nemproject.github.io/nem-docs/pages/Whitepapers/NEM_techRef.pdf をご覧ください)。
コード中のタイムサンプル収集を行っているNisTimeSynchronizerクラスを参照してください。
Harvesting
FORAGINGというスケジュールされたタスクがあり、毎秒ハーベスティングロジックを実行します(PeerNetworkScheduler.addForagingTaskを参照)。
HarvestingTask.harvestメソッドはこのスケジュールされたタスクによって呼び出され、まず期限切れのトランザクションを落とし、次にunlockedAccount(そのノードにデリゲートされているアカウント)ごとにブロックをハーベスティングしようとします。
state.isHit()メソッドは、計算されたヒット数がターゲットより小さい場合はヒット、つまりブロックの候補として成功したことを確認します。以下の検証がすべて成功し、ブロックスコアが他の候補の中で最も良い場合です。
ここでは、ヒット値や目標値の算出方法を説明します。
Wrapping up
この記事では、NEMのコードベースを詳しく見てみようと思いました。きっと、表面にはほとんど傷がついていないでしょう。ここでの目標は、短い記事の中で、より理解を深めるためにフォローしやすいように、いくつかのリードを公開することでした。お役に立てれば幸いです :)
お読みいただきありがとうございました。
この記事が気に入ったらサポートをしてみませんか?