title |
---|
Sudo Upgrade |
Since forkless runtime upgrades do not require network participants to restart their blockchain clients, the first step of this tutorial is to start the template node as-is. Build and start the unmodified Node Template.
cargo run --release -- --dev --tmp
Leave this node running! Notice that the node will not be restarted as part of this tutorial despite the fact that two runtime upgrades are performed. You will be editing and re-compiling the node's runtime code, but do not stop and restart the node to prove to yourself that the runtime upgrade is in fact done on an active (dev) network, not a rebuilt or restarted one.
By default, the well-known Alice account is
configured as the holder of the Sudo pallet's key in the development_config
function of the
template node's
chain specification file -
this is the configuration that is used when the node is launched with the --dev
flag. This means
that Alice's account will be the one used to perform runtime upgrades throughout this tutorial.
Dispatchable calls in Substrate are always associated with a
weight, which is used for resource accounting. FRAME's
System module bounds extrinsics to a block
BlockLength
and
BlockWeights
limit.
The set_code
function in
the System
module is
intentionally designed to consume the maximum weight that may fit in a block.
The runtime upgrade should consume the entire block to avoid extrinsics trying to execute on a different version of a runtime when called. Although theoretically one may be able to use transaction priority and carefully study the FRAME logic involved to allow for other extrinsics to be dispatched in the same block as the upgrade, it is a very poor idea to try to do this for almost any blockchain: it is worth to spend one block to keep this operation clean and reduce chance of error. This study is outside the scope of this tutorial.
The set_code
function's
weight annotation also specifies that the extrinsic call is in
the Operational
class of dispatchable
function, which identifies it as relating to network operations and impacts the accounting of its
resources, such as by exempting it from the
TransactionByteFee
.
As the name of the Sudo pallet implies, it provides
capabilities related to the management of a single
sudo
("superuser do") administrator. In FRAME, the Root
Origin is used to identify the runtime administrator; some of FRAME's features, including the
ability to update the runtime by way of
the set_code
function,
are only accessible to this administrator. The Sudo pallet maintains a single
storage item: the ID of the account that has access to the
pallet's dispatchable functions. The Sudo
pallet's sudo
function allows the holder of this account to invoke a dispatchable as the Root
origin.
The following pseudo-code demonstrates how this is achieved, refer to the Sudo pallet's source code to learn more.
fn sudo(origin, call) -> Result {
// Ensure caller is the account identified by the administrator key
let sender = ensure_signed(origin)?;
ensure!(sender == Self::key(), Error::RequireSudo);
// Dispatch the specified call as the Root origin
let res = call.dispatch(Origin::Root);
Ok()
}
In order to work around resource accounting within FRAME's safeguards, the Sudo pallet provides the
sudo_unchecked_weight
function, which provides the same capability as the sudo
function, but accepts an additional
parameter that is used to specify the (possibly zero) weight to use for the call. The
sudo_unchecked_weight
function is what will be used to invoke the runtime upgrade in this section
of this tutorial; in the next section, the Scheduler pallet will be used to manage the resources
consumed by the set_code
function.
Here we allow for a block that may take an indefinite time to compute intentionally: to ensure that our runtime upgrade does not fail, no matter how complex the operation is. It could take all the time it needs to succeed or fail.
Because the template node doesn't come with the Scheduler pallet included in its runtime, the first runtime upgrade performed in this tutorial will add that pallet. First, add the Scheduler pallet as a dependency in the template node's runtime Cargo file.
runtime/Cargo.toml
[dependencies.pallet-scheduler]
default-features = false
git = 'https://github.com/paritytech/substrate.git'
tag = 'monthly-2021-09+1'
version = '4.0.0-dev'
#--snip--
[features]
default = ['std']
std = [
#--snip--
'pallet-scheduler/std',
#--snip--
]
Next, add the pallet to the runtime:
runtime/src/lib.rs
// Define the types required by the Scheduler pallet.
parameter_types! {
pub MaximumSchedulerWeight: Weight = 10_000_000;
pub const MaxScheduledPerBlock: u32 = 50;
}
/// Configure the runtime's implementation of the Scheduler pallet.
impl pallet_scheduler::Config for Runtime {
type Event = Event;
type Origin = Origin;
type PalletsOrigin = OriginCaller;
type Call = Call;
type MaximumWeight = MaximumSchedulerWeight;
type ScheduleOrigin = frame_system::EnsureRoot<AccountId>;
type MaxScheduledPerBlock = MaxScheduledPerBlock;
type WeightInfo = ();
}
// Add the Scheduler pallet inside the construct_runtime! macro.
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
/*** snip ***/
Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event<T>},
}
);
The final step to preparing an upgraded FRAME runtime is to increment its
spec_version
,
which is a member of
the RuntimeVersion
struct:
runtime/src/lib.rs
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node-template"),
impl_name: create_runtime_str!("node-template"),
authoring_version: 1,
spec_version: 101, // *Increment* this value, the template uses 100 as a base
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
};
Take a moment to review the components of the RuntimeVersion
struct:
spec_name
: The name of the runtime/chain, e.g. Ethereum.impl_name
: The name of the client, e.g. OpenEthereum.authoring_version
: The authorship version for block authors.spec_version
: The version of the runtime/chain.impl_version
: The version of the client.apis
: The list of supported APIs.transaction_version
: The version of the dispatchable function interface.
In order to upgrade the runtime it is required to increase the spec_version
; refer to the
implementation of the
FRAME System module
and in particular the can_set_code
function to to see how this requirement and others are enforced
by runtime logic.
Note: keep your node running! You should use a new terminal to compile the runtime:
# Here we *only* build the runtime, the node has not been changed.
cargo build --release -p node-template-runtime
Get stuck? Here is a solution to check against. See the
diff
in the commit history for details.
Here the --release
flag will result in a longer compile time, but also generate a smaller build
artifact that is better suited for submitting to the blockchain network: storage minimization
and optimizations are critical for any blockchain.
As we are only building the runtime, Cargo looks in runtime
cargo.toml
file for requirements and only executes these. Notice the
runtime/build.rs
file that
cargo looks for build the Wasm of your runtime that is specified in runtime/src/lib.rs
.
When the --release
flag is specified, build artifacts are output to the
target/release
directory; when the flag is omitted they will be sent to target/debug
. Refer to
the official documentation to learn
more about building Rust code with Cargo.
Use this link to open the Polkadot JS Apps UI and automatically configure the UI to connect to the local node: https://polkadot.js.org/apps/#/extrinsics?rpc=ws://127.0.0.1:9944.
Some ad blockers and browser restrictions (e.g. the built-in Shield in Brave browser, and https requirement for socket connection in Firefox) interfere with connecting to a local node. Make sure to check them and, if needed, turn them off. You may not get connecting from a remote IP (like
polkadot.js.org/apps/
) to a local node working. If you are unable to solve this, we encourage you to host your app locally, like the apps UI.
Use Alice's account to invoke the sudoUncheckedWeight
function and use the setCode
function from the system
pallet as its parameter. In order to supply the build artifact that was generated by the previous
build step, toggle the "file upload" switch on the right-hand side of the "code" input field for the
parameter to the setCode
function. Click the "code" input field, and select the Wasm binary that
defines the upgraded runtime:
target/release/wbuild/node-template-runtime/node_template_runtime.compact.wasm
. Leave the value
for the _weight
parameter at the default of 0
. Click "Submit Transaction" and then "Sign and
Submit".
After the transaction has been included in a block, the version number in the upper-left-hand corner
of Polkadot JS Apps UI should reflect that the runtime version is now 101
.
If you still see your node producing blocks in the terminal it's running and reported on the UI, you have performed a successful forkless runtime upgrade! Congrats!!!
- Learn about storage migrations and attempt one alongside a runtime upgrade.