WEB3DEV

Cover image for Substrate Exclusivamente Nativo Sem Wasm
Adriano P. Araujo
Adriano P. Araujo

Posted on

Substrate Exclusivamente Nativo Sem Wasm

Image description

Embora a execução do Wasm seja uma característica principal do Substrate, pode haver casos em que você deseje desativá-lo. Neste artigo, mostrarei como construir uma cadeia Substrate exclusivamente nativa sem Wasm.

Aviso: Pense duas vezes sobre seu projeto!

Este não é um método recomendado para construir uma cadeia Substrate.

A equipe da Parity está removendo a execução nativa: The road to the native runtime free world #62. Ou seja, estamos indo na direção oposta.

Se você deseja usar algumas bibliotecas nativas que não oferecem suporte ao no-std dentro do runtime pallet, você deve considerar usar:

  1. trabalhadores offchain

  2. interface de tempo de execução (runtime_interface)

Ambos estão no nó externo, não no tempo de execução, então podem usar bibliotecas std e não estão limitados pelo Wasm.

Configuração do Ambiente

Ambiente: substrate-node-template | tag: polkadot-v0.9.40 | commit: 700c3a1

Suponha que você queira importar uma biblioteca Rust padrão chamada rust-bert em seu runtime pallet. O rust-bert é uma biblioteca de aprendizado de máquina que inclui modelos de linguagem grandes (LLMs -  large language models no original) como o GPT2.

Primeiro, faça o download do 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

Compilando

Adicione rust-bert como uma dependência em pallets/template/Cargo.toml.

Você também precisa especificar getrandom como uma dependência. Caso contrário, ele lançará um erro error: the wasm32-unknown-unknown target is not supported by default, you may need to enable the "js" feature

(Erro: o alvo wasm32-unknown-unknown não é suportado por padrão; talvez seja necessário habilitar o recurso "js" - em tradução livre)

. Para obter mais informações, consulte: https://docs.rs/getrandom/#webassembly-support

No runtime pallets, todas as suas dependências devem:

  1. Oferecer suporte a no-std.

  2. O std não deve estar habilitado por padrão. (isso é o que default-features = false realiza)

Caso contrário, você receberá um erro error[E0152]: found duplicate lang item panic_impl (erro[E0152]: encontrado item de linguagem duplicado panic_impl - em tradução livre) ao compilar. O motivo é que o std está vazando para o código do tempo de execução.

Você pode verificar esta pergunta feita no Stack Overflow para obter mais detalhes.

Em 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

No entanto, o rust-bert não oferece suporte a no-std. Mesmo se você adicionar default-feature = false no Cargo.toml, ainda lançará um erro error[E0152]: found duplicate lang item panic_impl ao executar o cargo build.

Para corrigir esse erro, você deve pular a compilação do código wasm adicionando o 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

Rodando

Neste ponto, você deve ter construído com sucesso um Substrate exclusivamente nativo, sem wasm.

No entanto, executar o binário de destino não é simples.

--execution native especifica a estratégia de execução como nativa em primeiro lugar.


./target/debug/node-template --dev --execution native

Error: Input("Development wasm not available")

Enter fullscreen mode Exit fullscreen mode

Procure por "Development wasm not available" no código-fonte, você verá que ele é gerado no 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

Como não temos wasm, devemos remover esta verificação.


let wasm_binary = WASM_BINARY.unwrap_or(&[] as &[u8]);

Enter fullscreen mode Exit fullscreen mode

Recompile e execute novamente. Um novo erro ocorre:


./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

Esse erro é muito mais difícil de depurar. Passei muito tempo procurando a causa raiz.

Processo de Depuração

Uma busca online encontrou apenas um problema semelhante: https://github.com/paritytech/substrate/issues/7675. O desenvolvedor principal @bkchr sugeriu criar um binário wasm fictício para contornar a verificação.

Não consegui encontrar informações relevantes sobre como construir um binário wasm fictício. Portanto, decidi depurar o código passo a passo. Finalmente, encontrei a causa raiz em uma dependência do sistema chamada native_executor.rs.

Existem duas implementações de executores: WasmExecutor e NativeElseWasmExecutor. Quando o --execution native é especificado, o NativeElseWasmExecutor será usado.

O NativeElseWasmExecutor envolve o WasmExecutor como seu campo.




/// Implementação genérica de CodeExecutor que usa um delegate para determinar a equivalência de código wasm

/// e despachar para código nativo quando possível, recorrendo ao WasmExecutor quando não for possível.

pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {

/// Informações da versão nativa do tempo de execução.

native_version: NativeVersion,

/// Executor wasm de fallback.

wasm:

WasmExecutor<ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>>,

}




Durante a execução do nó do Substrate, mesmo se o `NativeElseWasmExecutor` for usado, ele ainda tentará carregar o binário wasm em 2 métodos: `runtime_version` e `call`.



Vamos examinar o `runtime_version` primeiro:



Enter fullscreen mode Exit fullscreen mode

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()) // <--- Edição: Devemos retornar a versão nativa

        self.wasm.runtime_version(ext, runtime_code) // <--- Original: Ele tentará carregar o binário wasm

    }

}

Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos olhar para a função call.

Ela primeiro verificará se a versão nativa é compatível com a versão em cadeia. Se for compatível, ela chamará o executor nativo. Caso contrário, chamará o executor wasm.

No entanto, não temos um binário wasm válido, então sempre devemos chamar o executor nativo. Eu apenas uso esse código se 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) {

        // Edição

        // Não verifica wasm, pois é fictício, usa execução nativa diretamente

        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 {

                    // chama o executor 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 {

                    // chama o executor 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

Concluído!

Você pode usar meu fork do repositório do Substrate, onde o problema já está corrigido.

Você deve substituir o URL das dependências em 3 arquivos Cargo.toml:


git = "https://github.com/paritytech/substrate.git" -> git = "https://github.com/doutv/substrate.git"

Enter fullscreen mode Exit fullscreen mode

Compile e execute de novo,  e deve funcionar!


SKIP_WASM_BUILD=1 cargo build

./target/debug/node-template --dev --execution native

Enter fullscreen mode Exit fullscreen mode

Conseguimos construir com sucesso uma cadeia Substrate exclusivamente nativa, sem wasm!

Código final: fork do repositório do substrate-node-template na branch native-only-polkadot-v0.9.40, https://github.com/doutv/substrate-node-template/tree/native-only-polkadot-v0.9.40

Melhorias Futuras

Modificar o NativeElseWasmExecutor não é a melhor solução, você pode adicionar uma nova implementação executora NativeOnlyExecutor.


Este artigo foi escrito por Backdoor e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui

Top comments (0)