Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chain-spec-builder: Add support for codeSubstitutes #4685

Merged
merged 13 commits into from
Jun 25, 2024
16 changes: 16 additions & 0 deletions prdoc/pr_4685.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.github.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: Chain-spec-builder supports `codeSubstitutes`.

doc:
- audience: Node Operator
description: |
A new subcommand `add-code-substitute` is available for the `chain-spec-builder` binary. It allows users to provide a runtime that should be used from a given
block onwards. The `codeSubstitutes` field in the chain spec is used to force usage of a given runtime at a given block until the next runtime upgrade. It can be
used to progress chains that are stalled due to runtime bugs that prevent block-building. However, parachain usage is only possible in combination with an updated
validation function on the relay chain.

crates:
- name: staging-chain-spec-builder
bump: minor
36 changes: 30 additions & 6 deletions substrate/bin/utils/chain-spec-builder/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use chain_spec_builder::{
generate_chain_spec_for_runtime, ChainSpecBuilder, ChainSpecBuilderCmd, ConvertToRawCmd,
DisplayPresetCmd, ListPresetsCmd, UpdateCodeCmd, VerifyCmd,
generate_chain_spec_for_runtime, AddCodeSubstituteCmd, ChainSpecBuilder, ChainSpecBuilderCmd,
ConvertToRawCmd, DisplayPresetCmd, ListPresetsCmd, UpdateCodeCmd, VerifyCmd,
};
use clap::Parser;
use sc_chain_spec::{
update_code_in_json_chain_spec, GenericChainSpec, GenesisConfigBuilderRuntimeCaller,
set_code_substitute_in_json_chain_spec, update_code_in_json_chain_spec, GenericChainSpec,
GenesisConfigBuilderRuntimeCaller,
};
use staging_chain_spec_builder as chain_spec_builder;
use std::fs;

type ChainSpec = GenericChainSpec<(), ()>;

//avoid error message escaping
fn main() {
match inner_main() {
Expand All @@ -50,7 +53,7 @@ fn inner_main() -> Result<(), String> {
ref input_chain_spec,
ref runtime_wasm_path,
}) => {
let chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?;
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;

let mut chain_spec_json =
serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(false)?)
Expand All @@ -65,8 +68,29 @@ fn inner_main() -> Result<(), String> {
.map_err(|e| format!("to pretty failed: {e}"))?;
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
ChainSpecBuilderCmd::AddCodeSubstitute(AddCodeSubstituteCmd {
ref input_chain_spec,
ref runtime_wasm_path,
block_height,
}) => {
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;

let mut chain_spec_json =
serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(false)?)
.map_err(|e| format!("Conversion to json failed: {e}"))?;

set_code_substitute_in_json_chain_spec(
&mut chain_spec_json,
&fs::read(runtime_wasm_path.as_path())
.map_err(|e| format!("Wasm blob file could not be read: {e}"))?[..],
block_height,
);
let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json)
.map_err(|e| format!("to pretty failed: {e}"))?;
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
Copy link
Contributor

@michalkucharczyk michalkucharczyk Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: to avoid code duplication in UpdateCode and AddCodeSubstitute match arms we could add a function update_chain_spec_file_in_place which could take a closure that modifies &mut chain_spec_json provided as its input.

ChainSpecBuilderCmd::ConvertToRaw(ConvertToRawCmd { ref input_chain_spec }) => {
let chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?;
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;

let chain_spec_json =
serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(true)?)
Expand All @@ -77,7 +101,7 @@ fn inner_main() -> Result<(), String> {
fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?;
},
ChainSpecBuilderCmd::Verify(VerifyCmd { ref input_chain_spec }) => {
let chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?;
let chain_spec = ChainSpec::from_json_file(input_chain_spec.clone())?;
let _ = serde_json::from_str::<serde_json::Value>(&chain_spec.as_json(true)?)
.map_err(|e| format!("Conversion to json failed: {e}"))?;
},
Expand Down
20 changes: 20 additions & 0 deletions substrate/bin/utils/chain-spec-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub enum ChainSpecBuilderCmd {
ConvertToRaw(ConvertToRawCmd),
ListPresets(ListPresetsCmd),
DisplayPreset(DisplayPresetCmd),
AddCodeSubstitute(AddCodeSubstituteCmd),
}

/// Create a new chain spec by interacting with the provided runtime wasm blob.
Expand Down Expand Up @@ -222,6 +223,25 @@ pub struct UpdateCodeCmd {
pub runtime_wasm_path: PathBuf,
}

/// Add a code substitute in the chain spec.
///
/// The `codeSubstitute` object of the chain spec will be updated with the block height as key and
/// runtime code as value. This operation supports both plain and raw formats. The `codeSubstitute`
/// field instructs the node to use the provided runtime code at the given block height. This is
/// useful when the chain can not progress on its own due to a bug that prevents block-building.
///
/// Note: For parachains, the validation function on the relaychain needs to be adjusted too,
/// otherwise blocks built using the substituted parachain runtime will be rejected.
#[derive(Parser, Debug, Clone)]
pub struct AddCodeSubstituteCmd {
/// Chain spec to be updated.
pub input_chain_spec: PathBuf,
/// New runtime wasm blob that should replace the existing code.
pub runtime_wasm_path: PathBuf,
/// The block height at which the code should be substituted.
pub block_height: u64,
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this does not modify the file in-place. Maybe would be good to add a note about this. (Same case as in updateCode).

/// Converts the given chain spec into the raw format.
#[derive(Parser, Debug, Clone)]
pub struct ConvertToRawCmd {
Expand Down
10 changes: 10 additions & 0 deletions substrate/client/chain-spec/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,16 @@ pub fn update_code_in_json_chain_spec(chain_spec: &mut json::Value, code: &[u8])
}
}

/// This function sets a codeSubstitute in the chain spec.
pub fn set_code_substitute_in_json_chain_spec(
chain_spec: &mut json::Value,
code: &[u8],
block_height: u64,
) {
let substitutes = json::json!({"codeSubstitutes":{ &block_height.to_string(): sp_core::bytes::to_hex(code, false) }});
crate::json_patch::merge(chain_spec, substitutes);
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions substrate/client/chain-spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ pub mod json_patch;

pub use self::{
chain_spec::{
update_code_in_json_chain_spec, ChainSpec as GenericChainSpec, ChainSpecBuilder,
NoExtension,
set_code_substitute_in_json_chain_spec, update_code_in_json_chain_spec,
ChainSpec as GenericChainSpec, ChainSpecBuilder, NoExtension,
},
extension::{get_extension, get_extension_mut, Extension, Fork, Forks, GetExtension, Group},
genesis_block::{
Expand Down
Loading