1use std::rc::Rc;
11
12use crate::budget::AsBudget;
13use crate::host::metered_clone::MeteredClone;
14use crate::{
15 budget::Budget,
16 host::metered_map::MeteredOrdMap,
17 ledger_info::get_key_durability,
18 xdr::{ContractDataDurability, LedgerEntry, LedgerKey, ScErrorCode, ScErrorType, ScVal},
19 Env, Error, Host, HostError, Val,
20};
21
22pub type FootprintMap = MeteredOrdMap<Rc<LedgerKey>, AccessType, Budget>;
23pub type EntryWithLiveUntil = (Rc<LedgerEntry>, Option<u32>);
24pub type StorageMap = MeteredOrdMap<Rc<LedgerKey>, Option<EntryWithLiveUntil>, Budget>;
25
26#[derive(Clone, Hash)]
30pub(crate) struct InstanceStorageMap {
31 pub(crate) map: MeteredOrdMap<Val, Val, Host>,
32 pub(crate) is_modified: bool,
33}
34
35impl InstanceStorageMap {
36 pub(crate) fn from_map(map: Vec<(Val, Val)>, host: &Host) -> Result<Self, HostError> {
37 Ok(Self {
38 map: MeteredOrdMap::from_map(map, host)?,
39 is_modified: false,
40 })
41 }
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
48pub enum AccessType {
49 ReadOnly,
52 ReadWrite,
55}
56pub trait SnapshotSource {
62 fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError>;
65}
66
67#[derive(Clone, Default, Hash)]
77pub struct Footprint(pub FootprintMap);
78
79impl Footprint {
80 #[cfg(any(test, feature = "recording_mode"))]
81 pub(crate) fn record_access(
82 &mut self,
83 key: &Rc<LedgerKey>,
84 ty: AccessType,
85 budget: &Budget,
86 ) -> Result<(), HostError> {
87 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
88 match (existing, ty) {
89 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
90 (AccessType::ReadOnly, AccessType::ReadWrite) => {
91 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
94 Ok(())
95 }
96 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
97 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
98 }
99 } else {
100 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
101 Ok(())
102 }
103 }
104
105 pub(crate) fn enforce_access(
106 &mut self,
107 key: &Rc<LedgerKey>,
108 ty: AccessType,
109 budget: &Budget,
110 ) -> Result<(), HostError> {
111 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
118 match (existing, ty) {
119 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
120 (AccessType::ReadOnly, AccessType::ReadWrite) => {
121 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
122 }
123 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
124 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
125 }
126 } else {
127 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
128 }
129 }
130}
131
132#[derive(Clone, Default)]
133pub(crate) enum FootprintMode {
134 #[cfg(any(test, feature = "recording_mode"))]
135 Recording(Rc<dyn SnapshotSource>),
136 #[default]
137 Enforcing,
138}
139
140#[derive(Clone, Default)]
154pub struct Storage {
155 pub footprint: Footprint,
156 pub(crate) mode: FootprintMode,
157 pub map: StorageMap,
158}
159
160impl Storage {
163 pub fn check_supported_ledger_entry_type(le: &LedgerEntry) -> Result<(), HostError> {
169 use crate::xdr::LedgerEntryData::*;
170 match le.data {
171 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
172 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
173 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
174 }
175 }
176
177 pub fn check_supported_ledger_key_type(lk: &LedgerKey) -> Result<(), HostError> {
183 use LedgerKey::*;
184 match lk {
185 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
186 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
187 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
188 }
189 }
190
191 pub fn with_enforcing_footprint_and_map(footprint: Footprint, map: StorageMap) -> Self {
195 Self {
196 mode: FootprintMode::Enforcing,
197 footprint,
198 map,
199 }
200 }
201
202 #[cfg(any(test, feature = "recording_mode"))]
205 pub fn with_recording_footprint(src: Rc<dyn SnapshotSource>) -> Self {
206 Self {
207 mode: FootprintMode::Recording(src),
208 footprint: Footprint::default(),
209 map: Default::default(),
210 }
211 }
212
213 fn try_get_full_helper(
215 &mut self,
216 key: &Rc<LedgerKey>,
217 host: &Host,
218 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
219 let _span = tracy_span!("storage get");
220 Self::check_supported_ledger_key_type(key)?;
221 self.prepare_read_only_access(key, host)?;
222 match self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())? {
223 None => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
226 Some(pair_option) => Ok(pair_option.clone()),
227 }
228 }
229
230 pub(crate) fn try_get_full(
231 &mut self,
232 key: &Rc<LedgerKey>,
233 host: &Host,
234 key_val: Option<Val>,
235 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
236 let res = self
237 .try_get_full_helper(key, host)
238 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))?;
239 Ok(res)
240 }
241
242 pub(crate) fn get(
243 &mut self,
244 key: &Rc<LedgerKey>,
245 host: &Host,
246 key_val: Option<Val>,
247 ) -> Result<Rc<LedgerEntry>, HostError> {
248 self.try_get_full(key, host, key_val)?
249 .ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
250 .map(|e| e.0)
251 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
252 }
253
254 pub(crate) fn try_get(
257 &mut self,
258 key: &Rc<LedgerKey>,
259 host: &Host,
260 key_val: Option<Val>,
261 ) -> Result<Option<Rc<LedgerEntry>>, HostError> {
262 self.try_get_full(key, host, key_val)
263 .map(|ok| ok.map(|pair| pair.0))
264 }
265
266 pub(crate) fn get_with_live_until_ledger(
281 &mut self,
282 key: &Rc<LedgerKey>,
283 host: &Host,
284 key_val: Option<Val>,
285 ) -> Result<EntryWithLiveUntil, HostError> {
286 self.try_get_full(key, host, key_val)
287 .and_then(|maybe_entry| {
288 maybe_entry.ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
289 })
290 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
291 }
292
293 fn put_opt_helper(
295 &mut self,
296 key: &Rc<LedgerKey>,
297 val: Option<EntryWithLiveUntil>,
298 host: &Host,
299 ) -> Result<(), HostError> {
300 Self::check_supported_ledger_key_type(key)?;
301 if let Some(le) = &val {
302 Self::check_supported_ledger_entry_type(&le.0)?;
303 }
304 #[cfg(any(test, feature = "recording_mode"))]
305 self.handle_maybe_expired_entry(&key, host)?;
306
307 let ty = AccessType::ReadWrite;
308 match &self.mode {
309 #[cfg(any(test, feature = "recording_mode"))]
310 FootprintMode::Recording(_) => {
311 self.footprint.record_access(key, ty, host.budget_ref())?;
312 }
313 FootprintMode::Enforcing => {
314 self.footprint.enforce_access(key, ty, host.budget_ref())?;
315 }
316 };
317 self.map = self.map.insert(Rc::clone(key), val, host.budget_ref())?;
318 Ok(())
319 }
320
321 fn put_opt(
322 &mut self,
323 key: &Rc<LedgerKey>,
324 val: Option<EntryWithLiveUntil>,
325 host: &Host,
326 key_val: Option<Val>,
327 ) -> Result<(), HostError> {
328 self.put_opt_helper(key, val, host)
329 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
330 }
331
332 pub(crate) fn put(
342 &mut self,
343 key: &Rc<LedgerKey>,
344 val: &Rc<LedgerEntry>,
345 live_until_ledger: Option<u32>,
346 host: &Host,
347 key_val: Option<Val>,
348 ) -> Result<(), HostError> {
349 let _span = tracy_span!("storage put");
350 self.put_opt(key, Some((val.clone(), live_until_ledger)), host, key_val)
351 }
352
353 pub(crate) fn del(
363 &mut self,
364 key: &Rc<LedgerKey>,
365 host: &Host,
366 key_val: Option<Val>,
367 ) -> Result<(), HostError> {
368 let _span = tracy_span!("storage del");
369 self.put_opt(key, None, host, key_val)
370 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
371 }
372
373 pub(crate) fn has(
383 &mut self,
384 key: &Rc<LedgerKey>,
385 host: &Host,
386 key_val: Option<Val>,
387 ) -> Result<bool, HostError> {
388 let _span = tracy_span!("storage has");
389 Ok(self.try_get_full(key, host, key_val)?.is_some())
390 }
391
392 pub(crate) fn extend_ttl(
407 &mut self,
408 host: &Host,
409 key: Rc<LedgerKey>,
410 threshold: u32,
411 extend_to: u32,
412 key_val: Option<Val>,
413 ) -> Result<(), HostError> {
414 let _span = tracy_span!("extend key");
415 Self::check_supported_ledger_key_type(&key)?;
416
417 if threshold > extend_to {
418 return Err(host.err(
419 ScErrorType::Storage,
420 ScErrorCode::InvalidInput,
421 "threshold must be <= extend_to",
422 &[threshold.into(), extend_to.into()],
423 ));
424 }
425 #[cfg(any(test, feature = "recording_mode"))]
426 self.handle_maybe_expired_entry(&key, host)?;
427
428 let (entry, old_live_until) = self.get_with_live_until_ledger(&key, &host, key_val)?;
431 let old_live_until = old_live_until.ok_or_else(|| {
432 host.err(
433 ScErrorType::Storage,
434 ScErrorCode::InternalError,
435 "trying to extend invalid entry",
436 &[],
437 )
438 })?;
439
440 let ledger_seq: u32 = host.get_ledger_sequence()?.into();
441 if old_live_until < ledger_seq {
442 return Err(host.err(
443 ScErrorType::Storage,
444 ScErrorCode::InternalError,
445 "accessing no-longer-live entry",
446 &[old_live_until.into(), ledger_seq.into()],
447 ));
448 }
449
450 let mut new_live_until = host.with_ledger_info(|li| {
451 li.sequence_number.checked_add(extend_to).ok_or_else(|| {
452 HostError::from(Error::from_type_and_code(
457 ScErrorType::Context,
458 ScErrorCode::InternalError,
459 ))
460 })
461 })?;
462
463 if new_live_until > host.max_live_until_ledger()? {
464 if let Some(durability) = get_key_durability(&key) {
465 if matches!(durability, ContractDataDurability::Persistent) {
466 new_live_until = host.max_live_until_ledger()?;
467 } else {
468 return Err(host.err(
474 ScErrorType::Storage,
475 ScErrorCode::InvalidAction,
476 "trying to extend past max live_until ledger",
477 &[new_live_until.into()],
478 ));
479 }
480 } else {
481 return Err(host.err(
482 ScErrorType::Storage,
483 ScErrorCode::InternalError,
484 "durability is missing",
485 &[],
486 ));
487 }
488 }
489
490 if new_live_until > old_live_until && old_live_until.saturating_sub(ledger_seq) <= threshold
491 {
492 self.map = self.map.insert(
493 key,
494 Some((entry.clone(), Some(new_live_until))),
495 host.budget_ref(),
496 )?;
497 }
498 Ok(())
499 }
500
501 #[cfg(any(test, feature = "recording_mode"))]
502 pub(crate) fn is_key_live_in_snapshot(
505 &self,
506 host: &Host,
507 key: &Rc<LedgerKey>,
508 ) -> Result<bool, HostError> {
509 match &self.mode {
510 FootprintMode::Recording(snapshot) => {
511 let snapshot_value = snapshot.get(key)?;
512 if let Some((_, live_until_ledger)) = snapshot_value {
513 if let Some(live_until_ledger) = live_until_ledger {
514 let current_ledger_sequence =
515 host.with_ledger_info(|li| Ok(li.sequence_number))?;
516 Ok(live_until_ledger >= current_ledger_sequence)
517 } else {
518 Ok(true)
520 }
521 } else {
522 Ok(false)
524 }
525 }
526 FootprintMode::Enforcing => Err(host.err(
527 ScErrorType::Storage,
528 ScErrorCode::InternalError,
529 "trying to get snapshot value in enforcing mode",
530 &[],
531 )),
532 }
533 }
534
535 #[cfg(any(test, feature = "testutils"))]
538 pub(crate) fn get_from_map(
539 &self,
540 key: &Rc<LedgerKey>,
541 host: &Host,
542 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
543 match self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())? {
544 Some(pair_option) => Ok(pair_option.clone()),
545 None => Ok(None),
546 }
547 }
548
549 fn prepare_read_only_access(
550 &mut self,
551 key: &Rc<LedgerKey>,
552 host: &Host,
553 ) -> Result<(), HostError> {
554 let ty = AccessType::ReadOnly;
555 match self.mode {
556 #[cfg(any(test, feature = "recording_mode"))]
557 FootprintMode::Recording(ref src) => {
558 self.footprint.record_access(key, ty, host.budget_ref())?;
559 if !self
562 .map
563 .contains_key::<Rc<LedgerKey>>(key, host.budget_ref())?
564 {
565 let value = src.get(&key)?;
566 self.map = self.map.insert(key.clone(), value, host.budget_ref())?;
567 }
568 self.footprint.record_access(key, ty, host.budget_ref())?;
569 self.handle_maybe_expired_entry(key, host)?;
570 }
571 FootprintMode::Enforcing => {
572 self.footprint.enforce_access(key, ty, host.budget_ref())?;
573 }
574 };
575 Ok(())
576 }
577
578 #[cfg(any(test, feature = "recording_mode"))]
579 fn handle_maybe_expired_entry(
580 &mut self,
581 key: &Rc<LedgerKey>,
582 host: &Host,
583 ) -> Result<(), HostError> {
584 host.with_ledger_info(|li| {
585 let budget = host.budget_ref();
586 if let Some(Some((entry, live_until))) =
587 self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())?
588 {
589 if let Some(durability) = get_key_durability(key.as_ref()) {
590 let live_until = live_until.ok_or_else(|| {
591 host.err(
592 ScErrorType::Storage,
593 ScErrorCode::InternalError,
594 "unexpected contract entry without TTL",
595 &[],
596 )
597 })?;
598 if live_until < li.sequence_number {
599 match durability {
600 ContractDataDurability::Temporary => {
601 self.map = self.map.insert(key.clone(), None, budget)?;
602 }
603 ContractDataDurability::Persistent => {
604 self.footprint
605 .record_access(key, AccessType::ReadWrite, budget)?;
606 let new_live_until = li
607 .min_live_until_ledger_checked(
608 ContractDataDurability::Persistent,
609 )
610 .ok_or_else(|| {
611 host.err(ScErrorType::Storage,
612 ScErrorCode::InternalError,
613 "persistent entry TTL overflow, ledger is mis-configured",
614 &[],)
615 })?;
616 self.map = self.map.insert(
617 key.clone(),
618 Some((entry.clone(), Some(new_live_until))),
619 budget,
620 )?;
621 }
622 };
623 }
624 }
625 }
626 Ok(())
627 })
628 }
629
630 #[cfg(any(test, feature = "testutils"))]
631 pub(crate) fn reset_footprint(&mut self) {
632 self.footprint = Footprint::default();
633 }
634}
635
636fn get_key_type_string_for_error(lk: &LedgerKey) -> &str {
637 match lk {
638 LedgerKey::ContractData(cd) => match cd.key {
639 ScVal::LedgerKeyContractInstance => "contract instance",
640 ScVal::LedgerKeyNonce(_) => "nonce",
641 _ => "contract data key",
642 },
643 LedgerKey::ContractCode(_) => "contract code",
644 LedgerKey::Account(_) => "account",
645 LedgerKey::Trustline(_) => "account trustline",
646 _ => "ledger key",
650 }
651}
652
653impl Host {
654 fn decorate_storage_error(
655 &self,
656 err: HostError,
657 lk: &LedgerKey,
658 key_val: Option<Val>,
659 ) -> HostError {
660 let mut err = err;
661 self.with_debug_mode_allowing_new_objects(
662 || {
663 if !err.error.is_type(ScErrorType::Storage) {
664 return Ok(());
665 }
666 if !err.error.is_code(ScErrorCode::ExceededLimit)
667 && !err.error.is_code(ScErrorCode::MissingValue)
668 {
669 return Ok(());
670 }
671
672 let key_type_str = get_key_type_string_for_error(lk);
673 let can_create_new_objects = err.error.is_code(ScErrorCode::ExceededLimit);
680 let args = self
681 .get_args_for_error(lk, key_val, can_create_new_objects)
682 .unwrap_or_else(|_| vec![]);
683 if err.error.is_code(ScErrorCode::ExceededLimit) {
684 err = self.err(
685 ScErrorType::Storage,
686 ScErrorCode::ExceededLimit,
687 format!("trying to access {} outside of the footprint", key_type_str)
688 .as_str(),
689 args.as_slice(),
690 );
691 } else if err.error.is_code(ScErrorCode::MissingValue) {
692 err = self.err(
693 ScErrorType::Storage,
694 ScErrorCode::MissingValue,
695 format!("trying to get non-existing value for {}", key_type_str).as_str(),
696 args.as_slice(),
697 );
698 }
699
700 Ok(())
701 },
702 true,
703 );
704 err
705 }
706
707 fn get_args_for_error(
708 &self,
709 lk: &LedgerKey,
710 key_val: Option<Val>,
711 can_create_new_objects: bool,
712 ) -> Result<Vec<Val>, HostError> {
713 let mut res = vec![];
714 match lk {
715 LedgerKey::ContractData(cd) => {
716 if can_create_new_objects {
717 let address_val = self
718 .add_host_object(cd.contract.metered_clone(self.as_budget())?)?
719 .into();
720 res.push(address_val);
721 }
722 match &cd.key {
723 ScVal::LedgerKeyContractInstance => (),
724 ScVal::LedgerKeyNonce(n) => {
725 if can_create_new_objects {
726 res.push(self.add_host_object(n.nonce)?.into());
727 }
728 }
729 _ => {
730 if let Some(key) = key_val {
731 res.push(key);
732 }
733 }
734 }
735 }
736 LedgerKey::ContractCode(c) => {
737 if can_create_new_objects {
738 res.push(
739 self.add_host_object(self.scbytes_from_hash(&c.hash)?)?
740 .into(),
741 );
742 }
743 }
744 LedgerKey::Account(_) | LedgerKey::Trustline(_) => {
745 if can_create_new_objects {
746 res.push(self.account_address_from_key(lk)?)
747 }
748 }
749 _ => (),
753 };
754 Ok(res)
755 }
756}
757
758#[cfg(any(test, feature = "recording_mode"))]
759pub(crate) fn is_persistent_key(key: &LedgerKey) -> bool {
760 match key {
761 LedgerKey::ContractData(k) => {
762 matches!(k.durability, ContractDataDurability::Persistent)
763 }
764 LedgerKey::ContractCode(_) => true,
765 _ => false,
766 }
767}