- Overview
- Architecture
- Dependencies
- Configuration
- RPC API Reference
- Data Aggregation System
- Action Management System
- Deployment
- Development
miningos-wrk-ork (Orchestrator Worker) is the centralized coordination and aggregation layer for the MiningOS. It serves as the controller that:
- Manages distributed worker registration ("racks") via Hyperswarm P2P RPC
- Aggregates operational data from miners, containers, sensors, powermeters, and mining pools
- Coordinates write operations through a multi-voter approval system
- Provides unified query interface for the entire mining operation
- Stores historical time-series data with timezone-aware aggregation
┌─────────────────────────────────────────────────────┐
│ miningos-wrk-ork (Orchestrator) │
│ • Unified RPC interface │
│ • Action approval & voting │
│ • Cross-rack data aggregation │
└──────────────────┬──────────────────────────────────┘
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Rack 1 │ │ Rack 2 │ │ Rack N │ (Registered Workers)
│ miner │ │contain-│ │sensor │
│ worker │ │ er │ │ worker │
└────────┘ └────────┘ └────────┘
│ │ │
▼ ▼ ▼
[Hardware] [Hardware] [Hardware]
Key Concepts:
- Rack: A registered worker instance managing a specific type of device (miners, containers, sensors, etc.)
- Thing: An individual device or service managed by a rack
- Action: A write operation requiring approval votes before execution
- Aggregation: Consolidation of metrics across racks with statistical operations
---
title: Object Model of MiningOS including orchestrator
---
flowchart RL
bfx-wrk-base@{ shape: stadium, label: "*bfx-wrk-base*" }
tether-wrk-base@{ shape: stadium, label: "*tether-wrk-base*" }
tether-wrk-base--->bfx-wrk-base
miningos-wrk-ork@{ label: "*miningos-wrk-ork*" }
miningos-wrk-ork--->tether-wrk-base
style miningos-wrk-ork fill:#005,stroke-width:4px
- Storage: Hyperbee database (
racks) - Purpose: Maintains registered worker instances with RPC public keys
- Operations: Register, list, forget racks by type
- Facility:
svc-facs-action-approver - Storage: Hyperbee database (
action-approver) - States:
voting→ready→executing→done - Voting: Configurable positive/negative vote thresholds
- Library:
miningos-lib-stats - Storage: Hyperbee database (
tail-log-aggr) - Operations: sum, avg, obj_concat, arr_concat, alerts_aggr
- Time Ranges: Configurable intervals with timezone support
Rack Workers → RPC → Orchestrator → Aggregation → Storage
↓
Query Interface
↓
API Consumers
| Method | Operation | Description |
|---|---|---|
ping |
Read | Echo test (returns input) |
getInstanceId |
Read | Returns worker instance UUID |
Register a new worker instance.
Operation: Write
Request:
{
"id": "miner-wm-m56s-shard-0",
"type": "miner",
"info": {
"rpcPublicKey": "64-char-hex-public-key"
}
}Response: 1 (success)
Errors:
ERR_RACK_ID_INVALIDERR_RACK_TYPE_INVALIDERR_RACK_INFO_RPC_PUBKEY_INVALID
Example:
hp-rpc-cli -s wrk-st-0 -m registerRack -d '{
"id": "miner-wm-m56s-shard-0",
"type": "miner",
"info": {"rpcPublicKey": "abc123..."}
}'List registered racks.
Operation: Read
Request:
{
"type": "miner", // optional: filter by type prefix
"keys": 1 // optional: include RPC keys (default: hidden)
}Response:
[
{
"id": "miner-wm-m56s-shard-0",
"type": "miner",
"info": {
"rpcPublicKey": "..." // only if keys=1
}
}
]Errors:
ERR_TYPE_INVALID
Example:
hp-rpc-cli -s wrk-st-0 -m listRacks -d '{"keys":1}'Remove registered racks.
Operation: Write
Request:
{
"ids": ["rack-id-1", "rack-id-2"], // specific racks
"all": true // or all racks
}Response: Number of racks removed
Query devices across all racks.
Operation: Read
Request:
{
"query": { // MongoDB-style query
"last.snap.stats.status": {"$eq": "online"},
"tags": {"$in": ["t-miner"]}
},
"fields": { // Projection
"id": 1,
"last.snap.stats.hashrate": 1
},
"sort": { // Sort order
"last.snap.stats.hashrate": -1
},
"limit": 100
}Response:
[
{
"id": "miner-001",
"last": {
"snap": {
"stats": {
"hashrate": 125000000
}
}
}
}
]Notes:
- Aggregates results from all registered racks
- Supports full MongoDB query syntax via
mingo - Parallel queries with limit of 25 concurrent rack requests
Remove devices from racks.
Operation: Write
Request:
{
"ids": ["thing-id-1", "thing-id-2"],
"all": true // optional: forget all things
}Response: Number of things removed across all racks
Retrieve historical log entries.
Operation: Read
Request:
{
"logType": "error", // log type to retrieve
"query": {...}, // optional MongoDB query
"sort": {"ts": -1}, // optional sort
"limit": 100
}Response: Array of log entries from all racks
Query aggregated time-series data.
Operation: Read
Request:
{
"type": "miner", // device type
"key": "stat-5m", // data key
"tag": "t-miner", // tag filter
"fields": { // source fields
"hashrate_mhs_1m_sum": 1
},
"aggrFields": { // aggregated fields
"hashrate_mhs_1m_sum_aggr": 1,
"ts": 1,
"aggrTsRange": 1,
"aggrCount": 1,
"aggrIntervals": 1
},
"start": 1704067200000, // optional start timestamp
"end": 1704153600000, // optional end timestamp
"limit": 288, // max records
"applyAggrCrossthg": true // optional: apply cross-thing aggr
}Response:
[
{
"ts": 1704067200000,
"hashrate_mhs_1m_sum_aggr": 850000000000,
"aggrTsRange": "1704067200000-1704070800000",
"aggrCount": 25,
"aggrIntervals": 1
}
]Errors:
ERR_NOT_CONFIGUREDERR_TYPE_INVALIDERR_TYPE_AGGR_INVALID
Notes:
- Automatically adds
ts,aggrTsRange,aggrCount,aggrIntervalsto aggregated fields - Operations defined in
aggr.stats.json - Supports custom time ranges with
start/end
Query multiple aggregation types in parallel.
Operation: Read
Request:
{
"keys": [
{"type": "miner", "key": "stat-5m", "tag": "t-miner"},
{"type": "container", "key": "stat-5m", "tag": "t-container"}
],
"fields": {...}, // applied to all
"aggrFields": {...} // applied to all
}Response: Array of results (one per key)
Notes:
- Parallel execution with limit of 5 concurrent queries
- Each query must have
typespecified
Retrieve pre-aggregated daily data within a date range.
Operation: Read
Request:
{
"keys": [
{
"type": "miner",
"startDate": "2024-01-01",
"endDate": "2024-01-31",
"timezoneOffset": 0, // milliseconds
"fields": {
"hashrate_mhs_5m_avg_aggr": 1
},
"shouldReturnDailyData": false // true: daily array, false: range aggr
}
]
}Response:
[
{
"type": "miner",
"data": {
"hashrate_mhs_5m_avg_aggr": 123456789,
"aggrCount": 8640, // total snapshots
"aggrIntervals": 8640 // total intervals
},
"error": null
}
]With shouldReturnDailyData: true:
[
{
"type": "miner",
"data": [
{"ts": 1704067200000, "val": {...}},
{"ts": 1704153600000, "val": {...}}
],
"error": null
}
]Notes:
- Uses pre-stored daily aggregations from
tail-log-aggrdatabase - Requires
global.config.jsonwith timezone definitions - Timezone offset in milliseconds (e.g., UTC-3 = -10800000)
Add a comment to a thing.
Operation: Write
Request:
{
"rackId": "miner-rack-0",
"thingId": "miner-001",
"comment": "Replaced heat sink",
"user": "technician@example.com"
}Response: 1 (success) or 0 (failure)
Edit an existing comment.
Operation: Write
Request:
{
"rackId": "miner-rack-0",
"thingId": "miner-001",
"commentId": "comment-uuid",
"comment": "Updated comment text"
}Response: 1 (success) or 0 (failure)
Delete a comment.
Operation: Write
Request:
{
"rackId": "miner-rack-0",
"thingId": "miner-001",
"commentId": "comment-uuid"
}Response: 1 (success) or 0 (failure)
Submit an action for approval.
Operation: Write
Request:
{
"query": { // Target selection
"tags": {"$in": ["t-miner"]},
"last.snap.stats.status": {"$eq": "online"}
},
"action": "reboot", // Action name
"params": [], // Action parameters
"voter": "admin@example.com", // Initial voter
"authPerms": ["miner:rw"] // Required permissions
}Response:
{
"id": 1692277886841, // Action ID (null if failed)
"data": { // Full action object (when successful)
"id": 1692277886841,
"action": "reboot",
"params": [...],
"targets": {...},
"requiredPerms": ["miner"],
"votes": {...},
"state": "voting"
},
"errors": [] // Array of error messages
}Errors (Thrown - stop execution):
| Error | Condition |
|---|---|
ERR_QUERY_INVALID |
Query is not a plain object or contains invalid MongoDB syntax |
ERR_ACTION_INVALID |
Action is not a string or is empty |
ERR_PARAMS_INVALID |
Params is not an array |
ERR_ACTION_INVALID_MISSING_RACKID |
Rack actions (registerThing, updateThing, forgetThings) missing rackId in params[0] |
ERR_ACTION_INVALID_MISSING_ID |
updateThing action missing id in params[0] |
ERR_ACTION_INVALID_QUERY_ID |
forgetThings action has non-string query.id in params[0] |
Errors (Returned - action partially succeeds):
| Error | Condition |
|---|---|
ERR_ORK_ACTION_CALLS_EMPTY |
No targets found matching query and permissions |
| Rack-specific errors | Individual rack RPC failures (timeout, connection refused, etc.) |
Notes:
- When
ERR_ORK_ACTION_CALLS_EMPTYoccurs,idisnulland no action is created - Rack-level errors are non-fatal: action proceeds if at least one rack succeeds
- All racks are queried in parallel with limit of 5 concurrent requests
- Rack RPC timeout: 120 seconds
Submit multiple actions as a batch.
Operation: Write
Request:
{
"batchActionsPayload": [
{
"query": {...},
"action": "reboot",
"params": []
},
{
"query": {...},
"action": "setPowerMode",
"params": ["sleep"]
}
],
"voter": "admin@example.com",
"authPerms": ["miner:rw"],
"batchActionUID": "maintenance-001" // optional identifier
}Response: Array of action IDs and data (one per batch item)
Errors:
ERR_PAYLOAD_INVALID
Notes:
- Executes up to 5 actions in parallel
- Each action gets timestamp-prefixed UID:
{timestamp}-{batchActionUID}
Cast a vote on a pending action.
Operation: Write
Request:
{
"id": 1692277886841,
"voter": "supervisor@example.com",
"approve": true, // true=approve, false=deny
"authPerms": ["miner:rw"]
}Response: 1 (success)
Errors:
ERR_ACTION_DENIED(insufficient permissions)
Example:
hp-rpc-cli -s wrk-st-0 -m voteAction -d '{
"id": 1692890293574,
"voter": "supervisor@example.com",
"approve": true,
"authPerms": ["miner", "container"]
}'Retrieve a specific action.
Operation: Read
Request:
{
"id": 1692277886841,
"type": "voting" // voting/ready/executing/done
}Response:
{
"id": 1692277886841,
"action": "reboot",
"params": [],
"targets": {...},
"requiredPerms": ["miner"],
"votes": {
"admin@example.com": true,
"supervisor@example.com": true
},
"reqVotesPos": 2,
"reqVotesNeg": 1,
"state": "ready"
}Retrieve multiple actions by IDs.
Operation: Read
Request:
{
"ids": [1692277886841, 1692277999999]
}Response:
[
{
"type": "done",
"action": {...}
},
{
"type": "voting",
"action": {...}
}
]Notes:
- Searches all state databases (voting, ready, executing, done)
- Returns
nullfor IDs not found
Query actions with filters.
Operation: Read
Request:
{
"queries": [
{
"type": "voting", // required
"filter": { // timestamp range
"gte": 1692277886841,
"lte": 1692364286841
},
"opts": {
"limit": 100
},
"query": { // optional MongoDB query
"action": {"$eq": "reboot"}
},
"fields": { // optional projection
"id": 1,
"action": 1
}
}
],
"groupBatch": false // true: group by batchActionUID
}Response:
{
"voting": [
{
"id": 1692277886841,
"action": "reboot",
"targets": {...},
"votes": {...}
}
]
}With groupBatch: true:
{
"voting": [
{
"batchActionUID": "1692277886841-maintenance-001",
"id": "1692277886841",
"actions": [
{...},
{...}
]
}
]
}Filter Options:
gt: Greater than timestampgte: Greater than or equallt: Less than timestamplte: Less than or equal
Cancel multiple actions.
Operation: Write
Request:
{
"ids": [1692277886841, 1692277999999],
"voter": "admin@example.com"
}Response: Number of actions cancelled
Retrieve global site configuration.
Operation: Read
Request:
{
"fields": { // optional projection
"nominalSiteHashrate_MHS": 1,
"isAutoSleepAllowed": 1
}
}Response:
{
"nominalSiteHashrate_MHS": 709032000000,
"isAutoSleepAllowed": false
}Update global site configuration.
Operation: Write
Request:
{
"isAutoSleepAllowed": true
}Response: Updated global config object or null
Notes:
- Currently only supports updating
isAutoSleepAllowed - Modifies in-memory configuration (not persisted to file)
Retrieve worker configuration from racks.
Operation: Read
Request:
{
"type": "miner" // device type
}Response:
[
{
"rackId": "miner-rack-0",
"config": {...} // rack-specific config
}
]Retrieve thing configuration from racks.
Operation: Read
Request:
{
"type": "miner"
}Response:
[
{
"rackId": "miner-rack-0",
"requestValue": {...} // thing-specific config
}
]Retrieve worker settings from specific rack.
Operation: Read
Request:
{
"rackId": "miner-rack-0"
}Response: Worker settings object
Errors:
ERR_RACK_ID_INVALIDERR_GET_SETTINGS_FAILED
Save worker settings to specific rack.
Operation: Write
Request:
{
"rackId": "miner-rack-0",
"entries": {
"key1": "value1",
"key2": "value2"
}
}Response: 1 (success)
Errors:
ERR_RACK_ID_INVALIDERR_ENTRIES_INVALIDERR_SAVE_SETTINGS_FAILED
The orchestrator aggregates data from distributed racks using a two-tier system:
- Real-time Aggregation (
tailLog): On-demand aggregation of current data - Historical Storage (
tail-log-aggrDB): Pre-computed daily aggregations
1. Racks collect device snapshots (5m intervals)
↓
2. Orchestrator queries racks via tailLog RPC
↓
3. Apply aggregation operations (sum/avg/concat)
↓
4. Return consolidated metrics
↓
5. Background: Store daily aggregates to DB
↓
6. API: Query historical ranges via tailLogCustomRangeAggr
1. SUBMISSION
User/API calls pushAction
→ Query resolves to target things
→ Create action with reqVotesPos/reqVotesNeg
→ State: "voting"
2. VOTING
Users call voteAction
→ Permissions checked against requiredPerms
→ Vote recorded
→ If threshold met → State: "ready"
→ If denied → State: "cancelled"
3. EXECUTION
Background worker (actionIntvlMs interval)
→ Picks up "ready" actions
→ State: "executing"
→ Calls action on all targets via ActionCaller
→ Records results/errors per target
4. COMPLETION
→ State: "done"
→ Results available via getAction/queryActions
Format: {type}:{level}
Levels:
r: Read-onlyw: Write-onlyrw: Read-write
Examples:
miner:rw: Full access to miner actionscontainer:r: Read-only container accessminer:w: Write-only miner access (can execute actions, not query)
Permission Checking:
// User has ["miner:rw", "container:r"]
// Action requires ["miner"]
hasPermission(["miner:rw", "container:r"], "miner:w") // true
hasPermission(["miner:rw", "container:r"], "container:w") // falseConfiguration:
{
reqVotesPos: 2, // Requires 2 positive votes to approve
reqVotesNeg: 1 // 1 negative vote cancels action
}Logic:
- Positive votes accumulate until
reqVotesPosreached → action ready - Any negative vote → action cancelled
- Racks can specify different vote requirements (highest used)
Features:
- Submit multiple actions with shared UID
- Query/display actions as grouped batch
- Cancel entire batch with one call
Batch UID Format: {timestamp}-{user-provided-id}
Query Grouping:
queryActions({
queries: [{type: "done", ...}],
groupBatch: true // Groups actions by batchActionUID
})Class: ActionCaller (proxied for dynamic action methods)
Process:
-
getWriteCalls(query, action, params, permissions)- Queries all racks for matching targets
- Checks user permissions
- Returns
{targets, requiredPerms}
-
callTargets(action, params, targets)- Executes action on all target things
- Parallel execution (limit:
callTargetsLimitfrom config) - Records
resultorerrorper target call
Rack Actions: Special actions that operate on rack level (not individual things):
registerThingupdateThingforgetThingsrackReboot
- Node.js >= 20.0
# Clone repository
git clone https://github.com/tetherto/miningos-wrk-ork.git
cd miningos-wrk-ork
# Install dependencies
npm install
# Setup configuration files
./setup-config.sh
-
Network Configuration (
config/facs/net.config.json)- Add allowed RPC public keys to
allowarray - Add read-only keys to
allowReadOnlyarray - Set
allowLocal: truefor access from same server
- Add allowed RPC public keys to
-
Orchestrator Settings (
config/base.ork.json)- Adjust
actionIntvlMsfor action processing speed - Set
callTargetsLimitbased on infrastructure size - Configure
automationTypesfor automated actions
- Adjust
-
Global Configuration (
config/global.config.json)- Define timezones for your operation
- Set nominal site hashrate and capacity
- Configure power consumption thresholds
- Enable/disable auto-sleep:
isAutoSleepAllowed
# Development
DEBUG="*" node worker.js --wtype wrk-ork-proc-aggr --env development --cluster shard-1
# Production
node worker.js --wtype wrk-ork-proc-aggr --env production --cluster shard-1Required Arguments:
--wtype: Worker type (must bewrk-ork-proc-aggr)--env: Environment (development/production)--cluster: Cluster identifier (e.g.,shard-1)
Notes:
- Each orchestrator instance manages its own cluster of racks
- Use
--clusterto identify different shards - Racks register to specific orchestrator via RPC public key
Worker status saved to: status/wrk-ork-proc-aggr-{cluster}.json
{
"instanceId": "wrk-ork-proc-aggr-shard-1-uuid",
"rpcPublicKey": "64-char-hex",
"rpcClientKey": "64-char-hex"
}Hyperbee databases created in: store/{cluster}-db/
| Database | Purpose |
|---|---|
racks |
Registered rack registry |
action-approver |
Action states (voting/ready/executing/done) |
tail-log-aggr |
Historical daily aggregations |
# Run tests
npm test
# Lint code
npm run lint
# Fix linting issues
npm run lint:fixEnable debug output:
DEBUG="ork:*" node worker.js --wtype wrk-ork-proc-aggr --env development --cluster shard-1Debug specific methods:
this.debugGeneric('method name', data) // General logging
this.debugError('error context', error, true) // Error with alertCheck worker status:
cat status/wrk-ork-proc-aggr-shard-1.json| Error | Meaning |
|---|---|
ERR_PROC_RACK_UNDEFINED |
Cluster argument missing |
ERR_RACK_ID_INVALID |
Invalid or missing rack ID |
ERR_RACK_TYPE_INVALID |
Invalid or missing rack type |
ERR_RACK_INFO_RPC_PUBKEY_INVALID |
Missing RPC public key |
ERR_TYPE_INVALID |
Invalid or missing device type |
ERR_LOG_TYPE_INVALID |
Invalid log type |
ERR_QUERY_INVALID |
Invalid MongoDB query |
ERR_ACTION_INVALID |
Invalid action name |
ERR_PARAMS_INVALID |
Invalid action parameters |
ERR_KEYS_INVALID |
Invalid keys array |
ERR_TYPE_AGGR_INVALID |
Aggregation not configured for type |
ERR_START_DATE_INVALID |
Invalid start date format |
ERR_END_DATE_INVALID |
Invalid end date format |
ERR_TIMEZONE_OFFSET_INVALID |
Timezone not configured |
ERR_NOT_CONFIGURED |
Required config file missing |
ERR_TIMEZONES_NOT_CONFIGURED |
Global timezones missing |
ERR_GLOBAL_CONFIG_MISSING |
Global config not loaded |
ERR_GLOBAL_CONFIG_NOT_FOUND |
Global config not found |
ERR_CONFIG_INVALID |
Invalid config parameters |
ERR_PAYLOAD_INVALID |
Invalid batch payload |
ERR_QUERIES_INVALID |
Invalid queries array |
ERR_QUERIES_TYPE_INVALID |
Query missing type |
ERR_ORK_ACTION_CALLS_EMPTY |
No targets matched query |
ERR_ACTION_DENIED |
Insufficient permissions |
ERR_ACTION_INVALID_MISSING_RACKID |
Rack action missing rackId |
ERR_ACTION_INVALID_MISSING_ID |
Update action missing ID |
ERR_ACTION_INVALID_QUERY_ID |
Forget action missing query.id |
ERR_MISSING_WRITE_PERMISSIONS |
Read-only peer attempted write |
ERR_NET_INVALID_INSTANCE |
Invalid network facility |
ERR_RACKS_INSTANCE_INVALID |
Invalid racks database |
ERR_ENTRIES_INVALID |
Settings entries invalid |
ERR_GET_SETTINGS_FAILED |
Failed to get settings from rack |
ERR_SAVE_SETTINGS_FAILED |
Failed to save settings to rack |
Common Timezone Offsets:
| Timezone | Offset (ms) | UTC Offset |
|---|---|---|
| UTC | 0 | UTC+0 |
| EST | -18000000 | UTC-5 |
| CST | -21600000 | UTC-6 |
| MST | -25200000 | UTC-7 |
| PST | -28800000 | UTC-8 |
| CET | 3600000 | UTC+1 |
| EET | 7200000 | UTC+2 |
| UYT | -10800000 | UTC-3 |
| JST | 32400000 | UTC+9 |
Adding Timezone:
{
"aggrTailLogTimezones": [
{
"code": "PST",
"offset": -28800000
}
]
}