LoginSignup
1
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-03-10

はじめに

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

こちらの機能をInitiate a Forkless Runtime Upgradeのチュートリアルで確認する事ができます。

ただ、このチュートリアルではStorageデータのMigration(移行)については手順(2022.03.03時点)を見て試そうとしても良くわからなかったため、どのようにForklessUpgrade(Forkせずにコード変更)時にStorageのMigration(移行)を行うのかの具体的な手順を記載します。

導入手順

"はじめに"でも記載したBasic Storage Migrationをベースに導入していきます。

ForklessUpgradeを理解しておく

Initiate a Forkless Runtime Upgradeのチュートリアルを一通り終わらせForklessUpgradeを理解しておきます。
こちらのチュートリアルでは、SudoによるUpgrade, スケジュールによるUpgradeのやり方が記載されています。

Migration確認用にNicks Palletを導入

データMigrationを確認するためにAdd the Nicks Pallet to your Runtimeのチュートリアルを参考に、Nicks palletのソースをSubstrateテンプレートに導入していきます。

チュートリアルでは Nicks pallet をCargo.tomlでダウンロードして対応していますが、Nicks Palletソースを直接に導入します。

Cargo.toml修正

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

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

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

Nicks Palletのソースを元に、pallets配下にファイル達をコピーします。

image.png

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

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で確認します。

% cargo check -p pallet-nicks

Nicks palletのConfig traitを実装する

runtime/src/lib.rsのconstruct_runtime部分にNicksの記載を追加します。

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の最後に以下を追加します。

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の記述を追加します。

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

次のコマンドを実行して、新しい依存関係が正しく解決されているかを確認します。

% cargo check -p node-template-runtime

checkがエラーなく処理が終了したら、全体をcompileして終了です。

% cargo build --release

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

Nicksパレットのデータを作成するために、SubstrateへアクセスするためのFrontend Applicationが動く環境をCreate your first Substrate blockchainを参考に構築しておきます。

Substrate nodeを動かす

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

Frontend Applicationを動かす

% yarn start

Nicksパレットにデータ作成

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

画面上部のアカウントリストでAliceを選択しておきます。
image.png

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

image.png

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

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

image.png

Nicksパレットの実装変更

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

修正内容はこちらのコミットを参照してください。

保存するStorageを変更する

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

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コードをファイル一番下に追加します。

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の時)する際に呼び出されます。

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の記載をあげておく

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

Runtime

runtime/Cargo.toml

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

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

spec_versionをあげる

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

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 で使用するファイルです。

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

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

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

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

Upgrade the Runtimeを参考にWasmファイルの内容をチェーンに反映します。
反映する際に、ロジックの変更&データ移行も行われます。

Polkadot-JS applicationにブラウザでアクセスします。

以下を指定して[トランザクションを提出]、[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

成功していると以下のようなログが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

動作確認

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

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

image.png

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

image.png

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

今回使用したコードはgithubにあげてあります。

ただ、現状データ移行後に 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/

1
0
2

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
1
0