From b79125664894450693a28cb6d6e4358778b7968c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 4 Dec 2025 20:07:52 -0500 Subject: [PATCH] Remove `substrate-wasm-builder` By defining our own build script, we gain complete clarity and control over how the WASM is built. This also removes the need to patch the upstream due to it allowing pollution of the environment variables from the host. Notable appreciation is given to https://github.com/rust-lang/rust/issues/145491 for identifying an issue encountered here, with the associated PR clarifying the necessary flags for the linker to fix this. --- Cargo.lock | 72 ------------------------- orchestration/runtime/Dockerfile | 2 +- substrate/node/src/chain_spec.rs | 2 +- substrate/node/src/service/mod.rs | 17 +++--- substrate/runtime/Cargo.toml | 3 -- substrate/runtime/build.rs | 90 ++++++++++++++++++++++++++++++- substrate/runtime/src/lib.rs | 8 ++- 7 files changed, 104 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ac27c4b..c9308368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1445,38 +1445,6 @@ dependencies = [ name = "c-kzg" version = "2.99.99" -[[package]] -name = "camino" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.17", -] - [[package]] name = "cc" version = "1.2.48" @@ -7080,15 +7048,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "sc-allocator" version = "34.0.0" @@ -8096,10 +8055,6 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "serai-abi" @@ -9033,7 +8988,6 @@ dependencies = [ "sp-timestamp", "sp-transaction-pool", "sp-version", - "substrate-wasm-builder", ] [[package]] @@ -10117,22 +10071,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "substrate-wasm-builder" -version = "29.0.0" -source = "git+https://github.com/serai-dex/patch-polkadot-sdk#0cd66846e58f167b09b48a77c3f70bf7b2b67000" -dependencies = [ - "cargo_metadata", - "console", - "jobserver", - "parity-wasm", - "shlex", - "strum 0.27.2", - "tempfile", - "toml 0.9.8", - "walkdir", -] - [[package]] name = "subtle" version = "2.6.1" @@ -10874,16 +10812,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" diff --git a/orchestration/runtime/Dockerfile b/orchestration/runtime/Dockerfile index 084b55ba..a1aae19d 100644 --- a/orchestration/runtime/Dockerfile +++ b/orchestration/runtime/Dockerfile @@ -37,6 +37,6 @@ COPY --from=builder /bin/busybox /bin/ ENV LD_LIBRARY_PATH=/lib/ ENV PATH=/bin # Copy the artifact itself -COPY --from=builder /serai/target/release/wbuild/serai-runtime/serai_runtime.wasm /serai.wasm +COPY --from=builder /serai/target/release/serai_runtime.wasm /serai.wasm # By default, copy the artifact to `/volume`, presumably a provided volume CMD ["busybox", "cp", "/serai.wasm", "/volume/serai.wasm"] diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 9506b4ad..216d92ab 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -74,7 +74,7 @@ fn wasm_binary(dev: bool) -> Vec { } log::info!("using built-in wasm"); - serai_runtime::WASM_BINARY.ok_or("compiled in wasm not available").unwrap().to_vec() + serai_runtime::WASM.to_vec() } fn devnet_genesis(validators: &[&'static str], endowed_accounts: Vec) -> GenesisConfig { diff --git a/substrate/node/src/service/mod.rs b/substrate/node/src/service/mod.rs index 38288074..b3b22c07 100644 --- a/substrate/node/src/service/mod.rs +++ b/substrate/node/src/service/mod.rs @@ -6,7 +6,7 @@ use sp_timestamp::InherentDataProvider as TimestampInherent; use sp_consensus_babe::{SlotDuration, inherents::InherentDataProvider as BabeInherent}; use sp_io::SubstrateHostFunctions; -use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, WasmExecutor}; +use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, HeapAllocStrategy, WasmExecutor}; use sc_network::{Event, NetworkEventStream, NetworkBackend}; use sc_service::{error::Error as ServiceError, Configuration, TaskManager, TFullClient}; @@ -99,14 +99,13 @@ pub fn new_partial( }) .transpose()?; - #[allow(deprecated)] - let executor = Executor::new( - config.executor.wasm_method, - config.executor.default_heap_pages, - config.executor.max_runtime_instances, - None, - config.executor.runtime_cache_size, - ); + let executor = Executor::builder() + .with_execution_method(config.executor.wasm_method) + .with_onchain_heap_alloc_strategy(HeapAllocStrategy::Dynamic { maximum_pages: None }) + .with_offchain_heap_alloc_strategy(HeapAllocStrategy::Dynamic { maximum_pages: None }) + .with_max_runtime_instances(config.executor.max_runtime_instances) + .with_runtime_cache_size(config.executor.runtime_cache_size) + .build(); let (client, backend, keystore_container, task_manager) = { let telemetry = telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()); diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index d404f3c4..0b10a0a8 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -70,9 +70,6 @@ serai-dex-pallet = { path = "../dex", default-features = false } serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false } serai-in-instructions-pallet = { path = "../in-instructions", default-features = false } -[build-dependencies] -substrate-wasm-builder = { git = "https://github.com/serai-dex/patch-polkadot-sdk" } - [features] std = [ "scale/std", diff --git a/substrate/runtime/build.rs b/substrate/runtime/build.rs index 71bee10e..f1f72c9b 100644 --- a/substrate/runtime/build.rs +++ b/substrate/runtime/build.rs @@ -1,4 +1,90 @@ fn main() { - #[cfg(not(target_family = "wasm"))] - substrate_wasm_builder::WasmBuilder::build_using_defaults(); + use std::{path::PathBuf, fs, env, process::Command}; + + // Prevent recursing infinitely + if env::var("TARGET").unwrap() == "wasm32v1-none" { + return; + } + + // https://github.com/rust-lang/rust/issues/145491 + const ONE_45491: &str = "-C link-arg=--mllvm=-mcpu=mvp,--mllvm=-mattr=+mutable-globals"; + const WASM: &str = "-C link-arg=--export-table"; + const REQUIRED_BY_SUBSTRATE: &str = "--cfg substrate_runtime -C link-arg=--import-memory"; + const SAFETY: &str = "-C overflow-checks=true -C panic=abort"; + const COMPILATION: &str = + "-C symbol-mangling-version=v0 -C embed-bitcode=false -C linker-plugin-lto=true"; + + let profile = env::var("PROFILE").unwrap(); + let release = profile == "release"; + let rustflags = format!("{ONE_45491} {WASM} {REQUIRED_BY_SUBSTRATE} {SAFETY} {COMPILATION}"); + let rustflags = if release { + format!("{rustflags} -C codegen-units=1 -C strip=symbols -C debug-assertions=false") + } else { + rustflags + }; + + let target_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("target"); + + let cargo_command = || { + let cargo = env::var("CARGO").unwrap(); + let mut command = Command::new(&cargo); + command + .current_dir(env::var("CARGO_MANIFEST_DIR").unwrap()) + .env_clear() + .env("PATH", env::var("PATH").unwrap()) + .env("CARGO", cargo) + .env("RUSTC", env::var("RUSTC").unwrap()) + .env("RUSTFLAGS", &rustflags) + .env("CARGO_TARGET_DIR", &target_dir); + command + }; + + let workspace = { + let workspace = cargo_command() + .arg("locate-project") + .arg("--workspace") + .arg("--message-format") + .arg("plain") + .output() + .unwrap(); + assert!(workspace.status.success()); + let mut workspace = PathBuf::from(String::from_utf8(workspace.stdout).unwrap().trim()); + assert_eq!(workspace.file_name().unwrap(), "Cargo.toml"); + assert!(workspace.pop()); + workspace + }; + + // Re-run anytime the workspace changes + // TODO: Re-run anytime `Cargo.lock` or specifically the `src` folders change + println!("cargo::rerun-if-changed={}", workspace.display()); + + let mut command = cargo_command(); + command + .arg("rustc") + .arg("--package") + .arg(env::var("CARGO_PKG_NAME").unwrap()) + .arg("--target") + .arg("wasm32v1-none") + .arg("--crate-type") + .arg("cdylib") + .arg("--no-default-features"); + if release { + command.arg("--release"); + } + assert!(command.status().unwrap().success()); + + // Place the resulting WASM blob into the parent `target` directory + { + let wasm_file = env::var("CARGO_PKG_NAME").unwrap().replace('-', "_") + ".wasm"; + let src_file = target_dir.join("wasm32v1-none").join(&profile).join(&wasm_file); + let dst_file = { + let mut dst_dir = workspace.clone(); + // e.g. workspace/target/debug + dst_dir.extend(["target", &profile]); + let _ = fs::create_dir_all(&dst_dir); + // e.g. workspace/target/debug/serai_runtime.wasm + dst_dir.join(&wasm_file) + }; + fs::copy(&src_file, &dst_file).unwrap(); + } } diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 37aa3e4c..fd828405 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -20,5 +20,9 @@ mod std_runtime_api; pub use std_runtime_api::RuntimeApi; // If this isn't WASM, regardless of what it is, we include the WASM blob from the build script -#[cfg(not(target_family = "wasm"))] -include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +#[cfg(all(not(target_family = "wasm"), debug_assertions))] +pub const WASM: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/target/wasm32v1-none/debug/serai_runtime.wasm")); +#[cfg(all(not(target_family = "wasm"), not(debug_assertions)))] +pub const WASM: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/target/wasm32v1-none/release/serai_runtime.wasm"));