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

Add ledger range to getHealth endpoint #133

Merged
merged 4 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading