diff --git a/cmd/soroban-rpc/internal/jsonrpc.go b/cmd/soroban-rpc/internal/jsonrpc.go index 174f0158e..d0491e387 100644 --- a/cmd/soroban-rpc/internal/jsonrpc.go +++ b/cmd/soroban-rpc/internal/jsonrpc.go @@ -135,7 +135,7 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler { "getLedgerEntries": methods.NewGetLedgerEntriesHandler(params.Logger, params.LedgerEntryReader), "getTransaction": methods.NewGetTransactionHandler(params.TransactionStore), "sendTransaction": methods.NewSendTransactionHandler(params.Daemon, params.Logger, params.TransactionStore, cfg.NetworkPassphrase), - "simulateTransaction": methods.NewSimulateTransactionHandler(params.Logger, params.LedgerEntryReader, params.PreflightGetter), + "simulateTransaction": methods.NewSimulateTransactionHandler(params.Logger, params.LedgerEntryReader, params.LedgerReader, params.PreflightGetter), }), &bridgeOptions) return Handler{ diff --git a/cmd/soroban-rpc/internal/methods/simulate_transaction.go b/cmd/soroban-rpc/internal/methods/simulate_transaction.go index fe437352d..e412fd25a 100644 --- a/cmd/soroban-rpc/internal/methods/simulate_transaction.go +++ b/cmd/soroban-rpc/internal/methods/simulate_transaction.go @@ -2,6 +2,7 @@ package methods import ( "context" + "fmt" "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/handler" @@ -38,11 +39,11 @@ type SimulateTransactionResponse struct { } type PreflightGetter interface { - GetPreflight(ctx context.Context, readTx db.LedgerEntryReadTx, 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) (preflight.Preflight, error) } // NewSimulateTransactionHandler returns a json rpc handler to run preflight simulations -func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader, getter PreflightGetter) jrpc2.Handler { +func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader, getter PreflightGetter) jrpc2.Handler { return handler.New(func(ctx context.Context, request SimulateTransactionRequest) SimulateTransactionResponse { var txEnvelope xdr.TransactionEnvelope @@ -98,8 +99,26 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge Error: err.Error(), } } + // obtain bucket size + closeMeta, ok, err := ledgerReader.GetLedger(ctx, latestLedger) + if err != nil { + return SimulateTransactionResponse{ + Error: err.Error(), + } + } + if !ok { + return SimulateTransactionResponse{ + Error: fmt.Sprintf("missing meta for latest ledger (%d)", latestLedger), + } + } + if closeMeta.V != 2 { + return SimulateTransactionResponse{ + Error: fmt.Sprintf("latest ledger (%d) meta has unexpected verion (%d)", latestLedger, closeMeta.V), + } + } + bucketListSize := uint64(closeMeta.V2.TotalByteSizeOfBucketList) - result, err := getter.GetPreflight(ctx, readTx, sourceAccount, op.Body, footprint) + result, err := getter.GetPreflight(ctx, readTx, bucketListSize, sourceAccount, op.Body, footprint) 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 53bb42385..b745bd29a 100644 --- a/cmd/soroban-rpc/internal/preflight/pool.go +++ b/cmd/soroban-rpc/internal/preflight/pool.go @@ -135,7 +135,7 @@ func (m *metricsLedgerEntryWrapper) GetLedgerEntry(key xdr.LedgerKey, includeExp return ok, entry, err } -func (pwp *PreflightWorkerPool) GetPreflight(ctx context.Context, readTx db.LedgerEntryReadTx, 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) (Preflight, error) { if pwp.isClosed.Load() { return Preflight{}, errors.New("preflight worker pool is closed") } @@ -148,6 +148,7 @@ func (pwp *PreflightWorkerPool) GetPreflight(ctx context.Context, readTx db.Ledg OpBody: opBody, NetworkPassphrase: pwp.networkPassphrase, LedgerEntryReadTx: &wrappedTx, + BucketListSize: bucketListSize, Footprint: footprint, } resultC := make(chan workerResult) diff --git a/cmd/soroban-rpc/internal/preflight/preflight.go b/cmd/soroban-rpc/internal/preflight/preflight.go index 1014c337e..cc419f358 100644 --- a/cmd/soroban-rpc/internal/preflight/preflight.go +++ b/cmd/soroban-rpc/internal/preflight/preflight.go @@ -95,6 +95,7 @@ type PreflightParameters struct { Footprint xdr.LedgerFootprint NetworkPassphrase string LedgerEntryReadTx db.LedgerEntryReadTx + BucketListSize uint64 } type Preflight struct { @@ -126,8 +127,6 @@ func GoNullTerminatedStringSlice(str **C.char) []string { } func GetPreflight(ctx context.Context, params PreflightParameters) (Preflight, error) { - handle := cgo.NewHandle(snapshotSourceHandle{params.LedgerEntryReadTx, params.Logger}) - defer handle.Delete() switch params.OpBody.Type { case xdr.OperationTypeInvokeHostFunction: return getInvokeHostFunctionPreflight(params) @@ -154,6 +153,7 @@ func getFootprintExpirationPreflight(params PreflightParameters) (Preflight, err res := C.preflight_footprint_expiration_op( C.uintptr_t(handle), + C.uint64_t(params.BucketListSize), opBodyCString, footprintCString, ) @@ -215,6 +215,7 @@ func getInvokeHostFunctionPreflight(params PreflightParameters) (Preflight, erro defer handle.Delete() res := C.preflight_invoke_hf_op( C.uintptr_t(handle), + C.uint64_t(params.BucketListSize), invokeHostFunctionCString, sourceAccountCString, li, diff --git a/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go b/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go index c7acac08e..70a60b742 100644 --- a/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go +++ b/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go @@ -77,11 +77,12 @@ func TestGetLedgerEntrySucceeds(t *testing.T) { kp := keypair.Root(StandaloneNetworkPassphrase) account := txnbuild.NewSimpleAccount(kp.Address(), 0) + helloWorldContract := getHelloWorldContract(t) params := preflightTransactionParams(t, client, txnbuild.TransactionParams{ SourceAccount: &account, IncrementSequenceNum: true, Operations: []txnbuild.Operation{ - createInstallContractCodeOperation(account.AccountID, testContract), + createInstallContractCodeOperation(account.AccountID, helloWorldContract), }, BaseFee: txnbuild.MinBaseFee, Preconditions: txnbuild.Preconditions{ @@ -93,7 +94,7 @@ func TestGetLedgerEntrySucceeds(t *testing.T) { sendSuccessfulTransaction(t, client, kp, tx) - contractHash := sha256.Sum256(testContract) + contractHash := sha256.Sum256(helloWorldContract) keyB64, err := xdr.MarshalBase64(xdr.LedgerKey{ Type: xdr.LedgerEntryTypeContractCode, ContractCode: &xdr.LedgerKeyContractCode{ @@ -112,5 +113,5 @@ func TestGetLedgerEntrySucceeds(t *testing.T) { assert.GreaterOrEqual(t, result.LatestLedger, result.LastModifiedLedger) var entry xdr.LedgerEntryData assert.NoError(t, xdr.SafeUnmarshalBase64(result.XDR, &entry)) - assert.Equal(t, testContract, *entry.MustContractCode().Body.Code) + assert.Equal(t, helloWorldContract, *entry.MustContractCode().Body.Code) } diff --git a/cmd/soroban-rpc/internal/test/simulate_transaction_test.go b/cmd/soroban-rpc/internal/test/simulate_transaction_test.go index b554448fc..455353d3d 100644 --- a/cmd/soroban-rpc/internal/test/simulate_transaction_test.go +++ b/cmd/soroban-rpc/internal/test/simulate_transaction_test.go @@ -179,7 +179,9 @@ func preflightTransactionParams(t *testing.T, client *jrpc2.Client, params txnbu params.Operations = []txnbuild.Operation{op} - params.BaseFee += response.MinResourceFee + // Hack until we start including rent fees + minResourceFee := response.MinResourceFee * 120 / 100 + params.BaseFee += minResourceFee return params } diff --git a/cmd/soroban-rpc/lib/preflight.h b/cmd/soroban-rpc/lib/preflight.h index caac7a0ec..e4395a384 100644 --- a/cmd/soroban-rpc/lib/preflight.h +++ b/cmd/soroban-rpc/lib/preflight.h @@ -25,12 +25,14 @@ typedef struct CPreflightResult { uint64_t memory_bytes; } CPreflightResult; -CPreflightResult *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHasconst +CPreflightResult *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas + uint64_t bucket_list_size, // Bucket list size of current ledger const char *invoke_hf_op, // InvokeHostFunctionOp XDR in base64 const char *source_account, // AccountId XDR in base64 const struct CLedgerInfo ledger_info); -CPreflightResult *preflight_footprint_expiration_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHasconst +CPreflightResult *preflight_footprint_expiration_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas + uint64_t bucket_list_size, // Bucket list size of current ledger const char *op_body, // OperationBody XDR in base64 const char *footprint); // LedgerFootprint XDR in base64 diff --git a/cmd/soroban-rpc/lib/preflight/src/fees.rs b/cmd/soroban-rpc/lib/preflight/src/fees.rs index e6823e404..ada35a133 100644 --- a/cmd/soroban-rpc/lib/preflight/src/fees.rs +++ b/cmd/soroban-rpc/lib/preflight/src/fees.rs @@ -1,7 +1,8 @@ use ledger_storage; use soroban_env_host::budget::Budget; use soroban_env_host::fees::{ - compute_transaction_resource_fee, FeeConfiguration, TransactionResources, + compute_transaction_resource_fee, compute_write_fee_per_1kb, FeeConfiguration, + TransactionResources, WriteFeeConfiguration, }; use soroban_env_host::storage::{AccessType, Footprint, Storage, StorageMap}; use soroban_env_host::xdr; @@ -23,10 +24,11 @@ pub(crate) fn compute_host_function_transaction_data_and_min_fee( storage: &Storage, budget: &Budget, events: &Vec, + bucket_list_size: u64, ) -> Result<(SorobanTransactionData, i64), Box> { let soroban_resources = calculate_host_function_soroban_resources(snapshot_source, storage, budget, events)?; - let fee_configuration = get_fee_configuration(snapshot_source)?; + let fee_configuration = get_fee_configuration(snapshot_source, bucket_list_size)?; let read_write_entries = u32::try_from(soroban_resources.footprint.read_write.as_vec().len())?; @@ -165,6 +167,7 @@ fn get_configuration_setting( fn get_fee_configuration( ledger_storage: &ledger_storage::LedgerStorage, + bucket_list_size: u64, ) -> Result> { // TODO: to improve the performance of this function (which is invoked every single preflight call) we can // 1. modify ledger_storage.get() so that it can gather multiple entries at once @@ -202,14 +205,23 @@ fn get_fee_configuration( ); }; + let write_fee_configuration = WriteFeeConfiguration { + bucket_list_target_size_bytes: ledger_cost.bucket_list_target_size_bytes, + write_fee_1kb_bucket_list_low: ledger_cost.write_fee1_kb_bucket_list_low, + write_fee_1kb_bucket_list_high: ledger_cost.write_fee1_kb_bucket_list_high, + bucket_list_write_fee_growth_factor: ledger_cost.bucket_list_write_fee_growth_factor, + }; + // Taken from Stellar Core's InitialSorobanNetworkConfig in NetworkConfig.h let fee_configuration = FeeConfiguration { fee_per_instruction_increment: compute.fee_rate_per_instructions_increment, fee_per_read_entry: ledger_cost.fee_read_ledger_entry, fee_per_write_entry: ledger_cost.fee_write_ledger_entry, fee_per_read_1kb: ledger_cost.fee_read1_kb, - // TODO: This fild should had been removed by the env library - fee_per_write_1kb: 0, + fee_per_write_1kb: compute_write_fee_per_1kb( + bucket_list_size as i64, + &write_fee_configuration, + ), fee_per_historical_1kb: historical_data.fee_historical1_kb, fee_per_metadata_1kb: metadata.fee_extended_meta_data1_kb, fee_per_propagate_1kb: bandwidth.fee_propagate_data1_kb, @@ -296,6 +308,7 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( footprint: LedgerFootprint, ledgers_to_expire: u32, snapshot_source: &ledger_storage::LedgerStorage, + bucket_list_size: u64, ) -> Result<(SorobanTransactionData, i64), Box> { let read_bytes = calculate_unmodified_ledger_entry_bytes( footprint.read_only.as_vec(), @@ -324,7 +337,7 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( &soroban_resources.footprint, )?, }; - let fee_configuration = get_fee_configuration(snapshot_source)?; + let fee_configuration = get_fee_configuration(snapshot_source, bucket_list_size)?; let (min_fee, ref_fee) = compute_transaction_resource_fee(&transaction_resources, &fee_configuration); let transaction_data = SorobanTransactionData { @@ -338,6 +351,7 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( footprint: LedgerFootprint, snapshot_source: &ledger_storage::LedgerStorage, + bucket_list_size: u64, ) -> Result<(SorobanTransactionData, i64), Box> { let write_bytes = calculate_unmodified_ledger_entry_bytes( footprint.read_write.as_vec(), @@ -368,7 +382,7 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( &soroban_resources.footprint, )?, }; - let fee_configuration = get_fee_configuration(snapshot_source)?; + let fee_configuration = get_fee_configuration(snapshot_source, bucket_list_size)?; let (min_fee, ref_fee) = compute_transaction_resource_fee(&transaction_resources, &fee_configuration); let transaction_data = SorobanTransactionData { diff --git a/cmd/soroban-rpc/lib/preflight/src/lib.rs b/cmd/soroban-rpc/lib/preflight/src/lib.rs index 7f618feb6..b546e2a05 100644 --- a/cmd/soroban-rpc/lib/preflight/src/lib.rs +++ b/cmd/soroban-rpc/lib/preflight/src/lib.rs @@ -83,18 +83,26 @@ fn preflight_error(str: String) -> *mut CPreflightResult { #[no_mangle] pub extern "C" fn preflight_invoke_hf_op( - handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHasconst + handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas + bucket_list_size: u64, // Bucket list size for current ledger invoke_hf_op: *const libc::c_char, // InvokeHostFunctionOp XDR in base64 source_account: *const libc::c_char, // AccountId XDR in base64 ledger_info: CLedgerInfo, ) -> *mut CPreflightResult { catch_preflight_panic(Box::new(move || { - preflight_invoke_hf_op_or_maybe_panic(handle, invoke_hf_op, source_account, ledger_info) + preflight_invoke_hf_op_or_maybe_panic( + handle, + bucket_list_size, + invoke_hf_op, + source_account, + ledger_info, + ) })) } fn preflight_invoke_hf_op_or_maybe_panic( - handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas + handle: libc::uintptr_t, + bucket_list_size: u64, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas invoke_hf_op: *const libc::c_char, // InvokeHostFunctionOp XDR in base64 source_account: *const libc::c_char, // AccountId XDR in base64 ledger_info: CLedgerInfo, @@ -134,6 +142,7 @@ fn preflight_invoke_hf_op_or_maybe_panic( &storage, &budget, &diagnostic_events, + bucket_list_size, )?; let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; Ok(CPreflightResult { @@ -150,17 +159,24 @@ fn preflight_invoke_hf_op_or_maybe_panic( #[no_mangle] pub extern "C" fn preflight_footprint_expiration_op( - handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHasconst + handle: libc::uintptr_t, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas + bucket_list_size: u64, // Bucket list size for current ledger op_body: *const libc::c_char, // OperationBody XDR in base64 footprint: *const libc::c_char, // LedgerFootprint XDR in base64 ) -> *mut CPreflightResult { catch_preflight_panic(Box::new(move || { - preflight_footprint_expiration_op_or_maybe_panic(handle, op_body, footprint) + preflight_footprint_expiration_op_or_maybe_panic( + handle, + bucket_list_size, + op_body, + footprint, + ) })) } fn preflight_footprint_expiration_op_or_maybe_panic( handle: libc::uintptr_t, + bucket_list_size: u64, op_body: *const libc::c_char, footprint: *const libc::c_char, ) -> Result> { @@ -176,9 +192,10 @@ fn preflight_footprint_expiration_op_or_maybe_panic( ledger_footprint, op.ledgers_to_expire, snapshot_source, + bucket_list_size, ), OperationBody::RestoreFootprint(_) => { - preflight_restore_footprint(ledger_footprint, snapshot_source) + preflight_restore_footprint(ledger_footprint, snapshot_source, bucket_list_size) } op => Err(format!( "preflight_footprint_expiration_op(): unsupported operation type {}", @@ -192,12 +209,14 @@ fn preflight_bump_footprint_expiration( footprint: LedgerFootprint, ledgers_to_expire: u32, snapshot_source: &LedgerStorage, + bucket_list_size: u64, ) -> Result> { let (transaction_data, min_fee) = fees::compute_bump_footprint_exp_transaction_data_and_min_fee( footprint, ledgers_to_expire, snapshot_source, + bucket_list_size, )?; let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; Ok(CPreflightResult { @@ -215,9 +234,13 @@ fn preflight_bump_footprint_expiration( fn preflight_restore_footprint( footprint: LedgerFootprint, snapshot_source: &LedgerStorage, + bucket_list_size: u64, ) -> Result> { - let (transaction_data, min_fee) = - fees::compute_restore_footprint_transaction_data_and_min_fee(footprint, snapshot_source)?; + let (transaction_data, min_fee) = fees::compute_restore_footprint_transaction_data_and_min_fee( + footprint, + snapshot_source, + bucket_list_size, + )?; let transaction_data_cstr = CString::new(transaction_data.to_xdr_base64()?)?; Ok(CPreflightResult { error: null_mut(),