Runtime Upgrade System - Technical Documentation
Overview
The Splendor blockchain implements a sophisticated runtime upgrade system that enables protocol evolution without network disruption. This documentation provides technical details and visual representations of the upgrade process.
How Forkless Runtime Upgrades Work (Substrate & Polkadot)
One of the most powerful features of Substrate-based blockchains (like Splendor and Polkadot) is the ability to upgrade the runtime without requiring a hard fork. This is called a “forkless runtime upgrade” and is achieved by storing the blockchain’s logic as WebAssembly (Wasm) code on-chain. When an upgrade is approved (usually by on-chain governance), the new Wasm code is swapped in, and all nodes begin running the new logic automatically.
Key points:
- Upgrades are trustless and decentralized—no need for node operators to manually update software.
- The upgrade process is governed by on-chain proposals and voting, ensuring transparency and community involvement.
- Both solo chains and parachains can upgrade, but parachains require relay chain approval.
Runtime Versioning
Each runtime has a version number (spec_version, impl_version, transaction_version) that helps nodes determine compatibility and when to switch to the new code. The runtime version can be queried via RPC (e.g., state_getRuntimeVersion
).
Storage Migrations
When an upgrade changes how data is stored, a one-time migration function is run to update the storage format. This is implemented using the OnRuntimeUpgrade
trait in FRAME. Migrations are ordered and run before any new blocks are produced with the new logic.
Best Practices for Safe Upgrades
- Pre-checks: Use tools like
try-runtime
to simulate upgrades and catch errors before deploying on mainnet. - CI Integration: Automate checks for storage migrations and feature compatibility in your CI pipeline.
- Governance: Propose upgrades through on-chain governance, allowing the community to review and vote.
- Monitoring: After an upgrade, monitor node logs and chain metrics for any unexpected behavior.
Parachain-Specific Notes
- Parachains must use
authorize_upgrade
andapply_authorized_upgrade
to coordinate with the relay chain. - Storage migrations and runtime versioning work the same, but require extra approval steps.
Resources:
System Architecture
Storage Schema Evolution
Code Implementation
1. Storage Schema Definition
pub enum EthereumStorageSchema {
V1,
V2,
V3,
}
2. Runtime Upgrade Handler
impl frame_support::traits::OnRuntimeUpgrade for OnRuntimeUpgrade {
fn on_runtime_upgrade() -> frame_support::weights::Weight {
// Update storage schema
frame_support::storage::unhashed::put::<EthereumStorageSchema>(
PALLET_ETHEREUM_SCHEMA,
&EthereumStorageSchema::V3,
);
// Update validator set
<pallet_validator_admin::Validators<Runtime>>::put(vec![
// Validator configuration
]);
// Return weight consumed
<Runtime as frame_system::Config>::DbWeight::get().writes(1)
}
}
3. Versioned API Implementation
#[api_version(5)]
pub trait EthereumRuntimeRPCApi {
fn chain_id() -> u64;
fn account_basic(address: Address) -> fp_evm::Account;
fn gas_price() -> U256;
// ... other API methods
}
Upgrade Flow
Storage Migration Process
Code Examples
1. Pre-upgrade Validation
#[cfg(feature = "try-runtime")]
pub fn pre_migrate_block_v2() -> Result<Vec<u8>, &'static str> {
let item = b"CurrentBlock";
let block_v0 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV0>(
Self::name().as_bytes(),
item,
&[],
);
// Validation logic
}
2. Post-upgrade Verification
#[cfg(feature = "try-runtime")]
pub fn post_migrate_block_v2(v0_data: Vec<u8>) -> Result<(), &'static str> {
let (v0_number, v0_parent_hash, v0_transaction_len) = Decode::decode(
&mut v0_data.as_slice(),
)?;
// Verification logic
}
3. EIP-1559 Implementation
fn is_eip1559(&self, block_hash: B::Hash) -> bool {
let api = self.client.runtime_api();
if let Some(api_version) = Self::api_version(&api, block_hash) {
api_version >= 2
} else {
false
}
}