Aunque la ejecución de Wasm es la característica principal de Substrate, deberías desactivarlo en algunos casos de uso. En este artículo, te mostraré cómo construir una cadena sólo nativa de Substrate sin wasm.
Alerta: ¡Piensa dos veces en tu diseño!
Esta no es la forma recomendada de construir una cadena Substrate
El equipo de Parity está removiendo la ejecución nativa: The road to the native runtime free world #62. Estamos en la dirección opuesta.
Si quieres usar algunas bibliotecas nativas, las cuales no soportan no-std
dentro de la paleta runtime, deberías considerar usar:
- Trabajadores fuera de la cadena
- Runtime_interface
Ambos están en los nodos externos, no en runtime, así que pueden usar bibliotecas std
y no estar limitado por wasm.
Configuración del Entorno
Entorno: substrate-node-template | tag: polkadot-v0.9.40 | commit: 700c3a1
Supón que quieres importar una biblioteca Rust std
llamada rust-bert en tu paleta runtime. rust-bert
es una biblioteca de machine learning que incluye grandes modelos de lenguaje (Large Language Models, LLMs) como GPT2.
Primero, descarga substrate-node-template
git clone https://github.com/substrate-developer-hub/substrate-node-template
cd substrate-node-template
git checkout polkadot-v0.9.40
Construcción
Añade rust-bert
como dependencia en pallets/template/Cargo.toml
.
También necesitas especificar getrandom
como dependencia. De lo contrario, te arrojará un error error: the wasm32-unknown-unknown target is not supported by default, you may need to enable the "js" feature.
Para más información revisa: https://docs.rs/getrandom/#webassembly-support
En las paletas runtime, todas tus dependencias deben:
- Soportar
no-std
. -
std
no está activado por defecto. (esto es lo quedefault-features = false
logra)
De lo contrario, tendrás un error error[E0152]: found duplicate lang item panic_impl
cuando construyes. La razón es que std
tiene una fuga en el código runtime.
Puedes revisar esta consulta stackoverflow para más detalles.
En pallets/template/Cargo.toml
:
[dependencies]
rust-bert = { version = "0.21.0", default-features = false, features = ["remote", "download-libtorch"] }
getrandom = { version = "0.2", default-features = false, features = ["js"] }
Sin embargo, rust-bert
no soporta no-std
. Incluso si añades default-feature = false
en Cargo.toml
, igual te arrojará un error error[E0152]: found duplicate lang item panic_impl
cuando ejecutas cargo build
.
Para arreglar este error, deberías saltarte construir el código wasm, añadiendo un nuevo env SKIP_WASM_BUILD=1
.
SKIP_WASM_BUILD=1 cargo build
Ejecuta
Ahora, deberías haber construído exitosamente un Substrate nativo solo sin wasm.
Ejecutar el objetivo binario no es fácil.
--execution native
especifica la estrategia de ejecución primero a nativo
./target/debug/node-template --dev --execution native
Error: Input("Development wasm not available")
Busca Development wasm not available
en el codebase, te percatarás que es arrojado por node/src/chain_spec.rs
.
pub fn development_config() -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?;
// ...
}
Ya que no tenemos wasm, tenemos que remover esta casilla
let wasm_binary = WASM_BINARY.unwrap_or(&[] as &[u8]);
Reconstruye y ejecuta de nuevo, un nuevo error ocurrirá:
./target/debug/node-template --dev --execution native
2023-08-31 18:10:07 Substrate Node
2023-08-31 18:10:07 ✌️ version 4.0.0-dev-700c3a186e5
2023-08-31 18:10:07 ❤️ by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2023
2023-08-31 18:10:07 📋 Chain specification: Development
2023-08-31 18:10:07 🏷 Node name: evanescent-agreement-4299
2023-08-31 18:10:07 👤 Role: AUTHORITY
2023-08-31 18:10:07 💾 Database: RocksDb at /tmp/substrate8yJbyt/chains/dev/db/full
2023-08-31 18:10:07 ⛓ Native runtime: node-template-100 (node-template-1.tx1.au1)
Error: Service(Client(VersionInvalid("cannot deserialize module: HeapOther(\"I/O Error: UnexpectedEof\")")))
2023-08-31 18:10:08 Cannot create a runtime error=Other("cannot deserialize module: HeapOther(\"I/O Error: UnexpectedEof\")")
Este error es mucho más difícil de debuggear. Usé mucho tiempo para encontrar la raíz del problema.
Proceso de Debuggeo
Si buscas online encontrarás problemas similares: https://github.com/paritytech/substrate/issues/7675. El desarrollador central @bkchr sugiere crear un wasm binario ficticio para eludir la casilla.
No pude encontrar información relevante sobre cómo construir un wasm binario ficticio. Así que decidí debuggear el código paso a paso. Por último, encontré la raíz del problema: está en una de las dependencias del sistema: native_executor.rs
Hay dos implementaciones de ejecución: WasmExecutor
y NativeElseWasmExecutor
. Cuando --execution native
es especificado, NativeElseWasmExecutor
será usado.
NativeElseWasmExecutor
envuelve WasmExecutor
como su campo.
/// Una implementación genérica `CodeExecutor` que usa delegate para determinar la equivalencia del código wasm
/// y despachar el código nativo cuando sea posible, caer en `WasmExecutor` cuando no.
pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
/// Información de la versión nativa runtime.
native_version: NativeVersion,
/// Ejecutador fallback wasm.
wasm:
WasmExecutor<ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>>,
}
Mientras que el substrate del nodo ejecutado, incluso si NativeElseWasmExecutor
se usa, seguirá intentando cargar el binario wasm en 2 métodos: runtime_version
y call
.
Veamos la [versión runtime](https://github.com/paritytech/substrate/blob/98f2e3451c9143278ec53c6718940aeabcd3b68a/client/executor/src/native_executor.rs#L588)
primero:
impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
fn runtime_version(
&self,
ext: &mut dyn Externalities,
runtime_code: &RuntimeCode,
) -> Result<RuntimeVersion> {
Ok(self.native_version.runtime_version.clone()) // <--- Edit: Deberíamos regresar la versión nativa
self.wasm.runtime_version(ext, runtime_code) // <--- Original: intentará cargar el binario wasm
}
}
Veamos a [call](https://github.com/paritytech/substrate/blob/98f2e3451c9143278ec53c6718940aeabcd3b68a/client/executor/src/native_executor.rs#L607)
.
Primero revisará si la versión nativa es compatible con la versión en la cadena. Si es compatible, llamará a un ejecutador nativo. De otra forma, llamará al ejecutador wasm.
Sin embargo, no tenemos un binario wasm válido así que siempre deberíamos llamar al ejecutador nativo. Sólo uso el código dentro de de esta rama if use_native && can_call_with {}
.
impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
type Error = Error;
fn call(
&self,
ext: &mut dyn Externalities,
runtime_code: &RuntimeCode,
method: &str,
data: &[u8],
use_native: bool,
context: CallContext,
) -> (Result<Vec<u8>>, bool) {
// Edit
// No revises el wasm ya que es ficticio, usa los ejecutadores nativos directamente
let used_native = true;
let mut ext = AssertUnwindSafe(ext);
let result = match with_externalities_safe(&mut **ext, move || D::dispatch(method, data)) {
Ok(Some(value)) => Ok(value),
Ok(None) => Err(Error::MethodNotFound(method.to_owned())),
Err(err) => Err(err),
};
(result, used_native)
// Original
tracing::trace!(
target: "executor",
function = %method,
"Executing function",
);
let on_chain_heap_alloc_strategy = runtime_code
.heap_pages
.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
.unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy);
let heap_alloc_strategy = match context {
CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
CallContext::Onchain => on_chain_heap_alloc_strategy,
};
let mut used_native = false;
let result = self.wasm.with_instance(
runtime_code,
ext,
heap_alloc_strategy,
|_, mut instance, onchain_version, mut ext| {
let onchain_version =
onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
let can_call_with =
onchain_version.can_call_with(&self.native_version.runtime_version);
if use_native && can_call_with {
// llama al ejecutador nativo
tracing::trace!(
target: "executor",
native = %self.native_version.runtime_version,
chain = %onchain_version,
"Request for native execution succeeded",
);
used_native = true;
Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
.ok_or_else(|| Error::MethodNotFound(method.to_owned())))
} else {
// llama al ejecutador wasm
if !can_call_with {
tracing::trace!(
target: "executor",
native = %self.native_version.runtime_version,
chain = %onchain_version,
"Request for native execution failed",
);
}
with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
}
},
);
(result, used_native)
}
¡Listo!
Puedes usar mi repositorio de substrate forked, el cual ya tiene el problema arreglado.
Deberías reemplazar las dependencias irl en 3 archivos Cargo.toml
:
git = "https://github.com/paritytech/substrate.git"
-> git = "https://github.com/doutv/substrate.git"
Constrúyelo y ejecútalo, ¡ahora debería funcionar!
SKIP_WASM_BUILD=1 cargo build
./target/debug/node-template --dev --ejecución nativa
¡Hemos construído exitosamente una cadena Substrate nativo solo sin wasm!
Código final: el nodo de la plantilla substrate forked del repositorio en la rama solo nativa de polkadot v0.9.40, https://github.com/doutv/substrate-node-template/tree/native-only-polkadot-v0.9.40
Mejoras Adicionales
Modificar NativeElseWasmExecutor
no es la mejor solución, puedes añadir una nueva implementación de ejecución NativeOnlyExecutor
.
Este artículo es una traducción de Backdoor, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.
Discussion (0)