diff --git a/cmd/soroban-rpc/internal/db/ledgerentry.go b/cmd/soroban-rpc/internal/db/ledgerentry.go index 09bef902e6..a0c175a568 100644 --- a/cmd/soroban-rpc/internal/db/ledgerentry.go +++ b/cmd/soroban-rpc/internal/db/ledgerentry.go @@ -25,7 +25,7 @@ type LedgerEntryReader interface { type LedgerEntryReadTx interface { GetLatestLedgerSequence() (uint32, error) - GetLedgerEntry(key xdr.LedgerKey) (bool, xdr.LedgerEntry, error) + GetLedgerEntry(key xdr.LedgerKey, includeExpired bool) (bool, xdr.LedgerEntry, error) Done() error } @@ -181,7 +181,7 @@ func (l *ledgerEntryReadTx) GetLatestLedgerSequence() (uint32, error) { return latestLedgerSeq, err } -func (l *ledgerEntryReadTx) GetLedgerEntry(key xdr.LedgerKey) (bool, xdr.LedgerEntry, error) { +func (l *ledgerEntryReadTx) GetLedgerEntry(key xdr.LedgerKey, includeExpired bool) (bool, xdr.LedgerEntry, error) { encodedKey, err := encodeLedgerKey(l.buffer, key) if err != nil { return false, xdr.LedgerEntry{}, err @@ -208,14 +208,15 @@ func (l *ledgerEntryReadTx) GetLedgerEntry(key xdr.LedgerKey) (bool, xdr.LedgerE // Disallow access to entries that have expired. Expiration excludes the // "current" ledger, which we are building. - // TODO: Support allowing access, but recording for simulateTransaction - if expirationLedgerSeq, ok := result.Data.ExpirationLedgerSeq(); ok { - latestClosedLedger, err := l.GetLatestLedgerSequence() - if err != nil { - return false, xdr.LedgerEntry{}, err - } - if expirationLedgerSeq <= xdr.Uint32(latestClosedLedger) { - return false, xdr.LedgerEntry{}, nil + if !includeExpired { + if expirationLedgerSeq, ok := result.Data.ExpirationLedgerSeq(); ok { + latestClosedLedger, err := l.GetLatestLedgerSequence() + if err != nil { + return false, xdr.LedgerEntry{}, err + } + if expirationLedgerSeq <= xdr.Uint32(latestClosedLedger) { + return false, xdr.LedgerEntry{}, nil + } } } diff --git a/cmd/soroban-rpc/internal/db/ledgerentry_test.go b/cmd/soroban-rpc/internal/db/ledgerentry_test.go index 2500ceea76..71339d1fcc 100644 --- a/cmd/soroban-rpc/internal/db/ledgerentry_test.go +++ b/cmd/soroban-rpc/internal/db/ledgerentry_test.go @@ -32,7 +32,7 @@ func getLedgerEntryAndLatestLedgerSequenceWithErr(db db.SessionInterface, key xd return false, xdr.LedgerEntry{}, 0, err } - present, entry, err := tx.GetLedgerEntry(key) + present, entry, err := tx.GetLedgerEntry(key, false) if err != nil { return false, xdr.LedgerEntry{}, 0, err } @@ -497,14 +497,14 @@ func TestReadTxsDuringWriteTx(t *testing.T) { _, err = readTx1.GetLatestLedgerSequence() assert.Equal(t, ErrEmptyDB, err) - present, _, err := readTx1.GetLedgerEntry(key) + present, _, err := readTx1.GetLedgerEntry(key, false) assert.NoError(t, err) assert.False(t, present) assert.NoError(t, readTx1.Done()) _, err = readTx2.GetLatestLedgerSequence() assert.Equal(t, ErrEmptyDB, err) - present, _, err = readTx2.GetLedgerEntry(key) + present, _, err = readTx2.GetLedgerEntry(key, false) assert.NoError(t, err) assert.False(t, present) assert.NoError(t, readTx2.Done()) @@ -581,7 +581,7 @@ func TestWriteTxsDuringReadTxs(t *testing.T) { for _, readTx := range []LedgerEntryReadTx{readTx1, readTx2, readTx3} { _, err = readTx.GetLatestLedgerSequence() assert.Equal(t, ErrEmptyDB, err) - present, _, err := readTx.GetLedgerEntry(key) + present, _, err := readTx.GetLedgerEntry(key, false) assert.NoError(t, err) assert.False(t, present) } @@ -593,7 +593,7 @@ func TestWriteTxsDuringReadTxs(t *testing.T) { for _, readTx := range []LedgerEntryReadTx{readTx1, readTx2, readTx3} { _, err = readTx.GetLatestLedgerSequence() assert.Equal(t, ErrEmptyDB, err) - present, _, err := readTx.GetLedgerEntry(key) + present, _, err := readTx.GetLedgerEntry(key, false) assert.NoError(t, err) assert.False(t, present) } diff --git a/cmd/soroban-rpc/internal/methods/get_latest_ledger_test.go b/cmd/soroban-rpc/internal/methods/get_latest_ledger_test.go index 7161ebb246..ef8f1c5dc8 100644 --- a/cmd/soroban-rpc/internal/methods/get_latest_ledger_test.go +++ b/cmd/soroban-rpc/internal/methods/get_latest_ledger_test.go @@ -6,8 +6,9 @@ import ( "github.com/creachadair/jrpc2" "github.com/stellar/go/xdr" - "github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/db" "github.com/stretchr/testify/assert" + + "github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/db" ) const ( @@ -37,7 +38,7 @@ func (entryReaderTx ConstantLedgerEntryReaderTx) GetLatestLedgerSequence() (uint return expectedLatestLedgerSequence, nil } -func (entryReaderTx ConstantLedgerEntryReaderTx) GetLedgerEntry(key xdr.LedgerKey) (bool, xdr.LedgerEntry, error) { +func (entryReaderTx ConstantLedgerEntryReaderTx) GetLedgerEntry(key xdr.LedgerKey, includeExpired bool) (bool, xdr.LedgerEntry, error) { return false, xdr.LedgerEntry{}, nil } diff --git a/cmd/soroban-rpc/internal/methods/get_ledger_entries.go b/cmd/soroban-rpc/internal/methods/get_ledger_entries.go index 2b4cfcbf93..747eec3014 100644 --- a/cmd/soroban-rpc/internal/methods/get_ledger_entries.go +++ b/cmd/soroban-rpc/internal/methods/get_ledger_entries.go @@ -72,7 +72,7 @@ func NewGetLedgerEntriesHandler(logger *log.Entry, ledgerEntryReader db.LedgerEn var ledgerEntryResults []LedgerEntryResult for i, ledgerKey := range ledgerKeys { - present, ledgerEntry, err := tx.GetLedgerEntry(ledgerKey) + present, ledgerEntry, err := tx.GetLedgerEntry(ledgerKey, false) if err != nil { logger.WithError(err).WithField("request", request). Infof("could not obtain ledger entry %v at index %d from storage", ledgerKey, i) diff --git a/cmd/soroban-rpc/internal/methods/get_ledger_entry.go b/cmd/soroban-rpc/internal/methods/get_ledger_entry.go index 5f8a67087b..644d14b1e7 100644 --- a/cmd/soroban-rpc/internal/methods/get_ledger_entry.go +++ b/cmd/soroban-rpc/internal/methods/get_ledger_entry.go @@ -62,7 +62,7 @@ func NewGetLedgerEntryHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntr } } - present, ledgerEntry, err := tx.GetLedgerEntry(key) + present, ledgerEntry, err := tx.GetLedgerEntry(key, false) if err != nil { logger.WithError(err).WithField("request", request). Info("could not obtain ledger entry from storage") diff --git a/cmd/soroban-rpc/internal/preflight/pool.go b/cmd/soroban-rpc/internal/preflight/pool.go index 0ec39a5ff0..53bb42385c 100644 --- a/cmd/soroban-rpc/internal/preflight/pool.go +++ b/cmd/soroban-rpc/internal/preflight/pool.go @@ -127,9 +127,9 @@ type metricsLedgerEntryWrapper struct { ledgerEntriesFetched uint32 } -func (m *metricsLedgerEntryWrapper) GetLedgerEntry(key xdr.LedgerKey) (bool, xdr.LedgerEntry, error) { +func (m *metricsLedgerEntryWrapper) GetLedgerEntry(key xdr.LedgerKey, includeExpired bool) (bool, xdr.LedgerEntry, error) { startTime := time.Now() - ok, entry, err := m.LedgerEntryReadTx.GetLedgerEntry(key) + ok, entry, err := m.LedgerEntryReadTx.GetLedgerEntry(key, includeExpired) atomic.AddUint64(&m.totalDurationMs, uint64(time.Since(startTime).Milliseconds())) atomic.AddUint32(&m.ledgerEntriesFetched, 1) return ok, entry, err diff --git a/cmd/soroban-rpc/internal/preflight/preflight.go b/cmd/soroban-rpc/internal/preflight/preflight.go index 4786bf0aa7..1014c337eb 100644 --- a/cmd/soroban-rpc/internal/preflight/preflight.go +++ b/cmd/soroban-rpc/internal/preflight/preflight.go @@ -39,14 +39,14 @@ type snapshotSourceHandle struct { // It's used by the Rust preflight code to obtain ledger entries. // //export SnapshotSourceGet -func SnapshotSourceGet(handle C.uintptr_t, cLedgerKey *C.char) *C.char { +func SnapshotSourceGet(handle C.uintptr_t, cLedgerKey *C.char, includeExpired C.int) *C.char { h := cgo.Handle(handle).Value().(snapshotSourceHandle) ledgerKeyB64 := C.GoString(cLedgerKey) var ledgerKey xdr.LedgerKey if err := xdr.SafeUnmarshalBase64(ledgerKeyB64, &ledgerKey); err != nil { panic(err) } - present, entry, err := h.readTx.GetLedgerEntry(ledgerKey) + present, entry, err := h.readTx.GetLedgerEntry(ledgerKey, includeExpired != 0) if err != nil { h.logger.WithError(err).Error("SnapshotSourceGet(): GetLedgerEntry() failed") return nil @@ -72,7 +72,7 @@ func SnapshotSourceHas(handle C.uintptr_t, cLedgerKey *C.char) C.int { if err := xdr.SafeUnmarshalBase64(ledgerKeyB64, &ledgerKey); err != nil { panic(err) } - present, _, err := h.readTx.GetLedgerEntry(ledgerKey) + present, _, err := h.readTx.GetLedgerEntry(ledgerKey, false) if err != nil { h.logger.WithError(err).Error("SnapshotSourceHas(): GetLedgerEntry() failed") return 0 @@ -184,7 +184,7 @@ func getInvokeHostFunctionPreflight(params PreflightParameters) (Preflight, erro ConfigSetting: &xdr.LedgerKeyConfigSetting{ ConfigSettingId: xdr.ConfigSettingIdConfigSettingStateExpiration, }, - }) + }, false) if err != nil { return Preflight{}, err } diff --git a/cmd/soroban-rpc/internal/test/simulate_transaction_test.go b/cmd/soroban-rpc/internal/test/simulate_transaction_test.go index 0f4f270395..f6a09669cb 100644 --- a/cmd/soroban-rpc/internal/test/simulate_transaction_test.go +++ b/cmd/soroban-rpc/internal/test/simulate_transaction_test.go @@ -8,6 +8,7 @@ import ( "path" "runtime" "testing" + "time" "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/jhttp" @@ -638,7 +639,7 @@ func TestSimulateTransactionUnmarshalError(t *testing.T) { ) } -func TestSimulateTransactionBumpFootprint(t *testing.T) { +func TestSimulateTransactionBumpAndRestoreFootprint(t *testing.T) { test := NewTest(t) ch := jhttp.NewChannel(test.sorobanRPCURL(), nil) @@ -736,7 +737,7 @@ func TestSimulateTransactionBumpFootprint(t *testing.T) { IncrementSequenceNum: true, Operations: []txnbuild.Operation{ &txnbuild.BumpFootprintExpiration{ - LedgersToExpire: 100, + LedgersToExpire: 17, Ext: xdr.TransactionExt{ V: 1, SorobanData: &xdr.SorobanTransactionData{ @@ -766,4 +767,39 @@ func TestSimulateTransactionBumpFootprint(t *testing.T) { assert.Greater(t, newExpirationSeq, initialExpirationSeq) + // Wait until it expires + for i := 0; i < 30; i++ { + err = client.CallResult(context.Background(), "getLedgerEntry", getLedgerEntryrequest, &result) + if err != nil { + break + } + time.Sleep(time.Second) + } + + // and restore it + params = preflightTransactionParams(t, client, txnbuild.TransactionParams{ + SourceAccount: &account, + IncrementSequenceNum: true, + Operations: []txnbuild.Operation{ + &txnbuild.RestoreFootprint{ + Ext: xdr.TransactionExt{ + V: 1, + SorobanData: &xdr.SorobanTransactionData{ + Resources: xdr.SorobanResources{ + Footprint: xdr.LedgerFootprint{ + ReadWrite: []xdr.LedgerKey{key}, + }, + }, + }, + }, + }, + }, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{ + TimeBounds: txnbuild.NewInfiniteTimeout(), + }, + }) + tx, err = txnbuild.NewTransaction(params) + assert.NoError(t, err) + sendSuccessfulTransaction(t, client, sourceAccount, tx) } diff --git a/cmd/soroban-rpc/lib/preflight.h b/cmd/soroban-rpc/lib/preflight.h index f9bec96866..caac7a0ec1 100644 --- a/cmd/soroban-rpc/lib/preflight.h +++ b/cmd/soroban-rpc/lib/preflight.h @@ -35,7 +35,7 @@ CPreflightResult *preflight_footprint_expiration_op(uintptr_t handle, // Go Hand const char *footprint); // LedgerFootprint XDR in base64 // LedgerKey XDR in base64 string to LedgerEntry XDR in base64 string -extern char *SnapshotSourceGet(uintptr_t handle, char *ledger_key); +extern char *SnapshotSourceGet(uintptr_t handle, char *ledger_key, int include_expired); // LedgerKey XDR in base64 string to bool extern int SnapshotSourceHas(uintptr_t handle, char *ledger_key); diff --git a/cmd/soroban-rpc/lib/preflight/src/fees.rs b/cmd/soroban-rpc/lib/preflight/src/fees.rs index f918533f87..8ea2ed506b 100644 --- a/cmd/soroban-rpc/lib/preflight/src/fees.rs +++ b/cmd/soroban-rpc/lib/preflight/src/fees.rs @@ -120,9 +120,9 @@ fn calculate_host_function_soroban_resources( metadataSize = readBytes(footprint.readWrite) + writeBytes + eventsSize */ let original_write_ledger_entry_bytes = - calculate_unmodified_ledger_entry_bytes(fp.read_write.as_vec(), snapshot_source)?; + calculate_unmodified_ledger_entry_bytes(fp.read_write.as_vec(), snapshot_source, false)?; let read_bytes = - calculate_unmodified_ledger_entry_bytes(fp.read_only.as_vec(), snapshot_source)? + calculate_unmodified_ledger_entry_bytes(fp.read_only.as_vec(), snapshot_source, false)? + original_write_ledger_entry_bytes; let write_bytes = calculate_modified_read_write_ledger_entry_bytes(&storage.footprint, &storage.map, budget)?; @@ -150,7 +150,7 @@ fn get_configuration_setting( let key = LedgerKey::ConfigSetting(LedgerKeyConfigSetting { config_setting_id: setting_id, }); - match ledger_storage.get(&key)? { + match ledger_storage.get(&key, false)? { LedgerEntry { data: LedgerEntryData::ConfigSetting(cs), .. @@ -241,11 +241,12 @@ fn calculate_modified_read_write_ledger_entry_bytes( fn calculate_unmodified_ledger_entry_bytes( ledger_entries: &Vec, snapshot_source: &ledger_storage::LedgerStorage, + include_expired: bool, ) -> Result> { let mut res: u32 = 0; for lk in ledger_entries { res += u32::try_from(lk.to_xdr()?.len())?; - match snapshot_source.get_xdr(lk) { + match snapshot_source.get_xdr(lk, include_expired) { Ok(entry_bytes) => { res += u32::try_from(entry_bytes.len())?; } @@ -295,8 +296,11 @@ pub(crate) fn compute_bump_footprint_exp_transaction_data_and_min_fee( ledgers_to_expire: u32, snapshot_source: &ledger_storage::LedgerStorage, ) -> Result<(SorobanTransactionData, i64), Box> { - let read_bytes = - calculate_unmodified_ledger_entry_bytes(footprint.read_only.as_vec(), snapshot_source)?; + let read_bytes = calculate_unmodified_ledger_entry_bytes( + footprint.read_only.as_vec(), + snapshot_source, + false, + )?; let soroban_resources = SorobanResources { footprint, instructions: 0, @@ -334,8 +338,11 @@ pub(crate) fn compute_restore_footprint_transaction_data_and_min_fee( footprint: LedgerFootprint, snapshot_source: &ledger_storage::LedgerStorage, ) -> Result<(SorobanTransactionData, i64), Box> { - let write_bytes = - calculate_unmodified_ledger_entry_bytes(footprint.read_write.as_vec(), snapshot_source)?; + let write_bytes = calculate_unmodified_ledger_entry_bytes( + footprint.read_write.as_vec(), + snapshot_source, + true, + )?; let soroban_resources = SorobanResources { footprint, instructions: 0, diff --git a/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs b/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs index 610b34a46c..94c148586c 100644 --- a/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs +++ b/cmd/soroban-rpc/lib/preflight/src/ledger_storage.rs @@ -16,6 +16,7 @@ extern "C" { fn SnapshotSourceGet( handle: libc::uintptr_t, ledger_key: *const libc::c_char, + include_expired: libc::c_int, ) -> *const libc::c_char; // TODO: this function is unnecessary, we can just look for null in SnapshotSourceGet // LedgerKey XDR in base64 string to bool @@ -62,10 +63,16 @@ pub(crate) struct LedgerStorage { } impl LedgerStorage { - fn get_xdr_base64(&self, key: &LedgerKey) -> Result { + fn get_xdr_base64(&self, key: &LedgerKey, include_expired: bool) -> Result { let key_xdr = key.to_xdr_base64()?; let key_cstr = CString::new(key_xdr)?; - let res = unsafe { SnapshotSourceGet(self.golang_handle, key_cstr.as_ptr()) }; + let res = unsafe { + SnapshotSourceGet( + self.golang_handle, + key_cstr.as_ptr(), + include_expired.into(), + ) + }; if res.is_null() { return Err(Error::NotFound); } @@ -79,21 +86,21 @@ impl LedgerStorage { Ok(str) } - pub fn get(&self, key: &LedgerKey) -> Result { - let base64_str = self.get_xdr_base64(key)?; + pub fn get(&self, key: &LedgerKey, include_expired: bool) -> Result { + let base64_str = self.get_xdr_base64(key, include_expired)?; let entry = LedgerEntry::from_xdr_base64(base64_str)?; Ok(entry) } - pub fn get_xdr(&self, key: &LedgerKey) -> Result, Error> { - let base64_str = self.get_xdr_base64(key)?; + pub fn get_xdr(&self, key: &LedgerKey, include_expired: bool) -> Result, Error> { + let base64_str = self.get_xdr_base64(key, include_expired)?; Ok(base64::decode(base64_str)?) } } impl SnapshotSource for LedgerStorage { fn get(&self, key: &Rc) -> Result, HostError> { - let entry = self.get(key).map_err(|e| Error::to_host_error(&e))?; + let entry = self.get(key, false).map_err(|e| Error::to_host_error(&e))?; Ok(entry.into()) }