Skip to content

Commit e2cf56d

Browse files
committed
runtime: Register chain host functions explicitly instead of by dynamic discovery
Replace the dynamic loop that discovered chain-specific host functions by excluding BUILTIN_IMPORT_NAMES with explicit registration calls for each chain function (ethereum.call, ethereum.getBalance, ethereum.hasCode). Extract the dispatcher into a `link_chain_host_fn` helper that: - Takes a `&'static str` name, eliminating the `Box::leak` for name strings - Precomputes metrics strings once rather than on every call - Returns a proper error instead of panicking when a chain host function is not available for the current chain Delete the BUILTIN_IMPORT_NAMES constant (55 entries) which is no longer needed.
1 parent 0c3bd27 commit e2cf56d

File tree

1 file changed

+81
-123
lines changed

1 file changed

+81
-123
lines changed

runtime/wasm/src/module/instance.rs

Lines changed: 81 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -249,65 +249,82 @@ impl WasmInstance {
249249
}
250250
}
251251

252-
/// Import names that are handled by builtin host functions. Any import name
253-
/// in `import_name_to_modules` that is not in this set and is not `gas` is
254-
/// assumed to be a chain-specific host function.
255-
const BUILTIN_IMPORT_NAMES: &[&str] = &[
256-
"ethereum.encode",
257-
"ethereum.decode",
258-
"abort",
259-
"store.get",
260-
"store.loadRelated",
261-
"store.get_in_block",
262-
"store.set",
263-
"store.remove",
264-
"ipfs.cat",
265-
"ipfs.map",
266-
"ipfs.getBlock",
267-
"typeConversion.bytesToString",
268-
"typeConversion.bytesToHex",
269-
"typeConversion.bigIntToString",
270-
"typeConversion.bigIntToHex",
271-
"typeConversion.stringToH160",
272-
"typeConversion.bytesToBase58",
273-
"json.fromBytes",
274-
"json.try_fromBytes",
275-
"json.toI64",
276-
"json.toU64",
277-
"json.toF64",
278-
"json.toBigInt",
279-
"yaml.fromBytes",
280-
"yaml.try_fromBytes",
281-
"crypto.keccak256",
282-
"bigInt.plus",
283-
"bigInt.minus",
284-
"bigInt.times",
285-
"bigInt.dividedBy",
286-
"bigInt.dividedByDecimal",
287-
"bigInt.mod",
288-
"bigInt.pow",
289-
"bigInt.fromString",
290-
"bigInt.bitOr",
291-
"bigInt.bitAnd",
292-
"bigInt.leftShift",
293-
"bigInt.rightShift",
294-
"bigDecimal.toString",
295-
"bigDecimal.fromString",
296-
"bigDecimal.plus",
297-
"bigDecimal.minus",
298-
"bigDecimal.times",
299-
"bigDecimal.dividedBy",
300-
"bigDecimal.equals",
301-
"dataSource.create",
302-
"dataSource.createWithContext",
303-
"dataSource.address",
304-
"dataSource.network",
305-
"dataSource.context",
306-
"ens.nameByHash",
307-
"log.log",
308-
"arweave.transactionData",
309-
"box.profile",
310-
];
252+
/// Register a chain-specific host function dispatcher for the given import name.
253+
/// The registered closure looks up the `HostFn` by name from `caller.data().ctx.host_fns`
254+
/// at call time. If the module doesn't import this function, this is a no-op.
255+
fn link_chain_host_fn(
256+
linker: &mut Linker<WasmInstanceData>,
257+
import_name_to_modules: &BTreeMap<String, Vec<String>>,
258+
name: &'static str,
259+
) -> Result<(), anyhow::Error> {
260+
let modules = match import_name_to_modules.get(name) {
261+
Some(m) => m,
262+
None => return Ok(()),
263+
};
264+
265+
let name_for_metrics = name.replace('.', "_");
266+
let section_name = format!("host_export_{}", name_for_metrics);
267+
268+
for module in modules {
269+
let name_for_metrics = name_for_metrics.clone();
270+
let section_name = section_name.clone();
271+
linker.func_wrap_async(
272+
module,
273+
name,
274+
move |mut caller: wasmtime::Caller<'_, WasmInstanceData>, (call_ptr,): (u32,)| {
275+
let name_for_metrics = name_for_metrics.clone();
276+
let section_name = section_name.clone();
277+
Box::new(async move {
278+
let host_fn = caller
279+
.data()
280+
.ctx
281+
.host_fns
282+
.iter()
283+
.find(|hf| hf.name == name)
284+
.ok_or_else(|| {
285+
anyhow::anyhow!(
286+
"chain host function '{}' is not available for this chain",
287+
name
288+
)
289+
})?
290+
.cheap_clone();
291+
292+
let start = Instant::now();
293+
294+
let gas = caller.data().gas.cheap_clone();
295+
let host_metrics = caller.data().host_metrics.cheap_clone();
296+
let stopwatch = host_metrics.stopwatch.cheap_clone();
297+
let _section = stopwatch.start_section(&section_name);
298+
299+
let ctx = HostFnCtx {
300+
logger: caller.data().ctx.logger.cheap_clone(),
301+
block_ptr: caller.data().ctx.block_ptr.cheap_clone(),
302+
gas: gas.cheap_clone(),
303+
metrics: host_metrics.cheap_clone(),
304+
heap: &mut WasmInstanceContext::new(&mut caller),
305+
};
306+
let ret = (host_fn.func)(ctx, call_ptr).await.map_err(|e| match e {
307+
HostExportError::Deterministic(e) => {
308+
caller.data_mut().deterministic_host_trap = true;
309+
e
310+
}
311+
HostExportError::PossibleReorg(e) => {
312+
caller.data_mut().possible_reorg = true;
313+
e
314+
}
315+
HostExportError::Unknown(e) => e,
316+
})?;
317+
host_metrics.observe_host_fn_execution_time(
318+
start.elapsed().as_secs_f64(),
319+
&name_for_metrics,
320+
);
321+
Ok(ret)
322+
})
323+
},
324+
)?;
325+
}
326+
Ok(())
327+
}
311328

312329
/// Build a pre-linked `Linker` for a WASM module. This linker can be reused across triggers by
313330
/// calling `linker.instantiate_pre()` once and then `instance_pre.instantiate_async()` per trigger.
@@ -431,70 +448,11 @@ pub(crate) fn build_linker(
431448
};
432449
}
433450

434-
// Link chain-specific host fns. Any import name not in BUILTIN_IMPORT_NAMES and not "gas"
435-
// is assumed to be a chain-specific host function. We register a generic dispatcher that
436-
// looks up the actual HostFn by name from caller.data().ctx.host_fns at call time.
437-
for (import_name, modules) in import_name_to_modules {
438-
if import_name == "gas" || BUILTIN_IMPORT_NAMES.contains(&import_name.as_str()) {
439-
continue;
440-
}
441-
442-
// Leak the name so we get a &'static str for metrics. These are a small, fixed set of
443-
// chain host_fn names (e.g. "ethereum.call") so the leak is bounded.
444-
let name: &'static str = Box::leak(import_name.clone().into_boxed_str());
445-
446-
for module in modules {
447-
linker.func_wrap_async(
448-
module,
449-
name,
450-
move |mut caller: wasmtime::Caller<'_, WasmInstanceData>, (call_ptr,): (u32,)| {
451-
Box::new(async move {
452-
let host_fn = caller
453-
.data()
454-
.ctx
455-
.host_fns
456-
.iter()
457-
.find(|hf| hf.name == name)
458-
.expect("chain host_fn not found")
459-
.cheap_clone();
460-
461-
let start = Instant::now();
462-
463-
let gas = caller.data().gas.cheap_clone();
464-
let name_for_metrics = name.replace('.', "_");
465-
let host_metrics = caller.data().host_metrics.cheap_clone();
466-
let stopwatch = host_metrics.stopwatch.cheap_clone();
467-
let _section =
468-
stopwatch.start_section(&format!("host_export_{}", name_for_metrics));
469-
470-
let ctx = HostFnCtx {
471-
logger: caller.data().ctx.logger.cheap_clone(),
472-
block_ptr: caller.data().ctx.block_ptr.cheap_clone(),
473-
gas: gas.cheap_clone(),
474-
metrics: host_metrics.cheap_clone(),
475-
heap: &mut WasmInstanceContext::new(&mut caller),
476-
};
477-
let ret = (host_fn.func)(ctx, call_ptr).await.map_err(|e| match e {
478-
HostExportError::Deterministic(e) => {
479-
caller.data_mut().deterministic_host_trap = true;
480-
e
481-
}
482-
HostExportError::PossibleReorg(e) => {
483-
caller.data_mut().possible_reorg = true;
484-
e
485-
}
486-
HostExportError::Unknown(e) => e,
487-
})?;
488-
host_metrics.observe_host_fn_execution_time(
489-
start.elapsed().as_secs_f64(),
490-
&name_for_metrics,
491-
);
492-
Ok(ret)
493-
})
494-
},
495-
)?;
496-
}
497-
}
451+
// Chain-specific host functions. Each is registered explicitly rather than
452+
// discovered dynamically from imports.
453+
link_chain_host_fn(&mut linker, import_name_to_modules, "ethereum.call")?;
454+
link_chain_host_fn(&mut linker, import_name_to_modules, "ethereum.getBalance")?;
455+
link_chain_host_fn(&mut linker, import_name_to_modules, "ethereum.hasCode")?;
498456

499457
link!("ethereum.encode", ethereum_encode, params_ptr);
500458
link!("ethereum.decode", ethereum_decode, params_ptr, data_ptr);

0 commit comments

Comments
 (0)