Skip to content

Commit 2b4e8be

Browse files
feat(oracle): expose health endpoints and lag monitoring
- Add FeeEstimatorService to HealthModule providers - Add getPendingRequests() to LagMonitorService - Inject LagMonitorService into HealthController - GET /health includes pendingLagRequests count - GET /oracle/status includes full lag.pendingRequests array - Document endpoints, lag alerting, and monitoring setup in README
1 parent 2e29fe8 commit 2b4e8be

4 files changed

Lines changed: 75 additions & 2 deletions

File tree

oracle/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,62 @@ npm test
104104
- ✅ Already-finalized raffle handling
105105
- ✅ Error handling and retry behavior
106106

107+
## Health & Monitoring
108+
109+
### Endpoints
110+
111+
| Endpoint | Description |
112+
|---|---|
113+
| `GET /health` | Liveness check — returns `healthy`/`unhealthy` + pending lag count |
114+
| `GET /oracle/status` | Full status — metrics, lag, RPC health, multi-oracle state, recent errors |
115+
116+
**`GET /health` response:**
117+
```json
118+
{
119+
"status": "healthy",
120+
"timestamp": "2026-04-23T12:00:00.000Z",
121+
"pendingLagRequests": 0
122+
}
123+
```
124+
125+
**`GET /oracle/status` response (abbreviated):**
126+
```json
127+
{
128+
"status": "healthy",
129+
"metrics": {
130+
"queueDepth": 2,
131+
"lastProcessedAt": "2026-04-23T11:59:00.000Z",
132+
"totalProcessed": 142,
133+
"totalFailed": 1,
134+
"successRate": "99.30%",
135+
"streamStatus": "connected"
136+
},
137+
"lag": {
138+
"pendingCount": 1,
139+
"pendingRequests": [
140+
{ "requestId": "req-abc", "raffleId": 7, "requestedAtLedger": 1234500 }
141+
]
142+
},
143+
"rpc": [{ "url": "https://soroban-testnet.stellar.org", "healthy": true }],
144+
"recentErrors": []
145+
}
146+
```
147+
148+
### Lag Alerting
149+
150+
`LagMonitorService` tracks every `RandomnessRequested` event by ledger number. If a request is not fulfilled within **100 ledgers** (~8 minutes on Stellar), an `[ALERT]` log is emitted:
151+
152+
```
153+
[ALERT] Request req-abc for raffle 7 not fulfilled within 100 ledgers. Lag: 103
154+
```
155+
156+
### Recommended Monitoring Setup
157+
158+
- **Liveness probe**: `GET /health` — use as Kubernetes `livenessProbe`
159+
- **Alerting**: Scrape logs for `[ALERT]` pattern or wire a log aggregator
160+
- **Metrics**: `queueDepth > 10` warns; `queueDepth > 50` marks unhealthy
161+
- **Heartbeat**: Oracle pings the contract every `HEARTBEAT_INTERVAL_MS` (default: 1 hour)
162+
107163
## Configuration
108164

109165
The service requires the following environment variables for queue operations:

oracle/src/health/health.controller.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Controller, Get } from '@nestjs/common';
22
import { HealthService } from './health.service';
3+
import { LagMonitorService } from './lag-monitor.service';
34
import { OracleRegistryService } from '../multi-oracle/oracle-registry.service';
45
import { MultiOracleCoordinatorService } from '../multi-oracle/multi-oracle-coordinator.service';
56
import { TxSubmitterService } from '../submitter/tx-submitter.service';
@@ -8,6 +9,7 @@ import { TxSubmitterService } from '../submitter/tx-submitter.service';
89
export class HealthController {
910
constructor(
1011
private readonly healthService: HealthService,
12+
private readonly lagMonitor: LagMonitorService,
1113
private readonly oracleRegistry: OracleRegistryService,
1214
private readonly multiOracleCoordinator: MultiOracleCoordinatorService,
1315
private readonly txSubmitter: TxSubmitterService,
@@ -19,6 +21,7 @@ export class HealthController {
1921
return {
2022
status: isHealthy ? 'healthy' : 'unhealthy',
2123
timestamp: new Date().toISOString(),
24+
pendingLagRequests: this.lagMonitor.getPendingCount(),
2225
};
2326
}
2427

@@ -28,8 +31,8 @@ export class HealthController {
2831
const isHealthy = this.healthService.isHealthy();
2932
const multiOracleConfig = this.oracleRegistry.getConfig();
3033
const pendingTrackers = this.multiOracleCoordinator.getPendingTrackers();
31-
3234
const rpcStatus = await this.txSubmitter.getRpcStatus();
35+
const pendingLag = this.lagMonitor.getPendingRequests();
3336

3437
return {
3538
status: isHealthy ? 'healthy' : 'unhealthy',
@@ -50,6 +53,15 @@ export class HealthController {
5053
streamUptimeMs: metrics.streamUptimeMs,
5154
lastStreamError: metrics.lastStreamError,
5255
},
56+
lag: {
57+
pendingCount: pendingLag.length,
58+
pendingRequests: pendingLag.map(r => ({
59+
requestId: r.requestId,
60+
raffleId: r.raffleId,
61+
requestedAtLedger: r.requestedAtLedger,
62+
age: new Date().toISOString(),
63+
})),
64+
},
5365
multiOracle: {
5466
enabled: multiOracleConfig.enabled,
5567
mode: multiOracleConfig.enabled ? 'multi-oracle' : 'single-oracle',

oracle/src/health/health.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { LagMonitorService } from './lag-monitor.service';
55
import { HeartbeatService } from './heartbeat.service';
66
import { ContractService } from '../contract/contract.service';
77
import { TxSubmitterService } from '../submitter/tx-submitter.service';
8+
import { FeeEstimatorService } from '../submitter/fee-estimator.service';
89

910
@Module({
1011
controllers: [HealthController],
11-
providers: [HealthService, LagMonitorService, HeartbeatService, ContractService, TxSubmitterService],
12+
providers: [HealthService, LagMonitorService, HeartbeatService, ContractService, TxSubmitterService, FeeEstimatorService],
1213
exports: [HealthService, LagMonitorService, HeartbeatService],
1314
})
1415
export class HealthModule {}

oracle/src/health/lag-monitor.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@ export class LagMonitorService {
4747
getPendingCount(): number {
4848
return this.pendingRequests.size;
4949
}
50+
51+
getPendingRequests(): PendingRequest[] {
52+
return Array.from(this.pendingRequests.values());
53+
}
5054
}

0 commit comments

Comments
 (0)