Module Development
The BTCDecoded blvm-node includes a process-isolated module system that enables optional features (Lightning, merge mining, privacy enhancements) without affecting consensus or base node stability. Modules run in separate processes with IPC communication, providing security through isolation.
Core Principles
- Process Isolation: Each module runs in a separate process with isolated memory
- API Boundaries: Modules communicate only through well-defined APIs
- Crash Containment: Module failures don’t propagate to the base node
- Consensus Isolation: Modules cannot modify consensus rules, UTXO set, or block validation
- State Separation: Module state is completely separate from consensus state
Communication
Modules communicate with the node via Inter-Process Communication (IPC) using Unix domain sockets. Protocol uses length-delimited binary messages (bincode serialization) with message types: Requests, Responses, Events. Connection is persistent for request/response pattern; events use pub/sub pattern for real-time notifications.
Module Structure
Directory Layout
Each module should be placed in a subdirectory within the modules/ directory:
modules/
└── my-module/
├── Cargo.toml
├── src/
│ └── main.rs
└── module.toml # Module manifest (required)
Module Manifest (module.toml)
Every module must include a module.toml manifest file:
# ============================================================================
# Module Manifest
# ============================================================================
# ----------------------------------------------------------------------------
# Core Identity (Required)
# ----------------------------------------------------------------------------
name = "my-module"
version = "1.0.0"
entry_point = "my-module"
# ----------------------------------------------------------------------------
# Metadata (Optional)
# ----------------------------------------------------------------------------
description = "Description of what this module does"
author = "Your Name <your.email@example.com>"
# ----------------------------------------------------------------------------
# Capabilities
# ----------------------------------------------------------------------------
# Permissions this module requires to function
capabilities = [
"read_blockchain", # Query blockchain data
"subscribe_events", # Receive node events
]
# ----------------------------------------------------------------------------
# Dependencies
# ----------------------------------------------------------------------------
# Required dependencies (module cannot load without these)
[dependencies]
"blvm-lightning" = ">=1.0.0"
# Optional dependencies (module can work without these)
[optional_dependencies]
"blvm-mesh" = ">=0.5.0"
# ----------------------------------------------------------------------------
# Configuration Schema (Optional)
# ----------------------------------------------------------------------------
[config_schema]
poll_interval = "Polling interval in seconds (default: 5)"
Required Fields:
name: Module identifier (alphanumeric with dashes/underscores)version: Semantic version (e.g., “1.0.0”)entry_point: Binary name or path
Optional Fields:
description: Human-readable descriptionauthor: Module authorcapabilities: List of required permissionsdependencies: Required (hard) dependencies - module cannot load without themoptional_dependencies: Optional (soft) dependencies - module can work without them
Dependency Version Constraints:
>=1.0.0- Greater than or equal to version<=2.0.0- Less than or equal to version=1.2.3- Exact version match^1.0.0- Compatible version (>=1.0.0 and <2.0.0)~1.2.0- Patch updates only (>=1.2.0 and <1.3.0)
Module Development
Basic Module Structure
A minimal module implements the module lifecycle and connects to the node via IPC. There are two approaches:
Using ModuleIntegration (Recommended)
use blvm_node::module::integration::ModuleIntegration;
use blvm_node::module::EventType;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command-line arguments
let args = Args::parse();
// Connect to node using ModuleIntegration
// Note: socket_path must be PathBuf (convert from String if needed)
let socket_path = std::path::PathBuf::from(&args.socket_path);
let mut integration = ModuleIntegration::connect(
socket_path,
args.module_id.unwrap_or_else(|| "my-module".to_string()),
"my-module".to_string(),
env!("CARGO_PKG_VERSION").to_string(),
).await?;
// Subscribe to events
let event_types = vec![EventType::NewBlock, EventType::NewTransaction];
integration.subscribe_events(event_types).await?;
// Get NodeAPI
let node_api = integration.node_api();
// Get event receiver (broadcast::Receiver returns Result, not Option)
let mut event_receiver = integration.event_receiver();
// Main module loop
loop {
match event_receiver.recv().await {
Ok(ModuleMessage::Event(event_msg)) => {
// Process event
match event_msg.payload {
// Handle specific event types
_ => {}
}
}
Ok(_) => {} // Other message types
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
warn!("Event receiver lagged, skipped {} messages", skipped);
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
break; // Channel closed, exit loop
}
}
}
Ok(())
}
Using ModuleIpcClient + NodeApiIpc (Legacy)
use blvm_node::module::ipc::client::ModuleIpcClient;
use blvm_node::module::api::node_api::NodeApiIpc;
use blvm_node::module::ipc::protocol::{RequestMessage, RequestPayload, MessageType};
use std::sync::Arc;
use std::path::PathBuf;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command-line arguments
let args = Args::parse();
// Connect to node IPC socket (PathBuf required)
let socket_path = PathBuf::from(&args.socket_path);
let mut ipc_client = ModuleIpcClient::connect(&socket_path).await?;
// Perform handshake
let correlation_id = ipc_client.next_correlation_id();
let handshake_request = RequestMessage {
correlation_id,
request_type: MessageType::Handshake,
payload: RequestPayload::Handshake {
module_id: "my-module".to_string(),
module_name: "my-module".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
},
};
let response = ipc_client.request(handshake_request).await?;
// Verify handshake response...
// Create NodeAPI wrapper (requires Arc<Mutex<ModuleIpcClient>> and module_id)
let ipc_client_arc = Arc::new(tokio::sync::Mutex::new(ipc_client));
let node_api = Arc::new(NodeApiIpc::new(ipc_client_arc.clone(), "my-module".to_string()));
// Subscribe to events using NodeAPI
let event_types = vec![EventType::NewBlock, EventType::NewTransaction];
let mut event_receiver = node_api.subscribe_events(event_types).await?;
// Main module loop (mpsc::Receiver returns Option)
while let Some(event) = event_receiver.recv().await {
match event {
ModuleMessage::Event(event_msg) => {
// Process event
}
_ => {}
}
}
Ok(())
}
Recommendation: New modules should use ModuleIntegration for simplicity and consistency. The legacy approach is still supported for existing modules but requires more boilerplate code.
Module Lifecycle
Modules receive command-line arguments (--module-id, --socket-path, --data-dir) and configuration via environment variables (MODULE_CONFIG_*). Lifecycle: Initialization (connect IPC) → Start (subscribe events) → Running (process events/requests) → Stop (clean shutdown).
Querying Node Data
Modules can query blockchain data through the Node API. Recommended approach: Use NodeAPI methods directly:
#![allow(unused)]
fn main() {
// Get NodeAPI from integration
let node_api = integration.node_api();
// Get current chain tip
let chain_tip = node_api.get_chain_tip().await?;
// Get a block by hash
let block = node_api.get_block(&block_hash).await?;
// Get block header
let header = node_api.get_block_header(&block_hash).await?;
// Get transaction
let tx = node_api.get_transaction(&tx_hash).await?;
// Get UTXO
let utxo = node_api.get_utxo(&outpoint).await?;
// Get chain info
let chain_info = node_api.get_chain_info().await?;
}
Alternative (Low-Level IPC): For advanced use cases, you can use the IPC protocol directly:
#![allow(unused)]
fn main() {
// Note: This requires request_type field in RequestMessage
let request = RequestMessage {
correlation_id: client.next_correlation_id(),
request_type: MessageType::GetChainTip,
payload: RequestPayload::GetChainTip,
};
let response = client.send_request(request).await?;
}
Recommendation: Use NodeAPI methods for simplicity and type safety. Low-level IPC is only needed for custom protocols.
Available NodeAPI Methods:
Blockchain API:
get_block(hash)- Get block by hashget_block_header(hash)- Get block header by hashget_transaction(hash)- Get transaction by hashhas_transaction(hash)- Check if transaction existsget_chain_tip()- Get current chain tip hashget_block_height()- Get current block heightget_block_by_height(height)- Get block by heightget_utxo(outpoint)- Get UTXO by outpoint (read-only)get_chain_info()- Get chain information (tip, height, difficulty, etc.)
Mempool API:
get_mempool_transactions()- Get all transaction hashes in mempoolget_mempool_transaction(hash)- Get transaction from mempool by hashget_mempool_size()- Get mempool size informationcheck_transaction_in_mempool(hash)- Check if transaction is in mempoolget_fee_estimate(target_blocks)- Get fee estimate for target confirmation blocks
Network API:
get_network_stats()- Get network statisticsget_network_peers()- Get list of connected peers
Storage API:
storage_open_tree(name)- Open a storage tree (isolated per module)storage_insert(tree_id, key, value)- Insert a key-value pairstorage_get(tree_id, key)- Get a value by keystorage_remove(tree_id, key)- Remove a key-value pairstorage_contains_key(tree_id, key)- Check if key existsstorage_iter(tree_id)- Iterate over all key-value pairsstorage_transaction(tree_id, operations)- Execute atomic batch of operations
Filesystem API:
read_file(path)- Read a file from module’s data directorywrite_file(path, data)- Write data to a filedelete_file(path)- Delete a filelist_directory(path)- List directory contentscreate_directory(path)- Create a directoryget_file_metadata(path)- Get file metadata (size, type, timestamps)
Module Communication API:
call_module(target_module_id, method, params)- Call an API method on another modulepublish_event(event_type, payload)- Publish an event to other modulesregister_module_api(api)- Register module API for other modules to callunregister_module_api()- Unregister module APIdiscover_modules()- Discover all available modulesget_module_info(module_id)- Get information about a specific moduleis_module_available(module_id)- Check if a module is available
RPC API:
register_rpc_endpoint(method, description)- Register a JSON-RPC endpointunregister_rpc_endpoint(method)- Unregister an RPC endpoint
Timers API:
register_timer(interval_seconds, callback)- Register a periodic timercancel_timer(timer_id)- Cancel a registered timerschedule_task(delay_seconds, callback)- Schedule a one-time task
Metrics API:
report_metric(metric)- Report a metric to the nodeget_module_metrics(module_id)- Get module metricsget_all_metrics()- Get aggregated metrics from all modules
Lightning & Payment API:
get_lightning_node_url()- Get Lightning node connection infoget_lightning_info()- Get Lightning node informationget_payment_state(payment_id)- Get payment state by payment ID
Network Integration API:
send_mesh_packet_to_module(module_id, packet_data, peer_addr)- Send mesh packet to a module
For complete API reference, see NodeAPI trait.
Subscribing to Events
Modules can subscribe to real-time node events. The approach depends on which integration method you’re using:
Using ModuleIntegration
#![allow(unused)]
fn main() {
// Subscribe to events
let event_types = vec![EventType::NewBlock, EventType::NewTransaction];
integration.subscribe_events(event_types).await?;
// Get event receiver
let mut event_receiver = integration.event_receiver();
// Receive events in main loop
while let Some(event) = event_receiver.recv().await {
match event {
ModuleMessage::Event(event_msg) => {
// Handle event
}
_ => {}
}
}
}
Using ModuleClient
#![allow(unused)]
fn main() {
// Subscribe to events
let event_types = vec![EventType::NewBlock, EventType::NewTransaction];
client.subscribe_events(event_types).await?;
// Get event receiver
let mut event_receiver = client.event_receiver();
// Receive events in main loop
while let Some(event) = event_receiver.recv().await {
match event {
ModuleMessage::Event(event_msg) => {
// Handle event
}
_ => {}
}
}
}
Available Event Types:
Core Blockchain Events:
NewBlock- New block connected to chainNewTransaction- New transaction in mempoolBlockDisconnected- Block disconnected (chain reorg)ChainReorg- Chain reorganization occurred
Payment Events:
PaymentRequestCreated,PaymentSettled,PaymentFailed,PaymentVerified,PaymentRouteFound,PaymentRouteFailed,ChannelOpened,ChannelClosed
Mining Events:
BlockMined,BlockTemplateUpdated,MiningDifficultyChanged,MiningJobCreated,ShareSubmitted,MergeMiningReward,MiningPoolConnected,MiningPoolDisconnected
Network Events:
PeerConnected,PeerDisconnected,PeerBanned,MessageReceived,MessageSent,BroadcastStarted,BroadcastCompleted,RouteDiscovered,RouteFailed
Module Lifecycle Events:
ModuleLoaded,ModuleUnloaded,ModuleCrashed,ModuleDiscovered,ModuleInstalled,ModuleUpdated,ModuleRemoved
Configuration & Lifecycle Events:
ConfigLoaded,NodeStartupCompleted,NodeShutdown,NodeShutdownCompleted
Maintenance & Resource Events:
DataMaintenance,MaintenanceStarted,MaintenanceCompleted,HealthCheck,DiskSpaceLow,ResourceLimitWarning
Governance Events:
GovernanceProposalCreated,GovernanceProposalVoted,GovernanceProposalMerged,EconomicNodeRegistered,EconomicNodeVeto,VetoThresholdReached
Consensus Events:
BlockValidationStarted,BlockValidationCompleted,ScriptVerificationStarted,ScriptVerificationCompleted,DifficultyAdjusted,SoftForkActivated
Mempool Events:
MempoolTransactionAdded,MempoolTransactionRemoved,FeeRateChanged
And many more. For complete list, see EventType enum and Event System.
Configuration
Module system is configured in node config (see Node Configuration):
[modules]
enabled = true
modules_dir = "modules"
data_dir = "data/modules"
enabled_modules = [] # Empty = auto-discover all
[modules.module_configs.my-module]
setting1 = "value1"
Modules can have their own config.toml files, passed via environment variables.
Security Model
Permissions
Modules operate with whitelist-only access control. Each module declares required capabilities in its manifest. Capabilities use snake_case in module.toml and map to Permission enum variants.
Core Permissions:
read_blockchain- Access to blockchain dataread_utxo- Query UTXO set (read-only)read_chain_state- Query chain state (height, tip)subscribe_events- Subscribe to node eventssend_transactions- Submit transactions to mempool (future: may be restricted)
Additional Permissions:
read_mempool- Read mempool dataread_network- Read network data (peers, stats)network_access- Send network packetsread_lightning- Read Lightning network dataread_payment- Read payment dataread_storage,write_storage,manage_storage- Storage accessread_filesystem,write_filesystem,manage_filesystem- Filesystem accessregister_rpc_endpoint- Register RPC endpointsmanage_timers- Manage timers and scheduled tasksreport_metrics,read_metrics- Metrics accessdiscover_modules- Discover other modulespublish_events- Publish events to other modulescall_module- Call other modules’ APIsregister_module_api- Register module API for other modules to call
For complete list, see Permission enum.
Sandboxing
Modules are sandboxed to ensure security:
- Process Isolation: Separate process, isolated memory
- File System: Access limited to module data directory
- Network: No network access (modules can only communicate via IPC)
- Resource Limits: CPU, memory, file descriptor limits (Phase 2+)
Request Validation
All module API requests are validated:
- Permission checks (module has required permission)
- Consensus protection (no consensus-modifying operations)
- Resource limits (rate limiting, Phase 2+)
API Reference
NodeAPI Methods: See Querying Node Data section above for complete list of available methods.
Event Types: See Subscribing to Events section above for complete list of available event types.
Permissions: See Permissions section above for complete list of available permissions.
For detailed API reference, see:
For detailed API reference, see blvm-node/src/module/ (traits, IPC protocol, Node API, security).
See Also
- SDK Overview - SDK introduction and capabilities
- SDK API Reference - Complete SDK API documentation
- SDK Examples - Module development examples
- Module System Architecture - Module system design
- Module IPC Protocol - IPC communication details
- Modules Overview - Available modules
- Node Configuration - Configuring modules