diff --git a/cmd/soroban-rpc/internal/db/ledgerentry.go b/cmd/soroban-rpc/internal/db/ledgerentry.go index 9bf5bb7ec..c4e10c9b8 100644 --- a/cmd/soroban-rpc/internal/db/ledgerentry.go +++ b/cmd/soroban-rpc/internal/db/ledgerentry.go @@ -21,6 +21,7 @@ const ( type LedgerEntryReader interface { GetLatestLedgerSequence(ctx context.Context) (uint32, error) NewTx(ctx context.Context) (LedgerEntryReadTx, error) + NewCachedTx(ctx context.Context) (LedgerEntryReadTx, error) } type LedgerEntryReadTx interface { @@ -200,9 +201,15 @@ func (l *ledgerEntryReadTx) getBinaryLedgerEntry(key xdr.LedgerKey) (bool, strin return false, "", err } - entry, ok := l.ledgerEntryCacheReadTx.get(encodedKey) - if ok { - return ok, entry, nil + if l.ledgerEntryCacheReadTx != nil { + entry, ok := l.ledgerEntryCacheReadTx.get(encodedKey) + if ok { + if entry != nil { + return true, *entry, nil + } else { + return false, "", nil + } + } } sql := sq.Select("entry").From(ledgerEntriesTableName).Where(sq.Eq{"key": encodedKey}) @@ -212,13 +219,20 @@ func (l *ledgerEntryReadTx) getBinaryLedgerEntry(key xdr.LedgerKey) (bool, strin } switch len(results) { case 0: + if l.ledgerEntryCacheReadTx != nil { + l.ledgerEntryCacheReadTx.upsert(encodedKey, nil) + } return false, "", nil case 1: // expected length + result := results[0] + if l.ledgerEntryCacheReadTx != nil { + l.ledgerEntryCacheReadTx.upsert(encodedKey, &result) + } + return true, result, nil default: return false, "", fmt.Errorf("multiple entries (%d) for key %q in table %q", len(results), hex.EncodeToString([]byte(encodedKey)), ledgerEntriesTableName) } - return true, results[0], nil } func (l *ledgerEntryReadTx) GetLedgerEntry(key xdr.LedgerKey, includeExpired bool) (bool, xdr.LedgerEntry, error) { @@ -266,7 +280,7 @@ func (r ledgerEntryReader) GetLatestLedgerSequence(ctx context.Context) (uint32, return getLatestLedgerSequence(ctx, r.db) } -func (r ledgerEntryReader) NewTx(ctx context.Context) (LedgerEntryReadTx, error) { +func (r ledgerEntryReader) NewCachedTx(ctx context.Context) (LedgerEntryReadTx, error) { txSession := r.db.Clone() // We need to copy the cached ledger entries locally when we start the transaction // since otherwise we would break the consistency between the transaction and the cache. @@ -280,7 +294,6 @@ func (r ledgerEntryReader) NewTx(ctx context.Context) (LedgerEntryReadTx, error) return nil, err } cacheReadTx := r.db.ledgerEntryCache.newReadTx() - return &ledgerEntryReadTx{ ledgerEntryCacheReadTx: cacheReadTx, tx: txSession, @@ -288,6 +301,17 @@ func (r ledgerEntryReader) NewTx(ctx context.Context) (LedgerEntryReadTx, error) }, nil } +func (r ledgerEntryReader) NewTx(ctx context.Context) (LedgerEntryReadTx, error) { + txSession := r.db.Clone() + if err := txSession.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}); err != nil { + return nil, err + } + return &ledgerEntryReadTx{ + tx: txSession, + buffer: xdr.NewEncodingBuffer(), + }, nil +} + func encodeLedgerKey(buffer *xdr.EncodingBuffer, key xdr.LedgerKey) (string, error) { // this is safe since we are converting to string right away, which causes a copy binKey, err := buffer.LedgerKeyUnsafeMarshalBinaryCompress(key) diff --git a/cmd/soroban-rpc/internal/db/transactionalcache.go b/cmd/soroban-rpc/internal/db/transactionalcache.go index 784089573..d527d6de7 100644 --- a/cmd/soroban-rpc/internal/db/transactionalcache.go +++ b/cmd/soroban-rpc/internal/db/transactionalcache.go @@ -11,7 +11,8 @@ func newTransactionalCache() transactionalCache { func (c transactionalCache) newReadTx() transactionalCacheReadTx { ret := make(transactionalCacheReadTx, len(c.entries)) for k, v := range c.entries { - ret[k] = v + localV := v + ret[k] = &localV } return ret } @@ -23,13 +24,20 @@ func (c transactionalCache) newWriteTx(estimatedWriteCount int) transactionalCac } } -type transactionalCacheReadTx map[string]string +// nil indicates not present in the underlying storage +type transactionalCacheReadTx map[string]*string -func (r transactionalCacheReadTx) get(key string) (string, bool) { +// nil indicates not present in the underlying storage +func (r transactionalCacheReadTx) get(key string) (*string, bool) { val, ok := r[key] return val, ok } +// nil indicates not present in the underlying storage +func (r transactionalCacheReadTx) upsert(key string, value *string) { + r[key] = value +} + type transactionalCacheWriteTx struct { // nil indicates deletion pendingUpdates map[string]*string 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 ef8f1c5dc..623f12313 100644 --- a/cmd/soroban-rpc/internal/methods/get_latest_ledger_test.go +++ b/cmd/soroban-rpc/internal/methods/get_latest_ledger_test.go @@ -34,6 +34,10 @@ func (entryReader *ConstantLedgerEntryReader) NewTx(ctx context.Context) (db.Led return ConstantLedgerEntryReaderTx{}, nil } +func (entryReader *ConstantLedgerEntryReader) NewCachedTx(ctx context.Context) (db.LedgerEntryReadTx, error) { + return ConstantLedgerEntryReaderTx{}, nil +} + func (entryReaderTx ConstantLedgerEntryReaderTx) GetLatestLedgerSequence() (uint32, error) { return expectedLatestLedgerSequence, nil } diff --git a/cmd/soroban-rpc/internal/methods/simulate_transaction.go b/cmd/soroban-rpc/internal/methods/simulate_transaction.go index 02c24acfc..374737ddd 100644 --- a/cmd/soroban-rpc/internal/methods/simulate_transaction.go +++ b/cmd/soroban-rpc/internal/methods/simulate_transaction.go @@ -84,7 +84,7 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge } } - readTx, err := ledgerEntryReader.NewTx(ctx) + readTx, err := ledgerEntryReader.NewCachedTx(ctx) if err != nil { return SimulateTransactionResponse{ Error: "Cannot create read transaction", diff --git a/cmd/soroban-rpc/internal/preflight/preflight_test.go b/cmd/soroban-rpc/internal/preflight/preflight_test.go index 414756066..70d69feee 100644 --- a/cmd/soroban-rpc/internal/preflight/preflight_test.go +++ b/cmd/soroban-rpc/internal/preflight/preflight_test.go @@ -271,7 +271,7 @@ func getPreflightParameters(t testing.TB, inMemory bool) PreflightParameters { } err = tx.Commit(2) require.NoError(t, err) - ledgerEntryReadTx, err = db.NewLedgerEntryReader(dbInstance).NewTx(context.Background()) + ledgerEntryReadTx, err = db.NewLedgerEntryReader(dbInstance).NewCachedTx(context.Background()) require.NoError(t, err) } argSymbol := xdr.ScSymbol("world")