Skip to content

Commit

Permalink
Add ledger range to getHealth endpoint (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
2opremio authored Apr 12, 2024
1 parent 961cf64 commit 367a936
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 67 deletions.
7 changes: 7 additions & 0 deletions cmd/soroban-rpc/internal/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,10 @@ func readEvents(networkPassphrase string, ledgerCloseMeta xdr.LedgerCloseMeta) (
}
return events, err
}

// GetLedgerRange returns the first and latest ledger available in the store.
func (m *MemoryStore) GetLedgerRange() ledgerbucketwindow.LedgerRange {
m.lock.RLock()
defer m.lock.RUnlock()
return m.eventsByLedger.GetLedgerRange()
}
11 changes: 10 additions & 1 deletion cmd/soroban-rpc/internal/jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler {
Logger: func(text string) { params.Logger.Debug(text) },
},
}

// Get the largest history window
var ledgerRangeGetter methods.LedgerRangeGetter = params.EventStore
var retentionWindow = cfg.EventLedgerRetentionWindow
if cfg.TransactionLedgerRetentionWindow > cfg.EventLedgerRetentionWindow {
retentionWindow = cfg.TransactionLedgerRetentionWindow
ledgerRangeGetter = params.TransactionStore
}

handlers := []struct {
methodName string
underlyingHandler jrpc2.Handler
Expand All @@ -143,7 +152,7 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler {
}{
{
methodName: "getHealth",
underlyingHandler: methods.NewHealthCheck(params.TransactionStore, cfg.MaxHealthyLedgerLatency),
underlyingHandler: methods.NewHealthCheck(retentionWindow, ledgerRangeGetter, cfg.MaxHealthyLedgerLatency),
longName: "get_health",
queueLimit: cfg.RequestBacklogGetHealthQueueLimit,
requestDurationLimit: cfg.MaxGetHealthExecutionDuration,
Expand Down
29 changes: 29 additions & 0 deletions cmd/soroban-rpc/internal/ledgerbucketwindow/ledgerbucketwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,35 @@ func (w *LedgerBucketWindow[T]) Len() uint32 {
return uint32(len(w.buckets))
}

type LedgerInfo struct {
Sequence uint32
CloseTime int64
}

type LedgerRange struct {
FirstLedger LedgerInfo
LastLedger LedgerInfo
}

func (w *LedgerBucketWindow[T]) GetLedgerRange() LedgerRange {
length := w.Len()
if length == 0 {
return LedgerRange{}
}
firstBucket := w.Get(0)
lastBucket := w.Get(length - 1)
return LedgerRange{
FirstLedger: LedgerInfo{
Sequence: firstBucket.LedgerSeq,
CloseTime: firstBucket.LedgerCloseTimestamp,
},
LastLedger: LedgerInfo{
Sequence: lastBucket.LedgerSeq,
CloseTime: lastBucket.LedgerCloseTimestamp,
},
}
}

// Get obtains a bucket from the window
func (w *LedgerBucketWindow[T]) Get(i uint32) *LedgerBucket[T] {
length := w.Len()
Expand Down
3 changes: 2 additions & 1 deletion cmd/soroban-rpc/internal/methods/get_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/creachadair/jrpc2/handler"
"github.com/stellar/go/xdr"

"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow"
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/transactions"
)

Expand Down Expand Up @@ -67,7 +68,7 @@ type GetTransactionRequest struct {
}

type transactionGetter interface {
GetTransaction(hash xdr.Hash) (transactions.Transaction, bool, transactions.StoreRange)
GetTransaction(hash xdr.Hash) (transactions.Transaction, bool, ledgerbucketwindow.LedgerRange)
}

func GetTransaction(getter transactionGetter, request GetTransactionRequest) (GetTransactionResponse, error) {
Expand Down
28 changes: 21 additions & 7 deletions cmd/soroban-rpc/internal/methods/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,32 @@ import (
"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/handler"

"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/transactions"
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow"
)

type HealthCheckResult struct {
Status string `json:"status"`
Status string `json:"status"`
LatestLedger uint32 `json:"latestLedger"`
OldestLedger uint32 `json:"oldestLedger"`
LedgerRetentionWindow uint32 `json:"ledgerRetentionWindow"`
}

type LedgerRangeGetter interface {
GetLedgerRange() ledgerbucketwindow.LedgerRange
}

// NewHealthCheck returns a health check json rpc handler
func NewHealthCheck(txStore *transactions.MemoryStore, maxHealthyLedgerLatency time.Duration) jrpc2.Handler {
func NewHealthCheck(retentionWindow uint32, ledgerRangeGetter LedgerRangeGetter, maxHealthyLedgerLatency time.Duration) jrpc2.Handler {
return handler.New(func(ctx context.Context) (HealthCheckResult, error) {
ledgerInfo := txStore.GetLatestLedger()
if ledgerInfo.Sequence < 1 {
ledgerRange := ledgerRangeGetter.GetLedgerRange()
if ledgerRange.LastLedger.Sequence < 1 {
return HealthCheckResult{}, jrpc2.Error{
Code: jrpc2.InternalError,
Message: "data stores are not initialized",
}
}
lastKnownLedgerCloseTime := time.Unix(ledgerInfo.CloseTime, 0)

lastKnownLedgerCloseTime := time.Unix(ledgerRange.LastLedger.CloseTime, 0)
lastKnownLedgerLatency := time.Since(lastKnownLedgerCloseTime)
if lastKnownLedgerLatency > maxHealthyLedgerLatency {
roundedLatency := lastKnownLedgerLatency.Round(time.Second)
Expand All @@ -35,6 +43,12 @@ func NewHealthCheck(txStore *transactions.MemoryStore, maxHealthyLedgerLatency t
Message: msg,
}
}
return HealthCheckResult{Status: "healthy"}, nil
result := HealthCheckResult{
Status: "healthy",
LatestLedger: ledgerRange.LastLedger.Sequence,
OldestLedger: ledgerRange.FirstLedger.Sequence,
LedgerRetentionWindow: retentionWindow,
}
return result, nil
})
}
19 changes: 6 additions & 13 deletions cmd/soroban-rpc/internal/methods/send_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/stellar/go/xdr"

"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces"
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/transactions"
)

// SendTransactionResponse represents the transaction submission response returned Soroban-RPC
Expand Down Expand Up @@ -45,14 +44,8 @@ type SendTransactionRequest struct {
Transaction string `json:"transaction"`
}

// LatestLedgerStore is a store which returns the latest ingested ledger.
type LatestLedgerStore interface {
// GetLatestLedger returns the latest ingested ledger.
GetLatestLedger() transactions.LedgerInfo
}

// NewSendTransactionHandler returns a submit transaction json rpc handler
func NewSendTransactionHandler(daemon interfaces.Daemon, logger *log.Entry, store LatestLedgerStore, passphrase string) jrpc2.Handler {
func NewSendTransactionHandler(daemon interfaces.Daemon, logger *log.Entry, ledgerRangeGetter LedgerRangeGetter, passphrase string) jrpc2.Handler {
submitter := daemon.CoreClient()
return handler.New(func(ctx context.Context, request SendTransactionRequest) (SendTransactionResponse, error) {
var envelope xdr.TransactionEnvelope
Expand All @@ -74,7 +67,7 @@ func NewSendTransactionHandler(daemon interfaces.Daemon, logger *log.Entry, stor
}
txHash := hex.EncodeToString(hash[:])

ledgerInfo := store.GetLatestLedger()
latestLedgerInfo := ledgerRangeGetter.GetLedgerRange().LastLedger
resp, err := submitter.SubmitTransaction(ctx, request.Transaction)
if err != nil {
logger.WithError(err).
Expand Down Expand Up @@ -110,15 +103,15 @@ func NewSendTransactionHandler(daemon interfaces.Daemon, logger *log.Entry, stor
DiagnosticEventsXDR: events,
Status: resp.Status,
Hash: txHash,
LatestLedger: ledgerInfo.Sequence,
LatestLedgerCloseTime: ledgerInfo.CloseTime,
LatestLedger: latestLedgerInfo.Sequence,
LatestLedgerCloseTime: latestLedgerInfo.CloseTime,
}, nil
case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater:
return SendTransactionResponse{
Status: resp.Status,
Hash: txHash,
LatestLedger: ledgerInfo.Sequence,
LatestLedgerCloseTime: ledgerInfo.CloseTime,
LatestLedger: latestLedgerInfo.Sequence,
LatestLedgerCloseTime: latestLedgerInfo.CloseTime,
}, nil
default:
logger.WithField("status", resp.Status).
Expand Down
10 changes: 8 additions & 2 deletions cmd/soroban-rpc/internal/test/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/jhttp"
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/methods"
"github.com/stretchr/testify/assert"

"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow"
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/methods"
)

func TestHealth(t *testing.T) {
Expand All @@ -20,5 +22,9 @@ func TestHealth(t *testing.T) {
if err := client.CallResult(context.Background(), "getHealth", nil, &result); err != nil {
t.Fatalf("rpc call failed: %v", err)
}
assert.Equal(t, methods.HealthCheckResult{Status: "healthy"}, result)
assert.Equal(t, "healthy", result.Status)
assert.Equal(t, uint32(ledgerbucketwindow.DefaultEventLedgerRetentionWindow), result.LedgerRetentionWindow)
assert.Greater(t, result.OldestLedger, uint32(0))
assert.Greater(t, result.LatestLedger, uint32(0))
assert.GreaterOrEqual(t, result.LatestLedger, result.OldestLedger)
}
45 changes: 7 additions & 38 deletions cmd/soroban-rpc/internal/transactions/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ func (m *MemoryStore) IngestTransactions(ledgerCloseMeta xdr.LedgerCloseMeta) er
return nil
}

type LedgerInfo struct {
Sequence uint32
CloseTime int64
}

type Transaction struct {
Result []byte // XDR encoded xdr.TransactionResult
Meta []byte // XDR encoded xdr.TransactionMeta
Expand All @@ -153,48 +148,22 @@ type Transaction struct {
FeeBump bool
ApplicationOrder int32
Successful bool
Ledger LedgerInfo
}

type StoreRange struct {
FirstLedger LedgerInfo
LastLedger LedgerInfo
Ledger ledgerbucketwindow.LedgerInfo
}

// GetLatestLedger returns the latest ledger available in the store.
func (m *MemoryStore) GetLatestLedger() LedgerInfo {
// GetLedgerRange returns the first and latest ledger available in the store.
func (m *MemoryStore) GetLedgerRange() ledgerbucketwindow.LedgerRange {
m.lock.RLock()
defer m.lock.RUnlock()
if m.transactionsByLedger.Len() > 0 {
lastBucket := m.transactionsByLedger.Get(m.transactionsByLedger.Len() - 1)
return LedgerInfo{
Sequence: lastBucket.LedgerSeq,
CloseTime: lastBucket.LedgerCloseTimestamp,
}
}
return LedgerInfo{}
return m.transactionsByLedger.GetLedgerRange()
}

// GetTransaction obtains a transaction from the store and whether it's present and the current store range
func (m *MemoryStore) GetTransaction(hash xdr.Hash) (Transaction, bool, StoreRange) {
func (m *MemoryStore) GetTransaction(hash xdr.Hash) (Transaction, bool, ledgerbucketwindow.LedgerRange) {
startTime := time.Now()
m.lock.RLock()
defer m.lock.RUnlock()
var storeRange StoreRange
if m.transactionsByLedger.Len() > 0 {
firstBucket := m.transactionsByLedger.Get(0)
lastBucket := m.transactionsByLedger.Get(m.transactionsByLedger.Len() - 1)
storeRange = StoreRange{
FirstLedger: LedgerInfo{
Sequence: firstBucket.LedgerSeq,
CloseTime: firstBucket.LedgerCloseTimestamp,
},
LastLedger: LedgerInfo{
Sequence: lastBucket.LedgerSeq,
CloseTime: lastBucket.LedgerCloseTimestamp,
},
}
}
storeRange := m.transactionsByLedger.GetLedgerRange()
internalTx, ok := m.transactions[hash]
if !ok {
return Transaction{}, false, storeRange
Expand Down Expand Up @@ -229,7 +198,7 @@ func (m *MemoryStore) GetTransaction(hash xdr.Hash) (Transaction, bool, StoreRan
FeeBump: internalTx.feeBump,
Successful: internalTx.successful,
ApplicationOrder: internalTx.applicationOrder,
Ledger: LedgerInfo{
Ledger: ledgerbucketwindow.LedgerInfo{
Sequence: internalTx.bucket.LedgerSeq,
CloseTime: internalTx.bucket.LedgerCloseTimestamp,
},
Expand Down
11 changes: 6 additions & 5 deletions cmd/soroban-rpc/internal/transactions/transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces"
"github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow"
)

func expectedTransaction(t *testing.T, ledger uint32, feeBump bool) Transaction {
Expand All @@ -36,16 +37,16 @@ func expectedTransaction(t *testing.T, ledger uint32, feeBump bool) Transaction
return tx
}

func expectedLedgerInfo(ledgerSequence uint32) LedgerInfo {
return LedgerInfo{
func expectedLedgerInfo(ledgerSequence uint32) ledgerbucketwindow.LedgerInfo {
return ledgerbucketwindow.LedgerInfo{
Sequence: ledgerSequence,
CloseTime: ledgerCloseTime(ledgerSequence),
}

}

func expectedStoreRange(startLedger uint32, endLedger uint32) StoreRange {
return StoreRange{
func expectedStoreRange(startLedger uint32, endLedger uint32) ledgerbucketwindow.LedgerRange {
return ledgerbucketwindow.LedgerRange{
FirstLedger: expectedLedgerInfo(startLedger),
LastLedger: expectedLedgerInfo(endLedger),
}
Expand Down Expand Up @@ -295,7 +296,7 @@ func TestIngestTransactions(t *testing.T) {

_, ok, storeRange := store.GetTransaction(txHash(1, false))
require.False(t, ok)
require.Equal(t, StoreRange{}, storeRange)
require.Equal(t, ledgerbucketwindow.LedgerRange{}, storeRange)

// Insert ledger 1
require.NoError(t, store.IngestTransactions(txMeta(1, false)))
Expand Down

0 comments on commit 367a936

Please sign in to comment.