Skip to content

Oracle

Oracle Architecture

Kaizen uses a pull-based oracle model where the Executor pulls prices from the Oracle Service at checkpoint time.

Oracle Architecture

Pull vs Push

AspectPush (old)Pull (current)
MechanismOracle submits OracleFeedTxExecutor calls HTTP endpoint
Nonce managementRequiredNot needed
OrderingMempool-dependentBlock-level deterministic
LatencyVariablePredictable (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/health

Price Pulling Flow

Every 100ms checkpoint:

  1. Executor calls GET /prices with 10ms timeout
  2. On success: use returned prices
  3. On timeout/error: use last known prices (fallback)
  4. 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 thesis

Reasons:

  • 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 hours

Storage Requirements

MetricValue
Bytes per slot16 (timestamp 8 + price 8)
Total per market65536 × 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:

  1. Settlement attempt → Returns "NeedsBackfill" error
  2. Oracle Updater (or separate Backfill Relayer) fetches data externally
  3. Submit BackfillOracle transaction with Admin permission
  4. 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)
ParameterDefaultDescription
ring_buffer_size65536Number of price slots per market
max_stale_ms3000Maximum allowed age for oracle data (3 seconds)
service_urllocalhost:8550Oracle HTTP service URL
timeout_ms10HTTP request timeout in milliseconds