From 1a74baa2614004b882e5e5f54f6bb665ca85c4f7 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Thu, 11 Apr 2024 19:55:56 +0200 Subject: [PATCH] Add ledger range to getHealth endpoint --- cmd/soroban-rpc/internal/events/events.go | 7 +++ cmd/soroban-rpc/internal/jsonrpc.go | 2 +- .../ledgerbucketwindow/ledgerbucketwindow.go | 29 ++++++++++++ cmd/soroban-rpc/internal/methods/health.go | 29 +++++++++--- .../internal/transactions/transactions.go | 45 +++---------------- .../transactions/transactions_test.go | 11 ++--- 6 files changed, 73 insertions(+), 50 deletions(-) diff --git a/cmd/soroban-rpc/internal/events/events.go b/cmd/soroban-rpc/internal/events/events.go index 39fdc4ce..12e8e765 100644 --- a/cmd/soroban-rpc/internal/events/events.go +++ b/cmd/soroban-rpc/internal/events/events.go @@ -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() +} diff --git a/cmd/soroban-rpc/internal/jsonrpc.go b/cmd/soroban-rpc/internal/jsonrpc.go index d122b9a0..7b7096f7 100644 --- a/cmd/soroban-rpc/internal/jsonrpc.go +++ b/cmd/soroban-rpc/internal/jsonrpc.go @@ -143,7 +143,7 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler { }{ { methodName: "getHealth", - underlyingHandler: methods.NewHealthCheck(params.TransactionStore, cfg.MaxHealthyLedgerLatency), + underlyingHandler: methods.NewHealthCheck(params.TransactionStore, params.EventStore, cfg.MaxHealthyLedgerLatency), longName: "get_health", queueLimit: cfg.RequestBacklogGetHealthQueueLimit, requestDurationLimit: cfg.MaxGetHealthExecutionDuration, diff --git a/cmd/soroban-rpc/internal/ledgerbucketwindow/ledgerbucketwindow.go b/cmd/soroban-rpc/internal/ledgerbucketwindow/ledgerbucketwindow.go index 8234b607..7225a6b3 100644 --- a/cmd/soroban-rpc/internal/ledgerbucketwindow/ledgerbucketwindow.go +++ b/cmd/soroban-rpc/internal/ledgerbucketwindow/ledgerbucketwindow.go @@ -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() diff --git a/cmd/soroban-rpc/internal/methods/health.go b/cmd/soroban-rpc/internal/methods/health.go index ab46d62a..db577f54 100644 --- a/cmd/soroban-rpc/internal/methods/health.go +++ b/cmd/soroban-rpc/internal/methods/health.go @@ -8,24 +8,36 @@ import ( "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/handler" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/events" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/transactions" ) type HealthCheckResult struct { - Status string `json:"status"` + Status string `json:"status"` + LatestLedger uint32 `json:"latestLedger"` + FirstLedger uint32 `json:"firstLedger"` } // NewHealthCheck returns a health check json rpc handler -func NewHealthCheck(txStore *transactions.MemoryStore, maxHealthyLedgerLatency time.Duration) jrpc2.Handler { +func NewHealthCheck(txStore *transactions.MemoryStore, evStore *events.MemoryStore, maxHealthyLedgerLatency time.Duration) jrpc2.Handler { return handler.New(func(ctx context.Context) (HealthCheckResult, error) { - ledgerInfo := txStore.GetLatestLedger() - if ledgerInfo.Sequence < 1 { + txLedgerRange := txStore.GetLedgerRange() + evLedgerRange := evStore.GetLedgerRange() + if txLedgerRange.FirstLedger.Sequence < 1 || evLedgerRange.FirstLedger.Sequence < 1 { return HealthCheckResult{}, jrpc2.Error{ Code: jrpc2.InternalError, Message: "data stores are not initialized", } } - lastKnownLedgerCloseTime := time.Unix(ledgerInfo.CloseTime, 0) + mergedRange := evLedgerRange + if txLedgerRange.FirstLedger.Sequence < mergedRange.FirstLedger.Sequence { + mergedRange.FirstLedger = txLedgerRange.FirstLedger + } + if txLedgerRange.LastLedger.Sequence > mergedRange.LastLedger.Sequence { + mergedRange.LastLedger = txLedgerRange.LastLedger + } + + lastKnownLedgerCloseTime := time.Unix(mergedRange.LastLedger.CloseTime, 0) lastKnownLedgerLatency := time.Since(lastKnownLedgerCloseTime) if lastKnownLedgerLatency > maxHealthyLedgerLatency { roundedLatency := lastKnownLedgerLatency.Round(time.Second) @@ -35,6 +47,11 @@ func NewHealthCheck(txStore *transactions.MemoryStore, maxHealthyLedgerLatency t Message: msg, } } - return HealthCheckResult{Status: "healthy"}, nil + result := HealthCheckResult{ + Status: "healthy", + LatestLedger: mergedRange.LastLedger.Sequence, + FirstLedger: mergedRange.FirstLedger.Sequence, + } + return result, nil }) } diff --git a/cmd/soroban-rpc/internal/transactions/transactions.go b/cmd/soroban-rpc/internal/transactions/transactions.go index 6b24f429..e5abe55c 100644 --- a/cmd/soroban-rpc/internal/transactions/transactions.go +++ b/cmd/soroban-rpc/internal/transactions/transactions.go @@ -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 @@ -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 @@ -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, }, diff --git a/cmd/soroban-rpc/internal/transactions/transactions_test.go b/cmd/soroban-rpc/internal/transactions/transactions_test.go index 73ddbaa6..4b801835 100644 --- a/cmd/soroban-rpc/internal/transactions/transactions_test.go +++ b/cmd/soroban-rpc/internal/transactions/transactions_test.go @@ -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 { @@ -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), } @@ -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)))