WEB3DEV Español

Cover image for Substrate Nativo Solo Sin Wasm
Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Substrate Nativo Solo Sin Wasm

Image description

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:

  1. Trabajadores fuera de la cadena
  2. 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Soportar no-std.
  2. std no está activado por defecto. (esto es lo que default-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"] }
Enter fullscreen mode Exit fullscreen mode

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.

https://github.com/paritytech/substrate/blob/master/utils/wasm-builder/README.md#environment-variables

SKIP_WASM_BUILD=1 cargo build
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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())?;
// ...
}
Enter fullscreen mode Exit fullscreen mode

Ya que no tenemos wasm, tenemos que remover esta casilla

let wasm_binary = WASM_BINARY.unwrap_or(&[] as &[u8]);
Enter fullscreen mode Exit fullscreen mode

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\")")
Enter fullscreen mode Exit fullscreen mode

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>>,
}
Enter fullscreen mode Exit fullscreen mode

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
}
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

¡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"

Image description

Constrúyelo y ejecútalo, ¡ahora debería funcionar!

SKIP_WASM_BUILD=1 cargo build
./target/debug/node-template --dev --ejecución nativa
Enter fullscreen mode Exit fullscreen mode

¡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)