1use crate::{
75 LocalStorageLayer,
76 error::ExecutorError,
77 local::LocalSharedValue,
78 strings::executor::{magic_signature, storage_prefixes},
79};
80use smoldot::{
81 executor::{
82 self,
83 host::{Config as HostConfig, HostVmPrototype},
84 runtime_call::{self, OffchainContext, RuntimeCall},
85 storage_diff::TrieDiff,
86 vm::{ExecHint, HeapPages},
87 },
88 trie::{TrieEntryVersion, bytes_to_nibbles, nibbles_to_bytes_suffix_extend},
89};
90use std::{collections::BTreeMap, iter, iter::Once, sync::Arc};
91
92struct ArcLocalSharedValue(Arc<LocalSharedValue>);
93
94impl AsRef<[u8]> for ArcLocalSharedValue {
95 fn as_ref(&self) -> &[u8] {
96 self.0.as_ref().as_ref()
97 }
98}
99
100#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
102pub enum SignatureMockMode {
103 #[default]
105 None,
106 MagicSignature,
112 AlwaysValid,
114}
115
116#[derive(Debug, Clone)]
118pub struct RuntimeCallResult {
119 pub output: Vec<u8>,
121 pub storage_diff: Vec<(Vec<u8>, Option<Vec<u8>>)>,
125 pub offchain_storage_diff: Vec<(Vec<u8>, Option<Vec<u8>>)>,
127 pub logs: Vec<RuntimeLog>,
129}
130
131#[derive(Debug, Clone)]
133pub struct RuntimeLog {
134 pub message: String,
136 pub level: Option<u32>,
138 pub target: Option<String>,
140}
141
142#[derive(Debug, Clone)]
144pub struct ExecutorConfig {
145 pub signature_mock: SignatureMockMode,
147 pub allow_unresolved_imports: bool,
149 pub max_log_level: u32,
151 pub storage_proof_size: u64,
153}
154
155impl Default for ExecutorConfig {
156 fn default() -> Self {
157 Self {
158 signature_mock: SignatureMockMode::MagicSignature,
159 allow_unresolved_imports: false,
160 max_log_level: 3, storage_proof_size: 0,
162 }
163 }
164}
165
166#[derive(Clone)]
192pub struct RuntimeExecutor {
193 runtime_code: Arc<[u8]>,
195 heap_pages: HeapPages,
197 config: ExecutorConfig,
199}
200
201impl RuntimeExecutor {
202 pub fn new(
224 runtime_code: impl Into<Arc<[u8]>>,
225 heap_pages: Option<u32>,
226 ) -> Result<Self, ExecutorError> {
227 let runtime_code: Arc<[u8]> = runtime_code.into();
228 let heap_pages = heap_pages.map(HeapPages::from).unwrap_or(executor::DEFAULT_HEAP_PAGES);
229
230 let _prototype = HostVmPrototype::new(HostConfig {
232 module: &runtime_code,
233 heap_pages,
234 exec_hint: ExecHint::ValidateAndExecuteOnce,
235 allow_unresolved_imports: false,
236 })?;
237
238 Ok(Self { runtime_code, heap_pages, config: ExecutorConfig::default() })
239 }
240
241 pub fn with_config(
249 runtime_code: impl Into<Arc<[u8]>>,
250 heap_pages: Option<u32>,
251 config: ExecutorConfig,
252 ) -> Result<Self, ExecutorError> {
253 let mut executor = Self::new(runtime_code, heap_pages)?;
254 executor.config = config;
255 Ok(executor)
256 }
257
258 pub fn create_prototype(&self) -> Result<HostVmPrototype, ExecutorError> {
264 Ok(HostVmPrototype::new(HostConfig {
265 module: &self.runtime_code,
266 heap_pages: self.heap_pages,
267 exec_hint: ExecHint::ValidateAndCompile,
268 allow_unresolved_imports: self.config.allow_unresolved_imports,
269 })?)
270 }
271
272 pub async fn call_with_prototype(
288 &self,
289 prototype: Option<HostVmPrototype>,
290 method: &str,
291 args: &[u8],
292 storage: &LocalStorageLayer,
293 ) -> (Result<RuntimeCallResult, ExecutorError>, Option<HostVmPrototype>) {
294 let vm_proto = match prototype {
296 Some(proto) => proto,
297 None => match self.create_prototype() {
298 Ok(proto) => proto,
299 Err(e) => return (Err(e), None),
300 },
301 };
302
303 let mut vm = match runtime_call::run(runtime_call::Config {
305 virtual_machine: vm_proto,
306 function_to_call: method,
307 parameter: iter::once(args),
308 storage_main_trie_changes: TrieDiff::default(),
309 max_log_level: self.config.max_log_level,
310 calculate_trie_changes: false,
311 storage_proof_size_behavior:
312 runtime_call::StorageProofSizeBehavior::ConstantReturnValue(
313 self.config.storage_proof_size,
314 ),
315 }) {
316 Ok(vm) => vm,
317 Err((err, proto)) => {
318 return (
319 Err(ExecutorError::StartError {
320 method: method.to_string(),
321 message: err.to_string(),
322 }),
323 Some(proto),
324 );
325 },
326 };
327
328 let mut storage_changes: BTreeMap<Vec<u8>, Option<Vec<u8>>> = BTreeMap::new();
330 let mut offchain_storage_changes: BTreeMap<Vec<u8>, Option<Vec<u8>>> = BTreeMap::new();
331 let mut logs: Vec<RuntimeLog> = Vec::new();
332
333 loop {
335 vm = match vm {
336 RuntimeCall::Finished(result) => {
337 return match result {
338 Ok(success) => {
339 success.storage_changes.storage_changes_iter_unordered().for_each(
341 |(child, key, value)| {
342 let prefixed_key = if let Some(child) = child {
343 prefixed_child_key(
344 child.iter().copied(),
345 key.iter().copied(),
346 )
347 } else {
348 key.to_vec()
349 };
350 storage_changes.insert(prefixed_key, value.map(|v| v.to_vec()));
351 },
352 );
353
354 let output = success.virtual_machine.value().as_ref().to_vec();
356 let proto = success.virtual_machine.into_prototype();
357 (
358 Ok(RuntimeCallResult {
359 output,
360 storage_diff: storage_changes.into_iter().collect(),
361 offchain_storage_diff: offchain_storage_changes
362 .into_iter()
363 .collect(),
364 logs,
365 }),
366 Some(proto),
367 )
368 },
369 Err(err) => {
370 let proto = err.prototype;
371 (Err(err.detail.into()), Some(proto))
372 },
373 };
374 },
375
376 RuntimeCall::StorageGet(req) => {
377 let key = if let Some(child) = req.child_trie() {
378 prefixed_child_key(
379 child.as_ref().iter().copied(),
380 req.key().as_ref().iter().copied(),
381 )
382 } else {
383 req.key().as_ref().to_vec()
384 };
385
386 if let Some(value) = storage_changes.get(&key) {
388 req.inject_value(
389 value.as_ref().map(|v| (iter::once(v), TrieEntryVersion::V1)),
390 )
391 } else {
392 let block_number = storage.get_current_block_number();
394 let value = match storage.get(block_number, &key).await {
395 Ok(v) => v,
396 Err(e) => {
397 return (
398 Err(ExecutorError::StorageError {
399 key: hex::encode(&key),
400 message: e.to_string(),
401 }),
402 None,
403 );
404 },
405 };
406 let none_placeholder: Option<(Once<[u8; 0]>, TrieEntryVersion)> = None;
407 match value {
408 Some(value) if value.value.is_some() => req.inject_value(Some((
409 iter::once(ArcLocalSharedValue(value)),
410 TrieEntryVersion::V1,
411 ))),
412 _ => req.inject_value(none_placeholder),
413 }
414 }
415 },
416
417 RuntimeCall::ClosestDescendantMerkleValue(req) => {
418 static FAKE_MERKLE: [u8; 32] = [0u8; 32];
426 req.inject_merkle_value(Some(&FAKE_MERKLE))
427 },
428
429 RuntimeCall::NextKey(req) =>
430 if req.branch_nodes() {
431 req.inject_key(None::<Vec<_>>.map(|x| x.into_iter()))
432 } else {
433 let prefix = if let Some(child) = req.child_trie() {
434 prefixed_child_key(
435 child.as_ref().iter().copied(),
436 nibbles_to_bytes_suffix_extend(req.prefix()),
437 )
438 } else {
439 nibbles_to_bytes_suffix_extend(req.prefix()).collect::<Vec<_>>()
440 };
441
442 let key = if let Some(child) = req.child_trie() {
443 prefixed_child_key(
444 child.as_ref().iter().copied(),
445 nibbles_to_bytes_suffix_extend(req.key()),
446 )
447 } else {
448 nibbles_to_bytes_suffix_extend(req.key()).collect::<Vec<_>>()
449 };
450
451 let next = match storage.next_key(&prefix, &key).await {
452 Ok(v) => v,
453 Err(e) => {
454 return (
455 Err(ExecutorError::StorageError {
456 key: hex::encode(&key),
457 message: e.to_string(),
458 }),
459 None,
460 );
461 },
462 };
463
464 req.inject_key(next.map(|k| bytes_to_nibbles(k.into_iter())))
465 },
466
467 RuntimeCall::SignatureVerification(req) => match self.config.signature_mock {
468 SignatureMockMode::MagicSignature => {
469 if is_magic_signature(req.signature().as_ref()) {
470 req.resume_success()
471 } else {
472 req.verify_and_resume()
473 }
474 },
475 SignatureMockMode::AlwaysValid => req.resume_success(),
476 SignatureMockMode::None => req.verify_and_resume(),
477 },
478
479 RuntimeCall::OffchainStorageSet(req) => {
480 offchain_storage_changes.insert(
481 req.key().as_ref().to_vec(),
482 req.value().map(|x| x.as_ref().to_vec()),
483 );
484 req.resume()
485 },
486
487 RuntimeCall::Offchain(ctx) => match ctx {
488 OffchainContext::StorageGet(req) => {
489 let key = req.key().as_ref().to_vec();
490 let value = offchain_storage_changes.get(&key).cloned().flatten();
491 req.inject_value(value)
492 },
493 OffchainContext::StorageSet(req) => {
494 let key = req.key().as_ref().to_vec();
495 let current = offchain_storage_changes.get(&key);
496
497 let replace = match (current, req.old_value()) {
498 (Some(Some(current)), Some(old)) => old.as_ref().eq(current),
499 _ => true,
500 };
501
502 if replace {
503 offchain_storage_changes
504 .insert(key, req.value().map(|x| x.as_ref().to_vec()));
505 }
506
507 req.resume(replace)
508 },
509 OffchainContext::Timestamp(req) => {
510 let timestamp = std::time::SystemTime::now()
511 .duration_since(std::time::UNIX_EPOCH)
512 .map(|d| d.as_millis() as u64)
513 .unwrap_or(0);
514 req.inject_timestamp(timestamp)
515 },
516 OffchainContext::RandomSeed(req) => {
517 let seed = sp_core::blake2_256(
518 &std::time::SystemTime::now()
519 .duration_since(std::time::UNIX_EPOCH)
520 .map(|d| d.as_nanos().to_le_bytes())
521 .unwrap_or([0u8; 16]),
522 );
523 req.inject_random_seed(seed)
524 },
525 OffchainContext::SubmitTransaction(req) => req.resume(false),
526 },
527
528 RuntimeCall::LogEmit(req) => {
529 use smoldot::executor::host::LogEmitInfo;
530
531 let log = match req.info() {
532 LogEmitInfo::Num(v) =>
533 RuntimeLog { message: format!("{}", v), level: None, target: None },
534 LogEmitInfo::Utf8(v) =>
535 RuntimeLog { message: v.to_string(), level: None, target: None },
536 LogEmitInfo::Hex(v) =>
537 RuntimeLog { message: v.to_string(), level: None, target: None },
538 LogEmitInfo::Log { log_level, target, message } => RuntimeLog {
539 message: message.to_string(),
540 level: Some(log_level),
541 target: Some(target.to_string()),
542 },
543 };
544 logs.push(log);
545 req.resume()
546 },
547 }
548 }
549 }
550
551 pub async fn call(
573 &self,
574 method: &str,
575 args: &[u8],
576 storage: &LocalStorageLayer,
577 ) -> Result<RuntimeCallResult, ExecutorError> {
578 self.call_with_prototype(None, method, args, storage).await.0
579 }
580
581 pub fn runtime_version(&self) -> Result<RuntimeVersion, ExecutorError> {
585 let prototype = HostVmPrototype::new(HostConfig {
586 module: &self.runtime_code,
587 heap_pages: self.heap_pages,
588 exec_hint: ExecHint::ValidateAndExecuteOnce,
589 allow_unresolved_imports: true,
590 })?;
591
592 let version = prototype.runtime_version().decode();
593
594 Ok(RuntimeVersion {
595 spec_name: version.spec_name.to_string(),
596 impl_name: version.impl_name.to_string(),
597 authoring_version: version.authoring_version,
598 spec_version: version.spec_version,
599 impl_version: version.impl_version,
600 transaction_version: version.transaction_version.unwrap_or(0),
601 state_version: version.state_version.map(|v| v.into()).unwrap_or(0),
602 })
603 }
604}
605
606#[derive(Debug, Clone)]
608pub struct RuntimeVersion {
609 pub spec_name: String,
611 pub impl_name: String,
613 pub authoring_version: u32,
615 pub spec_version: u32,
617 pub impl_version: u32,
619 pub transaction_version: u32,
621 pub state_version: u8,
623}
624
625fn prefixed_child_key(child: impl Iterator<Item = u8>, key: impl Iterator<Item = u8>) -> Vec<u8> {
627 [storage_prefixes::DEFAULT_CHILD_STORAGE, &child.collect::<Vec<_>>(), &key.collect::<Vec<_>>()]
628 .concat()
629}
630
631fn is_magic_signature(signature: &[u8]) -> bool {
635 signature.starts_with(magic_signature::PREFIX) &&
636 signature[magic_signature::PREFIX.len()..]
637 .iter()
638 .all(|&b| b == magic_signature::PADDING)
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644
645 #[test]
646 fn magic_signature_accepts_valid_patterns() {
647 assert!(is_magic_signature(&[0xde, 0xad, 0xbe, 0xef, 0xcd, 0xcd]));
649 assert!(is_magic_signature(&[0xde, 0xad, 0xbe, 0xef, 0xcd, 0xcd, 0xcd, 0xcd]));
650 assert!(is_magic_signature(&[0xde, 0xad, 0xbe, 0xef])); assert!(!is_magic_signature(&[0xde, 0xad, 0xbe, 0xef, 0xcd, 0xcd, 0xcd, 0x00]));
654 assert!(!is_magic_signature(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
655 assert!(!is_magic_signature(&[0xde, 0xad, 0xbe])); }
657
658 #[test]
659 fn prefixed_child_key_combines_prefix_child_and_key() {
660 let child = b"child1".iter().copied();
661 let key = b"key1".iter().copied();
662 let result = prefixed_child_key(child, key);
663
664 assert!(result.starts_with(storage_prefixes::DEFAULT_CHILD_STORAGE));
665 assert!(result.ends_with(b"key1"));
666 }
667
668 #[test]
669 fn executor_config_has_sensible_defaults() {
670 let config = ExecutorConfig::default();
671 assert_eq!(config.signature_mock, SignatureMockMode::MagicSignature);
672 assert!(!config.allow_unresolved_imports);
673 assert_eq!(config.max_log_level, 3);
674 assert_eq!(config.storage_proof_size, 0);
675 }
676
677 #[test]
678 fn signature_mock_mode_defaults_to_none() {
679 let mode = SignatureMockMode::default();
682 assert_eq!(mode, SignatureMockMode::None);
683 }
684}