Skip to main content

soroban_simulation/
network_config.rs

1use anyhow::{anyhow, bail, Context, Result};
2use soroban_env_host::budget::Budget;
3use soroban_env_host::fees::{
4    compute_rent_write_fee_per_1kb, FeeConfiguration, RentFeeConfiguration,
5    RentWriteFeeConfiguration,
6};
7use soroban_env_host::storage::SnapshotSource;
8use soroban_env_host::xdr::{
9    ConfigSettingEntry, ConfigSettingId, ContractCostParams, LedgerEntry, LedgerEntryData,
10    LedgerKey, LedgerKeyConfigSetting,
11};
12use soroban_env_host::LedgerInfo;
13use std::rc::Rc;
14
15const CPU_SHADOW_LIMIT_FACTOR: u64 = 10;
16const MEMORY_SHADOW_LIMIT_FACTOR: u64 = 2;
17
18/// Network configuration necessary for Soroban operation simulations.
19///
20/// This should normally be loaded from the ledger.
21#[derive(Debug, Default, PartialEq, Eq)]
22pub struct NetworkConfig {
23    pub fee_configuration: FeeConfiguration,
24    pub rent_fee_configuration: RentFeeConfiguration,
25    pub tx_max_instructions: i64,
26    pub tx_memory_limit: u32,
27    pub cpu_cost_params: ContractCostParams,
28    pub memory_cost_params: ContractCostParams,
29    // Configuration to use in `LedgerInfo`.
30    pub min_temp_entry_ttl: u32,
31    pub min_persistent_entry_ttl: u32,
32    pub max_entry_ttl: u32,
33}
34
35fn load_configuration_setting(
36    snapshot: &impl SnapshotSource,
37    setting_id: ConfigSettingId,
38) -> Result<ConfigSettingEntry> {
39    let key = Rc::new(LedgerKey::ConfigSetting(LedgerKeyConfigSetting {
40        config_setting_id: setting_id,
41    }));
42    let (entry, _) = snapshot
43        .get(&key)?
44        .ok_or_else(|| anyhow!("setting {setting_id:?} is not present in the snapshot"))?;
45    if let LedgerEntry {
46        data: LedgerEntryData::ConfigSetting(cs),
47        ..
48    } = &*entry
49    {
50        Ok(cs.clone())
51    } else {
52        bail!("encountered unexpected config setting ledger entry {entry:#?}");
53    }
54}
55
56macro_rules! load_setting {
57    ($snapshot:ident, $enum_variant:ident) => {
58        match load_configuration_setting($snapshot, ConfigSettingId::$enum_variant)? {
59            ConfigSettingEntry::$enum_variant(setting) => setting,
60            _ => bail!(
61                "loaded unexpected config setting entry for {:?} key",
62                stringify!($enum_variant)
63            ),
64        }
65    };
66}
67
68fn load_bucket_list_size_from_snapshot(snapshot: &impl SnapshotSource) -> Result<u64> {
69    let window = load_setting!(snapshot, LiveSorobanStateSizeWindow);
70    let mut size_sum = 0_u64;
71    for size in window.iter() {
72        // This is just a sanity check, as both the number of the snapshots
73        // and the value of every snapshotted size are rather small.
74        size_sum = size_sum.saturating_add(*size);
75    }
76    if window.is_empty() {
77        bail!("live Soroban state size window is empty");
78    }
79    Ok(size_sum / window.len() as u64)
80}
81
82impl NetworkConfig {
83    /// Loads configuration from the ledger snapshot.
84    ///
85    /// This may only fail in case when provided snapshot doesn't contain
86    /// all the necessary entries or when these entries are mis-configured.
87    pub fn load_from_snapshot(
88        snapshot: &impl SnapshotSource,
89        #[cfg(not(feature = "unstable-next-api"))] _bucket_list_size: u64,
90    ) -> Result<Self> {
91        let compute = load_setting!(snapshot, ContractComputeV0);
92        let ledger_cost = load_setting!(snapshot, ContractLedgerCostV0);
93        let ledger_cost_ext = load_setting!(snapshot, ContractLedgerCostExtV0);
94        let historical_data = load_setting!(snapshot, ContractHistoricalDataV0);
95        let events = load_setting!(snapshot, ContractEventsV0);
96        let bandwidth = load_setting!(snapshot, ContractBandwidthV0);
97        let state_archival = load_setting!(snapshot, StateArchival);
98        let cpu_cost_params = load_setting!(snapshot, ContractCostParamsCpuInstructions);
99        let memory_cost_params = load_setting!(snapshot, ContractCostParamsMemoryBytes);
100
101        let write_fee_configuration = RentWriteFeeConfiguration {
102            state_target_size_bytes: ledger_cost.soroban_state_target_size_bytes,
103            rent_fee_1kb_state_size_low: ledger_cost.rent_fee1_kb_soroban_state_size_low,
104            rent_fee_1kb_state_size_high: ledger_cost.rent_fee1_kb_soroban_state_size_high,
105            state_size_rent_fee_growth_factor: ledger_cost.soroban_state_rent_fee_growth_factor,
106        };
107        let bucket_list_size: i64 = load_bucket_list_size_from_snapshot(snapshot)?
108            .try_into()
109            .context("bucket list size exceeds i64::MAX")?;
110        let rent_fee_per_1kb =
111            compute_rent_write_fee_per_1kb(bucket_list_size, &write_fee_configuration);
112
113        let fee_configuration = FeeConfiguration {
114            fee_per_instruction_increment: compute.fee_rate_per_instructions_increment,
115            fee_per_disk_read_entry: ledger_cost.fee_disk_read_ledger_entry,
116            fee_per_write_entry: ledger_cost.fee_write_ledger_entry,
117            fee_per_disk_read_1kb: ledger_cost.fee_disk_read1_kb,
118            fee_per_write_1kb: ledger_cost_ext.fee_write1_kb,
119            fee_per_historical_1kb: historical_data.fee_historical1_kb,
120            fee_per_contract_event_1kb: events.fee_contract_events1_kb,
121            fee_per_transaction_size_1kb: bandwidth.fee_tx_size1_kb,
122        };
123        let rent_fee_configuration = RentFeeConfiguration {
124            fee_per_rent_1kb: rent_fee_per_1kb,
125            fee_per_write_1kb: ledger_cost_ext.fee_write1_kb,
126            fee_per_write_entry: ledger_cost.fee_write_ledger_entry,
127            persistent_rent_rate_denominator: state_archival.persistent_rent_rate_denominator,
128            temporary_rent_rate_denominator: state_archival.temp_rent_rate_denominator,
129        };
130
131        Ok(Self {
132            fee_configuration,
133            rent_fee_configuration,
134            cpu_cost_params,
135            memory_cost_params,
136            min_temp_entry_ttl: state_archival.min_temporary_ttl,
137            min_persistent_entry_ttl: state_archival.min_persistent_ttl,
138            tx_max_instructions: compute.tx_max_instructions,
139            tx_memory_limit: compute.tx_memory_limit,
140            max_entry_ttl: state_archival.max_entry_ttl,
141        })
142    }
143
144    /// Fills the `ledger_info` fields that are loaded from the config.
145    ///
146    /// This should normally be used to populate TTL-related fields in
147    /// `LedgerInfo`, so that config loading logic can be encapsulated
148    /// just in `NetworkConfig`.
149    pub fn fill_config_fields_in_ledger_info(&self, ledger_info: &mut LedgerInfo) {
150        ledger_info.min_persistent_entry_ttl = self.min_persistent_entry_ttl;
151        ledger_info.min_temp_entry_ttl = self.min_temp_entry_ttl;
152        ledger_info.max_entry_ttl = self.max_entry_ttl;
153    }
154
155    pub(crate) fn create_budget(&self) -> Result<Budget> {
156        let cpu_shadow_limit =
157            (self.tx_max_instructions as u64).saturating_mul(CPU_SHADOW_LIMIT_FACTOR);
158        let mem_shadow_limit =
159            (self.tx_memory_limit as u64).saturating_mul(MEMORY_SHADOW_LIMIT_FACTOR);
160        Budget::try_from_configs_with_shadow_limits(
161            self.tx_max_instructions as u64,
162            self.tx_memory_limit as u64,
163            cpu_shadow_limit,
164            mem_shadow_limit,
165            self.cpu_cost_params.clone(),
166            self.memory_cost_params.clone(),
167        )
168        .context("cannot create budget from network configuration")
169    }
170}