Run an Avalon Worker
Start an MDK Worker for a Canaan Avalon device.
Overview
This page details how to run the Canaan Avalon Worker. The package is a directory-loaded Worker Plugin
(mdk-contract.json plus handler files) that mdk run hosts from mdk.yaml, or that your own Node.js service
hosts on WorkerRuntimeV2. Select the development (mock) or real-device path.
Prerequisites
Review common deployment prerequisites before you start.
Deployment-specific requirements:
- An MDK project with
mdk.yaml(mdk create), or a Node.js service or script in your deployment that hosts the Worker.@tetherto/mdk-worker-avalonis not published to npm, so the package resolves only through the workspace of an MDK repository clone afternpm installat its root: keep the project or script inside that clone - A supported Avalon device reachable from the machine or container running the Worker
- The miner API reachable over the native CGMiner TCP API, typically port
4028 - A
passwordfor each device: reads need no credentials, but write commands such as a pool change send it, and the Worker refuses to load a device without one (ERR_DEVICE_CONFIG_INVALID)
Development
Run against a mock
The Avalon Worker ships a mock A1346 that answers the CGMiner API with canned data. Declare the Worker in mdk.yaml
with mock: true and mdk run starts one mock per device, on the port each device names:
spec:
workers:
- name: avalon
package: '@tetherto/mdk-worker-avalon'
config:
mock: true
devices:
- id: AV-DEV-001
opts: { address: 127.0.0.1, port: 14030, password: admin }mdk runExpected output includes a line for the Worker:
✔ worker avalon ready 3538566ecb38…To host the Worker yourself instead of with mdk run, run the mock in one terminal and a host script in a second,
both from the repository root:
-
Start the mock on the CGMiner port:
npm run mock a1346 4028 -
Save this script as
avalon-dev.jsin the repository root. It is the boot call from Connect a miner, pointed at127.0.0.1:'use strict' const os = require('os') const path = require('path') const { getKernel } = require('@tetherto/mdk-core') const { WorkerRuntimeV2 } = require('@tetherto/mdk-worker') const main = async () => { const kernel = await getKernel({ handleSignals: true }) const runtime = new WorkerRuntimeV2(path.dirname(require.resolve('@tetherto/mdk-worker-avalon/package.json')), { workerId: 'avalon-dev', storeDir: path.join(os.tmpdir(), 'mdk-dev', 'avalon'), allowedKeys: [kernel.getClientPublicKey()], devices: [{ deviceId: 'AV-DEV-001', config: { address: '127.0.0.1', port: 4028, password: 'admin' } }] }) await runtime.start() await kernel.registerWorker(runtime.getPublicKey()) kernel._cleanup.push(() => runtime.stop()) // Ctrl+C stops the Worker before the Kernel console.log('Kernel HRPC key:', kernel.getPublicKey().toString('hex')) console.log('Ctrl+C to stop.') } main().catch((err) => { console.error(err); process.exit(1) }) -
Run the script in a second terminal:
node avalon-dev.js
Expected output, with your own key:
Kernel HRPC key: a4303ca6404442264fda55e37a35db3400eeab1a4b4b68f9e1413fff99f9521e
Ctrl+C to stop.Ctrl+C stops the Worker, then the Kernel: the script opts into signal handling and chains the Worker's stop, so a
second run starts without a lock error. Stop the services cleanly covers the contract.
Either way, the Kernel then pulls live telemetry (hashrate, power, temperature) from the mock through the Worker. The mock's options are in USAGE.md.
Connect a miner
2.1 Confirm the model
Avalon ships one model family today, a1346 — confirm this against the USAGE.md as new models are added.
2.2 Declare your miner
Avalon devices use the native CGMiner TCP API on port 4028. Reads need no credentials, but write commands such as a pool
change send the device password, and the Avalon Worker Plugin refuses to load a device with no password
(ERR_DEVICE_CONFIG_INVALID). Point the mdk.yaml entry at the real miner and drop mock:
spec:
workers:
- name: avalon
package: '@tetherto/mdk-worker-avalon'
config:
devices:
- id: AV-001
opts: { address: 192.168.1.30, port: 4028, password: admin }Without mdk run, add this to the Node.js service or script that hosts the Worker. The snippet seeds one Avalon
device; replace the example IP address and password with your miner's values:
const path = require('path')
const { getKernel } = require('@tetherto/mdk-core')
const { WorkerRuntimeV2 } = require('@tetherto/mdk-worker')
const main = async () => {
const kernel = await getKernel()
const runtime = new WorkerRuntimeV2(path.dirname(require.resolve('@tetherto/mdk-worker-avalon/package.json')), {
workerId: 'avalon-rack-1',
storeDir: './store/avalon-rack-1',
allowedKeys: [kernel.getClientPublicKey()],
devices: [{ deviceId: 'AV-001', config: { address: '192.168.1.30', port: 4028, password: 'admin' } }]
})
await runtime.start()
await kernel.registerWorker(runtime.getPublicKey())
}
main().catch((err) => { console.error(err); process.exit(1) })allowedKeys lists the RPC callers the Worker accepts. The Worker refuses to start without it
(ERR_WORKER_ALLOWED_KEYS_REQUIRED), so pass the Kernel's client key.
Make sure each miner's IP is reachable from the machine or container running the Worker before registering. Commands act on physical hardware — prioritize thermal safety.
The device list is fixed at construction — there is no registerThing/updateThing/forgetThings equivalent.
Adding, updating, or removing a miner means editing the device list and restarting the Worker.
The full device option reference and the mock createServer options are in the Worker's USAGE.md
and the shared install pattern.
Troubleshooting
An unreachable miner doesn't fail at load time: the Worker reports the device online and the failure surfaces as
ERR_MINER_UNREACHABLE inside the telemetry channel or command response that touched the network. If the Worker
doesn't come up, or a mock port is already in use, follow miner troubleshooting.
Next steps
- Decide how to run the Worker service — Deployment topologies
- Review telemetry units, command shapes, and error codes —
mdk-contract.json