soroban_simulation/
snapshot_source.rs

1use anyhow::{anyhow, Result};
2use soroban_env_host::ledger_info::get_key_durability;
3use soroban_env_host::storage::EntryWithLiveUntil;
4use soroban_env_host::xdr::{
5    AccountEntry, AccountEntryExt, AccountEntryExtensionV1, AccountEntryExtensionV1Ext,
6    AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountEntryExtensionV3,
7    ContractDataDurability, ExtensionPoint, LedgerEntryData, Liabilities, ScErrorCode, ScErrorType,
8    SponsorshipDescriptor, TimePoint,
9};
10use soroban_env_host::LedgerInfo;
11use soroban_env_host::{storage::SnapshotSource, xdr::LedgerKey, HostError};
12use std::cell::RefCell;
13use std::collections::{BTreeMap, BTreeSet};
14use std::rc::Rc;
15
16use crate::simulation::{
17    simulate_restore_op, RestoreOpSimulationResult, SimulationAdjustmentConfig,
18};
19use crate::NetworkConfig;
20
21/// The `SnapshotSource` implementation that automatically restores
22/// the archived ledger entries accessed during `get` calls.
23///
24/// Use this to suppress the default auto-restoration behavior during the host
25/// function execution: by restoring the entries inside the snapshot the final
26/// simulation result would be 'as if' the entries have already been restored.
27/// This might be useful if the auto-restoration causes the resources to go
28/// above the per-transaction limit.
29///
30/// This does not define a concrete implementation for ledger lookups
31/// and just wraps a `SnapshotSource`.
32///
33/// The restored entries will have the rent automatically bumped to
34/// `min_persistent_entry_ttl`, which is consistent with the behavior
35/// of `RestoreFootprintOp` if it was called in the same ledger.
36pub struct AutoRestoringSnapshotSource<T: SnapshotSource> {
37    snapshot_source: Rc<T>,
38    min_persistent_live_until_ledger: u32,
39    current_ledger_sequence: u32,
40    restored_ledger_keys: RefCell<BTreeSet<Rc<LedgerKey>>>,
41}
42
43impl<T: SnapshotSource> AutoRestoringSnapshotSource<T> {
44    pub fn new(snapshot_source: Rc<T>, ledger_info: &LedgerInfo) -> Result<Self, anyhow::Error> {
45        Ok(Self {
46            snapshot_source,
47            min_persistent_live_until_ledger: ledger_info.min_live_until_ledger_checked(ContractDataDurability::Persistent).ok_or_else(|| anyhow!("minimum persistent live until ledger overflows - ledger info is misconfigured"))?,
48            current_ledger_sequence: ledger_info.sequence_number,
49            restored_ledger_keys: RefCell::new(Default::default()),
50        })
51    }
52
53    /// Resets all the restored keys recorded so far.
54    pub fn reset_restored_keys(&self) {
55        self.restored_ledger_keys.borrow_mut().clear();
56    }
57
58    /// Simulates a `RestoreFootprintOp` for all the keys that have been
59    /// restored so far.
60    pub fn simulate_restore_keys_op(
61        &self,
62        network_config: &NetworkConfig,
63        adjustment_config: &SimulationAdjustmentConfig,
64        ledger_info: &LedgerInfo,
65    ) -> Result<Option<RestoreOpSimulationResult>> {
66        let restored_keys = self.restored_ledger_keys.borrow();
67        if restored_keys.is_empty() {
68            return Ok(None);
69        }
70        simulate_restore_op(
71            self.snapshot_source.as_ref(),
72            network_config,
73            adjustment_config,
74            ledger_info,
75            restored_keys
76                .iter()
77                .map(|k| k.as_ref().clone())
78                .collect::<Vec<LedgerKey>>()
79                .as_ref(),
80        )
81        .map(|res| Some(res))
82    }
83}
84
85impl<T: SnapshotSource> SnapshotSource for AutoRestoringSnapshotSource<T> {
86    fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError> {
87        let entry_with_live_until = self.snapshot_source.get(key)?;
88        if let Some((entry, live_until)) = entry_with_live_until {
89            if let Some(durability) = get_key_durability(key.as_ref()) {
90                let live_until = live_until.ok_or_else(|| {
91                    // Entries with durability must have TTL.
92                    HostError::from((ScErrorType::Storage, ScErrorCode::InternalError))
93                })?;
94                if live_until < self.current_ledger_sequence {
95                    return match durability {
96                        ContractDataDurability::Temporary => Ok(None),
97                        ContractDataDurability::Persistent => {
98                            let mut restored_ledger_keys =
99                                self.restored_ledger_keys.try_borrow_mut().map_err(|_| {
100                                    HostError::from((
101                                        ScErrorType::Context,
102                                        ScErrorCode::InternalError,
103                                    ))
104                                })?;
105                            restored_ledger_keys.insert(key.clone());
106                            Ok(Some((entry, Some(self.min_persistent_live_until_ledger))))
107                        }
108                    };
109                }
110            }
111            Ok(Some((entry, live_until)))
112        } else {
113            Ok(None)
114        }
115    }
116}
117
118#[derive(Default)]
119struct LedgerEntryUpdater {
120    updated_entries_cache: BTreeMap<Rc<LedgerKey>, Option<EntryWithLiveUntil>>,
121}
122
123impl LedgerEntryUpdater {
124    fn maybe_update_entry(
125        &mut self,
126        key: &Rc<LedgerKey>,
127        entry: Option<EntryWithLiveUntil>,
128    ) -> Option<EntryWithLiveUntil> {
129        if let Some(e) = self.updated_entries_cache.get(key) {
130            return e.clone();
131        }
132        if let Some((entry, live_until)) = &entry {
133            match &entry.data {
134                LedgerEntryData::Account(_) => {
135                    let mut updated_entry = (**entry).clone();
136                    match &mut updated_entry.data {
137                        LedgerEntryData::Account(acc) => {
138                            update_account_entry(acc);
139                        }
140                        _ => (),
141                    }
142                    let entry_with_live_until = Some((Rc::new(updated_entry), *live_until));
143                    self.updated_entries_cache
144                        .insert(key.clone(), entry_with_live_until.clone());
145                    return entry_with_live_until;
146                }
147                _ => (),
148            }
149        }
150        entry
151    }
152}
153
154enum SnapshotSourceHolder<'a> {
155    Ref(&'a dyn SnapshotSource),
156    Rc(Rc<dyn SnapshotSource>),
157}
158
159impl SnapshotSource for SnapshotSourceHolder<'_> {
160    fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError> {
161        match self {
162            SnapshotSourceHolder::Ref(r) => r.get(key),
163            SnapshotSourceHolder::Rc(r) => r.get(key),
164        }
165    }
166}
167
168// This is an internal wrapper for the snapshot sources used in the simulation.
169// The purpose of the wrapper is to emulate any ledger entry modifying logic
170// that Core might perform before the Soroban host operation is invoked. For example,
171// Core might create the account extensions, which would impact the read bytes
172// amount and simulated CPU instructions.
173pub(crate) struct SimulationSnapshotSource<'a> {
174    inner_snapshot: SnapshotSourceHolder<'a>,
175    entry_updater: RefCell<LedgerEntryUpdater>,
176}
177
178impl<'a> SimulationSnapshotSource<'a> {
179    pub(crate) fn new(snapshot: &'a dyn SnapshotSource) -> Self {
180        Self {
181            inner_snapshot: SnapshotSourceHolder::Ref(snapshot),
182            entry_updater: RefCell::new(Default::default()),
183        }
184    }
185
186    pub(crate) fn new_from_rc(snapshot: Rc<dyn SnapshotSource>) -> Self {
187        Self {
188            inner_snapshot: SnapshotSourceHolder::Rc(snapshot),
189            entry_updater: RefCell::new(Default::default()),
190        }
191    }
192}
193
194impl SnapshotSource for SimulationSnapshotSource<'_> {
195    fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError> {
196        Ok(self
197            .entry_updater
198            .borrow_mut()
199            .maybe_update_entry(key, self.inner_snapshot.get(key)?))
200    }
201}
202
203fn update_account_entry(account_entry: &mut AccountEntry) {
204    match &mut account_entry.ext {
205        AccountEntryExt::V0 => {
206            let mut ext = AccountEntryExtensionV1 {
207                liabilities: Liabilities {
208                    buying: 0,
209                    selling: 0,
210                },
211                ext: AccountEntryExtensionV1Ext::V0,
212            };
213            fill_account_ext_v2(&mut ext, account_entry.signers.len());
214            account_entry.ext = AccountEntryExt::V1(ext);
215        }
216        AccountEntryExt::V1(ext) => {
217            fill_account_ext_v2(ext, account_entry.signers.len());
218        }
219    }
220}
221
222fn fill_account_ext_v2(account_ext_v1: &mut AccountEntryExtensionV1, signers_count: usize) {
223    match &mut account_ext_v1.ext {
224        AccountEntryExtensionV1Ext::V0 => {
225            let mut ext = AccountEntryExtensionV2 {
226                num_sponsored: 0,
227                num_sponsoring: 0,
228                signer_sponsoring_i_ds: vec![SponsorshipDescriptor(None); signers_count]
229                    .try_into()
230                    .unwrap_or_default(),
231                ext: AccountEntryExtensionV2Ext::V0,
232            };
233            fill_account_ext_v3(&mut ext);
234            account_ext_v1.ext = AccountEntryExtensionV1Ext::V2(ext);
235        }
236        AccountEntryExtensionV1Ext::V2(ext) => fill_account_ext_v3(ext),
237    }
238}
239
240fn fill_account_ext_v3(account_ext_v2: &mut AccountEntryExtensionV2) {
241    match account_ext_v2.ext {
242        AccountEntryExtensionV2Ext::V0 => {
243            account_ext_v2.ext = AccountEntryExtensionV2Ext::V3(AccountEntryExtensionV3 {
244                ext: ExtensionPoint::V0,
245                seq_ledger: 0,
246                seq_time: TimePoint(0),
247            });
248        }
249        AccountEntryExtensionV2Ext::V3(_) => (),
250    }
251}