1use crate::{
2 module_factory::ModuleFactory,
3 runtime::{ContractRuntime, ExecutionMode, SystemRuntime},
4 RuntimeContext,
5};
6use fluentbase_types::{
7 byteorder::{ByteOrder, LittleEndian},
8 import_linker_v1_preview, Address, BytecodeOrHash, ExitCode, HashMap, B256,
9};
10use rwasm::{ExecutionEngine, ImportLinker, RwasmModule, StrategyDefinition, TrapCode};
11use std::{cell::RefCell, mem::take, sync::Arc};
12
13#[derive(Default, Clone, Debug)]
17pub struct ExecutionResult {
18 pub exit_code: i32,
20 pub fuel_consumed: u64,
22 pub fuel_refunded: i64,
24 pub output: Vec<u8>,
26 pub return_data: Vec<u8>,
28}
29
30impl ExecutionResult {
31 pub fn take_and_continue(&mut self, is_interrupted: bool) -> Self {
32 let mut result = take(self);
33 if is_interrupted {
35 self.output = take(&mut result.output);
36 self.return_data = take(&mut result.return_data);
37 }
38 result
39 }
40}
41
42#[derive(Debug, Default, Clone)]
44pub struct ExecutionInterruption {
45 pub fuel_consumed: u64,
47 pub fuel_refunded: i64,
49 pub return_data: Vec<u8>,
51}
52
53#[derive(Clone, Debug)]
55pub enum RuntimeResult {
56 Result(ExecutionResult),
58 Interruption(ExecutionInterruption),
60}
61
62impl RuntimeResult {
63 pub fn into_execution_result(self) -> ExecutionResult {
65 match self {
66 RuntimeResult::Result(result) => result,
67 _ => unreachable!(),
68 }
69 }
70}
71
72pub trait RuntimeExecutor {
73 fn execute(&mut self, bytecode_or_hash: BytecodeOrHash, ctx: RuntimeContext)
77 -> ExecutionResult;
78
79 fn resume(
83 &mut self,
84 call_id: u32,
85 return_data: &[u8],
86 fuel16_ptr: u32,
87 fuel_consumed: u64,
88 fuel_refunded: i64,
89 exit_code: i32,
90 ) -> ExecutionResult;
91
92 fn forget_runtime(&mut self, call_id: u32);
94
95 fn warmup(&mut self, bytecode: RwasmModule, hash: B256, address: Address);
97
98 fn reset_call_id_counter(&mut self);
102
103 fn memory_read(
104 &mut self,
105 call_id: u32,
106 offset: usize,
107 buffer: &mut [u8],
108 ) -> Result<(), TrapCode>;
109}
110
111pub struct ThreadLocalExecutor;
112
113thread_local! {
114 pub static LOCAL_RUNTIME_EXECUTOR: RefCell<RuntimeFactoryExecutor> = RefCell::new(RuntimeFactoryExecutor::new(import_linker_v1_preview()));
115}
116
117impl RuntimeExecutor for ThreadLocalExecutor {
118 fn execute(
119 &mut self,
120 bytecode_or_hash: BytecodeOrHash,
121 ctx: RuntimeContext,
122 ) -> ExecutionResult {
123 LOCAL_RUNTIME_EXECUTOR
124 .with_borrow_mut(|runtime_executor| runtime_executor.execute(bytecode_or_hash, ctx))
125 }
126
127 fn resume(
128 &mut self,
129 call_id: u32,
130 return_data: &[u8],
131 fuel16_ptr: u32,
132 fuel_consumed: u64,
133 fuel_refunded: i64,
134 exit_code: i32,
135 ) -> ExecutionResult {
136 LOCAL_RUNTIME_EXECUTOR.with_borrow_mut(|runtime_executor| {
137 runtime_executor.resume(
138 call_id,
139 return_data,
140 fuel16_ptr,
141 fuel_consumed,
142 fuel_refunded,
143 exit_code,
144 )
145 })
146 }
147
148 fn forget_runtime(&mut self, call_id: u32) {
149 LOCAL_RUNTIME_EXECUTOR
150 .with_borrow_mut(|runtime_executor| runtime_executor.forget_runtime(call_id))
151 }
152
153 fn warmup(&mut self, bytecode: RwasmModule, hash: B256, address: Address) {
154 LOCAL_RUNTIME_EXECUTOR
155 .with_borrow_mut(|runtime_executor| runtime_executor.warmup(bytecode, hash, address))
156 }
157
158 fn reset_call_id_counter(&mut self) {
159 LOCAL_RUNTIME_EXECUTOR
160 .with_borrow_mut(|runtime_executor| runtime_executor.reset_call_id_counter())
161 }
162
163 fn memory_read(
164 &mut self,
165 call_id: u32,
166 offset: usize,
167 buffer: &mut [u8],
168 ) -> Result<(), TrapCode> {
169 LOCAL_RUNTIME_EXECUTOR.with_borrow_mut(|runtime_executor| {
170 runtime_executor.memory_read(call_id, offset, buffer)
171 })
172 }
173}
174
175pub fn default_runtime_executor() -> impl RuntimeExecutor {
177 ThreadLocalExecutor {}
178}
179
180pub struct RuntimeFactoryExecutor {
181 pub module_factory: ModuleFactory,
183 pub recoverable_runtimes: HashMap<u32, ExecutionMode>,
185 pub import_linker: Arc<ImportLinker>,
187 pub transaction_call_id_counter: u32,
189}
190
191impl RuntimeFactoryExecutor {
192 pub fn new(import_linker: Arc<ImportLinker>) -> Self {
193 Self {
194 module_factory: ModuleFactory::new(),
195 recoverable_runtimes: HashMap::new(),
196 import_linker,
197 transaction_call_id_counter: 1,
198 }
199 }
200
201 pub fn try_remember_runtime(
203 &mut self,
204 runtime_result: RuntimeResult,
205 runtime: ExecutionMode,
206 ) -> ExecutionResult {
207 let interruption = match runtime_result {
208 RuntimeResult::Result(result) => {
209 return result;
211 }
212 RuntimeResult::Interruption(interruption) => interruption,
213 };
214
215 let call_id = self.transaction_call_id_counter;
217
218 if call_id > i32::MAX as u32 {
220 return ExecutionResult {
221 exit_code: ExitCode::UnknownError.into_i32(),
222 fuel_consumed: interruption.fuel_consumed,
223 fuel_refunded: interruption.fuel_refunded,
224 output: vec![],
225 return_data: vec![],
226 };
227 }
228
229 self.transaction_call_id_counter += 1;
231 let prev = self.recoverable_runtimes.insert(call_id, runtime);
232 debug_assert!(prev.is_none());
233
234 ExecutionResult {
235 exit_code: call_id as i32,
237 fuel_consumed: interruption.fuel_consumed,
239 fuel_refunded: interruption.fuel_refunded,
240 output: interruption.return_data,
242 return_data: vec![],
243 }
244 }
245
246 fn handle_execution_result(
251 &mut self,
252 next_result: Result<(), TrapCode>,
253 fuel_consumed: Option<u64>,
254 ctx: &mut RuntimeContext,
255 ) -> RuntimeResult {
256 let mut execution_result = ctx
257 .execution_result
258 .take_and_continue(ctx.resumable_context.is_some());
259 if let Some(store_fuel_consumed) = fuel_consumed {
264 execution_result.fuel_consumed = store_fuel_consumed;
265 }
266
267 match next_result {
272 Ok(_) => {
273 }
275 Err(TrapCode::InterruptionCalled) => {
276 }
279 Err(err) => {
280 execution_result.exit_code = ExitCode::from(err).into_i32();
281 }
282 }
283
284 if next_result == Err(TrapCode::InterruptionCalled) {
286 let ExecutionResult {
287 fuel_consumed,
288 fuel_refunded,
289 mut return_data,
290 ..
291 } = execution_result;
292 if let Some(resumable_return_data) = ctx.take_resumable_context_serialized() {
298 return_data = resumable_return_data;
299 }
300 return RuntimeResult::Interruption(ExecutionInterruption {
301 fuel_consumed,
302 fuel_refunded,
303 return_data,
304 });
305 }
306
307 RuntimeResult::Result(execution_result)
308 }
309}
310impl RuntimeExecutor for RuntimeFactoryExecutor {
311 fn execute(
312 &mut self,
313 bytecode_or_hash: BytecodeOrHash,
314 ctx: RuntimeContext,
315 ) -> ExecutionResult {
316 let system_runtime_params = match &bytecode_or_hash {
317 BytecodeOrHash::Bytecode { address, hash, .. } => {
318 fluentbase_types::is_execute_using_system_runtime(address)
319 .then_some((*address, *hash))
320 }
321 BytecodeOrHash::Hash(_) => None,
322 };
323
324 let module = self.module_factory.get_module_or_init(bytecode_or_hash);
326
327 let fuel_limit_value = ctx.fuel_limit;
329 let fuel_limit = Some(fuel_limit_value);
330
331 let mut exec_mode = if let Some((address, code_hash)) = system_runtime_params {
332 let consume_fuel = fluentbase_types::is_engine_metered_precompile(&address);
333 let runtime = SystemRuntime::new(
334 module,
335 self.import_linker.clone(),
336 code_hash,
337 ctx,
338 consume_fuel,
339 );
340 ExecutionMode::System(runtime)
341 } else {
342 let engine = ExecutionEngine::acquire_shared();
343 let strategy = StrategyDefinition::Rwasm { engine, module };
345 let runtime =
346 ContractRuntime::new(strategy, self.import_linker.clone(), ctx, fuel_limit);
347 if let Some(trap_code) = runtime.as_ref().err() {
355 return ExecutionResult {
356 exit_code: ExitCode::from(trap_code).into_i32(),
357 fuel_consumed: fuel_limit_value,
358 fuel_refunded: 0,
359 output: vec![],
360 return_data: vec![],
361 };
362 }
363 ExecutionMode::Contract(runtime.unwrap())
364 };
365
366 let result = exec_mode.execute();
368 let fuel_consumed = exec_mode
369 .remaining_fuel()
370 .zip(fuel_limit)
371 .map(|(remaining_fuel, store_fuel)| store_fuel - remaining_fuel);
372
373 let runtime_result =
374 self.handle_execution_result(result, fuel_consumed, exec_mode.context_mut());
375 self.try_remember_runtime(runtime_result, exec_mode)
376 }
377
378 fn resume(
379 &mut self,
380 call_id: u32,
381 return_data: &[u8],
382 fuel16_ptr: u32,
383 fuel_consumed: u64,
384 fuel_refunded: i64,
385 exit_code: i32,
386 ) -> ExecutionResult {
387 let Some(mut runtime) = self.recoverable_runtimes.remove(&call_id) else {
388 unreachable!(
389 "runtime: missing recoverable runtime for resume, this should never happen: call_id={}, fuel_consumed={}, exit_code={}",
390 call_id, fuel_consumed, exit_code
391 )
392 };
393 let mut fuel_remaining = runtime.remaining_fuel();
394 let resume_inner = |runtime: &mut ExecutionMode| {
395 runtime.context_mut().execution_result.return_data = return_data.to_vec();
397 if fuel16_ptr > 0 {
398 let mut buffer = [0u8; 16];
399 LittleEndian::write_u64(&mut buffer[..8], fuel_consumed);
400 LittleEndian::write_i64(&mut buffer[8..], fuel_refunded);
401 runtime.memory_write(fuel16_ptr as usize, &buffer)?;
402 }
403 runtime.resume(exit_code, fuel_consumed)
404 };
405 let result = resume_inner(&mut runtime);
406 if result != Err(TrapCode::OutOfFuel) {
408 fuel_remaining = fuel_remaining.map(|v| v.checked_sub(fuel_consumed).unwrap());
410 }
411 let fuel_consumed = runtime
412 .remaining_fuel()
413 .and_then(|remaining_fuel| Some(fuel_remaining? - remaining_fuel));
414 let runtime_result =
415 self.handle_execution_result(result, fuel_consumed, runtime.context_mut());
416 self.try_remember_runtime(runtime_result, runtime)
417 }
418
419 fn forget_runtime(&mut self, call_id: u32) {
420 _ = self.recoverable_runtimes.remove(&call_id);
421 }
422
423 fn warmup(&mut self, bytecode: RwasmModule, hash: B256, address: Address) {
424 self.module_factory
425 .get_module_or_init(BytecodeOrHash::Bytecode {
426 bytecode,
427 hash,
428 address,
429 });
430 }
431
432 fn reset_call_id_counter(&mut self) {
433 self.transaction_call_id_counter = 1;
435 self.recoverable_runtimes.clear();
437 }
438
439 fn memory_read(
440 &mut self,
441 call_id: u32,
442 offset: usize,
443 buffer: &mut [u8],
444 ) -> Result<(), TrapCode> {
445 let runtime_ref = self.recoverable_runtimes.get_mut(&call_id).expect(
446 "runtime: missing recoverable runtime for memory read, this should never happen",
447 );
448 runtime_ref.memory_read(offset, buffer)
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use crate::{
455 executor::{ExecutionInterruption, RuntimeFactoryExecutor, RuntimeResult},
456 runtime::{ContractRuntime, ExecutionMode},
457 RuntimeContext,
458 };
459 use fluentbase_types::{import_linker_v1_preview, ExitCode};
460 use rwasm::{ExecutionEngine, RwasmModule, StrategyDefinition};
461
462 #[test]
463 fn call_id_overflow() {
464 let mut executor = RuntimeFactoryExecutor::new(import_linker_v1_preview());
465
466 executor.transaction_call_id_counter = i32::MAX as u32 + 1;
468
469 let interruption = RuntimeResult::Interruption(ExecutionInterruption {
470 fuel_consumed: 100,
471 fuel_refunded: 0,
472 return_data: vec![1, 2, 3],
473 });
474
475 let engine = ExecutionEngine::acquire_shared();
476 let module = RwasmModule::default();
477 let ctx = RuntimeContext::default();
478
479 let strategy_runtime = ContractRuntime::new(
480 StrategyDefinition::Rwasm { module, engine },
481 executor.import_linker.clone(),
482 ctx,
483 None,
484 )
485 .unwrap();
486 let runtime = ExecutionMode::Contract(strategy_runtime);
487
488 let result = executor.try_remember_runtime(interruption, runtime);
490
491 assert_eq!(result.exit_code, ExitCode::UnknownError.into_i32());
493 assert_eq!(result.fuel_consumed, 100);
494 assert_eq!(result.fuel_refunded, 0);
495 assert!(result.output.is_empty());
496 }
497}