diff --git a/cmd/soroban-rpc/internal/methods/simulate_transaction.go b/cmd/soroban-rpc/internal/methods/simulate_transaction.go index 49d95eecb..47badadfd 100644 --- a/cmd/soroban-rpc/internal/methods/simulate_transaction.go +++ b/cmd/soroban-rpc/internal/methods/simulate_transaction.go @@ -15,7 +15,8 @@ import ( ) type SimulateTransactionRequest struct { - Transaction string `json:"transaction"` + Transaction string `json:"transaction"` + ResourceConfig *preflight.ResourceConfig `json:"resourceConfig,omitempty"` } type SimulateTransactionCost struct { @@ -46,7 +47,7 @@ type SimulateTransactionResponse struct { } type PreflightGetter interface { - GetPreflight(ctx context.Context, readTx db.LedgerEntryReadTx, bucketListSize uint64, sourceAccount xdr.AccountId, opBody xdr.OperationBody, footprint xdr.LedgerFootprint) (preflight.Preflight, error) + GetPreflight(ctx context.Context, readTx db.LedgerEntryReadTx, bucketListSize uint64, sourceAccount xdr.AccountId, opBody xdr.OperationBody, footprint xdr.LedgerFootprint, resourceConfig *preflight.ResourceConfig) (preflight.Preflight, error) } // NewSimulateTransactionHandler returns a json rpc handler to run preflight simulations @@ -113,7 +114,7 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge } } - result, err := getter.GetPreflight(ctx, readTx, bucketListSize, sourceAccount, op.Body, footprint) + result, err := getter.GetPreflight(ctx, readTx, bucketListSize, sourceAccount, op.Body, footprint, request.ResourceConfig) if err != nil { return SimulateTransactionResponse{ Error: err.Error(), diff --git a/cmd/soroban-rpc/internal/preflight/pool.go b/cmd/soroban-rpc/internal/preflight/pool.go index da391b576..6a49df3e7 100644 --- a/cmd/soroban-rpc/internal/preflight/pool.go +++ b/cmd/soroban-rpc/internal/preflight/pool.go @@ -139,7 +139,7 @@ func (m *metricsLedgerEntryWrapper) GetLedgerEntries(keys ...xdr.LedgerKey) ([]d return entries, err } -func (pwp *PreflightWorkerPool) GetPreflight(ctx context.Context, readTx db.LedgerEntryReadTx, bucketListSize uint64, sourceAccount xdr.AccountId, opBody xdr.OperationBody, footprint xdr.LedgerFootprint) (Preflight, error) { +func (pwp *PreflightWorkerPool) GetPreflight(ctx context.Context, readTx db.LedgerEntryReadTx, bucketListSize uint64, sourceAccount xdr.AccountId, opBody xdr.OperationBody, footprint xdr.LedgerFootprint, resourceConfig *ResourceConfig) (Preflight, error) { if pwp.isClosed.Load() { return Preflight{}, errors.New("preflight worker pool is closed") } @@ -154,6 +154,7 @@ func (pwp *PreflightWorkerPool) GetPreflight(ctx context.Context, readTx db.Ledg LedgerEntryReadTx: &wrappedTx, BucketListSize: bucketListSize, Footprint: footprint, + ResourceConfig: resourceConfig, EnableDebug: pwp.enableDebug, } resultC := make(chan workerResult) diff --git a/cmd/soroban-rpc/internal/preflight/preflight.go b/cmd/soroban-rpc/internal/preflight/preflight.go index 20ddcb4bc..99c23ec43 100644 --- a/cmd/soroban-rpc/internal/preflight/preflight.go +++ b/cmd/soroban-rpc/internal/preflight/preflight.go @@ -35,6 +35,10 @@ type snapshotSourceHandle struct { logger *log.Entry } +const ( + defaultInstructionLeeway uint64 = 1000000 +) + // SnapshotSourceGet takes a LedgerKey XDR in base64 string and returns its matching LedgerEntry XDR in base64 string // It's used by the Rust preflight code to obtain ledger entries. // @@ -71,6 +75,10 @@ func FreeGoXDR(xdr C.xdr_t) { C.free(unsafe.Pointer(xdr.xdr)) } +type ResourceConfig struct { + InstructionLeeway uint64 `json:"instructionLeeway"` +} + type PreflightParameters struct { Logger *log.Entry SourceAccount xdr.AccountId @@ -79,6 +87,7 @@ type PreflightParameters struct { NetworkPassphrase string LedgerEntryReadTx db.LedgerEntryReadTx BucketListSize uint64 + ResourceConfig *ResourceConfig EnableDebug bool } @@ -216,12 +225,20 @@ func getInvokeHostFunctionPreflight(params PreflightParameters) (Preflight, erro handle := cgo.NewHandle(snapshotSourceHandle{params.LedgerEntryReadTx, params.Logger}) defer handle.Delete() + instructionLeeway := defaultInstructionLeeway + if params.ResourceConfig != nil { + instructionLeeway = params.ResourceConfig.InstructionLeeway + } + resourceConfig := C.resource_config_t{ + instruction_leeway: C.uint64_t(instructionLeeway), + } res := C.preflight_invoke_hf_op( C.uintptr_t(handle), C.uint64_t(params.BucketListSize), invokeHostFunctionCXDR, sourceAccountCXDR, li, + resourceConfig, C.bool(params.EnableDebug), ) FreeGoXDR(invokeHostFunctionCXDR) diff --git a/cmd/soroban-rpc/lib/preflight.h b/cmd/soroban-rpc/lib/preflight.h index fce089c95..81db0c54f 100644 --- a/cmd/soroban-rpc/lib/preflight.h +++ b/cmd/soroban-rpc/lib/preflight.h @@ -25,6 +25,10 @@ typedef struct xdr_vector_t { size_t len; } xdr_vector_t; +typedef struct resource_config_t { + uint64_t instruction_leeway; // Allow this many extra instructions when budgeting +} resource_config_t; + typedef struct preflight_result_t { char *error; // Error string in case of error, otherwise null xdr_vector_t auth; // array of SorobanAuthorizationEntries @@ -43,6 +47,7 @@ preflight_result_t *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to for const xdr_t invoke_hf_op, // InvokeHostFunctionOp XDR const xdr_t source_account, // AccountId XDR const ledger_info_t ledger_info, + const resource_config_t resource_config, bool enable_debug); preflight_result_t *preflight_footprint_ttl_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet diff --git a/cmd/soroban-rpc/lib/preflight/src/fees.rs b/cmd/soroban-rpc/lib/preflight/src/fees.rs index 1cfc16b16..7e28f9e4f 100644 --- a/cmd/soroban-rpc/lib/preflight/src/fees.rs +++ b/cmd/soroban-rpc/lib/preflight/src/fees.rs @@ -23,12 +23,15 @@ use state_ttl::{get_restored_ledger_sequence, TTLLedgerEntry}; use std::cmp::max; use std::convert::{TryFrom, TryInto}; +use crate::CResourceConfig; + #[allow(clippy::too_many_arguments)] pub(crate) fn compute_host_function_transaction_data_and_min_fee( op: &InvokeHostFunctionOp, pre_storage: &LedgerStorage, post_storage: &Storage, budget: &Budget, + resource_config: CResourceConfig, events: &[DiagnosticEvent], invocation_result: &ScVal, bucket_list_size: u64, @@ -36,7 +39,7 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee( ) -> Result<(SorobanTransactionData, i64)> { let ledger_changes = get_ledger_changes(budget, post_storage, pre_storage, TtlEntryMap::new())?; let soroban_resources = - calculate_host_function_soroban_resources(&ledger_changes, &post_storage.footprint, budget) + calculate_host_function_soroban_resources(&ledger_changes, &post_storage.footprint, budget, resource_config) .context("cannot compute host function resources")?; let contract_events_size = @@ -128,6 +131,7 @@ fn calculate_host_function_soroban_resources( ledger_changes: &[LedgerEntryChange], footprint: &Footprint, budget: &Budget, + resource_config: CResourceConfig ) -> Result { let ledger_footprint = storage_footprint_to_ledger_footprint(footprint) .context("cannot convert storage footprint to ledger footprint")?; @@ -143,7 +147,7 @@ fn calculate_host_function_soroban_resources( .get_cpu_insns_consumed() .context("cannot get instructions consumed")?; let instructions = max( - budget_instructions + 1000000, + budget_instructions + resource_config.instruction_leeway, budget_instructions * 120 / 100, ); Ok(SorobanResources { diff --git a/cmd/soroban-rpc/lib/preflight/src/lib.rs b/cmd/soroban-rpc/lib/preflight/src/lib.rs index f49a131e8..40f23b55c 100644 --- a/cmd/soroban-rpc/lib/preflight/src/lib.rs +++ b/cmd/soroban-rpc/lib/preflight/src/lib.rs @@ -81,6 +81,12 @@ fn get_default_c_xdr_vector() -> CXDRVector { } } +#[repr(C)] +#[derive(Copy, Clone)] +pub struct CResourceConfig { + pub instruction_leeway: u64 +} + #[repr(C)] #[derive(Copy, Clone)] pub struct CPreflightResult { @@ -133,6 +139,7 @@ pub extern "C" fn preflight_invoke_hf_op( invoke_hf_op: CXDR, // InvokeHostFunctionOp XDR in base64 source_account: CXDR, // AccountId XDR in base64 ledger_info: CLedgerInfo, + resource_config: CResourceConfig, enable_debug: bool, ) -> *mut CPreflightResult { catch_preflight_panic(Box::new(move || { @@ -142,6 +149,7 @@ pub extern "C" fn preflight_invoke_hf_op( invoke_hf_op, source_account, ledger_info, + resource_config, enable_debug, ) })) @@ -153,6 +161,7 @@ fn preflight_invoke_hf_op_or_maybe_panic( invoke_hf_op: CXDR, // InvokeHostFunctionOp XDR in base64 source_account: CXDR, // AccountId XDR in base64 ledger_info: CLedgerInfo, + resource_config: CResourceConfig, enable_debug: bool, ) -> Result { let invoke_hf_op = @@ -166,6 +175,7 @@ fn preflight_invoke_hf_op_or_maybe_panic( invoke_hf_op, source_account, LedgerInfo::from(ledger_info), + resource_config, enable_debug, )?; Ok(result.into()) diff --git a/cmd/soroban-rpc/lib/preflight/src/preflight.rs b/cmd/soroban-rpc/lib/preflight/src/preflight.rs index a818b7dd2..cfafe14c7 100644 --- a/cmd/soroban-rpc/lib/preflight/src/preflight.rs +++ b/cmd/soroban-rpc/lib/preflight/src/preflight.rs @@ -16,6 +16,8 @@ use std::convert::{TryFrom, TryInto}; use std::iter::FromIterator; use std::rc::Rc; +use crate::CResourceConfig; + pub(crate) struct RestorePreamble { pub(crate) transaction_data: SorobanTransactionData, pub(crate) min_fee: i64, @@ -40,6 +42,7 @@ pub(crate) fn preflight_invoke_hf_op( invoke_hf_op: InvokeHostFunctionOp, source_account: AccountId, ledger_info: LedgerInfo, + resource_config: CResourceConfig, enable_debug: bool, ) -> Result { let ledger_storage_rc = Rc::new(ledger_storage); @@ -117,6 +120,7 @@ pub(crate) fn preflight_invoke_hf_op( &ledger_storage_rc, &storage, &budget, + resource_config, &diagnostic_events, &result, bucket_list_size,