Oracle
Oracle Architecture
Kaizen uses a pull-based oracle model where the Executor pulls prices from the Oracle Service at checkpoint time.
Pull vs Push
| Aspect | Push (old) | Pull (current) |
|---|---|---|
| Mechanism | Oracle submits OracleFeedTx | Executor calls HTTP endpoint |
| Nonce management | Required | Not needed |
| Ordering | Mempool-dependent | Block-level deterministic |
| Latency | Variable | Predictable (10ms timeout) |
Oracle Service
The Oracle Service connects to multiple WebSocket sources (Binance, Coinbase, Kraken, OKX) and exposes an HTTP API with aggregated prices:
# Get current prices
GET http://oracle:8550/prices
# Response
{
"timestamp": 1733251200000,
"prices": {
"BTC/USDT": {
"base": "0x0000...0001",
"quote": "0x0000...0100",
"price": "104250000000",
"decimals": 6,
"lastUpdate": 1733251199950
}
}
}
# Health check
GET http://oracle:8550/healthPrice Pulling Flow
Every 100ms checkpoint:
- Executor calls
GET /priceswith 10ms timeout - On success: use returned prices
- On timeout/error: use last known prices (fallback)
- If stale > 3s: RFQ operations paused
// Simplified flow
let oracle_snapshot = match oracle_client.fetch_prices(timestamp).await {
Ok(prices) => prices,
Err(_) => fallback_to_last_known()
};
engine.begin_block(height, timestamp, &oracle_snapshot);Oracle Health Check
When accepting new orders, check if oracle is "fresh":
If last update was more than 3 seconds ago → Reject thesisReasons:
- Prevent orders from executing at wrong prices during oracle failures
- 3 seconds is 30 blocks. Sufficient margin.
Ring Buffer
Oracle prices come in every 100ms. Storing all history in state would be too large, so Ring Buffer keeps only recent data in state.
Ring Buffer Size: 65536 slots
Slot Duration: 100ms
Total Duration: 65536 × 100ms = 6553.6 seconds ≈ 1.8 hoursStorage Requirements
| Metric | Value |
|---|---|
| Bytes per slot | 16 (timestamp 8 + price 8) |
| Total per market | 65536 × 16 = 1MB |
| 50 markets | ~50MB |
Small enough to not burden RocksDB.
Why Ring Buffer is Needed
For breach detection, must verify price at specific points. For example, to verify "price was out of range 10 minutes ago", that timestamp's data must exist.
Backfill
When past data not in ring buffer is needed:
- Settlement attempt → Returns "NeedsBackfill" error
- Oracle Updater (or separate Backfill Relayer) fetches data externally
- Submit BackfillOracle transaction with Admin permission
- Retry settlement → Success
External oracles (Pyth, Chainlink) provide history APIs, so backfill is always possible except for garbage tokens.
Configuration
[oracle]
ring_buffer_size = 65536 # Price history slots
max_stale_ms = 3000 # Max oracle staleness (3s)
service_url = "http://oracle:8550" # Oracle service URL
timeout_ms = 10 # Pull timeout (10ms)| Parameter | Default | Description |
|---|---|---|
ring_buffer_size | 65536 | Number of price slots per market |
max_stale_ms | 3000 | Maximum allowed age for oracle data (3 seconds) |
service_url | localhost:8550 | Oracle HTTP service URL |
timeout_ms | 10 | HTTP request timeout in milliseconds |
