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
68impl NetworkConfig {
69    /// Loads configuration from the ledger snapshot.
70    ///
71    /// This may only fail in case when provided snapshot doesn't contain
72    /// all the necessary entries or when these entries are mis-configured.
73    pub fn load_from_snapshot(
74        snapshot: &impl SnapshotSource,
75        bucket_list_size: u64,
76    ) -> Result<Self> {
77        let compute = load_setting!(snapshot, ContractComputeV0);
78        let ledger_cost = load_setting!(snapshot, ContractLedgerCostV0);
79        let ledger_cost_ext = load_setting!(snapshot, ContractLedgerCostExtV0);
80        let historical_data = load_setting!(snapshot, ContractHistoricalDataV0);
81        let events = load_setting!(snapshot, ContractEventsV0);
82        let bandwidth = load_setting!(snapshot, ContractBandwidthV0);
83        let state_archival = load_setting!(snapshot, StateArchival);
84        let cpu_cost_params = load_setting!(snapshot, ContractCostParamsCpuInstructions);
85        let memory_cost_params = load_setting!(snapshot, ContractCostParamsMemoryBytes);
86
87        let write_fee_configuration = RentWriteFeeConfiguration {
88            state_target_size_bytes: ledger_cost.soroban_state_target_size_bytes,
89            rent_fee_1kb_state_size_low: ledger_cost.rent_fee1_kb_soroban_state_size_low,
90            rent_fee_1kb_state_size_high: ledger_cost.rent_fee1_kb_soroban_state_size_high,
91            state_size_rent_fee_growth_factor: ledger_cost.soroban_state_rent_fee_growth_factor,
92        };
93        let bucket_list_size: i64 = bucket_list_size
94            .try_into()
95            .context("bucket list size exceeds i64::MAX")?;
96        let rent_fee_per_1kb =
97            compute_rent_write_fee_per_1kb(bucket_list_size, &write_fee_configuration);
98
99        let fee_configuration = FeeConfiguration {
100            fee_per_instruction_increment: compute.fee_rate_per_instructions_increment,
101            fee_per_disk_read_entry: ledger_cost.fee_disk_read_ledger_entry,
102            fee_per_write_entry: ledger_cost.fee_write_ledger_entry,
103            fee_per_disk_read_1kb: ledger_cost.fee_disk_read1_kb,
104            fee_per_write_1kb: ledger_cost_ext.fee_write1_kb,
105            fee_per_historical_1kb: historical_data.fee_historical1_kb,
106            fee_per_contract_event_1kb: events.fee_contract_events1_kb,
107            fee_per_transaction_size_1kb: bandwidth.fee_tx_size1_kb,
108        };
109        let rent_fee_configuration = RentFeeConfiguration {
110            fee_per_rent_1kb: rent_fee_per_1kb,
111            fee_per_write_1kb: ledger_cost_ext.fee_write1_kb,
112            fee_per_write_entry: ledger_cost.fee_write_ledger_entry,
113            persistent_rent_rate_denominator: state_archival.persistent_rent_rate_denominator,
114            temporary_rent_rate_denominator: state_archival.temp_rent_rate_denominator,
115        };
116
117        Ok(Self {
118            fee_configuration,
119            rent_fee_configuration,
120            cpu_cost_params,
121            memory_cost_params,
122            min_temp_entry_ttl: state_archival.min_temporary_ttl,
123            min_persistent_entry_ttl: state_archival.min_persistent_ttl,
124            tx_max_instructions: compute.tx_max_instructions,
125            tx_memory_limit: compute.tx_memory_limit,
126            max_entry_ttl: state_archival.max_entry_ttl,
127        })
128    }
129
130    /// Fills the `ledger_info` fields that are loaded from the config.
131    ///
132    /// This should normally be used to populate TTL-related fields in
133    /// `LedgerInfo`, so that config loading logic can be encapsulated
134    /// just in `NetworkConfig`.
135    pub fn fill_config_fields_in_ledger_info(&self, ledger_info: &mut LedgerInfo) {
136        ledger_info.min_persistent_entry_ttl = self.min_persistent_entry_ttl;
137        ledger_info.min_temp_entry_ttl = self.min_temp_entry_ttl;
138        ledger_info.max_entry_ttl = self.max_entry_ttl;
139    }
140
141    pub(crate) fn create_budget(&self) -> Result<Budget> {
142        let cpu_shadow_limit =
143            (self.tx_max_instructions as u64).saturating_mul(CPU_SHADOW_LIMIT_FACTOR);
144        let mem_shadow_limit =
145            (self.tx_memory_limit as u64).saturating_mul(MEMORY_SHADOW_LIMIT_FACTOR);
146        Budget::try_from_configs_with_shadow_limits(
147            self.tx_max_instructions as u64,
148            self.tx_memory_limit as u64,
149            cpu_shadow_limit,
150            mem_shadow_limit,
151            self.cpu_cost_params.clone(),
152            self.memory_cost_params.clone(),
153        )
154        .context("cannot create budget from network configuration")
155    }
156}