1use std::{cmp::max, rc::Rc};
6
7#[cfg(any(test, feature = "recording_mode"))]
8use crate::{
9 auth::RecordedAuthPayload,
10 storage::is_persistent_key,
11 xdr::{ContractEvent, ReadXdr, ScVal, SorobanAddressCredentials, SorobanCredentials, WriteXdr},
12 DEFAULT_XDR_RW_LIMITS,
13};
14use crate::{
15 budget::{AsBudget, Budget},
16 crypto::sha256_hash_from_bytes,
17 events::Events,
18 fees::LedgerEntryRentChange,
19 host::{
20 metered_clone::{MeteredAlloc, MeteredClone, MeteredContainer, MeteredIterator},
21 metered_xdr::{metered_from_xdr_with_budget, metered_write_xdr},
22 TraceHook,
23 },
24 storage::{AccessType, Footprint, FootprintMap, SnapshotSource, Storage, StorageMap},
25 xdr::{
26 AccountId, ContractDataDurability, ContractEventType, DiagnosticEvent, HostFunction,
27 LedgerEntry, LedgerEntryData, LedgerEntryType, LedgerFootprint, LedgerKey,
28 LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData, LedgerKeyTrustLine,
29 ScErrorCode, ScErrorType, SorobanAuthorizationEntry, SorobanResources, TtlEntry,
30 },
31 DiagnosticLevel, Error, Host, HostError, LedgerInfo, MeteredOrdMap,
32};
33use crate::{ledger_info::get_key_durability, ModuleCache};
34use crate::{storage::EntryWithLiveUntil, vm::wasm_module_memory_cost};
35#[cfg(any(test, feature = "recording_mode"))]
36use sha2::{Digest, Sha256};
37
38type TtlEntryMap = MeteredOrdMap<Rc<LedgerKey>, Rc<TtlEntry>, Budget>;
39type RestoredKeySet = MeteredOrdMap<Rc<LedgerKey>, (), Budget>;
40
41pub struct InvokeHostFunctionResult {
43 pub encoded_invoke_result: Result<Vec<u8>, HostError>,
45 pub ledger_changes: Vec<LedgerEntryChange>,
54 pub encoded_contract_events: Vec<Vec<u8>>,
59}
60
61#[cfg(any(test, feature = "recording_mode"))]
63pub struct InvokeHostFunctionRecordingModeResult {
64 pub invoke_result: Result<ScVal, HostError>,
66 pub resources: SorobanResources,
68 pub restored_rw_entry_indices: Vec<u32>,
76 pub auth: Vec<SorobanAuthorizationEntry>,
79 pub ledger_changes: Vec<LedgerEntryChange>,
86 pub contract_events: Vec<ContractEvent>,
90 pub contract_events_and_return_value_size: u32,
93}
94
95#[derive(Default)]
99pub struct LedgerEntryChange {
100 pub read_only: bool,
102 pub encoded_key: Vec<u8>,
104 pub old_entry_size_bytes_for_rent: u32,
108 pub encoded_new_value: Option<Vec<u8>>,
111 pub new_entry_size_bytes_for_rent: u32,
116 pub ttl_change: Option<LedgerEntryLiveUntilChange>,
119}
120#[derive(Debug, Eq, PartialEq, Clone)]
122pub struct LedgerEntryLiveUntilChange {
123 pub key_hash: Vec<u8>,
125 pub durability: ContractDataDurability,
127 pub entry_type: LedgerEntryType,
129 pub old_live_until_ledger: u32,
131 pub new_live_until_ledger: u32,
134}
135
136fn build_restored_key_set(
143 budget: &Budget,
144 resources: &SorobanResources,
145 restored_rw_entry_indices: &[u32],
146) -> Result<Option<RestoredKeySet>, HostError> {
147 if restored_rw_entry_indices.is_empty() {
148 return Ok(None);
149 }
150 let rw_footprint = &resources.footprint.read_write;
151 let mut key_set = RestoredKeySet::default();
152 for e in restored_rw_entry_indices {
153 key_set = key_set.insert(
154 Rc::new(
155 rw_footprint
156 .get(*e as usize)
157 .ok_or_else(|| {
158 HostError::from(Error::from_type_and_code(
159 ScErrorType::Storage,
160 ScErrorCode::InternalError,
161 ))
162 })?
163 .metered_clone(budget)?,
164 ),
165 (),
166 budget,
167 )?;
168 }
169 Ok(Some(key_set))
170}
171
172fn get_ledger_changes(
176 budget: &Budget,
177 storage: &Storage,
178 init_storage_snapshot: &(impl SnapshotSource + ?Sized),
179 init_ttl_entries: TtlEntryMap,
180 min_live_until_ledger: u32,
181 restored_keys: &Option<RestoredKeySet>,
182 #[cfg(any(test, feature = "recording_mode"))] current_ledger_seq: u32,
183) -> Result<Vec<LedgerEntryChange>, HostError> {
184 let mut changes = Vec::with_capacity(storage.map.len());
187
188 let footprint_map = &storage.footprint.0;
189 let internal_error = || {
193 HostError::from(Error::from_type_and_code(
194 ScErrorType::Storage,
195 ScErrorCode::InternalError,
196 ))
197 };
198 for (key, entry_with_live_until_ledger) in storage.map.iter(budget)? {
199 let mut entry_change = LedgerEntryChange::default();
200 metered_write_xdr(budget, key.as_ref(), &mut entry_change.encoded_key)?;
201 let durability = get_key_durability(key);
202
203 if let Some(durability) = durability {
204 let key_hash = match init_ttl_entries.get::<Rc<LedgerKey>>(key, budget)? {
205 Some(ttl_entry) => ttl_entry.key_hash.0.to_vec(),
206 None => sha256_hash_from_bytes(entry_change.encoded_key.as_slice(), budget)?,
207 };
208
209 entry_change.ttl_change = Some(LedgerEntryLiveUntilChange {
210 key_hash,
211 entry_type: key.discriminant(),
212 durability,
213 old_live_until_ledger: 0,
214 new_live_until_ledger: 0,
215 });
216 }
217 let entry_with_live_until = init_storage_snapshot.get(key)?;
218 if let Some((old_entry, old_live_until_ledger)) = entry_with_live_until {
219 let mut buf = vec![];
220 metered_write_xdr(budget, old_entry.as_ref(), &mut buf)?;
221
222 entry_change.old_entry_size_bytes_for_rent =
223 entry_size_for_rent(budget, &old_entry, buf.len() as u32)?;
224
225 if let Some(ref mut ttl_change) = &mut entry_change.ttl_change {
226 ttl_change.old_live_until_ledger =
227 old_live_until_ledger.ok_or_else(internal_error)?;
228 #[cfg(any(test, feature = "recording_mode"))]
234 if ttl_change.old_live_until_ledger < current_ledger_seq {
235 ttl_change.old_live_until_ledger = 0;
236 entry_change.old_entry_size_bytes_for_rent = 0;
237 }
238 }
239 }
240 if let Some((_, new_live_until_ledger)) = entry_with_live_until_ledger {
241 if let Some(ref mut ttl_change) = &mut entry_change.ttl_change {
242 ttl_change.new_live_until_ledger = max(
244 new_live_until_ledger.ok_or_else(internal_error)?,
245 ttl_change.old_live_until_ledger,
246 );
247 }
248 }
249 let maybe_access_type: Option<AccessType> =
250 footprint_map.get::<Rc<LedgerKey>>(key, budget)?.copied();
251 match maybe_access_type {
252 Some(AccessType::ReadOnly) => {
253 entry_change.read_only = true;
254 }
255 Some(AccessType::ReadWrite) => {
256 if let Some((entry, _)) = entry_with_live_until_ledger {
257 let mut entry_buf = vec![];
258 metered_write_xdr(budget, entry.as_ref(), &mut entry_buf)?;
259 entry_change.new_entry_size_bytes_for_rent =
260 entry_size_for_rent(budget, &entry, entry_buf.len() as u32)?;
261 entry_change.encoded_new_value = Some(entry_buf);
262
263 if let Some(restored_keys) = &restored_keys {
264 if restored_keys.contains_key::<LedgerKey>(key, budget)? {
265 entry_change.old_entry_size_bytes_for_rent = 0;
266 if let Some(ref mut ttl_change) = &mut entry_change.ttl_change {
267 ttl_change.old_live_until_ledger = 0;
268 ttl_change.new_live_until_ledger =
269 max(ttl_change.new_live_until_ledger, min_live_until_ledger);
270 }
271 }
272 }
273 }
274 }
275 None => {
276 return Err(internal_error());
277 }
278 }
279 changes.push(entry_change);
280 }
281 Ok(changes)
282}
283
284#[cfg(any(test, feature = "recording_mode"))]
294fn add_footprint_only_ledger_changes(
295 budget: &Budget,
296 storage: &Storage,
297 changes: &mut Vec<LedgerEntryChange>,
298) -> Result<(), HostError> {
299 for (key, access_type) in storage.footprint.0.iter(budget)? {
300 if storage.map.contains_key::<Rc<LedgerKey>>(key, budget)? {
303 continue;
304 }
305 let mut entry_change = LedgerEntryChange::default();
306 metered_write_xdr(budget, key.as_ref(), &mut entry_change.encoded_key)?;
307 entry_change.read_only = matches!(*access_type, AccessType::ReadOnly);
308 changes.push(entry_change);
309 }
310 Ok(())
311}
312
313pub fn extract_rent_changes(ledger_changes: &[LedgerEntryChange]) -> Vec<LedgerEntryRentChange> {
319 ledger_changes
320 .iter()
321 .filter_map(|entry_change| {
322 if let (Some(ttl_change), optional_encoded_new_value) =
325 (&entry_change.ttl_change, &entry_change.encoded_new_value)
326 {
327 let new_size_bytes_for_rent = if optional_encoded_new_value.is_some() {
328 entry_change.new_entry_size_bytes_for_rent
329 } else {
330 entry_change.old_entry_size_bytes_for_rent
331 };
332
333 if ttl_change.old_live_until_ledger >= ttl_change.new_live_until_ledger
335 && entry_change.old_entry_size_bytes_for_rent >= new_size_bytes_for_rent
336 {
337 return None;
338 }
339 Some(LedgerEntryRentChange {
340 is_persistent: matches!(
341 ttl_change.durability,
342 ContractDataDurability::Persistent
343 ),
344 is_code_entry: matches!(ttl_change.entry_type, LedgerEntryType::ContractCode),
345 old_size_bytes: entry_change.old_entry_size_bytes_for_rent,
346 new_size_bytes: new_size_bytes_for_rent,
347 old_live_until_ledger: ttl_change.old_live_until_ledger,
348 new_live_until_ledger: ttl_change.new_live_until_ledger,
349 })
350 } else {
351 None
352 }
353 })
354 .collect()
355}
356
357pub fn entry_size_for_rent(
366 budget: &Budget,
367 entry: &LedgerEntry,
368 entry_xdr_size: u32,
369) -> Result<u32, HostError> {
370 Ok(match &entry.data {
371 LedgerEntryData::ContractCode(contract_code_entry) => entry_xdr_size.saturating_add(
372 wasm_module_memory_cost(budget, contract_code_entry)?.min(u32::MAX as u64) as u32,
373 ),
374 _ => entry_xdr_size,
375 })
376}
377
378#[allow(clippy::too_many_arguments)]
397pub fn invoke_host_function<T: AsRef<[u8]>, I: ExactSizeIterator<Item = T>>(
398 budget: &Budget,
399 enable_diagnostics: bool,
400 encoded_host_fn: T,
401 encoded_resources: T,
402 restored_rw_entry_indices: &[u32],
403 encoded_source_account: T,
404 encoded_auth_entries: I,
405 ledger_info: LedgerInfo,
406 encoded_ledger_entries: I,
407 encoded_ttl_entries: I,
408 base_prng_seed: T,
409 diagnostic_events: &mut Vec<DiagnosticEvent>,
410 trace_hook: Option<TraceHook>,
411 module_cache: Option<ModuleCache>,
412) -> Result<InvokeHostFunctionResult, HostError> {
413 let _span0 = tracy_span!("invoke_host_function");
414
415 let resources: SorobanResources =
416 metered_from_xdr_with_budget(encoded_resources.as_ref(), &budget)?;
417 let restored_keys = build_restored_key_set(&budget, &resources, &restored_rw_entry_indices)?;
418 let footprint = build_storage_footprint_from_xdr(&budget, resources.footprint)?;
419 let current_ledger_seq = ledger_info.sequence_number;
420 let min_live_until_ledger = ledger_info
421 .min_live_until_ledger_checked(ContractDataDurability::Persistent)
422 .ok_or_else(|| {
423 HostError::from(Error::from_type_and_code(
424 ScErrorType::Context,
425 ScErrorCode::InternalError,
426 ))
427 })?;
428 let (storage_map, init_ttl_map) = build_storage_map_from_xdr_ledger_entries(
429 &budget,
430 &footprint,
431 encoded_ledger_entries,
432 encoded_ttl_entries,
433 current_ledger_seq,
434 #[cfg(any(test, feature = "recording_mode"))]
435 false,
436 )?;
437
438 let init_storage_map = storage_map.metered_clone(budget)?;
439
440 let storage = Storage::with_enforcing_footprint_and_map(footprint, storage_map);
441 let host = Host::with_storage_and_budget(storage, budget.clone());
442 let have_trace_hook = trace_hook.is_some();
443 if let Some(th) = trace_hook {
444 host.set_trace_hook(Some(th))?;
445 }
446 let auth_entries = host.build_auth_entries_from_xdr(encoded_auth_entries)?;
447 let host_function: HostFunction = host.metered_from_xdr(encoded_host_fn.as_ref())?;
448 let source_account: AccountId = host.metered_from_xdr(encoded_source_account.as_ref())?;
449 host.set_source_account(source_account)?;
450 host.set_ledger_info(ledger_info)?;
451 host.set_authorization_entries(auth_entries)?;
452 let seed32: [u8; 32] = base_prng_seed.as_ref().try_into().map_err(|_| {
453 host.err(
454 ScErrorType::Context,
455 ScErrorCode::InternalError,
456 "base PRNG seed is not 32-bytes long",
457 &[],
458 )
459 })?;
460 host.set_base_prng_seed(seed32)?;
461 if enable_diagnostics {
462 host.set_diagnostic_level(DiagnosticLevel::Debug)?;
463 }
464 if let Some(module_cache) = module_cache {
465 host.set_module_cache(module_cache)?;
466 }
467 let result = {
468 let _span1 = tracy_span!("Host::invoke_function");
469 host.invoke_function(host_function)
470 };
471 if have_trace_hook {
472 host.set_trace_hook(None)?;
473 }
474 let (storage, events) = host.try_finish()?;
475 if enable_diagnostics {
476 extract_diagnostic_events(&events, diagnostic_events);
477 }
478 let encoded_invoke_result = result.and_then(|res| {
479 let mut encoded_result_sc_val = vec![];
480 metered_write_xdr(&budget, &res, &mut encoded_result_sc_val).map(|_| encoded_result_sc_val)
481 });
482 if encoded_invoke_result.is_ok() {
483 let init_storage_snapshot = StorageMapSnapshotSource {
484 budget: &budget,
485 map: &init_storage_map,
486 };
487 let ledger_changes = get_ledger_changes(
488 &budget,
489 &storage,
490 &init_storage_snapshot,
491 init_ttl_map,
492 min_live_until_ledger,
493 &restored_keys,
494 #[cfg(any(test, feature = "recording_mode"))]
495 current_ledger_seq,
496 )?;
497 let encoded_contract_events = encode_contract_events(budget, &events)?;
498 Ok(InvokeHostFunctionResult {
499 encoded_invoke_result,
500 ledger_changes,
501 encoded_contract_events,
502 })
503 } else {
504 Ok(InvokeHostFunctionResult {
505 encoded_invoke_result,
506 ledger_changes: vec![],
507 encoded_contract_events: vec![],
508 })
509 }
510}
511
512#[cfg(any(test, feature = "recording_mode"))]
513impl Host {
514 fn to_xdr_non_metered(&self, v: &impl WriteXdr) -> Result<Vec<u8>, HostError> {
515 v.to_xdr(DEFAULT_XDR_RW_LIMITS).map_err(|_| {
516 self.err(
517 ScErrorType::Value,
518 ScErrorCode::InvalidInput,
519 "could not convert XDR struct to bytes - the input is too deep or too large",
520 &[],
521 )
522 })
523 }
524
525 fn xdr_roundtrip<T>(&self, v: &T) -> Result<T, HostError>
526 where
527 T: WriteXdr + ReadXdr,
528 {
529 self.metered_from_xdr(self.to_xdr_non_metered(v)?.as_slice())
530 }
531}
532
533#[cfg(any(test, feature = "recording_mode"))]
534fn storage_footprint_to_ledger_footprint(
535 footprint: &Footprint,
536) -> Result<LedgerFootprint, HostError> {
537 let mut read_only: Vec<LedgerKey> = Vec::with_capacity(footprint.0.len());
538 let mut read_write: Vec<LedgerKey> = Vec::with_capacity(footprint.0.len());
539 for (key, access_type) in &footprint.0 {
540 match access_type {
541 AccessType::ReadOnly => read_only.push((**key).clone()),
542 AccessType::ReadWrite => read_write.push((**key).clone()),
543 }
544 }
545 Ok(LedgerFootprint {
546 read_only: read_only
547 .try_into()
548 .map_err(|_| HostError::from((ScErrorType::Storage, ScErrorCode::InternalError)))?,
549 read_write: read_write
550 .try_into()
551 .map_err(|_| HostError::from((ScErrorType::Storage, ScErrorCode::InternalError)))?,
552 })
553}
554
555#[cfg(any(test, feature = "recording_mode"))]
556impl RecordedAuthPayload {
557 fn into_auth_entry_with_emulated_signature(
558 self,
559 ) -> Result<SorobanAuthorizationEntry, HostError> {
560 const EMULATED_SIGNATURE_SIZE: usize = 512;
561
562 match (self.address, self.nonce) {
563 (Some(address), Some(nonce)) => Ok(SorobanAuthorizationEntry {
564 credentials: SorobanCredentials::Address(SorobanAddressCredentials {
565 address,
566 nonce,
567 signature_expiration_ledger: 0,
568 signature: ScVal::Bytes(
569 vec![0_u8; EMULATED_SIGNATURE_SIZE].try_into().unwrap(),
570 ),
571 }),
572 root_invocation: self.invocation,
573 }),
574 (None, None) => Ok(SorobanAuthorizationEntry {
575 credentials: SorobanCredentials::SourceAccount,
576 root_invocation: self.invocation,
577 }),
578 (_, _) => Err((ScErrorType::Auth, ScErrorCode::InternalError).into()),
579 }
580 }
581}
582
583#[cfg(any(test, feature = "recording_mode"))]
584fn clear_signature(auth_entry: &mut SorobanAuthorizationEntry) {
585 match &mut auth_entry.credentials {
586 SorobanCredentials::Address(address_creds) => {
587 address_creds.signature = ScVal::Void;
588 }
589 SorobanCredentials::SourceAccount => {}
590 }
591}
592
593#[cfg(any(test, feature = "recording_mode"))]
594pub enum RecordingInvocationAuthMode {
596 Enforcing(Vec<SorobanAuthorizationEntry>),
598 Recording(bool),
602}
603
604#[cfg(any(test, feature = "recording_mode"))]
637#[allow(clippy::too_many_arguments)]
638pub fn invoke_host_function_in_recording_mode(
639 budget: &Budget,
640 enable_diagnostics: bool,
641 host_fn: &HostFunction,
642 source_account: &AccountId,
643 auth_mode: RecordingInvocationAuthMode,
644 ledger_info: LedgerInfo,
645 ledger_snapshot: Rc<dyn SnapshotSource>,
646 base_prng_seed: [u8; 32],
647 diagnostic_events: &mut Vec<DiagnosticEvent>,
648) -> Result<InvokeHostFunctionRecordingModeResult, HostError> {
649 let storage = Storage::with_recording_footprint(ledger_snapshot.clone());
650 let host = Host::with_storage_and_budget(storage, budget.clone());
651 let is_recording_auth = matches!(auth_mode, RecordingInvocationAuthMode::Recording(_));
652 let ledger_seq = ledger_info.sequence_number;
653 let min_live_until_ledger = ledger_info
654 .min_live_until_ledger_checked(ContractDataDurability::Persistent)
655 .ok_or_else(|| {
656 HostError::from(Error::from_type_and_code(
657 ScErrorType::Context,
658 ScErrorCode::InternalError,
659 ))
660 })?;
661 let host_function = host.xdr_roundtrip(host_fn)?;
662 let source_account: AccountId = host.xdr_roundtrip(source_account)?;
663 host.set_source_account(source_account)?;
664 host.set_ledger_info(ledger_info)?;
665 host.set_base_prng_seed(base_prng_seed)?;
666
667 match &auth_mode {
668 RecordingInvocationAuthMode::Enforcing(auth_entries) => {
669 host.set_authorization_entries(auth_entries.clone())?;
670 }
671 RecordingInvocationAuthMode::Recording(disable_non_root_auth) => {
672 host.switch_to_recording_auth(*disable_non_root_auth)?;
673 }
674 }
675
676 if enable_diagnostics {
677 host.set_diagnostic_level(DiagnosticLevel::Debug)?;
678 }
679 let invoke_result = host.invoke_function(host_function);
680 let mut contract_events_and_return_value_size = 0_u32;
681 if let Ok(res) = &invoke_result {
682 let mut encoded_result_sc_val = vec![];
683 metered_write_xdr(&budget, res, &mut encoded_result_sc_val)?;
684 contract_events_and_return_value_size = contract_events_and_return_value_size
685 .saturating_add(encoded_result_sc_val.len() as u32);
686 }
687
688 let mut output_auth = if let RecordingInvocationAuthMode::Enforcing(auth_entries) = auth_mode {
689 auth_entries
690 } else {
691 let recorded_auth = host.get_recorded_auth_payloads()?;
692 recorded_auth
693 .into_iter()
694 .map(|a| a.into_auth_entry_with_emulated_signature())
695 .collect::<Result<Vec<SorobanAuthorizationEntry>, HostError>>()?
696 };
697
698 let encoded_auth_entries = output_auth
699 .iter()
700 .map(|e| host.to_xdr_non_metered(e))
701 .collect::<Result<Vec<Vec<u8>>, HostError>>()?;
702 let decoded_auth_entries = host.build_auth_entries_from_xdr(encoded_auth_entries.iter())?;
703 if is_recording_auth {
704 host.set_authorization_entries(decoded_auth_entries)?;
705 for auth_entry in &mut output_auth {
706 clear_signature(auth_entry);
707 }
708 }
709
710 let (footprint, disk_read_bytes, init_ttl_map, restored_rw_entry_ids, restored_keys) = host
711 .with_mut_storage(|storage| {
712 let footprint = storage_footprint_to_ledger_footprint(&storage.footprint)?;
713 let _footprint_from_xdr = build_storage_footprint_from_xdr(&budget, footprint.clone())?;
714
715 let mut encoded_ledger_entries = Vec::with_capacity(storage.footprint.0.len());
716 let mut encoded_ttl_entries = Vec::with_capacity(storage.footprint.0.len());
717 let mut disk_read_bytes = 0_u32;
718 let mut current_rw_id = 0;
719 let mut restored_rw_entry_ids = vec![];
720 let mut restored_keys = RestoredKeySet::default();
721
722 for (lk, access_type) in &storage.footprint.0 {
723 let entry_with_live_until = ledger_snapshot.get(lk)?;
724 if let Some((le, live_until)) = entry_with_live_until {
725 let encoded_le = host.to_xdr_non_metered(&*le)?;
726 match &le.data {
727 LedgerEntryData::ContractData(_) | LedgerEntryData::ContractCode(_) => {
728 if let Some(live_until) = live_until {
729 if live_until < ledger_seq && is_persistent_key(lk.as_ref()) {
732 if !matches!(*access_type, AccessType::ReadWrite) {
734 return Err(HostError::from(Error::from_type_and_code(
735 ScErrorType::Storage,
736 ScErrorCode::InternalError,
737 )));
738 }
739 disk_read_bytes =
741 disk_read_bytes.saturating_add(encoded_le.len() as u32);
742 restored_rw_entry_ids.push(current_rw_id);
743 restored_keys =
744 restored_keys.insert(lk.clone(), (), &budget)?;
745 }
746 }
747 }
748 _ => {
749 disk_read_bytes =
751 disk_read_bytes.saturating_add(encoded_le.len() as u32);
752 }
753 }
754
755 encoded_ledger_entries.push(encoded_le);
756 if let Some(live_until_ledger) = live_until {
757 let key_xdr = host.to_xdr_non_metered(lk.as_ref())?;
758 let key_hash: [u8; 32] = Sha256::digest(&key_xdr).into();
759 let ttl_entry = TtlEntry {
760 key_hash: key_hash.try_into().map_err(|_| {
761 HostError::from((ScErrorType::Context, ScErrorCode::InternalError))
762 })?,
763 live_until_ledger_seq: live_until_ledger,
764 };
765 encoded_ttl_entries.push(host.to_xdr_non_metered(&ttl_entry)?);
766 } else {
767 encoded_ttl_entries.push(vec![]);
768 }
769 }
770 if matches!(*access_type, AccessType::ReadWrite) {
771 current_rw_id += 1;
772 }
773 }
774 let (init_storage, init_ttl_map) = build_storage_map_from_xdr_ledger_entries(
775 &budget,
776 &storage.footprint,
777 encoded_ledger_entries.iter(),
778 encoded_ttl_entries.iter(),
779 ledger_seq,
780 true,
781 )?;
782 let _init_storage_clone = init_storage.metered_clone(budget)?;
783 Ok((
784 footprint,
785 disk_read_bytes,
786 init_ttl_map,
787 restored_rw_entry_ids,
788 restored_keys,
789 ))
790 })?;
791 let mut resources = SorobanResources {
792 footprint,
793 instructions: 0,
794 disk_read_bytes,
795 write_bytes: 0,
796 };
797 let _resources_roundtrip: SorobanResources =
798 host.metered_from_xdr(host.to_xdr_non_metered(&resources)?.as_slice())?;
799 let (storage, events) = host.try_finish()?;
800 if enable_diagnostics {
801 extract_diagnostic_events(&events, diagnostic_events);
802 }
803 let restored_keys = if restored_keys.map.is_empty() {
804 None
805 } else {
806 Some(restored_keys)
807 };
808 let (ledger_changes, contract_events) = if invoke_result.is_ok() {
809 let mut ledger_changes = get_ledger_changes(
810 &budget,
811 &storage,
812 &*ledger_snapshot,
813 init_ttl_map,
814 min_live_until_ledger,
815 &restored_keys,
816 ledger_seq,
817 )?;
818 budget.with_shadow_mode(|| {
822 add_footprint_only_ledger_changes(budget, &storage, &mut ledger_changes)
823 });
824
825 let encoded_contract_events = encode_contract_events(budget, &events)?;
826 for e in &encoded_contract_events {
827 contract_events_and_return_value_size =
828 contract_events_and_return_value_size.saturating_add(e.len() as u32);
829 }
830 let contract_events: Vec<ContractEvent> = events
831 .0
832 .into_iter()
833 .filter(|e| !e.failed_call && e.event.type_ != ContractEventType::Diagnostic)
834 .map(|e| e.event)
835 .collect();
836
837 (ledger_changes, contract_events)
838 } else {
839 (vec![], vec![])
840 };
841 resources.instructions = budget.get_cpu_insns_consumed()? as u32;
842 for ledger_change in &ledger_changes {
843 if !ledger_change.read_only {
844 if let Some(new_entry) = &ledger_change.encoded_new_value {
845 resources.write_bytes =
846 resources.write_bytes.saturating_add(new_entry.len() as u32);
847 }
848 }
849 }
850
851 Ok(InvokeHostFunctionRecordingModeResult {
852 invoke_result,
853 resources,
854 restored_rw_entry_indices: restored_rw_entry_ids,
855 auth: output_auth,
856 ledger_changes,
857 contract_events,
858 contract_events_and_return_value_size,
859 })
860}
861
862pub fn encode_contract_events(budget: &Budget, events: &Events) -> Result<Vec<Vec<u8>>, HostError> {
864 let ce = events
865 .0
866 .iter()
867 .filter(|e| !e.failed_call && e.event.type_ != ContractEventType::Diagnostic)
868 .map(|e| {
869 let mut buf = vec![];
870 metered_write_xdr(budget, &e.event, &mut buf)?;
871 Ok(buf)
872 })
873 .collect::<Result<Vec<Vec<u8>>, HostError>>()?;
874 Vec::<Vec<u8>>::charge_bulk_init_cpy(ce.len() as u64, budget)?;
877 Ok(ce)
878}
879
880fn extract_diagnostic_events(events: &Events, diagnostic_events: &mut Vec<DiagnosticEvent>) {
881 for event in &events.0 {
884 diagnostic_events.push(DiagnosticEvent {
885 in_successful_contract_call: !event.failed_call,
886 event: event.event.clone(),
887 });
888 }
889}
890
891pub(crate) fn ledger_entry_to_ledger_key(
892 le: &LedgerEntry,
893 budget: &Budget,
894) -> Result<LedgerKey, HostError> {
895 match &le.data {
896 LedgerEntryData::Account(a) => Ok(LedgerKey::Account(LedgerKeyAccount {
897 account_id: a.account_id.metered_clone(budget)?,
898 })),
899 LedgerEntryData::Trustline(tl) => Ok(LedgerKey::Trustline(LedgerKeyTrustLine {
900 account_id: tl.account_id.metered_clone(budget)?,
901 asset: tl.asset.metered_clone(budget)?,
902 })),
903 LedgerEntryData::ContractData(cd) => Ok(LedgerKey::ContractData(LedgerKeyContractData {
904 contract: cd.contract.metered_clone(budget)?,
905 key: cd.key.metered_clone(budget)?,
906 durability: cd.durability,
907 })),
908 LedgerEntryData::ContractCode(code) => Ok(LedgerKey::ContractCode(LedgerKeyContractCode {
909 hash: code.hash.metered_clone(budget)?,
910 })),
911 _ => {
912 return Err(Error::from_type_and_code(
913 ScErrorType::Storage,
914 ScErrorCode::InternalError,
915 )
916 .into());
917 }
918 }
919}
920
921fn build_storage_footprint_from_xdr(
922 budget: &Budget,
923 footprint: LedgerFootprint,
924) -> Result<Footprint, HostError> {
925 let mut footprint_map = FootprintMap::new();
926
927 for key in footprint.read_write.as_vec() {
928 Storage::check_supported_ledger_key_type(&key)?;
929 footprint_map = footprint_map.insert(
930 Rc::metered_new(key.metered_clone(budget)?, budget)?,
931 AccessType::ReadWrite,
932 budget,
933 )?;
934 }
935
936 for key in footprint.read_only.as_vec() {
937 Storage::check_supported_ledger_key_type(&key)?;
938 footprint_map = footprint_map.insert(
939 Rc::metered_new(key.metered_clone(budget)?, budget)?,
940 AccessType::ReadOnly,
941 budget,
942 )?;
943 }
944 Ok(Footprint(footprint_map))
945}
946
947fn build_storage_map_from_xdr_ledger_entries<T: AsRef<[u8]>, I: ExactSizeIterator<Item = T>>(
948 budget: &Budget,
949 footprint: &Footprint,
950 encoded_ledger_entries: I,
951 encoded_ttl_entries: I,
952 ledger_num: u32,
953 #[cfg(any(test, feature = "recording_mode"))] is_recording_mode: bool,
954) -> Result<(StorageMap, TtlEntryMap), HostError> {
955 let mut storage_map = StorageMap::new();
956 let mut ttl_map = TtlEntryMap::new();
957
958 if encoded_ledger_entries.len() != encoded_ttl_entries.len() {
959 return Err(
960 Error::from_type_and_code(ScErrorType::Storage, ScErrorCode::InternalError).into(),
961 );
962 }
963
964 for (entry_buf, ttl_buf) in encoded_ledger_entries.zip(encoded_ttl_entries) {
965 let mut live_until_ledger: Option<u32> = None;
966
967 let le = Rc::metered_new(
968 metered_from_xdr_with_budget::<LedgerEntry>(entry_buf.as_ref(), budget)?,
969 budget,
970 )?;
971 let key = Rc::metered_new(ledger_entry_to_ledger_key(&le, budget)?, budget)?;
972 if !ttl_buf.as_ref().is_empty() {
973 let ttl_entry = Rc::metered_new(
974 metered_from_xdr_with_budget::<TtlEntry>(ttl_buf.as_ref(), budget)?,
975 budget,
976 )?;
977 #[cfg(not(any(test, feature = "recording_mode")))]
981 if ttl_entry.live_until_ledger_seq < ledger_num {
982 #[cfg(any(test, feature = "recording_mode"))]
983 if !is_recording_mode {
984 return Err(Error::from_type_and_code(
985 ScErrorType::Storage,
986 ScErrorCode::InternalError,
987 )
988 .into());
989 }
990 }
991 #[cfg(any(test, feature = "recording_mode"))]
995 if ttl_entry.live_until_ledger_seq < ledger_num {
996 #[cfg(any(test, feature = "recording_mode"))]
997 if !is_recording_mode {
998 return Err(Error::from_type_and_code(
999 ScErrorType::Storage,
1000 ScErrorCode::InternalError,
1001 )
1002 .into());
1003 }
1004 if !crate::storage::is_persistent_key(key.as_ref()) {
1007 continue;
1008 }
1009 }
1010
1011 live_until_ledger = Some(ttl_entry.live_until_ledger_seq);
1012
1013 ttl_map = ttl_map.insert(key.clone(), ttl_entry, budget)?;
1014 } else if matches!(le.as_ref().data, LedgerEntryData::ContractData(_))
1015 || matches!(le.as_ref().data, LedgerEntryData::ContractCode(_))
1016 {
1017 return Err(Error::from_type_and_code(
1018 ScErrorType::Storage,
1019 ScErrorCode::InternalError,
1020 )
1021 .into());
1022 }
1023
1024 if !footprint.0.contains_key::<LedgerKey>(&key, budget)? {
1025 return Err(Error::from_type_and_code(
1026 ScErrorType::Storage,
1027 ScErrorCode::InternalError,
1028 )
1029 .into());
1030 }
1031 storage_map = storage_map.insert(key, Some((le, live_until_ledger)), budget)?;
1032 }
1033
1034 for k in footprint.0.keys(budget)? {
1036 if !storage_map.contains_key::<LedgerKey>(k, budget)? {
1037 storage_map = storage_map.insert(Rc::clone(k), None, budget)?;
1038 }
1039 }
1040 Ok((storage_map, ttl_map))
1041}
1042
1043impl Host {
1044 fn build_auth_entries_from_xdr<T: AsRef<[u8]>, I: ExactSizeIterator<Item = T>>(
1045 &self,
1046 encoded_contract_auth_entries: I,
1047 ) -> Result<Vec<SorobanAuthorizationEntry>, HostError> {
1048 encoded_contract_auth_entries
1049 .map(|buf| self.metered_from_xdr::<SorobanAuthorizationEntry>(buf.as_ref()))
1050 .metered_collect::<Result<Vec<SorobanAuthorizationEntry>, HostError>>(
1051 self.as_budget(),
1052 )?
1053 }
1054}
1055
1056struct StorageMapSnapshotSource<'a> {
1057 budget: &'a Budget,
1058 map: &'a StorageMap,
1059}
1060
1061impl SnapshotSource for StorageMapSnapshotSource<'_> {
1062 fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError> {
1063 if let Some(Some((entry, live_until_ledger))) =
1064 self.map.get::<Rc<LedgerKey>>(key, self.budget)?
1065 {
1066 Ok(Some((Rc::clone(entry), *live_until_ledger)))
1067 } else {
1068 Ok(None)
1069 }
1070 }
1071}