Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

soroban-rpc: get expiration ledger sequence at source #916

Merged
merged 43 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
522ce05
Bump Rust and Go dependencies
2opremio Aug 31, 2023
333f364
Start migrating soroban-rpc
2opremio Aug 31, 2023
f3ddedb
Fix get_ledger_changes() invocation
2opremio Aug 31, 2023
13aae97
wip migrating the soroban-cli (incomplete!)
leighmcculloch Sep 1, 2023
43f45c2
Appease rust
2opremio Sep 1, 2023
3b76940
Bump core dependencies
2opremio Sep 1, 2023
7f8024a
Bump Go dependencies again
2opremio Sep 1, 2023
6e515b8
Fix test comparisons
2opremio Sep 1, 2023
2781026
Fix BumpAndRestoreFootprint test
2opremio Sep 1, 2023
f5a9803
Improve restore test a bit further
2opremio Sep 1, 2023
1a58165
Adjust tests further
2opremio Sep 1, 2023
eb90f6b
Fix typo
2opremio Sep 1, 2023
f1cd7c6
Fix GetPreflight test
2opremio Sep 1, 2023
0e414da
Get CLI to compile and fix most TODOs
2opremio Sep 1, 2023
ed9fb10
run cargo md-gen
2opremio Sep 1, 2023
44bc323
Appease clippy
2opremio Sep 1, 2023
f6210f5
Add comment about expiration entry efficiency
2opremio Sep 1, 2023
ff1a045
Address review feedback
2opremio Sep 1, 2023
315b6b1
Take a stab at extracting the spec from the state
2opremio Sep 1, 2023
b1519a4
Merge branch 'main' into state-expiration-updates
2opremio Sep 1, 2023
d95def2
update
tsachiherman Sep 1, 2023
5aac804
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 4, 2023
ac85d7e
linter
tsachiherman Sep 4, 2023
ef33050
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 4, 2023
bf51bfe
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 5, 2023
a441688
limit get_ledger_entries to expiration ledger entries.
tsachiherman Sep 5, 2023
42ccee9
fix cli serialization
tsachiherman Sep 5, 2023
9b9af64
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Sep 6, 2023
ad8d4ab
update test
tsachiherman Sep 6, 2023
bd3e641
further fix..
tsachiherman Sep 6, 2023
1928ea8
further fix.
tsachiherman Sep 6, 2023
b721201
further fix
tsachiherman Sep 6, 2023
f2655d2
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Oct 11, 2023
83d044b
Merge branch 'main' into tsachi/ledgerentries
tsachiherman Oct 11, 2023
ec013b7
update test
tsachiherman Oct 11, 2023
babcb57
fix typo
tsachiherman Oct 11, 2023
b4838f8
fix: update CLI and tests for new getLedgerEntries (#1021)
willemneal Oct 12, 2023
caccfb3
step
tsachiherman Oct 12, 2023
0b3c41a
Merge branch 'tsachi/ledgerentries' of https://github.com/stellar/sor…
tsachiherman Oct 12, 2023
1c47ba8
update
tsachiherman Oct 12, 2023
29f0ae7
fix tests
tsachiherman Oct 12, 2023
e095f83
updating
tsachiherman Oct 12, 2023
9c44d71
update per code review
tsachiherman Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 67 additions & 11 deletions cmd/soroban-rpc/internal/db/ledgerentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import (
"context"
"crypto/sha256"
"database/sql"
"fmt"

Expand All @@ -23,8 +24,9 @@ type LedgerEntryReader interface {
}

type LedgerKeyAndEntry struct {
Key xdr.LedgerKey
Entry xdr.LedgerEntry
Key xdr.LedgerKey
Entry xdr.LedgerEntry
ExpirationLedgerSeq *uint32 // optional expiration ledger seq, whne applicable.
tsachiherman marked this conversation as resolved.
Show resolved Hide resolved
}

type LedgerEntryReadTx interface {
Expand Down Expand Up @@ -216,32 +218,71 @@ func (l *ledgerEntryReadTx) getRawLedgerEntries(keys ...string) (map[string]stri
return result, nil
}

func GetLedgerEntry(tx LedgerEntryReadTx, key xdr.LedgerKey) (bool, xdr.LedgerEntry, error) {
func GetLedgerEntry(tx LedgerEntryReadTx, key xdr.LedgerKey) (bool, xdr.LedgerEntry, *uint32, error) {
keyEntries, err := tx.GetLedgerEntries(key)
if err != nil {
return false, xdr.LedgerEntry{}, err
return false, xdr.LedgerEntry{}, nil, err
}
switch len(keyEntries) {
case 0:
return false, xdr.LedgerEntry{}, nil
return false, xdr.LedgerEntry{}, nil, nil
case 1:
// expected length
return true, keyEntries[0].Entry, nil
return true, keyEntries[0].Entry, keyEntries[0].ExpirationLedgerSeq, nil
default:
return false, xdr.LedgerEntry{}, fmt.Errorf("multiple entries (%d) for key %v", len(keyEntries), key)
return false, xdr.LedgerEntry{}, nil, fmt.Errorf("multiple entries (%d) for key %v", len(keyEntries), key)
}
}

// isExpirableKey check to see if the key type is expected to be accompanied by a LedgerExpirationEntry
func isExpirableKey(key xdr.LedgerKey) bool {
switch key.Type {
case xdr.LedgerEntryTypeContractData:
return true
case xdr.LedgerEntryTypeContractCode:
return true
default:
}
return false
}

func entryKeyToExpirationEntryKey(key xdr.LedgerKey) (xdr.LedgerKey, error) {
buf, err := key.MarshalBinary()
if err != nil {
return xdr.LedgerKey{}, err
}
var expirationEntry xdr.LedgerKey
err = expirationEntry.SetExpiration(xdr.Hash(sha256.Sum256(buf)))
if err != nil {
return xdr.LedgerKey{}, err
}
return expirationEntry, nil
}

func (l *ledgerEntryReadTx) GetLedgerEntries(keys ...xdr.LedgerKey) ([]LedgerKeyAndEntry, error) {
encodedKeys := make([]string, len(keys))
encodedKeys := make([]string, len(keys), 2*len(keys))
encodedKeyToKey := make(map[string]xdr.LedgerKey, len(keys))
for i, k := range keys {
encodedKeyToEncodedExpirationLedgerKey := make(map[string]string, len(keys))
for _, k := range keys {
encodedKey, err := encodeLedgerKey(l.buffer, k)
if err != nil {
return nil, err
}
encodedKeys[i] = encodedKey
encodedKeys = append(encodedKeys, encodedKey)
encodedKeyToKey[encodedKey] = k
if !isExpirableKey(k) {
continue
}
expirationEntryKey, err := entryKeyToExpirationEntryKey(k)
if err != nil {
return nil, err
}
encodedExpirationKey, err := encodeLedgerKey(l.buffer, expirationEntryKey)
if err != nil {
return nil, err
}
encodedKeyToEncodedExpirationLedgerKey[encodedKey] = encodedExpirationKey
encodedKeys = append(encodedKeys, encodedExpirationKey)
}

rawResult, err := l.getRawLedgerEntries(encodedKeys...)
Expand All @@ -259,7 +300,22 @@ func (l *ledgerEntryReadTx) GetLedgerEntries(keys ...xdr.LedgerKey) ([]LedgerKey
if err := xdr.SafeUnmarshal([]byte(encodedEntry), &entry); err != nil {
return nil, errors.Wrap(err, "cannot decode ledger entry from DB")
}
result = append(result, LedgerKeyAndEntry{key, entry})
encodedExpKey, has := encodedKeyToEncodedExpirationLedgerKey[encodedKey]
if !has {
result = append(result, LedgerKeyAndEntry{key, entry, nil})
continue
}
encodedExpEntry, ok := rawResult[encodedExpKey]
if !ok {
// missing expiration key. this should no happen.
return nil, errors.New("missing expiration key entry")
}
var expEntry xdr.LedgerEntry
if err := xdr.SafeUnmarshal([]byte(encodedExpEntry), &expEntry); err != nil {
return nil, errors.Wrap(err, "cannot decode expiration ledger entry from DB")
}
expSeq := uint32(expEntry.Data.Expiration.ExpirationLedgerSeq)
result = append(result, LedgerKeyAndEntry{key, entry, &expSeq})
}

return result, nil
Expand Down
83 changes: 61 additions & 22 deletions cmd/soroban-rpc/internal/db/ledgerentry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
"github.com/stellar/go/xdr"
)

func getLedgerEntryAndLatestLedgerSequenceWithErr(db *DB, key xdr.LedgerKey) (bool, xdr.LedgerEntry, uint32, error) {
func getLedgerEntryAndLatestLedgerSequenceWithErr(db *DB, key xdr.LedgerKey) (bool, xdr.LedgerEntry, uint32, *uint32, error) {
tx, err := NewLedgerEntryReader(db).NewTx(context.Background())
if err != nil {
return false, xdr.LedgerEntry{}, 0, err
return false, xdr.LedgerEntry{}, 0, nil, err
}
var doneErr error
defer func() {
Expand All @@ -27,21 +27,21 @@ func getLedgerEntryAndLatestLedgerSequenceWithErr(db *DB, key xdr.LedgerKey) (bo

latestSeq, err := tx.GetLatestLedgerSequence()
if err != nil {
return false, xdr.LedgerEntry{}, 0, err
return false, xdr.LedgerEntry{}, 0, nil, err
}

present, entry, err := GetLedgerEntry(tx, key)
present, entry, expSeq, err := GetLedgerEntry(tx, key)
if err != nil {
return false, xdr.LedgerEntry{}, 0, err
return false, xdr.LedgerEntry{}, 0, nil, err
}

return present, entry, latestSeq, doneErr
return present, entry, latestSeq, expSeq, doneErr
}

func getLedgerEntryAndLatestLedgerSequence(t require.TestingT, db *DB, key xdr.LedgerKey) (bool, xdr.LedgerEntry, uint32) {
present, entry, latestSeq, err := getLedgerEntryAndLatestLedgerSequenceWithErr(db, key)
func getLedgerEntryAndLatestLedgerSequence(t require.TestingT, db *DB, key xdr.LedgerKey) (bool, xdr.LedgerEntry, uint32, *uint32) {
present, entry, latestSeq, expSeq, err := getLedgerEntryAndLatestLedgerSequenceWithErr(db, key)
require.NoError(t, err)
return present, entry, latestSeq
return present, entry, latestSeq, expSeq
}

func TestGoldenPath(t *testing.T) {
Expand Down Expand Up @@ -74,12 +74,20 @@ func TestGoldenPath(t *testing.T) {
}
key, entry := getContractDataLedgerEntry(t, data)
assert.NoError(t, writer.UpsertLedgerEntry(entry))

expLedgerKey, err := entryKeyToExpirationEntryKey(key)
assert.NoError(t, err)
expLegerEntry := getExpirationLedgerEntry(expLedgerKey)
assert.NoError(t, writer.UpsertLedgerEntry(expLegerEntry))

ledgerSequence := uint32(23)
assert.NoError(t, tx.Commit(ledgerSequence))

present, obtainedEntry, obtainedLedgerSequence := getLedgerEntryAndLatestLedgerSequence(t, db, key)
present, obtainedEntry, obtainedLedgerSequence, expSeq := getLedgerEntryAndLatestLedgerSequence(t, db, key)
assert.True(t, present)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)
require.NotNil(t, expSeq)
assert.Equal(t, uint32(expLegerEntry.Data.Expiration.ExpirationLedgerSeq), *expSeq)
assert.Equal(t, obtainedEntry.Data.Type, xdr.LedgerEntryTypeContractData)
assert.Equal(t, xdr.Hash{0xca, 0xfe}, *obtainedEntry.Data.ContractData.Contract.ContractId)
assert.Equal(t, six, *obtainedEntry.Data.ContractData.Val.U32)
Expand All @@ -100,8 +108,9 @@ func TestGoldenPath(t *testing.T) {
ledgerSequence = uint32(24)
assert.NoError(t, tx.Commit(ledgerSequence))

present, obtainedEntry, obtainedLedgerSequence = getLedgerEntryAndLatestLedgerSequence(t, db, key)
present, obtainedEntry, obtainedLedgerSequence, expSeq = getLedgerEntryAndLatestLedgerSequence(t, db, key)
assert.True(t, present)
require.NotNil(t, expSeq)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)
assert.Equal(t, eight, *obtainedEntry.Data.ContractData.Val.U32)

Expand All @@ -115,8 +124,9 @@ func TestGoldenPath(t *testing.T) {
ledgerSequence = uint32(25)
assert.NoError(t, tx.Commit(ledgerSequence))

present, _, obtainedLedgerSequence = getLedgerEntryAndLatestLedgerSequence(t, db, key)
present, _, obtainedLedgerSequence, expSeq = getLedgerEntryAndLatestLedgerSequence(t, db, key)
assert.False(t, present)
assert.Nil(t, expSeq)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)

obtainedLedgerSequence, err = NewLedgerEntryReader(db).GetLatestLedgerSequence(context.Background())
Expand Down Expand Up @@ -161,8 +171,9 @@ func TestDeleteNonExistentLedgerEmpty(t *testing.T) {
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)

// And that the entry doesn't exist
present, _, obtainedLedgerSequence := getLedgerEntryAndLatestLedgerSequence(t, db, key)
present, _, obtainedLedgerSequence, expSeq := getLedgerEntryAndLatestLedgerSequence(t, db, key)
assert.False(t, present)
require.Nil(t, expSeq)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)
}

Expand All @@ -181,6 +192,16 @@ func getContractDataLedgerEntry(t require.TestingT, data xdr.ContractDataEntry)
return key, entry
}

func getExpirationLedgerEntry(key xdr.LedgerKey) xdr.LedgerEntry {
var expLegerEntry xdr.LedgerEntry
expLegerEntry.Data.Expiration = &xdr.ExpirationEntry{
KeyHash: key.Expiration.KeyHash,
ExpirationLedgerSeq: 100,
}
expLegerEntry.Data.Type = key.Type
return expLegerEntry
}

// Make sure that (multiple, simultaneous) read transactions can happen while a write-transaction is ongoing,
// and write is only visible once the transaction is committed
func TestReadTxsDuringWriteTx(t *testing.T) {
Expand Down Expand Up @@ -214,6 +235,11 @@ func TestReadTxsDuringWriteTx(t *testing.T) {
key, entry := getContractDataLedgerEntry(t, data)
assert.NoError(t, writer.UpsertLedgerEntry(entry))

expLedgerKey, err := entryKeyToExpirationEntryKey(key)
assert.NoError(t, err)
expLegerEntry := getExpirationLedgerEntry(expLedgerKey)
assert.NoError(t, writer.UpsertLedgerEntry(expLegerEntry))

// Before committing the changes, make sure multiple concurrent transactions can query the DB
readTx1, err := NewLedgerEntryReader(db).NewTx(context.Background())
assert.NoError(t, err)
Expand All @@ -222,16 +248,18 @@ func TestReadTxsDuringWriteTx(t *testing.T) {

_, err = readTx1.GetLatestLedgerSequence()
assert.Equal(t, ErrEmptyDB, err)
present, _, err := GetLedgerEntry(readTx1, key)
present, _, expSeq, err := GetLedgerEntry(readTx1, key)
require.Nil(t, expSeq)
assert.NoError(t, err)
assert.False(t, present)
assert.NoError(t, readTx1.Done())

_, err = readTx2.GetLatestLedgerSequence()
assert.Equal(t, ErrEmptyDB, err)
present, _, err = GetLedgerEntry(readTx2, key)
present, _, expSeq, err = GetLedgerEntry(readTx2, key)
assert.NoError(t, err)
assert.False(t, present)
assert.Nil(t, expSeq)
assert.NoError(t, readTx2.Done())

// Finish the write transaction and check that the results are present
Expand All @@ -242,10 +270,11 @@ func TestReadTxsDuringWriteTx(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)

present, obtainedEntry, obtainedLedgerSequence := getLedgerEntryAndLatestLedgerSequence(t, db, key)
present, obtainedEntry, obtainedLedgerSequence, expSeq := getLedgerEntryAndLatestLedgerSequence(t, db, key)
assert.True(t, present)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)
assert.Equal(t, six, *obtainedEntry.Data.ContractData.Val.U32)
assert.NotNil(t, expSeq)
}

// Make sure that a write transaction can happen while multiple read transactions are ongoing,
Expand Down Expand Up @@ -292,6 +321,11 @@ func TestWriteTxsDuringReadTxs(t *testing.T) {
key, entry := getContractDataLedgerEntry(t, data)
assert.NoError(t, writer.UpsertLedgerEntry(entry))

expLedgerKey, err := entryKeyToExpirationEntryKey(key)
assert.NoError(t, err)
expLegerEntry := getExpirationLedgerEntry(expLedgerKey)
assert.NoError(t, writer.UpsertLedgerEntry(expLegerEntry))

// Third read transaction, after the first insert has happened in the write transaction
readTx3, err := NewLedgerEntryReader(db).NewTx(context.Background())
assert.NoError(t, err)
Expand All @@ -300,7 +334,7 @@ func TestWriteTxsDuringReadTxs(t *testing.T) {
for _, readTx := range []LedgerEntryReadTx{readTx1, readTx2, readTx3} {
_, err = readTx.GetLatestLedgerSequence()
assert.Equal(t, ErrEmptyDB, err)
present, _, err := GetLedgerEntry(readTx, key)
present, _, _, err := GetLedgerEntry(readTx, key)
assert.NoError(t, err)
assert.False(t, present)
}
Expand All @@ -312,7 +346,7 @@ func TestWriteTxsDuringReadTxs(t *testing.T) {
for _, readTx := range []LedgerEntryReadTx{readTx1, readTx2, readTx3} {
_, err = readTx.GetLatestLedgerSequence()
assert.Equal(t, ErrEmptyDB, err)
present, _, err := GetLedgerEntry(readTx, key)
present, _, _, err := GetLedgerEntry(readTx, key)
assert.NoError(t, err)
assert.False(t, present)
}
Expand All @@ -323,8 +357,9 @@ func TestWriteTxsDuringReadTxs(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)

present, obtainedEntry, obtainedLedgerSequence := getLedgerEntryAndLatestLedgerSequence(t, db, key)
present, obtainedEntry, obtainedLedgerSequence, expSeq := getLedgerEntryAndLatestLedgerSequence(t, db, key)
assert.True(t, present)
require.NotNil(t, expSeq)
assert.Equal(t, ledgerSequence, obtainedLedgerSequence)
assert.Equal(t, six, *obtainedEntry.Data.ContractData.Val.U32)

Expand Down Expand Up @@ -367,8 +402,12 @@ func TestConcurrentReadersAndWriter(t *testing.T) {
assert.NoError(t, err)
writer := tx.LedgerEntryWriter()
for i := 0; i < 200; i++ {
_, entry := getContractDataLedgerEntry(t, data(i))
key, entry := getContractDataLedgerEntry(t, data(i))
assert.NoError(t, writer.UpsertLedgerEntry(entry))
expLedgerKey, err := entryKeyToExpirationEntryKey(key)
assert.NoError(t, err)
expLegerEntry := getExpirationLedgerEntry(expLedgerKey)
assert.NoError(t, writer.UpsertLedgerEntry(expLegerEntry))
}
assert.NoError(t, tx.Commit(ledgerSequence))
logMessageCh <- fmt.Sprintf("Wrote ledger %d", ledgerSequence)
Expand Down Expand Up @@ -399,7 +438,7 @@ func TestConcurrentReadersAndWriter(t *testing.T) {
return
default:
}
found, ledgerEntry, ledger, err := getLedgerEntryAndLatestLedgerSequenceWithErr(db, key)
found, ledgerEntry, ledger, _, err := getLedgerEntryAndLatestLedgerSequenceWithErr(db, key)
if err != nil {
if err != ErrEmptyDB {
t.Fatalf("reader %d failed with error %v\n", keyVal, err)
Expand Down Expand Up @@ -479,7 +518,7 @@ func benchmarkLedgerEntry(b *testing.B, cached bool, includeExpired bool) {
assert.NoError(b, err)
for i := 0; i < numQueriesPerOp; i++ {
b.StartTimer()
found, _, err := GetLedgerEntry(readTx, key)
found, _, _, err := GetLedgerEntry(readTx, key)
b.StopTimer()
assert.NoError(b, err)
assert.True(b, found)
Expand Down
3 changes: 3 additions & 0 deletions cmd/soroban-rpc/internal/methods/get_ledger_entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type LedgerEntryResult struct {
XDR string `json:"xdr"`
// Last modified ledger for this entry.
LastModifiedLedger int64 `json:"lastModifiedLedgerSeq,string"`
// The expiration ledger, available for entries that have expiration ledgers.
ExpirationLedger *uint32 `json:"expirationLedgerSeq,string,omitempty"`
}

type GetLedgerEntriesResponse struct {
Expand Down Expand Up @@ -103,6 +105,7 @@ func NewGetLedgerEntriesHandler(logger *log.Entry, ledgerEntryReader db.LedgerEn
Key: request.Keys[i],
XDR: ledgerXDR,
LastModifiedLedger: int64(ledgerKeyAndEntry.Entry.LastModifiedLedgerSeq),
ExpirationLedger: ledgerKeyAndEntry.ExpirationLedgerSeq,
})
}

Expand Down
5 changes: 4 additions & 1 deletion cmd/soroban-rpc/internal/methods/get_ledger_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type GetLedgerEntryResponse struct {
XDR string `json:"xdr"`
LastModifiedLedger int64 `json:"lastModifiedLedgerSeq,string"`
LatestLedger int64 `json:"latestLedger,string"`
// The expiration ledger, available for entries that have expiration ledgers.
ExpirationLedger *uint32 `json:"expirationLedgerSeq,string,omitempty"`
}

// NewGetLedgerEntryHandler returns a json rpc handler to retrieve the specified ledger entry from stellar core
Expand Down Expand Up @@ -61,7 +63,7 @@ func NewGetLedgerEntryHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntr
}
}

present, ledgerEntry, err := db.GetLedgerEntry(tx, key)
present, ledgerEntry, ledgerExpirationSeq, err := db.GetLedgerEntry(tx, key)
if err != nil {
logger.WithError(err).WithField("request", request).
Info("could not obtain ledger entry from storage")
Expand All @@ -81,6 +83,7 @@ func NewGetLedgerEntryHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntr
response := GetLedgerEntryResponse{
LastModifiedLedger: int64(ledgerEntry.LastModifiedLedgerSeq),
LatestLedger: int64(latestLedger),
ExpirationLedger: ledgerExpirationSeq,
}
if response.XDR, err = xdr.MarshalBase64(ledgerEntry.Data); err != nil {
logger.WithError(err).WithField("request", request).
Expand Down
Loading