Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

soroban-rpc: refactor libpreflight #875

Merged
merged 17 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cmd/soroban-rpc/lib/preflight/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ publish = false
crate-type = ["staticlib"]

[dependencies]
# TODO: base64 and thiserror are also used by the CLI,
# should we make them workspace dependencies?
anyhow = "1.0.75"
base64 = { workspace = true }
thiserror = { workspace = true }
libc = "0.2.147"
sha2 = { workspace = true }
soroban-env-host = { workspace = true }

150 changes: 83 additions & 67 deletions cmd/soroban-rpc/lib/preflight/src/fees.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::{bail, ensure, Context, Error, Result};
use ledger_storage;
use soroban_env_host::budget::Budget;
use soroban_env_host::e2e_invoke::{extract_rent_changes, get_ledger_changes, LedgerEntryChange};
Expand All @@ -19,7 +20,6 @@ use soroban_env_host::xdr::{
use state_expiration::{get_restored_ledger_sequence, ExpirableLedgerEntry};
use std::cmp::max;
use std::convert::{TryFrom, TryInto};
use std::error;

pub(crate) fn compute_host_function_transaction_data_and_min_fee(
op: &InvokeHostFunctionOp,
Expand All @@ -29,14 +29,15 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee(
events: &Vec<DiagnosticEvent>,
bucket_list_size: u64,
current_ledger_seq: u32,
) -> Result<(SorobanTransactionData, i64), Box<dyn error::Error>> {
) -> Result<(SorobanTransactionData, i64)> {
let ledger_changes = get_ledger_changes(budget, post_storage, pre_storage)?;
let soroban_resources = calculate_host_function_soroban_resources(
&ledger_changes,
&post_storage.footprint,
budget,
events,
)?;
)
.context("cannot compute host function resources")?;

let read_write_entries = u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?;

Expand All @@ -51,7 +52,8 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee(
transaction_size_bytes: estimate_max_transaction_size_for_operation(
&OperationBody::InvokeHostFunction(op.clone()),
&soroban_resources.footprint,
)?,
)
.context("cannot estimate maximum transaction size")?,
contract_events_size_bytes: soroban_resources.contract_events_size_bytes,
};
let rent_changes = extract_rent_changes(&ledger_changes);
Expand All @@ -69,7 +71,7 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee(
fn estimate_max_transaction_size_for_operation(
op: &OperationBody,
fp: &LedgerFootprint,
) -> Result<u32, Box<dyn error::Error>> {
) -> Result<u32> {
let source = MuxedAccount::MuxedEd25519(MuxedAccountMed25519 {
id: 0,
ed25519: Uint256([0; 32]),
Expand Down Expand Up @@ -124,8 +126,9 @@ fn calculate_host_function_soroban_resources(
footprint: &Footprint,
budget: &Budget,
events: &Vec<DiagnosticEvent>,
) -> Result<SorobanResources, Box<dyn error::Error>> {
let ledger_footprint = storage_footprint_to_ledger_footprint(footprint)?;
) -> Result<SorobanResources> {
let ledger_footprint = storage_footprint_to_ledger_footprint(footprint)
.context("cannot convert storage footprint to ledger footprint")?;
let read_bytes: u32 = ledger_changes
.iter()
.map(|c| c.encoded_key.len() as u32 + c.old_entry_size_bytes)
Expand All @@ -138,13 +141,14 @@ fn calculate_host_function_soroban_resources(
})
.sum();

let contract_events_size_bytes = calculate_event_size_bytes(events)?;
let contract_events_size_bytes =
calculate_event_size_bytes(events).context("cannot calculate events size")?;

// Add a 15% leeway with a minimum of 50k instructions
let instructions = max(
budget.get_cpu_insns_consumed()? + 50000,
budget.get_cpu_insns_consumed()? * 115 / 100,
);
let budget_instructions = budget
.get_cpu_insns_consumed()
.context("cannot get instructions consumed")?;
2opremio marked this conversation as resolved.
Show resolved Hide resolved
let instructions = max(budget_instructions + 50000, budget_instructions * 115 / 100);
Ok(SorobanResources {
footprint: ledger_footprint,
instructions: u32::try_from(instructions)?,
Expand All @@ -157,54 +161,43 @@ fn calculate_host_function_soroban_resources(
fn get_fee_configurations(
ledger_storage: &ledger_storage::LedgerStorage,
bucket_list_size: u64,
) -> Result<(FeeConfiguration, RentFeeConfiguration), Box<dyn error::Error>> {
) -> Result<(FeeConfiguration, RentFeeConfiguration)> {
let ConfigSettingEntry::ContractComputeV0(compute) =
ledger_storage.get_configuration_setting(ConfigSettingId::ContractComputeV0)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for ComputeV0 key".into(),
);
bail!("unexpected config setting entry for ComputeV0 key");

};

let ConfigSettingEntry::ContractLedgerCostV0(ledger_cost) =
ledger_storage.get_configuration_setting(ConfigSettingId::ContractLedgerCostV0)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for LedgerCostV0 key".into(),
);
bail!(
"unexpected config setting entry for LedgerCostV0 key");
2opremio marked this conversation as resolved.
Show resolved Hide resolved
};

let ConfigSettingEntry::ContractHistoricalDataV0(historical_data) =
ledger_storage.get_configuration_setting(ConfigSettingId::ContractHistoricalDataV0)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for HistoricalDataV0 key"
.into(),
);
bail!("unexpected config setting entry for HistoricalDataV0 key");
};

let ConfigSettingEntry::ContractEventsV0(events) =
ledger_storage.get_configuration_setting(ConfigSettingId::ContractEventsV0)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for ContractEventsV0 key".into(),
);
bail!("unexpected config setting entry for EventsV0 key");
};

let ConfigSettingEntry::ContractBandwidthV0(bandwidth) =
ledger_storage.get_configuration_setting(ConfigSettingId::ContractBandwidthV0)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for BandwidthV0 key".into(),
);
bail!("unexpected config setting entry for BandwidthV0 key");
};

let ConfigSettingEntry::StateExpiration(state_expiration) =
ledger_storage.get_configuration_setting(ConfigSettingId::StateExpiration)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for StateExpiration key".into(),
);
bail!("unexpected config setting entry for StateExpiration key");
};

let write_fee_configuration = WriteFeeConfiguration {
Expand Down Expand Up @@ -239,20 +232,28 @@ fn calculate_unmodified_ledger_entry_bytes(
ledger_entries: &Vec<LedgerKey>,
pre_storage: &ledger_storage::LedgerStorage,
include_expired: bool,
) -> Result<u32, Box<dyn error::Error>> {
) -> Result<u32> {
let mut res: usize = 0;
for lk in ledger_entries {
let key_size = lk.to_xdr()?.len();
let entry_size = pre_storage.get_xdr(lk, include_expired)?.len();
let key_xdr = lk
.to_xdr()
.with_context(|| format!("cannot marshall ledger key {lk:?}"))?;
let key_size = key_xdr.len();
let entry_xdr = pre_storage
.get_xdr(lk, include_expired)
.with_context(|| format!("cannot get xdr of ledger entry with key {lk:?}"))?;
let entry_size = entry_xdr.len();
res += key_size + entry_size;
}
Ok(res as u32)
}

fn calculate_event_size_bytes(events: &Vec<DiagnosticEvent>) -> Result<u32, Box<dyn error::Error>> {
fn calculate_event_size_bytes(events: &Vec<DiagnosticEvent>) -> Result<u32> {
let mut res: u32 = 0;
for e in events {
let event_xdr = e.to_xdr()?;
let event_xdr = e
.to_xdr()
.with_context(|| format!("cannot marshall event {e:?}"))?;
2opremio marked this conversation as resolved.
Show resolved Hide resolved
res += u32::try_from(event_xdr.len())?;
}
Ok(res)
Expand Down Expand Up @@ -280,9 +281,10 @@ fn finalize_transaction_data_and_min_fee(
rent_changes: &Vec<LedgerEntryRentChange>,
current_ledger_seq: u32,
bucket_list_size: u64,
) -> Result<(SorobanTransactionData, i64), Box<dyn error::Error>> {
) -> Result<(SorobanTransactionData, i64)> {
let (fee_configuration, rent_fee_configuration) =
get_fee_configurations(pre_storage, bucket_list_size)?;
get_fee_configurations(pre_storage, bucket_list_size)
.context("failed to obtain configuration settings from the network")?;
let (non_refundable_fee, refundable_fee) =
compute_transaction_resource_fee(transaction_resources, &fee_configuration);
let rent_fee = compute_rent_fee(&rent_changes, &rent_fee_configuration, current_ledger_seq);
Expand All @@ -304,12 +306,17 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee(
ledger_storage: &ledger_storage::LedgerStorage,
bucket_list_size: u64,
current_ledger_seq: u32,
) -> Result<(SorobanTransactionData, i64), Box<dyn error::Error>> {
) -> Result<(SorobanTransactionData, i64)> {
let mut rent_changes: Vec<LedgerEntryRentChange> = Vec::new();
for key in (&footprint).read_only.as_vec() {
let unmodified_entry = ledger_storage.get(key, false)?;
let unmodified_entry = ledger_storage
.get(key, false)
.with_context(|| format!("cannot get ledger entry with key {key:?}"))?;
let size = (key.to_xdr()?.len() + unmodified_entry.to_xdr()?.len()) as u32;
let expirable_entry: Box<dyn ExpirableLedgerEntry> = (&unmodified_entry).try_into()?;
let expirable_entry: Box<dyn ExpirableLedgerEntry> =
(&unmodified_entry).try_into().map_err(|e: String| {
Error::msg(e.clone()).context("incorrect ledger entry type in footprint")
})?;
let new_expiration_ledger = current_ledger_seq + ledgers_to_expire;
if new_expiration_ledger <= expirable_entry.expiration_ledger_seq() {
// The bump would be ineffective
Expand All @@ -328,27 +335,30 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee(
footprint.read_only.as_vec(),
ledger_storage,
false,
)?;
)
.context("cannot calculate read_bytes resource")?;
let soroban_resources = SorobanResources {
footprint,
instructions: 0,
read_bytes,
write_bytes: 0,
contract_events_size_bytes: 0,
};
let transaction_size_bytes = estimate_max_transaction_size_for_operation(
&OperationBody::BumpFootprintExpiration(BumpFootprintExpirationOp {
ext: ExtensionPoint::V0,
ledgers_to_expire: ledgers_to_expire,
}),
&soroban_resources.footprint,
)
.context("cannot estimate maximum transaction size")?;
let transaction_resources = TransactionResources {
instructions: 0,
read_entries: u32::try_from(soroban_resources.footprint.read_only.as_vec().len())?,
write_entries: 0,
read_bytes: soroban_resources.read_bytes,
write_bytes: 0,
transaction_size_bytes: estimate_max_transaction_size_for_operation(
&OperationBody::BumpFootprintExpiration(BumpFootprintExpirationOp {
ext: ExtensionPoint::V0,
ledgers_to_expire: ledgers_to_expire,
}),
&soroban_resources.footprint,
)?,
transaction_size_bytes,
contract_events_size_bytes: 0,
};
finalize_transaction_data_and_min_fee(
Expand All @@ -366,23 +376,26 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee(
ledger_storage: &ledger_storage::LedgerStorage,
bucket_list_size: u64,
current_ledger_seq: u32,
) -> Result<(SorobanTransactionData, i64), Box<dyn error::Error>> {
) -> Result<(SorobanTransactionData, i64)> {
let ConfigSettingEntry::StateExpiration(state_expiration) =
ledger_storage.get_configuration_setting(ConfigSettingId::StateExpiration)?
else {
return Err(
"get_fee_configuration(): unexpected config setting entry for StateExpiration key".into(),
);
bail!("get_fee_configuration(): unexpected config setting entry for StateExpiration key");
};
let mut rent_changes: Vec<LedgerEntryRentChange> = Vec::new();
for key in footprint.read_write.as_vec() {
let unmodified_entry = ledger_storage.get(key, true)?;
let unmodified_entry = ledger_storage
.get(key, true)
.with_context(|| format!("cannot get ledger entry with key {key:?}"))?;
2opremio marked this conversation as resolved.
Show resolved Hide resolved
let size = (key.to_xdr()?.len() + unmodified_entry.to_xdr()?.len()) as u32;
let expirable_entry: Box<dyn ExpirableLedgerEntry> = (&unmodified_entry).try_into()?;
if expirable_entry.durability() != Persistent {
let err = format!("Non-persistent key ({:?}) in footprint", key).into();
return Err(err);
}
let expirable_entry: Box<dyn ExpirableLedgerEntry> =
(&unmodified_entry).try_into().map_err(|e: String| {
Error::msg(e.clone()).context("incorrect ledger entry type in footprint")
})?;
ensure!(
expirable_entry.durability() == Persistent,
"non-persistent entry in footprint: key = {key:?}"
);
if !expirable_entry.has_expired(current_ledger_seq) {
// noop (the entry hadn't expired)
continue;
Expand All @@ -404,7 +417,8 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee(
footprint.read_write.as_vec(),
ledger_storage,
true,
)?;
)
.context("cannot calculate write_bytes resource")?;
let soroban_resources = SorobanResources {
footprint,
instructions: 0,
Expand All @@ -413,18 +427,20 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee(
contract_events_size_bytes: 0,
};
let entry_count = u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?;
let transaction_size_bytes = estimate_max_transaction_size_for_operation(
&OperationBody::RestoreFootprint(RestoreFootprintOp {
ext: ExtensionPoint::V0,
}),
&soroban_resources.footprint,
)
.context("cannot estimate maximum transaction size")?;
let transaction_resources = TransactionResources {
instructions: 0,
read_entries: entry_count,
write_entries: entry_count,
read_bytes: soroban_resources.read_bytes,
write_bytes: soroban_resources.write_bytes,
transaction_size_bytes: estimate_max_transaction_size_for_operation(
&OperationBody::RestoreFootprint(RestoreFootprintOp {
ext: ExtensionPoint::V0,
}),
&soroban_resources.footprint,
)?,
transaction_size_bytes,
contract_events_size_bytes: 0,
};
finalize_transaction_data_and_min_fee(
Expand Down
11 changes: 5 additions & 6 deletions cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ pub(crate) enum Error {
UnexpectedConfigLedgerEntry { setting_id: String },
}

impl Error {
fn to_host_error(&self) -> HostError {
match self {
impl From<Error> for HostError {
fn from(value: Error) -> Self {
match value {
Error::NotFound => ScError::Storage(ScErrorCode::MissingValue).into(),
Error::Xdr(_) => ScError::Value(ScErrorCode::InvalidInput).into(),
_ => ScError::Context(ScErrorCode::InternalError).into(),
Expand Down Expand Up @@ -193,8 +193,7 @@ impl LedgerStorage {

impl SnapshotSource for LedgerStorage {
fn get(&self, key: &Rc<LedgerKey>) -> Result<Rc<LedgerEntry>, HostError> {
let mut entry = <LedgerStorage>::get(self, key, self.restore_tracker.is_some())
.map_err(|e| Error::to_host_error(&e))?;
let mut entry = <LedgerStorage>::get(self, key, self.restore_tracker.is_some())?;
if let Some(ref tracker) = self.restore_tracker {
// If the entry expired, we modify it to make it seem like it was restored
tracker.track_and_restore(key, &mut entry);
Expand All @@ -206,7 +205,7 @@ impl SnapshotSource for LedgerStorage {
let entry = match <LedgerStorage>::get(self, key, self.restore_tracker.is_some()) {
Err(e) => match e {
Error::NotFound => return Ok(false),
_ => return Err(Error::to_host_error(&e)),
_ => return Err(HostError::from(e)),
},
Ok(le) => le,
};
Expand Down
Loading