This is a middleware system that bridges Qubic nodes with external oracle services (price feeds, etc.). It enables ability to access real-world data.
┌─────────────────────────────────┐
│ Qubic Core Nodes │
│ (Multiple nodes, whitelisted) │
└────────────────┬────────────────┘
│ TCP (Port 31841)
│ OracleMachineQuery/Reply
▼
┌─────────────────────────────────┐
│ Oracle Machine Node │
│ ┌───────────────────────────┐ │
│ │ NodeConnection │ │ ← Qubic node connections
│ │ (TCP Server) │ │
│ └─────────────┬─────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ RequestHandler │ │ ← Routes queries, manages cache
│ │ + OracleCache │ │
│ └─────────────┬─────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ InterfaceClient[0..N] │ │ ← One client per oracle service
│ │ (Worker Threads) │ │
│ └─────────────┬─────────────┘ │
└────────────────┼────────────────┘
┌────────┴────────┐
▼ ▼
┌─────────┐ ┌──────────┐
│ Price │ │ Weather │
│ Service │ │ Service │
│ :31842 │ │ :31843 │
└────┬────┘ └──────────┘
│
┌────┴────┬─────────┐
▼ ▼ ▼
Mock CoinGecko (Other)
Provider Provider Providers
1. Qubic Node sends OracleMachineQuery
↓
2. NodeConnection receives and validates
↓
3. RequestHandler checks cache
↓ (cache miss)
4. InterfaceClient forwards to oracle service
↓
5. Oracle service fetches data (e.g., from CoinGecko API)
↓
6. Response cached and returned to Qubic node
| Module | Responsibility |
|---|---|
| OracleMachineNode | Main orchestrator, lifecycle management, signal handling |
| NodeConnection | TCP server, IP whitelist validation, session management |
| RequestHandler | Query parsing, cache lookup, routing to InterfaceClients |
| InterfaceClient | Persistent connection to oracle service, async request queue |
| OracleCache | TTL-based caching with automatic cleanup |
| BaseOracleService | Abstract base for oracle service implementations |
File: node/src/oracle_machine_node.cpp
The main entry point that:
- Initializes all modules
- Sets up signal handlers (SIGINT, SIGTERM)
- Starts NodeConnection and InterfaceClients
- Coordinates graceful shutdown
// Lifecycle
OracleMachineNode node;
node.initialize(); // Setup modules
node.start(); // Start TCP server + workers
node.waitForShutdown(); // Block until Ctrl+C
node.stop(); // Graceful cleanupFile: node/src/node_connection.cpp
TCP server that:
- Listens on configurable port (default 31841, this should be changed to 21841 if mainet)
- Validates incoming IPs against whitelist
- Creates sessions for accepted connections
- Routes messages to RequestHandler
File: node/src/request_handler.cpp
Query processor that:
- Parses OracleMachineQuery messages
- Checks OracleCache before forwarding
- Routes to appropriate InterfaceClient by
oracleInterfaceIndex - Builds OracleMachineReply responses
File: node/src/interface_client.cpp
Service client that:
- Maintains persistent TCP connection to oracle service
- Uses worker thread with request queue
- Supports sync (
query()) and async (queryAsync()) operations - Auto-reconnects on connection loss
The InterfaceClient uses a producer-consumer pattern for async request processing.
File: node/src/oracle_cache.cpp
Caching layer: Improvement later
- Key:
{oracleQueryId, oracleInterfaceIndex}
File: submodules/qubic_core/src/network_messages/header.h
Offset | Size | Field | Type | Description
-------|------|----------|----------|----------------------------------
0 | 3 | _size | uint24 | Total message size (little-endian)
3 | 1 | _type | uint8 | Message type (190=Query, 191=Reply)
4 | 4 | _dejavu | uint32 | Deduplication identifier
Offset | Size | Field | Type | Description
-------|------|-----------------------|--------|---------------------------
0 | 8 | oracleQueryId | uint64 | Query identifier for reply correlation
8 | 4 | oracleInterfaceIndex | uint32 | Interface type (0=Price, 1=Weather)
12 | 4 | timeoutInMilliseconds | uint32 | Query timeout
Message Type: 190
File: submodules/qubic_core/src/oracle_core/core_om_network_messages.h
Offset | Size | Field | Type | Description
-------|------|-------------------------|--------|---------------------------
0 | 8 | oracleQueryId | uint64 | Matches query ID
8 | 2 | oracleMachineErrorFlags | uint16 | Error/status flags
10 | 2 | _padding0 | uint16 | Reserved
12 | 4 | _padding1 | uint32 | Reserved
Message Type: 191
Interface Index: 0
Query Payload (Price::OracleQuery):
Offset | Size | Field | Type | Description
-------|------|-----------|-------------|------------------
0 | 32 | oracle | m256i (id) | Provider name
32 | 12 | timestamp | DateAndTime | Query timestamp
44 | 32 | currency1 | m256i (id) | Base currency
76 | 32 | currency2 | m256i (id) | Quote currency
Reply Payload (Price::OracleReply):
Offset | Size | Field | Type | Description
-------|------|-------------|--------|------------------
0 | 8 | numerator | sint64 | Price numerator
8 | 8 | denominator | sint64 | Price denominator
File: submodules/qubic_core/src/oracle_interfaces/Price.h
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
libcurl4-openssl-dev \
libyaml-cpp-dev \
ca-certificatesRequirements:
- CMake >= 3.21
- C++20 compiler
- libcurl (for price service)
git clone <repository-url>
cd oracle-machine
# Initialize submodules
git submodule update --init --recursive# Create build directory
mkdir build && cd build
# Configure (Release)
cmake .. -DCMAKE_BUILD_TYPE=Release
# Or Debug with symbols
cmake .. -DCMAKE_BUILD_TYPE=Debug
# Build
make -j4
# Binaries are in build/bin/
ls bin/
# oracle_machine_node
# price_oracle_servicecd docker
# Full build (creates dev, runtime, and final images)
./build_docker.sh --build-all 1 --version latest
# Quick rebuild (reuses dev image)
./build_docker.sh --build-all 0 --version latestDocker images created:
oracle-machine:dev- Build environment with compilersoracle-machine:rt- Minimal runtime baseoracle-machine:latest- Final image with binaries
| Variable | Default | Description |
|---|---|---|
OM_SERVER_PORT |
31841 | Oracle Machine node listening port |
QUBIC_NODES |
10.29.1.22,192.168.1.2 | Comma-separated whitelist of Qubic node IPs |
PRICE_SERVICE_HOST |
0.0.0.0 | Price service host |
PRICE_SERVICE_PORT |
9001 | Price service port |
WEATHER_SERVICE_HOST |
0.0.0.0 | Weather service host |
WEATHER_SERVICE_PORT |
9002 | Weather service port |
# Copy template
cp example_env .env
# Edit configuration
vim .envExample .env:
OM_SERVER_PORT=31841
QUBIC_NODES=10.29.1.22,127.0.0.1
PRICE_SERVICE_HOST=127.0.0.1
PRICE_SERVICE_PORT=31842
WEATHER_SERVICE_HOST=127.0.0.1
WEATHER_SERVICE_PORT=31843The main different is if you deploy the Docker on the same machine, you need config the SERVICE_HOST as Docker service name
For Docker deployment, create docker/docker_local.env:
OM_SERVER_PORT=31841
QUBIC_NODES=10.29.1.22,127.0.0.1
# Use Docker service names
PRICE_SERVICE_HOST=price-oracle-service
PRICE_SERVICE_PORT=31842
HOST_UID=1000
HOST_GID=1000Terminal 1 - Price Oracle Service:
source .env
./build/bin/price_oracle_service --log ./logs/price_service.logTerminal 2 - Oracle Machine Node:
source .env
./build/bin/oracle_machine_node --log ./logs/om_node.logcd docker
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f oracle-machine-node
docker-compose logs -f price-oracle-service
# Stop services
docker-compose downTo add a new oracle service (e.g., Weather), you need:
- Define interface structures in
qubic_core - Create service implementation inheriting from
BaseOracleService - Implement data providers
- Add configuration entries
- Create CMakeLists.txt
oracles/
├── core/
│ ├── base_oracle_service.h # Base class
│ └── base_oracle_service.cpp
└── your_service/ # NEW
├── CMakeLists.txt
└── src/
├── main.cpp
├── your_service.h
├── your_service.cpp
└── your_provider.h # Optional: data providers
-
Define Interface in Qubic Core
- Create
submodules/qubic_core/src/oracle_interfaces/YourInterface.h - Define unique
oracleInterfaceIndex(next available number) - Define
OracleQuerystruct (input fields) - Define
OracleReplystruct (output fields)
- Create
-
Create Service Directory
- Create
oracles/your_service/folder - Add
CMakeLists.txtandsrc/subfolder - Use
oracles/price/as template
- Create
-
Implement Service Class
- Inherit from
BaseOracleService - Implement
processInterfaceQuery()method - Return
0for success, non-zero for errors
- Inherit from
-
Implement Data Provider(s)
- Create provider class to fetch external data
- Handle caching and rate limiting as needed
-
Create Main Entry Point
- Load config, initialize logger
- Create service and call
start()
-
Register in Build System
- Add
add_subdirectory(your_service)tooracles/CMakeLists.txt
- Add
-
Add Configuration
- Add interface to
libs/om_common/include/om_common/default_config.h - Add
YOUR_SERVICE_HOSTandYOUR_SERVICE_PORTto.env
- Add interface to
| Purpose | File |
|---|---|
| Base class | oracles/core/base_oracle_service.h |
| Example service | oracles/price/src/price_service.cpp |
| Example CMake | oracles/price/CMakeLists.txt |
| Interface example | submodules/qubic_core/src/oracle_interfaces/Price.h |
| Config defaults | libs/om_common/include/om_common/default_config.h |