Skip to content

Commit

Permalink
soroban-rpc: State Expiration (#695)
Browse files Browse the repository at this point in the history
* Update go for new xdr

* WIP -- working on extensions

* Implement ExtendLedgerEntry

* Disallow access to expired ledgerEntries

* update go dep

* Include current ledger in expiry

* update ledgerEntry tests for new xdr

* Add unit test for ExtendEntry

* Add test for extending a non-existent ledger entry

* Add tests for expired contract entries

* Test creating and extending in the same batch

* Show expired ledger entries now that core will autobump

* Revert "Show expired ledger entries now that core will autobump"

This reverts commit b1426a7.

* Refactor expiry checking

* Replace assert.NotNil with require.NotNil

* Clearer naming

* better test error checking

* Add validation on upserting ledger entrys that the key matches

* Simplify UpsertLedgerEntry since key is derived from the entry

* Update go dependency to 3f69f56e3743bbd520e4c25f44b8fc49b1b81936
  • Loading branch information
Paul Bellamy authored Jul 4, 2023
1 parent 910db9a commit d4f85d4
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 39 deletions.
75 changes: 73 additions & 2 deletions cmd/soroban-rpc/internal/db/ledgerentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package db
import (
"context"
"database/sql"
"encoding/base64"
"encoding/hex"
"fmt"

sq "github.com/Masterminds/squirrel"

"github.com/stellar/go/support/db"
"github.com/stellar/go/support/errors"
"github.com/stellar/go/xdr"
)

Expand All @@ -28,7 +30,8 @@ type LedgerEntryReadTx interface {
}

type LedgerEntryWriter interface {
UpsertLedgerEntry(key xdr.LedgerKey, entry xdr.LedgerEntry) error
ExtendLedgerEntry(key xdr.LedgerKey, expirationLedgerSeq xdr.Uint32) error
UpsertLedgerEntry(entry xdr.LedgerEntry) error
DeleteLedgerEntry(key xdr.LedgerKey) error
}

Expand All @@ -40,7 +43,61 @@ type ledgerEntryWriter struct {
maxBatchSize int
}

func (l ledgerEntryWriter) UpsertLedgerEntry(key xdr.LedgerKey, entry xdr.LedgerEntry) error {
func (l ledgerEntryWriter) ExtendLedgerEntry(key xdr.LedgerKey, expirationLedgerSeq xdr.Uint32) error {
// TODO: How do we figure out the current expiration? We might need to read
// from the DB, but in the case of creating a new entry and immediately
// extending it, or extending multiple times in the same ledger, the
// expirationLedgerSeq might be buffered but not flushed yet.
if key.Type != xdr.LedgerEntryTypeContractCode && key.Type != xdr.LedgerEntryTypeContractData {
panic("ExtendLedgerEntry can only be used for contract code and data")
}

encodedKey, err := encodeLedgerKey(l.buffer, key)
if err != nil {
return err
}

var entry xdr.LedgerEntry
var existing string
// See if we have a pending (unflushed) update for this key
queued := l.keyToEntryBatch[encodedKey]
if queued != nil && *queued != "" {
existing = *queued
} else {
// Nothing in the flush buffer. Load the entry from the db
err = sq.StatementBuilder.RunWith(l.stmtCache).Select("entry").From(ledgerEntriesTableName).Where(sq.Eq{"key": encodedKey}).QueryRow().Scan(&existing)
if err == sql.ErrNoRows {
return fmt.Errorf("no entry for key %q in table %q", base64.StdEncoding.EncodeToString([]byte(encodedKey)), ledgerEntriesTableName)
} else if err != nil {
return err
}
}

// Unmarshal the existing entry
if err := xdr.SafeUnmarshal([]byte(existing), &entry); err != nil {
return err
}

// Update the expiration
switch entry.Data.Type {
case xdr.LedgerEntryTypeContractData:
entry.Data.ContractData.ExpirationLedgerSeq = expirationLedgerSeq
case xdr.LedgerEntryTypeContractCode:
entry.Data.ContractCode.ExpirationLedgerSeq = expirationLedgerSeq
}

// Marshal the entry back and stage it
return l.UpsertLedgerEntry(entry)
}

func (l ledgerEntryWriter) UpsertLedgerEntry(entry xdr.LedgerEntry) error {
// We can do a little extra validation to ensure the entry and key match,
// because the key can be derived from the entry.
key, err := entry.LedgerKey()
if err != nil {
return errors.Wrap(err, "could not get ledger key from entry")
}

encodedKey, err := encodeLedgerKey(l.buffer, key)
if err != nil {
return err
Expand Down Expand Up @@ -148,6 +205,20 @@ func (l *ledgerEntryReadTx) GetLedgerEntry(key xdr.LedgerKey) (bool, xdr.LedgerE
if err = xdr.SafeUnmarshal([]byte(ledgerEntryBin), &result); err != nil {
return false, xdr.LedgerEntry{}, err
}

// 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
}
}

return true, result, nil
}

Expand Down
Loading

0 comments on commit d4f85d4

Please sign in to comment.