Compilando Hooks
Restrições
Seção intitulada “Restrições”Todos os Hooks são compilados em um único módulo webassembly antes de poderem ser instalados em uma conta Xahau.
Um Hook sempre implementa e exporta exatamente uma ou ambas das seguintes funções:
int64_t hook(uint32_t ctx) { ... } obrigatória
- Executada sempre que uma transação chega ou sai da conta onde o Hook está instalado (
ctx = 0) ou - Executada quando executada como uma Parte Interessada Transacional Fraca (
ctx > 0).
int64_t cbak(uint32_t ctx) { ... } opcional
- Executada quando uma transação emitida é aceita com sucesso em um ledger (
ctx = 0) ou - Executada quando uma transação emitida não pode ser aceita em nenhum ledger (
ctx = 1).
Os Hooks não podem especificar outras funções. Em vez disso, devem usar macros de forma inteligente para realizar todos os cálculos dentro dessas duas funções. Essa é parte de uma restrição computacional dos hooks para manter seus tempos de execução previsíveis.
Adicionalmente, os Hooks não têm acesso a memória heap. Toda a memória necessária deve ser reservada e utilizada na pilha.
Exemplo
Seção intitulada “Exemplo”Aqui está um exemplo de Hook escrito em C. O Hook imprime 0…3 no log de rastreamento antes de aceitar a transação de origem.
#include <stdint.h>#define GUARD(maxiter) _g(__LINE__, (maxiter)+1)extern int32_t _g (uint32_t id, uint32_t maxiter);extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);extern int64_t trace_num (uint32_t read_ptr, uint32_t read_len, int64_t number);
int64_t hook(uint32_t ctx){ for (int i = 0; GUARD(3), i < 3; ++i) { trace_num("test", 4, i); } accept (0,0,0); return 0;}Compilação
Seção intitulada “Compilação”Uma variedade de compiladores irá gerar webassembly válido a partir de um arquivo fonte C. Uma vez compilado, um Hook existe como um arquivo binário .wasm. Este contém um módulo webassembly. Usando wasmcc para compilar e a ferramenta wasm2wat para converter para webassembly legível, essa forma binária pode ser renderizada para a forma legível. Abaixo aparece o resultado da compilação do exemplo acima.
(module (type (;0;) (func (param i32 i32) (result i32))) (type (;1;) (func (param i32 i32 i64) (result i64))) (type (;2;) (func)) (type (;3;) (func (param i32) (result i64))) (import "env" "_g" (func $_g (type 0))) (import "env" "trace_num" (func $trace_num (type 1))) (import "env" "accept" (func $accept (type 1))) (func $__wasm_call_ctors (type 2)) (func $cbak (type 3) (param i32) (result i64) (local i32 i32 i32 i64) global.get 0 local.set 1 i32.const 16 local.set 2 local.get 1 local.get 2 i32.sub local.set 3 i64.const 0 local.set 4 local.get 3 local.get 0 i64.store offset=8 local.get 4 return) (func $hook (type 3) (param i32) (result i64) (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i64 i32 i32 i32 i64 i32 i32 i32) global.get 0 local.set 1 i32.const 16 local.set 2 local.get 1 local.get 2 i32.sub local.set 3 local.get 3 global.set 0 i32.const 0 local.set 4 local.get 3 local.get 0 i64.store offset=8 local.get 3 local.get 4 i32.store offset=4 block ;; label = @1 loop ;; label = @2 i32.const 3 local.set 5 i32.const 14 local.set 6 i32.const 4 local.set 7 local.get 6 local.get 7 call $_g drop local.get 3 i32.load offset=4 local.set 8 local.get 8 local.set 9 local.get 5 local.set 10 local.get 9 local.get 10 i32.lt_s local.set 11 i32.const 1 local.set 12 local.get 11 local.get 12 i32.and local.set 13 local.get 13 i32.eqz br_if 1 (;@1;) i32.const 1024 local.set 14 i32.const 4 local.set 15 local.get 3 i32.load offset=4 local.set 16 local.get 16 local.set 17 local.get 17 i64.extend_i32_s local.set 18 local.get 14 local.get 15 local.get 18 call $trace_num drop local.get 3 i32.load offset=4 local.set 19 i32.const 1 local.set 20 local.get 19 local.get 20 i32.add local.set 21 local.get 3 local.get 21 i32.store offset=4 br 0 (;@2;) end end i64.const 0 local.set 22 i32.const 0 local.set 23 local.get 23 local.get 23 local.get 22 call $accept drop i32.const 16 local.set 24 local.get 3 local.get 24 i32.add local.set 25 local.get 25 global.set 0 local.get 22 return) (table (;0;) 1 1 funcref) (memory (;0;) 2) (global (;0;) (mut i32) (i32.const 66576)) (global (;1;) i32 (i32.const 1029)) (global (;2;) i32 (i32.const 1024)) (global (;3;) i32 (i32.const 66576)) (global (;4;) i32 (i32.const 1024)) (export "memory" (memory 0)) (export "__wasm_call_ctors" (func $__wasm_call_ctors)) (export "__data_end" (global 1)) (export "__global_base" (global 2)) (export "__heap_base" (global 3)) (export "__dso_handle" (global 4)) (export "cbak" (func $cbak)) (export "hook" (func $hook)) (data (;0;) (i32.const 1024) "test\00"))O desenvolvedor médio de Hooks nunca precisará examinar o webassembly diretamente. No entanto, é um exercício conceitual útil revisar o conteúdo do Hook de exemplo.
Acima podemos ver:
- Três funções são importadas da API de Hooks (
_g,accept,trace_num) - Duas funções são definidas pelo hook (
cbak,hook) - Duas funções são exportadas pelo hook (novamente:
cbak,hook) - Alguns dados estáticos (constantes) são registrados no hook (veja
datano final).
É muito importante notar que um Hook deve apenas importar funções disponíveis para ele da API de Hooks e deve apenas exportar as funções cbak e hook. Adicionalmente, todos os hooks devem importar _g da API de Hooks, que é a função guard.
Exportações Indesejadas
Seção intitulada “Exportações Indesejadas”A maioria dos compiladores webassembly (incluindo o acima) produz exportações adicionais para seus próprios propósitos de vinculação. Em muitos casos, a geração dessas é difícil ou impossível de desativar.
Exportações indesejadas levarão a um Hook de outra forma válido ser rejeitado. Portanto, após a compilação, os desenvolvedores devem usar o Hook Cleaner Utility para removê-las. Deixar de fazer isso resultará na rejeição do seu Hook.