SubstrateのForklessUpgrade時にデータMigration(移行)も試す

# はじめに

SubstrateではForkせずにコード(SubstrateのRuntime)を変更する事ができます。

こちらの機能を[Initiate a Forkless Runtime Upgrade](https://docs.substrate.io/tutorials/v3/forkless-upgrades/)のチュートリアルで確認する事ができます。

ただ、このチュートリアルではStorageデータのMigration(移行)については[手順(2022.03.03時点)](https://docs.substrate.io/how-to-guides/v3/storage-migrations/basics/)を見て試そうとしても良くわからなかったため、どのようにForklessUpgrade(Forkせずにコード変更)時にStorageのMigration(移行)を行うのかの具体的な手順を記載します。

# 導入手順

"はじめに"でも記載した[Basic Storage Migration](https://docs.substrate.io/how-to-guides/v3/storage-migrations/basics/)をベースに導入していきます。

## ForklessUpgradeを理解しておく

[Initiate a Forkless Runtime Upgrade](https://docs.substrate.io/tutorials/v3/forkless-upgrades/)のチュートリアルを一通り終わらせForklessUpgradeを理解しておきます。
こちらのチュートリアルでは、SudoによるUpgrade, スケジュールによるUpgradeのやり方が記載されています。

## Migration確認用にNicks Palletを導入

データMigrationを確認するために[Add the Nicks Pallet to your Runtime](https://docs.substrate.io/tutorials/v3/add-a-pallet/)のチュートリアルを参考に、Nicks palletのソースをSubstrateテンプレートに導入していきます。

チュートリアルでは Nicks pallet をCargo.tomlでダウンロードして対応していますが、[Nicks Palletソース](https://github.com/paritytech/substrate/tree/master/frame/nicks)を直接に導入します。

### Cargo.toml修正

workspaceのmembersに'pallets/nicks'を追加します。

```toml
[workspace]
members = [
    "pallets/nicks",
```

### Nicks Palletのソースを substrate-node-template に移植

[Nicks Palletのソース](https://github.com/paritytech/substrate/tree/master/frame/nicks)を元に、pallets配下にファイル達をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/1a6b250a-e421-1dab-deca-694d748d3aca.png)

また、nicksのCargo.tomlのdependencies, dev-dependenciesの部分をversionをsubstrate-node-templateに合わせるために書き換えます。

```toml:pallets/nicks/Cargo.toml
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
sp-std = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
sp-io = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
sp-runtime = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }

[dev-dependencies]
sp-core = { version = "5.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
pallet-balances = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.17" }
```

ソースを移植したら一度Nicksの追加が問題ないかcargo checkで確認します。

```bash
% cargo check -p pallet-nicks
```

### Nicks palletのConfig traitを実装する

runtime/src/lib.rsのconstruct_runtime部分にNicksの記載を追加します。
```rust:runtime/src/lib.rs
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        /* --snip-- */
        Balances: pallet_balances,

        /*** Add This Line ***/
        Nicks: pallet_nicks,
    }
);
```

また、runtime/src/lib.rsの最後に以下を追加します。

```rust:runtime/src/lib.rs
/// Add this code block to your template for Nicks:
parameter_types! {
    // Choose a fee that incentivizes desireable behavior.
    pub const NickReservationFee: u128 = 100;
    pub const MinNickLength: u32 = 8;
    // Maximum bounds on storage are important to secure your chain.
    pub const MaxNickLength: u32 = 32;
}

impl pallet_nicks::Config for Runtime {
    // The Balances pallet implements the ReservableCurrency trait.
    // `Balances` is defined in `construct_runtime!` macro. See below.
    // https://docs.substrate.io/rustdocs/latest/pallet_balances/index.html#implementations-2
    type Currency = Balances;

    // Use the NickReservationFee from the parameter_types block.
    type ReservationFee = NickReservationFee;

    // No action is taken when deposits are forfeited.
    type Slashed = ();

    // Configure the FRAME System Root origin as the Nick pallet admin.
    // https://docs.substrate.io/rustdocs/latest/frame_system/enum.RawOrigin.html#variant.Root
    type ForceOrigin = frame_system::EnsureRoot<AccountId>;

    // Use the MinNickLength from the parameter_types block.
    type MinLength = MinNickLength;

    // Use the MaxNickLength from the parameter_types block.
    type MaxLength = MaxNickLength;

    // The ubiquitous event type.
    type Event = Event;
}
```

最後にruntimeのCargo.tomlのdependencies, featuresにnicksの記述を追加します。

```toml:runtime/Cargo.toml
[dependencies]
pallet-nicks = { version = "4.0.0-dev", default-features = false, path = "../pallets/nicks" }
:
:
[features]
default = ["std"]
std = [
	"pallet-nicks/std",
```

次のコマンドを実行して、新しい依存関係が正しく解決されているかを確認します。
```bash
% cargo check -p node-template-runtime
```

checkがエラーなく処理が終了したら、全体をcompileして終了です。
```bash
% cargo build --release
```

## Nicksパレットにデータを作成する

Nicksパレットのデータを作成するために、Substrateへアクセスするための[Frontend Application](https://github.com/substrate-developer-hub/substrate-front-end-template)が動く環境を[Create your first Substrate blockchain](https://docs.substrate.io/tutorials/v3/create-your-first-substrate-chain/)を参考に構築しておきます。

### Substrate nodeを動かす

```bash
% ./target/release/node-template --dev --base-path /tmp/substrate
```

### Frontend Applicationを動かす

```bash
% yarn start
```

### Nicksパレットにデータ作成

ブラウザで http://localhost:8000/substrate-front-end-template にアクセスします。

画面上部のアカウントリストでAliceを選択しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/fd1ec5de-2c83-f6ee-acf6-f185a9ad3140.png)

Pallet InteractorのExtrinsicで、nicks - setName をリストボックスから選択します。
その後、nameのテキストボックスに任意の名前を入れる事ができます。
ここでは"test1name1"と入れる事にし、Signedボタンを押してデータをチェーン側に取り込みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/a97bde12-db05-6efd-c5b3-792ee10847cd.png)

今度はアカウントBobに切り替えて、同様にsetNameで"test2 name2"と入れてSignedボタンを押してください。ちなみに、test2 と name2 の間に半角スペースを入れてますが、これは後でデータ移行を行う際に name を lastname と firstname の２つの項目に分けるためにこのような事をしています。

各アカウントのaddressを入れてデータが作成されていることを確認しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/60bb87a2-da0e-87a0-62d9-9d7e3ddbd37d.png)

## Nicksパレットの実装変更

保存しているNicksのデータでは name のみの１項目しか指定して保存できませんが、lastname と firstname の２つの項目に保存できるように変更します。

修正内容は[こちらのコミット](https://github.com/shohu/substrate-forkless-sample/commit/ceb093113fd651c2f034e54bbe255eb90b294d2f)を参照してください。

### 保存するStorageを変更する

保存するStorageの変更をします。
lastname と firstname の２つを保存できるように変更します。

```rust:pallets/nicks/src/lib.rs
	#[pallet::storage]
	pub(super) type NameOf<T: Config> =
    	// StorageMap<_, Twox64Concat, T::AccountId, (BoundedVec<u8, T::MaxLength>, BalanceOf<T>)>;
        StorageMap<_, Twox64Concat, T::AccountId, ((BoundedVec<u8, T::MaxLength>, Option<BoundedVec<u8, T::MaxLength>>), BalanceOf<T>)>;
```

### pallet::call などの変更

Storage の変更に伴い set_name などのメソッドを変更します。

## Migration実装

Migrationコードをファイル一番下に追加します。

```rust:pallets/nicks/src/lib.rs
pub fn migrate<T: Config>() -> Weight {
	use frame_support::traits::StorageVersion;
	let version = StorageVersion::get::<Pallet<T>>();
	let mut weight: Weight = 0;
    log::info!(" >>> Updating MyNicks storage. version {:?} ", version);
	if version < 2 {
		weight = weight.saturating_add(v2::migrate::<T>());
		StorageVersion::new(2).put::<Pallet<T>>();
	}
	weight
}

mod v2 {
	use super::*;
	use frame_support::traits::Get;

	pub fn migrate<T: Config>() -> Weight {
		let mut weight: Weight = 0;
        <NameOf<T>>::translate::<(Vec<u8>, BalanceOf<T>), _>(
            |_key, (nick, deposit)| {
                log::info!("     Migrated nickname for {:?}..., nick = {:?}, deposit = {:?}", _key, nick, deposit);
                weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
                // We split the nick at ' ' (<space>).
                match nick.iter().rposition(|&x| x == b" "[0]) {
                    Some(ndx) => Some(((
                        nick[0..ndx].to_vec().try_into().unwrap(),
                        Some(nick[ndx + 1..].to_vec().try_into().unwrap()),
					), deposit)),
                    None => Some(((nick.try_into().unwrap(), None), deposit))
                }
		    }
        );
		weight
	}
}
```

呼び出し元は以下のように pallet::hooks で on_runtime_upgrade を追加する。RuntimeUpgrade(setCodeの時)する際に呼び出されます。

```rust:pallets/nicks/src/lib.rs
#[frame_support::pallet]
pub mod pallet {
: 
: 
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
		fn on_runtime_upgrade() -> Weight {
			migrate::<T>()
		}
	}
```

## Versionを変更する

Versionを上げないとソースのアップデートが反映されないためVersionを上げます。

### Nicks

以下versionの記載をあげておく

```toml:pallets/nicks/Cargo.toml
[package]
name = "pallet-nicks"
version = "4.0.0-dev.3"
```

### Runtime

#### runtime/Cargo.toml

以下versionの記載をあげておく

```toml:runtime/Cargo.toml
[dependencies.pallet-nicks]
default-features = false
path = '../pallets/nicks'
version = '4.0.0-dev.3'
```

#### spec_versionをあげる

このspec_versionをあげておかないと、Runtime Upgradeができないので注意してください。

```rust:runtime/src/lib.rs
#[sp_version::runtime_version]
pub const VERSION: RuntimeVersion = RuntimeVersion {
	spec_name: create_runtime_str!("node-template"),
	impl_name: create_runtime_str!("node-template"),
	authoring_version: 1,
	spec_version: 100,  //この値を*インクリメント*します。テンプレートは100をベースとして使用します   
	impl_version: 1,
	apis: RUNTIME_API_VERSIONS,
	transaction_version: 1,
	state_version: 1,
};
```

### Wasmファイル作成

更新する内容が詰まっている Wasmファイル を作成します。
この Wasmファイル をあとで setCode で使用するファイルです。

```bash
% cargo build --release -p node-template-runtime
```

上記コマンドを実施すると以下にWasmファイルができます。

./target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm

## チェーンにソース反映&データ移行

[Upgrade the Runtime](https://docs.substrate.io/tutorials/v3/forkless-upgrades/#upgrade-the-runtime)を参考にWasmファイルの内容をチェーンに反映します。
反映する際に、ロジックの変更&データ移行も行われます。

[Polkadot-JS application](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/extrinsics)にブラウザでアクセスします。

以下を指定して[トランザクションを提出]、[Sign and Submit]の順にボタンを押します。

- 選択されたアカウントを使用する - ALICE
- 次のextrinsicを提出する - sudo.sudoUncheckedWeight
- call: Call - system.setCode
- code - target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm をアップロード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/7e3f3abd-0e82-2cad-5476-256766922904.png)

成功していると以下のようなログがnodeに出力されます。

```
2022-03-08 14:06:30 🙌 Starting consensus session on top of parent 0x00150dd1ec48b95a21610db3e3de3a15873b5a182ee10fb4f13b0cf200051730
2022-03-08 14:06:30 ✅ no migration for System
2022-03-08 14:06:30 ✅ no migration for RandomnessCollectiveFlip
2022-03-08 14:06:30 ✅ no migration for Timestamp
2022-03-08 14:06:30 ✅ no migration for Aura
2022-03-08 14:06:30 ✅ no migration for Grandpa
2022-03-08 14:06:30 ✅ no migration for Balances
2022-03-08 14:06:30 ✅ no migration for TransactionPayment
2022-03-08 14:06:30 ✅ no migration for Sudo
2022-03-08 14:06:30 ✅ no migration for TemplateModule
2022-03-08 14:06:30 ⚠️ Nicks declares internal migrations (which *might* execute). On-chain `StorageVersion(0)` vs current storage version `StorageVersion(0)`
2022-03-08 14:06:30  >>> Updating MyNicks storage. version StorageVersion(0)
2022-03-08 14:06:30      Migrated nickname for ..., nick = [116, 101, 115, 116, 49, 110, 97, 109, 101, 49], deposit = 100
2022-03-08 14:06:30      Migrated nickname for ..., nick = [116, 101, 115, 116, 50, 32, 110, 97, 109, 101, 50], deposit = 100
```
トランザクションがブロックに含まれた後、Polkadot JS Apps UIの左上隅にあるバージョン番号が、修正したspec_versionである101である事を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/ffe51cb4-bff1-cd64-395d-c42c36e37f3d.png)

# 動作確認

ブラウザで http://localhost:8000/substrate-front-end-template にアクセスします。

Pallet InteractorのExtrinsicで、Query - nicks - nameOf をリストボックスから選択したあとに、Aliceのアドレスを指定して[Query]ボタンを押すと、first=test1name1(0x74657374316e616d6531), last=nullである事を確認できるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/3ea3571e-8b3e-4787-fdd4-7d646ba99120.png)

また、Bobのアドレスを指定すると、first=test2(0x7465737432), last=name2(0x6e616d6532)である事を確認できるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2560317/970562b9-7d0d-52ae-781a-77e889ef7349.png)

* https://rakko.tools/tools/77/ のサイトで16進数をtextに変換して文字を確認できます。

これで、Nicks Palletのデータ移行を確認できました。

今回使用したコードは[github](https://github.com/shohu/substrate-forkless-sample)にあげてあります。

**ただ、現状データ移行後に setName にUIが対応できていないのでまた別途時間がある時に対応したコードを上げたいと思います**

# 終わりに

本記事ではStorageデータのMigration(移行)についての具体的な手順について記載しました。
これでチェーン運用中にチェーン停止せずともソース変更とデータ移行ができると思います。
こちらの記事が何かしらの参考になれば幸いです。

# 参考

https://github.com/substrate-developer-hub/migration-example/pull/2/files
https://docs.substrate.io/how-to-guides/v3/storage-migrations/basics/