LoginSignup
0
0

More than 1 year has passed since last update.

SubstrateでNominated Proof of Stake(NPoS)を試してみる

Last updated at Posted at 2022-03-15

はじめに

PolkadotではNominated Proof of Stake(NPoS)というコンセンサスアルゴリズムを採用しておりますが、今回はこのNPoSをSubstrateで試してみます。
ただ、Substrateでブロックチェーンを構築するためのベースであるSubstrateテンプレートではデフォルトでProof of Authority(PoA)を採用しているため、NPoSは別で導入する必要があります。
今回の記事ではSubstrate node templateにNPoSを導入してみたのですが、結構手こずったのでどのように導入して環境構築していくか?を記事にしたいと思います。

導入手順

NPoSを導入するにあたり、Staking Palletを導入する必要がありますが、特に公式のドキュメントやチュートリアルなどはありません。ただ、導入された方もいてこちらの記事が参考になりますが、現状の最新のSubstrateテンプレートに対応していなかったため独自でStaking Palletを導入してみます。

今回の修正は上記記事のgithubソースを参考にSubstrateテンプレートとどのような違いがあるかを調査し、どのような修正をしたら良いか?を考えてNPoSを導入してみました。

最終的な修正後のソース

今回の最終的に修正したソースはこちらに上げておきました。

node部分のソース回収

Substrateテンプレートのnode箇所ではチェーン構成ファイル(chain_spec.rs)などのノードの基礎的なソースがありますが、これをNPoSに対応していく必要があります。

NPoSの導入がされているSubstrate本体のソースコードを元にします。以下コードに参考substrate側ソースをコピーします。

node/src/chain_spec.rs : 参考substrate側ソース
node/src/cli.rs : 参考substrate側ソース
node/src/command.rs : 参考substrate側ソース
node/src/rpc.rs : 参考substrate側ソース
node/src/service.rs : 参考substrate側ソース
node/Cargo.toml : 上記のソース修正に伴うpallet達の追加・更新など。substrateのtag=monthly-2022-02にする

以下ファイルは追加

node/res/flaming-fir.json : 参考substrate側ソース

Cargo.toml

palletのversionを揃えるのがややこしいです。
sp-finality-grandpa を grandpa-primitives という名前に統一します。

// sp-finality-grandpa = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", tag ="monthly-2022-02" }
grandpa-primitives = { version = "4.0.0-dev", package = "sp-finality-grandpa", git = "https://github.com/paritytech/substrate.git", tag = "monthly-2022-02" }

同様に sc-finality-grandpa

// sc-finality-grandpa = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", tag ="monthly-2022-02" }
grandpa = { version = "0.10.0-dev", package = "sc-finality-grandpa", git = "https://github.com/paritytech/substrate.git", tag = "monthly-2022-02" }

基本的には参考にしたSubstrate側ソースで全部上書きした後にCompileできるように整えていきました。

node_runtime -> node_template_runtime に修正

substrateをそのままコピーするとsubstrateソースのruntimeを参照するコードになっているので、これをSubstrateテンプレートのruntimeに合わせます。

runtime/Cargo.toml
[package]
name = "node-template-runtime"

以下が該当したので、これらのソースで node_runtime:: を node_template_runtime:: に置換します。

node/src/chain_spec.rs
node/src/command.rs
node/src/service.rs

command.rs

// use crate::{chain_spec, service, service::new_partial, Cli, Subcommand};
use crate::{chain_spec, service, service::new_partial, cli::Cli, cli::Subcommand};

rpc.rs

sc_finality_grandpa を grandpa に変更

// use sc_finality_grandpa::{
// 	FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState,
// };
use grandpa::{
	FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState,
};

runtime側の修正

runtime/src/constants.rs : 参考substrate側ソース
runtime/src/impls.rs : 参考substrate側ソース
runtime/src/lib.rs : 参考substrate側ソース
runtime/src/voter_bags.rs : 参考substrate側ソース
runtime/Cargo.toml : 上記のソース修正に伴うpallet達の追加・更新など。substrateのtag=monthly-2022-02

Validator の Session Key を設定

Validator の Session Key を取得するためにscripts/prepare-test-net.shをダウンロードします。

prepare-test-net.shの修正

そのままだとSECRETがハードコーディングになってしまっているため以下のように修正します。

# Copy paste your mnemonic here.
# SECRET="panda dose welcome ostrich brief pull lawn table arrest worth ranch faculty"
if [ -z "$SECRET" ]; then
  echo 'Please set the "SECRET" environment variable and try again.' >&2
  exit 2
fi

また以下部分を(grandpa, babeなどの)種別を見やすくするために以下のように修正しておきます。

	# printf "// $ADDRESS\nhex![\"${ACCOUNT#'0x'}\"].$INTO(),"
	printf "// [$2] $ADDRESS\nhex![\"${ACCOUNT#'0x'}\"].$INTO(),"

その後、以下コマンドを打ちます。
その際にSECRET部分を任意のニーモニックに変更し、最後の数字部分を、作成するアカウント分の数に変更して(下記は3個のアドレスを作成)

% SECRET="panda dose welcome ostrich brief pull lawn table arrest worth ranch faculty" sh scripts/prepare-test-net.sh 3
(
// [stash] 5FNCTJVDxfFnmUYKHqbJHjUi7UFbZ6pzC39sL6E5RVpB4vc9
hex!["920c238572e2b31c2efd19dad1a5674c8188388d9a30d0d01847759a5dc64069"].into(),
// [controller] 5GgaLpTUcgbCTGnwVkCjSSzZ5jTaEPuxtWGRDhi8M1BP1hTs
hex!["cc4c78c7f22298f17e0e2dcefb7cff85b30e19dc1699cb9d1de00e5ea65a433d"].into(),
// [grandpa] 5Fm7Lc3XDxxbH4LBKxn1tf44P1R5M5cm2vmuLZbUnPFLfu5p
hex!["a3859016b0b17b7ed6a5b2efcb4ce0e2b6b56ec8594d416c0ea3685929f0a15c"].unchecked_into(),
// [babe] 5CyLUbfTe941tZDvQQ5AYPXZ6zzqwS987DTwFGnZ3yPFX5wB
hex!["2824087e4d670acc6f2ac4251736b7fb581b5bff414437b6abc88dc118ea8d5c"].unchecked_into(),
// [im_online] 5CahSqUXepwzCkbC7KNUSghUcuJxPDPKiQ4ow144Gb9qBPsX
hex!["16dffa9a82c7bb62f0f9929407223bf156458a4e7970ec4007ab2da7fb389f7d"].unchecked_into(),
// [authority_discovery] 5Eeard4qtNM8DBvqDEKn5GBAspbT7QEvhAjxSsYePB26XAiJ
hex!["724f3e6ec8a61ea3dc5b76c00a049f84fd7f212443b01241e0a2bb4ce503b345"].unchecked_into(),
),
: 
: 

上記で生成されたキーコードを node/src/chain_spec.rs の initial_authorities 部分に差し替えます。

ちなみに、こちらのキーは上から順に stash, controller, grandpa, babe, im_online, authority_discovery です。

Eraの時間変更方法

デフォルトの状態ですとValidatorの更新タイミングが1時間に1回です。この1時間の期間はEraと呼ばれています。
テストする際にこのEraが長いととてもテストしずらいため、Eraを以下のようにして短くします。

runtime/src/constants.rs
// pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES;
pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES;

Epochを10分に1回更新するのではなく、1分に1回更新する事になり、Eraの間隔が6分(Era = 6 * Epoch)になります。
こうしておくと早くValidatorリストの更新が確認しやすくなり、後での動作確認が素早くできるようになります。

Compile & チェーンファイルを作成

まず Compile して substrate の binary を作成します。

// compile
cargo build --release

Compileが成功したら、現状の設定でチェーンファイルを作成します。このファイルは後で使用します。

// Generate configuration file
./target/release/node-template build-spec --disable-default-bootnode --chain staging > customSpec.json

起動するノードの公開鍵を生成

subkeyとニーモニックを使ってノードの公開鍵を生成します。

% subkey inspect --scheme ed25519 "fire penalty pony chase gift loan grid mule tape wrestle stuff salute"
Secret phrase `fire penalty pony chase gift loan grid mule tape wrestle stuff salute` is account:
  Secret seed:       0x09c047c99b49d03c96f5915497cc5e7ffb0fce31b732abf35f2d7d1bfd89de13
  Public key (hex):  0x74a8cfbadb5d2b0178ec124791bfa8346ac3550a4f689923c806428090055277
  Public key (SS58): 5EhfZPbz9JVAXWmHpKA1zc6jpBZXS3ExvmrvZrjZ6AMa2Vzq
  Account ID:        0x74a8cfbadb5d2b0178ec124791bfa8346ac3550a4f689923c806428090055277
  SS58 Address:      5EhfZPbz9JVAXWmHpKA1zc6jpBZXS3ExvmrvZrjZ6AMa2Vzq

こちらの公開鍵(Public key (hex))を元に以下のようにノードを起動します。

% ./target/release/node-template --tmp --dev --node-key 0x74a8cfbadb5d2b0178ec124791bfa8346ac3550a4f689923c806428090055277
2022-03-09 10:12:53 Substrate Node
2022-03-09 10:12:53 ✌️  version 4.0.0-dev-ea6a66e-x86_64-macos
2022-03-09 10:12:53 ❤️  by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2022
2022-03-09 10:12:53 📋 Chain specification: Development
2022-03-09 10:12:53 🏷  Node name: extra-large-oven-1882
2022-03-09 10:12:53 👤 Role: AUTHORITY
2022-03-09 10:12:53 💾 Database: RocksDb at /var/folders/nh/fsz2n5z16753v95_x9ly7s_m0000gn/T/substrateP5eTIJ/chains/dev/db/full
2022-03-09 10:12:53 ⛓  Native runtime: node-268 (substrate-node-0.tx2.au10)
2022-03-09 10:12:53 [0] 💸 generated 1 npos voters, 1 from validators and 0 nominators
2022-03-09 10:12:53 🔨 Initializing Genesis block/state (state: 0x1053…8d4c, header-hash: 0xe291…b333)
2022-03-09 10:12:53 👴 Loading GRANDPA authority set from genesis on what appears to be first startup.
2022-03-09 10:12:53 👶 Creating empty BABE epoch changes on what appears to be first startup.
2022-03-09 10:12:53 Using default protocol ID "sup" because none is configured in the chain specs
2022-03-09 10:12:53 🏷  Local node identity is: 12D3KooWRm651Kd5GmsLTHJbgX5chQS5npx9ttLgo46UsegCMoNM

すると nodeのid(上記だと12D3KooWRm651Kd5GmsLTHJbgX5chQS5npx9ttLgo46UsegCMoNM)を表示するので、こちらを customSpec.json の BootNodes[] の部分に以下のように加えます。

customSpec.json
{
:
  "bootNodes": [
    "/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWRm651Kd5GmsLTHJbgX5chQS5npx9ttLgo46UsegCMoNM"
  ],
:
}

session keys のスクリプト追加

grandpaやbabeなどのkeyをチェーンに追加するためのスクリプトを追加します。
スクリプトはこちらのソースをダウンロードして scripts/session_keys に配置します。

audi, babe, gran, imolファイル作成&更新

また session_keys 配下のダウンロードした audi, babe, gran, imol のファイルを必要に応じて追加&修正します。
まず、作成するアカウント分だけ各種ファイルを用意し以下部分を修正します。

  • 省略タイプ名 : audi, babe, gran, imol のいずれか
  • ニーモニック : prepare-test-net.sh実行時に使用したSECRET
  • 順番 : ファイル名の末尾数字
  • タイプ名 : authority_discovery, babe, grandpa, im_online のいずれか
  • HEX文字列 : prepare-test-net.sh実行時に出力した各タイプのHEX文字列(ただし0xを先頭につける
{
    "jsonrpc":"2.0",
    "id":1,
    "method":"author_insertKey",
    "params": [
        "[省略タイプ名]",
        "[ニーモニック]//[順番]//[タイプ名]",
        "[HEX文字列]"
    ]
}

run.sh の修正

scripts/session_keys に run.sh が含まれています。
今までの手順通りであれば必要ありませんが、Validatorを4つにするなどする場合は、必要に応じて run.sh を修正します。

修正例としては、以下のような実装がされていますが port番号 や、-d で指定するデータファイル名の変更などです。

curl http://localhost:9935  -H "Content-Type:application/json;charset=utf-8" -d "@babe3"

Sudoアカウント設定

Sudoアカウント用にキーを設定します。

% subkey inspect --scheme sr25519 "royal novel glad piece waste napkin little pioneer decline fancy train sell"
Secret phrase `royal novel glad piece waste napkin little pioneer decline fancy train sell` is account:
  Secret seed:       0x5b482eab1018eaee6293d3aaf15e4cc26fedd711b1ad4fe127adc11367ac3e9b
  Public key (hex):  0xa2bf32e50edd79c181888da41c80c67c191e9e6b29d3f2efb102ca0e2b53c558
  Public key (SS58): 5Fk6QsYKvDXxdXumGdHnNQ7V7FziREy6qn8WjDLEWF8WsbU3
  Account ID:        0xa2bf32e50edd79c181888da41c80c67c191e9e6b29d3f2efb102ca0e2b53c558
  SS58 Address:      5Fk6QsYKvDXxdXumGdHnNQ7V7FziREy6qn8WjDLEWF8WsbU3

customSpec.json の sudo箇所に Public key (SS58) を入れます。

customSpec.json
"sudo": {
  "key": "5Fk6QsYKvDXxdXumGdHnNQ7V7FziREy6qn8WjDLEWF8WsbU3"
},

ノード起動用のチェーンファイル作成

先ほどは--chainの指定をstagingしていたため、これを修正したcustomSpec.jsonをベースにチェーンファイルを再度作成します。

% ./target/release/node-template build-spec --disable-default-bootnode --chain customSpec.json > ./testnet.json

Validatorの数を調整

生成したtestnet.jsonのvalidatorCountを3、minimumValidatorCountはvalidatorCountと同じか低い値にしておきます(デフォルトで3になっていると思います)。
validatorCountは、実際に動くValidatorの数でこれ以上のValidator候補がいてもwaiting状態になってしまいます。

      "staking": {
        "validatorCount": 3,
        "minimumValidatorCount": 3,

Validator起動

以下コマンドをターミナルを分けて実行します。

// First validator node
% ./target/release/node-template --chain testnet.json -d data/validator1 --name validator1 --in-peers 256 --validator --ws-external --rpc-external --rpc-cors all --rpc-methods=unsafe --node-key 0x74a8cfbadb5d2b0178ec124791bfa8346ac3550a4f689923c806428090055277

// Second validator node 2
% ./target/release/node-template --chain testnet.json -d data/validator2 --name validator2 --validator --port 30334 --ws-port 9946 --rpc-port 9934 --ws-external --rpc-external --rpc-cors all --rpc-methods=unsafe

// Third validator node
% ./target/release/node-template --chain testnet.json -d data/validator3 --name validator3 --validator --port 30335 --ws-port 9947 --rpc-port 9935 --ws-external --rpc-external --rpc-cors all --rpc-methods=unsafe

Session Key を挿入

Validatorを起動したあとに、以下コマンドを実行して事前に準備しておいたSession Key情報をチェーンに挿入します。

% cd scripts/session_keys
% sh run.sh

Session Keyの挿入が成功すると以下のようにブロックが進んでいきます。
finalizedが進まないことがあるので、その場合はValidatorノードを再起動してください。

2021-06-11 09:40:45  ✨ Imported #3 (0xf18c…97f0)
2021-06-11 09:40:48  ✨ Imported #4 (0x4bdc…aa87)
2021-06-11 09:40:48  💤 Idle (2 peers), best: #4 (0x4bdc…aa87), finalized #1 (0xad8e…f0aa), ⬇ 5.0kiB/s ⬆ 4.8kiB/s
2021-06-11 09:40:51  ✨ Imported #5 (0x4064…6d67)
2021-06-11 09:40:53  💤 Idle (2 peers), best: #5 (0x4064…6d67), finalized #3 (0xf18c…97f0), ⬇ 2.5kiB/s ⬆ 2.2kiB/s

画面確認

ブラウザで
https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/staking
にアクセスするとStaking画面が表示されます。
これでNPoS環境が整いました。

image.png

確認手順

Validatorの追加や削除などをして動作を確認していきます。

Validator追加

まず新しいアカウント用の12個の文字列のニーモニックを用意しておきます。ニーモニックはこちらのサイトで生成できます。

ここでは

pulp nose again buddy edge foam aim atom hello follow gauge maze

とします。

Polkadot{.js} extensionをブラウザ(ここではchromeを想定)にインストールします。
インストール後に、Extensionを起動して[Import account from pre-existing seed]を選らびます。

image.png

そして、先ほど取得したニーモニックを入れ画面の指示に従いアカウントを作成します。

image.png

指定したニーモニックでアカウントが作成できました。

image.png

追加Validatorノード起動

ファイル作成

新たに追加する session keys の audi, babe, gran, imolファイル 作成します。

scripts/session_keys/audi4
scripts/session_keys/babe4
scripts/session_keys/gran4
scripts/session_keys/imol4

作成方法は上記で紹介した方法を見て下さい
また[HEX文字列]を取得するために先ほどのニーモニックを使ってアカウントキー情報を出してファイルに入れ込みます。
[ニーモニック][順番]も変更する必要があるので注意してください。

% SECRET="pulp nose again buddy edge foam aim atom hello follow gauge maze" sh scripts/prepare-test-net.sh 1

Validator起動

新たに追加するValidatorを起動します。
ブロック同期が一旦はじまりますが、すぐに停止します。

% ./target/release/node-template --chain testnet.json -d data/validator4 --name validator4 --validator --port 30336 --ws-port 9948 --rpc-port 9936 --ws-external --rpc-external --rpc-cors all --rpc-methods=unsafe
: 
: 
2022-03-11 11:47:54 💤 Idle (1 peers), best: #11 (0x1989…3405), finalized #9 (0x069d…b64b), ⬇ 0.9kiB/s ⬆ 0.9kiB/s
2022-03-11 11:47:59 💤 Idle (1 peers), best: #11 (0x1989…3405), finalized #9 (0x069d…b64b), ⬇ 0.8kiB/s ⬆ 0.8kiB/s

キー挿入

上記ファイルを追加したあとに以下コマンドで key を insert します。

% cd scripts/session_keys
% curl http://localhost:9936  -H "Content-Type:application/json;charset=utf-8" -d "@babe4"
% curl http://localhost:9936  -H "Content-Type:application/json;charset=utf-8" -d "@gran4"
% curl http://localhost:9936  -H "Content-Type:application/json;charset=utf-8" -d "@imol4"
% curl http://localhost:9936  -H "Content-Type:application/json;charset=utf-8" -d "@audi4"

すると、ブロック同期が再開されます。

注意

GRANDPA voter error: Signing("Failed to sign GRANDPA vote for round 2 targetting 0xa2fedd20813e1bdcf0bd99f64ad907132ff5d6fd3cb00e2ed0ad1261664c01a1")

のようなエラーが追加ノードで出力された場合は、session keyの挿入がうまくいっていません。
ファイルの中身、もしくはprepare-test-net.shの修正が正しく行われているか確認してください。

Staking

初期残高の準備をする

Validatorノードが起動していたら、全て一度停止し、データを一度全部削除してしまいます。

% rm -rf data/validator*/**

testnet.json の 以下配列に先ほど作成したアカウントのアドレスと残高を追加します。

      "balances": {
        "balances": [
:
          [
            "5FjB53jPiNqSTSGa6RK3fAKaBmMxCaJrrxaKbm6jJpzWbqq1",
            5000000000000000000000
          ]

その後、全validator起動して、session keyを挿入し、 全validatorを再起動します。
こちらに再起動ソースを用意しておきました。適宜修正して使ってください。

% ./scripts/restart-env.sh

Stakingをする

追加ノードをValidatorノードとして認識させるためにStakingしていきます。
[Account actions]タブを押した後に[+ Validator]ボタンを押します。

image.png

Stakingは初期ValidatorのStake量よりも多くしておきます。

image.png

Stakingを行うために rotateKey が必要になるため以下のように新たに追加するノードに curl でアクセスして取得しておきます。

$ curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys", "params":[]}' http://localhost:9936 | jq .
{
  "jsonrpc": "2.0",
  "result": "0x229f73aae462af30df2fc2f4bdfb57360af224787b65ce656f44a3b995be706e34d48bdf307c33da5b8a381871bd9a816d3f0ab56ebb6b954bf67b34a8d2314084814b71b3b734bc4610970fc3de342cbcbf655337c8eadbdc9f116d07788f1b18852ee79d75d0c06fa4e9a8e529123d9f72d59e70b21b7c4aad2fa0393d4663",
  "id": 1
}

画面の指示に従って tx を発行します。

image.png

image.png

無事Stakingが行われると以下画面が表示されます。

image.png

またStakingを行うとWaitingにノードが表示されます。

image.png

この時点ではまだValidatorとして認識されていません。

image.png

しばらくするとEraが更新されます。その際にValidatorセットの見直しが行われて追加ValidatorノードがValidatorとして認識されます。

image.png

これでValidatorリストへValidatorの追加が行えました。

Validatorを削除する

ValidatorリストからValidatorを削除してみます。
追加したValidatorに対してChillを行います。

下記画面のように[Account Actions]タブを開いて、Stopを押します。

image.png

すると下記画面のように Sending transaction staking.chill() するか聞かれるのでtx発行します。

image.png

すると、stopボタンがなくなり、Validate もしくは Nominateボタンが表示されるようになります。
これでChillした事になります。

image.png

このChillをした後に、Eraが更新されると先ほどリストから外されWaitingになっていたValidatorが戻り、新しく追加ValidatorはWaitingからも外れます(ChillすることによってStakeが外されたため)

image.png

終わりに

簡単にですがNPoS環境でStakingやChillによりValidatorリストへのValidator追加、削除を試してみました。
まだまだ、Nominateなど試していない動作はありますが、今回の手順を行えばNPoS環境はとりあえず整うので、自分で色々と動作を試しても良いかもしれません。

今回のソースはこちらに上げておきました。

余談

customSpec.jsonのminimumValidatorCountはこの数よりValidator稼働していないとチェーンが停止するらしいのですが、validatorCountより動いているvalidatorの数が少ないと Era が切り替わらないので、この値の詳細がよくわからずです。

また、NPoSを使う際に予め稼働するValidator数を設定する必要がありそうで、チェーン稼働中にValidatorセットの最大数を増やせるかはまだ良く分かってません。。。

参考

[Tutorial] Deploy a Substrate NPoS network in 3 minutes

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0