This document describes the high-level architecture of mujina-miner, Bitcoin mining software built in Rust.
Mujina-miner is organized as a single Rust crate with well-defined modules that separate concerns while maintaining simplicity. The architecture is fully async using Tokio for concurrent I/O operations.
- tokio: Async runtime for concurrent I/O operations
- tokio-serial: Async serial port communication
- rust-bitcoin: Core Bitcoin types and utilities
- axum: HTTP server framework
- tracing: Structured logging and diagnostics
src/
+-- bin/ # Binary entry points
| +-- minerd.rs # mujina-minerd - Main daemon
| +-- cli.rs # mujina-cli - Command line interface
| `-- tui.rs # mujina-tui - Terminal UI
+-- lib.rs # Library root
+-- error.rs # Common error types
+-- types/ # Core types (Difficulty, HashRate, Job, Share)
+-- config.rs # Configuration loading and validation
+-- daemon.rs # Daemon lifecycle management
+-- board/ # Hash board implementations
+-- transport/ # Physical transport layer
+-- mgmt_protocol/ # Board management protocols
+-- hw_trait/ # Hardware interface traits (I2C, SPI, GPIO, Serial)
+-- peripheral/ # Peripheral chip drivers
+-- asic/ # Mining ASIC drivers
+-- backplane.rs # Backplane: board communication and lifecycle
+-- scheduler.rs # Work scheduling and distribution
+-- stratum_v1/ # Stratum v1 pool client
+-- job_source/ # Unified mining job sources (pools, solo, testing)
+-- api/ # HTTP API and WebSocket
+-- api_client/ # Shared API client library
| +-- mod.rs # Client implementation
| `-- types.rs # API DTOs and models
`-- tracing.rs # Logging and observability
The main daemon binary entry point. Handles:
- Signal handling (SIGINT/SIGTERM)
- Tokio runtime initialization
- Graceful shutdown coordination
- Top-level task spawning
Command-line interface for controlling the miner:
- Uses the
api_clientmodule to communicate with the daemon - Provides commands for status, configuration, pool management
- Suitable for scripting and automation
Terminal user interface for interactive monitoring:
- Uses the
api_clientmodule to communicate with the daemon - Built with ratatui for rich terminal graphics
- Real-time hashrate graphs and statistics
- Keyboard-driven interface for operators
- Connects via API WebSocket for live updates
Centralized error types using thiserror. Provides a unified Error enum
for the entire crate with conversions from underlying error types.
Core domain types shared across modules. This module re-exports commonly used types from rust-bitcoin and defines mining-specific types. Using rust-bitcoin provides battle-tested implementations of Bitcoin primitives while avoiding reinventing fundamental types.
The types module is organized into submodules:
difficulty.rs- Integer difficulty type with Target conversionhash_rate.rs- Hashrate measurement with unit conversionsbitcoin_impls.rs- U256-bitcoin type conversions
Configuration management:
- TOML file parsing with serde
- Config validation
- Hot-reload support via file watching
- Default values and config merging
Daemon lifecycle management:
- systemd notification support
- PID file handling
- Resource cleanup
- Health monitoring
The hardware communication layer is organized in distinct levels, each with a specific responsibility. This design enables maximum code reuse and testability.
+--------------------------------------------------------------+
| Board Implementation |
| orchestrates all components for a specific board model |
+--------------------------------------------------------------+
| |
| |
+-----------------------------+ +------------------------------+
| Peripheral | | ASICs |
| peripheral drivers | | +----------------------+ |
| +------+ +-------++-------+ | | | BM13xx Family | |
| | TMP75| |INA260 ||EMC2101| | | | +------+ +------+ | |
| +---+--+ +---+---++---+---+ | | | |BM1370| |BM1362| | |
+-----------------------------+ | | +---+--+ +---+--+ | |
| | | | +----------------------+ |
+--------+--------+ +------------------------------+
| +----+---+
+-----------+---------------+
|
+--------------------------------------------------------------+
| Hardware Abstraction Layer (hw_trait) |
| I2C, SPI, GPIO, Serial trait definitions |
+--------------------------------------------------------------+
|
+--------------------------------------------------------------+
| Management Protocols (mgmt_protocol) |
| Implement hw_traits over board protocols |
+--------------------------------------------------------------+
|
+--------------------------------------------------------------+
| Transport Layers |
| Physical connections to boards |
| USB Serial, PCIe, Ethernet (future) |
+--------------------------------------------------------------+
Physical connections to hash boards. This layer handles:
- USB device discovery and enumeration via libudev (Linux)
- Real-time hotplug detection and events
- Opening and configuring serial ports
- Managing dual-channel devices (management + data channels)
- No protocol knowledge - just raw byte streams
- Emits
BoardConnected/BoardDisconnectedevents
Platform support:
- Linux: Uses libudev for USB device discovery and monitoring
- macOS: Planned (will use IOKit framework)
The transport layer discovers devices based on VID/PID and associated serial ports, then emits events to the backplane for board initialization.
Protocol implementations for hash board management. This layer:
- Implements specific packet formats (e.g., bitaxe-raw's 7-byte header)
- Provides protocol operations: GPIO control, ADC readings, I2C passthrough
- Handles command/response sequencing and error checking
- Translates high-level operations into protocol packets
- Provides adapters that implement
hw_traitinterfaces over protocols
Hardware interface traits and native implementations. This layer:
- Defines traits like
I2c,Spi,Gpio,Serialthat drivers use - Allows the same driver to work with, e.g., Linux I2C or I2C-over-protocol
- Provides native Linux implementations for local buses
Reusable drivers for peripheral chips (not mining ASICs). These drivers:
- Are generic over
hw_traitinterfaces (e.g, work with anyI2cimplementation) - Can be tested with mock implementations
- Used on various board types
Mining ASIC drivers - the heart of mining operations:
- Implements drivers for different ASIC families (BM13xx, etc.)
- Manages chip initialization, frequency control, and status
- Communicates via hw_trait::Serial (which may be a direct passthrough or tunneled through mgmt_protocol)
- Handles chip-specific protocols and command sequences
Here's how the architectural layers compose in practice:
// board/bitaxe_gamma.rs
use crate::mgmt_protocol::bitaxe_raw::BitaxeRawProtocol;
use crate::peripheral::{TMP75, INA260};
use crate::asic::bm13xx::{BM1370, ChipChain};
use crate::board::Board;
pub struct BitaxeGammaBoard {
protocol: BitaxeRawProtocol,
asic_chain: ChipChain<BM1370>,
temp_sensor: TMP75<BitaxeRawI2c>,
power_monitor: INA260<BitaxeRawI2c>,
}
impl BitaxeGammaBoard {
// Each board type knows its exact construction requirements
pub async fn new(mgmt_serial: impl Serial, data_serial: impl Serial) -> Result<Self> {
// Create protocol handler
let mut protocol = BitaxeRawProtocol::new(mgmt_serial);
// Initialize hardware via protocol
protocol.set_gpio(3, false).await?; // Reset ASIC (GPIO 3)
tokio::time::sleep(Duration::from_millis(100)).await;
protocol.set_gpio(3, true).await?; // Release reset
// Create ASIC chain with single BM1370
let mut asic_chain = ChipChain::<BM1370>::new(data_serial);
asic_chain.enumerate_chips().await?;
asic_chain.set_frequency(500.0).await?;
// Create I2C adapter from protocol
let i2c = protocol.i2c_adapter();
// Create peripheral drivers
let temp_sensor = TMP75::new(i2c.clone(), 0x48);
let power_monitor = INA260::new(i2c, 0x40);
Ok(Self {
protocol,
asic_chain,
temp_sensor,
power_monitor,
})
}
}The backplane responds to transport discovery events by looking up the appropriate board type and constructing it:
// backplane.rs - simplified
async fn handle_transport_connected(&mut self, event: TransportEvent) {
// Look up board type in registry based on transport properties
let board_type = match &event {
TransportEvent::UsbConnected { vid, pid, .. } => {
self.board_registry.find_by_usb(*vid, *pid)?
}
TransportEvent::PcieConnected { device_id, .. } => {
self.board_registry.find_by_pcie(*device_id)?
}
};
// Each board type knows how to construct itself from transport
let board: Box<dyn Board> = match board_type {
BoardType::BitaxeGamma => {
// Extract dual serial ports from transport event
let (mgmt_port, data_port) = event.into_dual_serial()?;
// Board constructor handles all protocol and driver setup
Box::new(BitaxeGammaBoard::new(mgmt_port, data_port).await?)
}
BoardType::S19Pro => {
// S19 uses a single multiplexed serial connection
let serial = event.into_serial()?;
// S19 board handles its own protocol complexity
Box::new(S19ProBoard::new(serial).await?)
}
BoardType::Avalon1366 => {
// Some boards might use multiple transports
let (control, chain1, chain2, chain3) = event.into_quad_serial()?;
Box::new(AvalonBoard::new(control, [chain1, chain2, chain3]).await?)
}
};
// Register board with scheduler
self.scheduler.add_board(board);
}This architecture achieves several key objectives:
- Driver Reusability: The same peripheral driver (e.g., TMP75 temp sensor) works on:
- Different boards (Bitaxe, S19, Avalon)
- Different I2C implementations (Linux native, USB-tunneled, protocol-based)
- Test environments with mock I2C
- ASIC Driver Portability: BM13xx drivers work with any Serial implementation:
- Direct serial port on development boards
- Protocol-multiplexed chains on production miners
- Mock serial for testing
- Clean Abstractions: Drivers depend only on hw_trait interfaces, not specific hardware
- Testability: Every driver can be tested in isolation with trait mocks
- Composability: Boards compose existing drivers rather than reimplementing:
- Pick the ASIC driver for your chips
- Pick the peripheral drivers for your sensors
- Wire them together with your board's specific transport
Hash board implementations that compose all hardware elements:
Boardtrait defining the interface for all hash boardsbitaxe.rs- Original Bitaxe board implementationember_one.rs- EmberOne board using layered architectureregistry.rs- Board type registry for dynamic instantiation
Board responsibilities:
- Hardware initialization and lifecycle management
- Creating hash threads for work assignment
- Providing threads with chip communication mechanisms (implementation-specific)
- Optionally sharing peripheral access to enable thread autonomy
- Monitoring hardware health (temperature, power, faults)
- Coordinating shutdown and cleanup
The board-thread relationship is intentionally board-specific to allow diverse hardware designs. Some boards may give threads direct hardware access, while others may mediate all hardware operations.
Mining ASIC drivers:
- Current:
bm13xx/family driver with protocol documentation - Future: Other ASIC families (BM1397, etc.)
- Handles: work distribution, nonce collection, frequency control
- Communicates through hw_trait layer for maximum flexibility
Hash threads are the scheduler's abstraction for mining work assignment. The
HashThread trait in asic/hash_thread.rs defines the minimal interface the
scheduler needs:
- Work assignment methods (
update_work(),replace_work(),go_idle()) - Event reporting channel for shares and status updates
- Capability and status queries
- Future: Scheduler will control thread hashrate for power management
This module also defines hardware abstraction traits (AsicEnable,
VoltageRegulator) and BoardPeripherals for boards to pass abstract hardware
interfaces to thread implementations.
ASIC-specific thread implementations live alongside their protocol code (e.g.,
asic/bm13xx/thread.rs for BM13xx chips). This colocates the thread actor with
the protocol it uses, while the scheduler-facing trait stays in a central
location.
Everything else---chip communication, peripheral access, internal architecture---is board-specific implementation detail. This design allows radically different board architectures:
Communication with chips: Boards may transfer serial I/O ownership to threads, use message passing, share a communication bus, or any other pattern that suits the hardware.
Peripheral access: Threads may need direct access to board peripherals for real-time optimization (voltage tuning, thermal throttling, fault recovery). Boards may provide this access via shared references (e.g., Arc-wrapped peripherals), message-based requests, or keep all peripheral control board-mediated. The choice depends on the hardware design and optimization requirements.
Shutdown coordination: Board-to-thread shutdown signaling is implementation-specific, using watch channels, cancellation tokens, or custom mechanisms as appropriate.
This flexibility enables diverse hardware designs without requiring scheduler changes. The scheduler sees only a uniform HashThread interface, while boards and threads collaborate in hardware-appropriate ways.
Communication substrate between boards and scheduler:
- Listens to
transportdiscovery events - Identifies board types (USB VID/PID or probing)
- Creates/destroys board instances
- Maintains active board registry
- Extracts hash threads from boards and routes to scheduler
- Boards remain active for hardware lifecycle management
- Coordinates emergency shutdowns and hotplug
Unified interface for all mining job sources:
messages.rs- Source-scheduler communication (SourceEvent, SourceCommand)job.rs- JobTemplate and Share typesstratum_v1.rs- Stratum v1 job source adapter (wraps stratum_v1 module)dummy.rs- Synthetic job generator for testing and load managementversion.rs,extranonce2.rs,merkle.rs- Work generation helpers- Provides consistent interface for scheduler regardless of job origin
Stratum v1 pool client implementation:
client.rs- Main client with connection management and message handlingconnection.rs- TCP connection handlingmessages.rs- Stratum protocol message types- Supports version rolling and share difficulty management
Orchestrates the mining operation:
- Receives work from any
JobSourceimplementation - Distributes work to boards/chips
- Collects and routes shares
- Implements work scheduling strategies
- Manages board lifecycle
HTTP API server (new). See API documentation for the contract and conventions.
- Built on Axum (async web framework)
- RESTful endpoints for status, control, configuration
- WebSocket support for real-time updates
- OpenTelemetry integration
- Prometheus metrics endpoint
Structured logging and observability:
- tracing subscriber setup
- journald or stdout output
- Log level configuration
- Performance tracing spans
Mining Pool <--[Stratum]--> job_source::StratumV1
Bitcoin Node <--[RPC]-----> job_source::Solo
Dummy Work Generator -----> job_source::Dummy
|
v
scheduler::Scheduler
|
+--------------+--------------+
| |
v v
board::Board board::Board
| |
v v
asic::BM13xxChip asic::BM13xxChip
| |
v v
transport::UsbSerial transport::UsbSerial
Hotplug Flow:
USB Device --> transport --> BoardConnected Event --> backplane
|
v
Creates Board
|
v
Registers with Scheduler
|
v
Scheduler talks directly to Board
Shares flow through three filtering stages from hardware to pool:
-
Chip Hardware Target: ASICs are configured with a low difficulty target (e.g., diff 100-1000) to generate frequent shares for health monitoring. At diff 100, a 500 GH/s chip produces ~1 share/second.
-
Thread Processing: HashThreads receive ALL chip shares and compute the hash for each one (required to determine what difficulty it meets). Since the hash computation cost is already paid, threads forward all shares to the scheduler rather than filtering them. This keeps threads simple and gives the scheduler ground truth for per-thread hashrate measurement.
-
Scheduler Filtering: The scheduler filters shares against the job target before forwarding to the JobSource. Only pool-worthy shares are submitted. The scheduler uses all shares for internal statistics and health monitoring.
Message Volume: Even at aggressive chip targets, message volume is manageable. A 12-chip board at diff 100 produces ~10-15 shares/second, well within mpsc channel capacity.
Design Rationale: Forwarding all shares centralizes filtering logic in the scheduler, provides accurate per-thread monitoring, and simplifies thread implementation. The alternative (filtering in threads) would require duplicating target comparison logic and prevent the scheduler from measuring actual hardware performance.
All I/O operations are async using Tokio:
- Serial communication uses
tokio-serial - HTTP server uses
axum(built on Tokio) - Background tasks use
tokio::spawn - Graceful shutdown via
CancellationToken - Concurrent operations via
TaskTracker
The architecture supports extension through several mechanisms:
- New Board Types: Implement the
Boardtrait - New ASIC Families: Add modules under
asic/ - New Pool Protocols: Implement
PoolClienttrait - New Management Protocols: Add under
mgmt_protocol/ - Custom Schedulers: Pluggable scheduling strategies
- Additional Peripheral Chips: Add drivers to
peripheral/ - New Connection Types: Extend
transport/(PCIe, Ethernet)
Configuration is managed through TOML files with hot-reload support:
/etc/mujina/mujina.toml- System configuration- Board-specific settings
- Pool credentials and priorities
- Temperature limits and safety settings
- API server configuration
- No hardcoded credentials
- TLS support for API endpoints
- Privilege dropping after startup
- Isolated board control (no direct chip access from API)
- Rate limiting on API endpoints
Mujina-miner provides multiple interfaces for different use cases:
The primary user interface is a modern web application that lives in a
separate repository (mujina-web):
- Built with modern web technologies (React/Vue/Svelte)
- Communicates exclusively through the HTTP API
- Provides rich visualizations and easy configuration
- Suitable for remote management
- Can be served by any web server or CDN
Repository: github.com/mujina/mujina-web (example)
Included in this repository as mujina-cli:
- Direct API client for automation and scripting
- Supports all daemon operations
- JSON output mode for parsing
- Configuration file management
Included in this repository as mujina-tui:
- Interactive terminal dashboard
- Real-time monitoring without web browser
- Ideal for SSH sessions
- Keyboard shortcuts for common operations
The api_client module provides:
- Rust types for all API requests/responses
- Async HTTP client using reqwest
- WebSocket support for real-time data
- Shared between CLI and TUI
- Could be published as separate crate for third-party tools
This repository contains the core miner daemon and terminal-based tools:
mujina-miner/
+-- Cargo.toml
+-- README.md
+-- docs/
| +-- architecture.md # This file
| +-- api.md # API documentation
| `-- deployment.md # Installation guide
+-- configs/
| `-- example.toml # Example configuration
+-- src/ # Rust source code
+-- systemd/
| `-- mujina-minerd.service
`-- debian/ # Debian packaging
The web interface lives in a separate repository to allow:
- Independent development cycles
- Different programming languages
- Separate CI/CD pipelines
- Alternative web UIs from the community