はじめに
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配下にファイル達をコピーします。
また、nicksのCargo.tomlのdependencies, dev-dependenciesの部分をversionをsubstrate-node-templateに合わせるために書き換えます。
[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の記載を追加します。
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の最後に以下を追加します。
/// 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の記述を追加します。
[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 にアクセスします。
Pallet InteractorのExtrinsicで、nicks - setName をリストボックスから選択します。
その後、nameのテキストボックスに任意の名前を入れる事ができます。
ここでは"test1name1"と入れる事にし、Signedボタンを押してデータをチェーン側に取り込みます。
今度はアカウントBobに切り替えて、同様にsetNameで"test2 name2"と入れてSignedボタンを押してください。ちなみに、test2 と name2 の間に半角スペースを入れてますが、これは後でデータ移行を行う際に name を lastname と firstname の2つの項目に分けるためにこのような事をしています。
各アカウントのaddressを入れてデータが作成されていることを確認しておきます。
Nicksパレットの実装変更
保存しているNicksのデータでは name のみの1項目しか指定して保存できませんが、lastname と firstname の2つの項目に保存できるように変更します。
修正内容はこちらのコミットを参照してください。
保存するStorageを変更する
保存するStorageの変更をします。
lastname と firstname の2つを保存できるように変更します。
#[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コードをファイル一番下に追加します。
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の時)する際に呼び出されます。
#[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の記載をあげておく
[package]
name = "pallet-nicks"
version = "4.0.0-dev.3"
Runtime
runtime/Cargo.toml
以下versionの記載をあげておく
[dependencies.pallet-nicks]
default-features = false
path = '../pallets/nicks'
version = '4.0.0-dev.3'
spec_versionをあげる
このspec_versionをあげておかないと、Runtime Upgradeができないので注意してください。
#[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 をアップロード
成功していると以下のようなログが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である事を確認します。
動作確認
ブラウザで http://localhost:8000/substrate-front-end-template にアクセスします。
Pallet InteractorのExtrinsicで、Query - nicks - nameOf をリストボックスから選択したあとに、Aliceのアドレスを指定して[Query]ボタンを押すと、first=test1name1(0x74657374316e616d6531), last=nullである事を確認できるはずです。
また、Bobのアドレスを指定すると、first=test2(0x7465737432), last=name2(0x6e616d6532)である事を確認できるはずです。
- https://rakko.tools/tools/77/ のサイトで16進数をtextに変換して文字を確認できます。
これで、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/