NetworkHeightエクステンション読んでみた

## エクステンションの概要

ピア 5 機（デフォルト値）からブロック高を読み取って、その中央値をネットワークのブロック高とするエクステンション。

自ノードのブロック高がネットワークのブロック高と離れすぎてないか確認するのに使われたりするぞ！

ソースはこちら。

https://github.com/symbol/symbol/tree/dev/client/catapult/extensions/networkheight

## ディレクトリ構成

ディレクトリ構成はこんな感じ。
シンプ〜ル！

```
networkheight
│  CMakeLists.txt
│  NetworkHeightExtension.cpp
│
├─src
│      CMakeLists.txt
│      NetworkChainHeight.h
│      NetworkHeightConfiguration.cpp
│      NetworkHeightConfiguration.h
│      NetworkHeightService.cpp
│      NetworkHeightService.h
│
└─tests
        CMakeLists.txt
        NetworkHeightConfigurationTests.cpp
        NetworkHeightServiceTests.cpp
```

## networkheight ディレクトリ

### NetworkHeightExtension.cpp ファイル

```cpp
extern "C" PLUGIN_API
void RegisterExtension(catapult::extensions::ProcessBootstrapper& bootstrapper) {
	catapult::networkheight::RegisterExtension(bootstrapper);
}
```

はじまりの地。ここから始まる。
同じファイルにある`RegisterExtension`を呼び出してる。

---

```cpp
namespace catapult { namespace networkheight {
	namespace {
		void RegisterExtension(extensions::ProcessBootstrapper& bootstrapper) {
			// register service(s)
			auto config = NetworkHeightConfiguration::LoadFromPath(bootstrapper.resourcesPath());
			bootstrapper.extensionManager().addServiceRegistrar(CreateNetworkHeightServiceRegistrar(config));
		}
	}
}}
```

「はじまりの地」から呼び出されるメソッド。
リソースファイルを読み込んで、`CreateNetworkHeightServiceRegistrar`をサービスとして登録している。

## src ディレクトリ

### NetworkChainHeight.h ファイル

```cpp:NetworkChainHeight.h
namespace catapult { namespace networkheight {
	/// Atomic network chain height.
	using NetworkChainHeight = std::atomic<Height::ValueType>;
}}
```

ネットワーク上のブロック高を保存する`NetworkChainHeight`がアトミック変数として定義されています。

> アトミック変数とは、不可分な読み出しや書き込み、および読み書きを同時に行える変数である。

### NetworkHeightConfiguration.h ファイル

ソース省略。

`NetworkHeightConfiguration`構造体宣言してる程度。

### NetworkHeightConfiguration.cpp ファイル

ソース省略。

リソースファイル`config-networkheight.properties`から、`maxNodes`を取得する。

### NetworkHeightService.h ファイル

ソース省略。

橋渡し的なやつ。

### NetworkHeightService.cpp ファイル

```cpp
DECLARE_SERVICE_REGISTRAR(NetworkHeight)(const NetworkHeightConfiguration& networkHeightConfig) {
	return std::make_unique<NetworkHeightServiceRegistrar>(networkHeightConfig);
}
```

`addServiceRegistrar`で登録した`CreateNetworkHeightServiceRegistrar(config)`の定義元です。名前が全然違いますが、`DECLARE_SERVICE_REGISTRAR`で定義されてます。
ここで`NetworkHeightServiceRegistrar`を生成します。

---

```cpp
extensions::ServiceRegistrarInfo info() const override {
	return { "NetworkHeight", extensions::ServiceRegistrarPhase::Post_Remote_Peers };
}
```

サービスの登録タイミングは`Post_Remote_Peers`。リモートピアが登録された後です。

---

```cpp
void registerServiceCounters(extensions::ServiceLocator&) override {
	// no additional counters
}
```

カウンタはなし。

---

```cpp
void registerServices(extensions::ServiceLocator& locator, extensions::ServiceState& state) override {
	auto pNetworkChainHeight = std::make_shared<NetworkChainHeight>(0);
	locator.registerRootedService("networkChainHeight", pNetworkChainHeight);

	// if the local node's chain is too far behind, some operations like harvesting or
	// processing of pushed elements should not be allowed
	// for a public network this should always return true since an evil node could supply a fake chain height
	state.hooks().setChainSyncedPredicate([&storage = state.storage(), &networkChainHeight = *pNetworkChainHeight]() {
		auto storageView = storage.view();
		return networkChainHeight.load() < storageView.chainHeight().unwrap() + 4;
	});

	// add tasks
	state.tasks().push_back(CreateChainHeightDetectionTask(
			state.hooks().remoteChainHeightsRetriever(),
			m_networkHeightConfig.MaxNodes,
			*pNetworkChainHeight));
}
```

サービス登録部分。
`NetworkChainHeight`をゼロで初期化。

フックに`ChainSyncedPredicate`の処理を登録。これは自ノードのブロック高がネットワーク上のブロック高より 4 以上離れてないかチェックする処理です。他のエクステンション（harvesting 等）から呼び出されます。

https://github.com/symbol/symbol/blob/520ccf4fa9fa2606ae03716521aa1270398424b9/client/catapult/extensions/harvesting/src/HarvestingService.cpp#L109

タスクに`CreateChainHeightDetectionTask`を登録。
引数にあるフック`remoteChainHeightsRetriever`は、sync エクステンション内で定義されています。

https://github.com/symbol/symbol/blob/520ccf4fa9fa2606ae03716521aa1270398424b9/client/catapult/extensions/sync/src/NetworkPacketWritersService.cpp#L40

---

```cpp
thread::Task CreateChainHeightDetectionTask(
		const extensions::RemoteChainHeightsRetriever& remoteChainHeightsRetriever,
		uint8_t maxNodes,
		NetworkChainHeight& networkChainHeight) {
	const auto& retriever = remoteChainHeightsRetriever;
	return thread::CreateNamedTask("network chain height detection", [retriever, maxNodes, &networkChainHeight]() {
		CATAPULT_LOG(trace) << "starting chain height task";
		return retriever(maxNodes).then([&networkChainHeight](auto&& heightsFuture) {
			auto heights = heightsFuture.get();
			if (heights.empty())
				return thread::TaskResult::Continue;

			std::sort(heights.begin(), heights.end());
			auto size = heights.size();
			auto medianHeight = 1 == size % 2
					? heights[size / 2].unwrap()
					: (heights[size / 2 - 1] + heights[size / 2]).unwrap() / 2;
			auto currentHeight = networkChainHeight.load();
			if (currentHeight < medianHeight) {
				networkChainHeight = medianHeight;
				CATAPULT_LOG(debug) << "network chain height increased from " << currentHeight << " to " << medianHeight;
			}

			return thread::TaskResult::Continue;
		});
	});
}
```

`retriever`(`remoteChainHeightsRetriever`) を使って、ピア最大`maxNodes`機からブロック高を取得する。何も取得できなかったらコンテニュー。

取得できたブロック高たちをソートして中央値を求める。

現在保持しているネットワークブロック高と先ほど求めた中央値を比べ、中央値の方大きい場合保持しているネットワークブロック高を更新。

## タスクの設定

`config-task.properties`ファイル内に設定がある。

```config-task.properties
### networkheight ###

[network chain height detection]
startDelay = 1s
repeatDelay = 15s
```

デフォルトでは 15 秒間隔でチェックしてるようだ。