use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use crate::module::ipc::protocol::{EventPayload, ModuleMessage};
use crate::{Block, BlockHeader, Hash, Transaction};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ModuleState {
Stopped,
Initializing,
Running,
Stopping,
Error(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleMetadata {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub capabilities: Vec<String>,
pub dependencies: HashMap<String, String>,
pub optional_dependencies: HashMap<String, String>,
pub entry_point: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleInfo {
pub module_id: String,
pub module_name: String,
pub version: String,
pub capabilities: Vec<String>,
pub status: ModuleState,
pub api_version: u32,
}
#[async_trait]
pub trait ModuleHooks: Send + Sync {
async fn get_fee_estimate_cached(&self, target_blocks: u32)
-> Result<Option<u64>, ModuleError>;
async fn get_mempool_stats_cached(&self) -> Result<Option<MempoolSize>, ModuleError>;
}
#[async_trait]
pub trait Module: Send + Sync {
async fn init(&mut self, context: ModuleContext) -> Result<(), ModuleError>;
async fn start(&mut self) -> Result<(), ModuleError>;
async fn stop(&mut self) -> Result<(), ModuleError>;
async fn shutdown(&mut self) -> Result<(), ModuleError>;
fn metadata(&self) -> &ModuleMetadata;
fn state(&self) -> ModuleState;
}
#[derive(Debug, Clone)]
pub struct ModuleContext {
pub module_id: String,
pub socket_path: String,
pub data_dir: String,
pub config: HashMap<String, String>,
}
impl ModuleContext {
pub fn new(
module_id: String,
socket_path: String,
data_dir: String,
config: HashMap<String, String>,
) -> Self {
Self {
module_id,
socket_path,
data_dir,
config,
}
}
pub fn get_config(&self, key: &str) -> Option<&String> {
self.config.get(key)
}
pub fn get_config_or(&self, key: &str, default: &str) -> String {
self.config
.get(key)
.map(|s| s.as_str())
.unwrap_or(default)
.to_string()
}
}
#[async_trait]
pub trait NodeAPI: Send + Sync {
async fn get_block(&self, hash: &Hash) -> Result<Option<Block>, ModuleError>;
async fn get_block_header(&self, hash: &Hash) -> Result<Option<BlockHeader>, ModuleError>;
async fn get_transaction(&self, hash: &Hash) -> Result<Option<Transaction>, ModuleError>;
async fn has_transaction(&self, hash: &Hash) -> Result<bool, ModuleError>;
async fn get_chain_tip(&self) -> Result<Hash, ModuleError>;
async fn get_block_height(&self) -> Result<u64, ModuleError>;
async fn get_utxo(
&self,
outpoint: &crate::OutPoint,
) -> Result<Option<crate::UTXO>, ModuleError>;
async fn subscribe_events(
&self,
event_types: Vec<EventType>,
) -> Result<tokio::sync::mpsc::Receiver<ModuleMessage>, ModuleError>;
async fn get_mempool_transactions(&self) -> Result<Vec<Hash>, ModuleError>;
async fn get_mempool_transaction(
&self,
tx_hash: &Hash,
) -> Result<Option<Transaction>, ModuleError>;
async fn get_mempool_size(&self) -> Result<MempoolSize, ModuleError>;
async fn get_network_stats(&self) -> Result<NetworkStats, ModuleError>;
async fn get_network_peers(&self) -> Result<Vec<PeerInfo>, ModuleError>;
async fn get_chain_info(&self) -> Result<ChainInfo, ModuleError>;
async fn get_block_by_height(&self, height: u64) -> Result<Option<Block>, ModuleError>;
async fn get_lightning_node_url(&self) -> Result<Option<String>, ModuleError>;
async fn get_lightning_info(&self) -> Result<Option<LightningInfo>, ModuleError>;
async fn get_payment_state(
&self,
payment_id: &str,
) -> Result<Option<PaymentState>, ModuleError>;
async fn check_transaction_in_mempool(&self, tx_hash: &Hash) -> Result<bool, ModuleError>;
async fn get_fee_estimate(&self, target_blocks: u32) -> Result<u64, ModuleError>;
async fn register_rpc_endpoint(
&self,
method: String,
description: String,
) -> Result<(), ModuleError>;
async fn unregister_rpc_endpoint(&self, method: &str) -> Result<(), ModuleError>;
async fn register_timer(
&self,
interval_seconds: u64,
callback: Arc<dyn crate::module::timers::manager::TimerCallback>,
) -> Result<crate::module::timers::manager::TimerId, ModuleError>;
async fn cancel_timer(
&self,
timer_id: crate::module::timers::manager::TimerId,
) -> Result<(), ModuleError>;
async fn schedule_task(
&self,
delay_seconds: u64,
callback: Arc<dyn crate::module::timers::manager::TaskCallback>,
) -> Result<crate::module::timers::manager::TaskId, ModuleError>;
async fn report_metric(
&self,
metric: crate::module::metrics::manager::Metric,
) -> Result<(), ModuleError>;
async fn get_module_metrics(
&self,
module_id: &str,
) -> Result<Vec<crate::module::metrics::manager::Metric>, ModuleError>;
async fn get_all_metrics(
&self,
) -> Result<
std::collections::HashMap<String, Vec<crate::module::metrics::manager::Metric>>,
ModuleError,
>;
async fn read_file(&self, path: String) -> Result<Vec<u8>, ModuleError>;
async fn write_file(&self, path: String, data: Vec<u8>) -> Result<(), ModuleError>;
async fn delete_file(&self, path: String) -> Result<(), ModuleError>;
async fn list_directory(&self, path: String) -> Result<Vec<String>, ModuleError>;
async fn create_directory(&self, path: String) -> Result<(), ModuleError>;
async fn get_file_metadata(
&self,
path: String,
) -> Result<crate::module::ipc::protocol::FileMetadata, ModuleError>;
async fn initialize_module(
&self,
module_id: String,
module_data_dir: std::path::PathBuf,
base_data_dir: std::path::PathBuf,
) -> Result<(), ModuleError>;
async fn discover_modules(&self) -> Result<Vec<ModuleInfo>, ModuleError>;
async fn get_module_info(&self, module_id: &str) -> Result<Option<ModuleInfo>, ModuleError>;
async fn is_module_available(&self, module_id: &str) -> Result<bool, ModuleError>;
async fn publish_event(
&self,
event_type: EventType,
payload: EventPayload,
) -> Result<(), ModuleError>;
async fn call_module(
&self,
target_module_id: Option<&str>,
method: &str,
params: Vec<u8>,
) -> Result<Vec<u8>, ModuleError>;
async fn register_module_api(
&self,
api: Arc<dyn crate::module::inter_module::api::ModuleAPI>,
) -> Result<(), ModuleError>;
async fn unregister_module_api(&self) -> Result<(), ModuleError>;
async fn send_mesh_packet_to_module(
&self,
module_id: &str,
packet_data: Vec<u8>,
peer_addr: String,
) -> Result<(), ModuleError>;
async fn send_mesh_packet_to_peer(
&self,
peer_addr: String,
packet_data: Vec<u8>,
) -> Result<(), ModuleError>;
async fn send_stratum_v2_message_to_peer(
&self,
peer_addr: String,
message_data: Vec<u8>,
) -> Result<(), ModuleError>;
async fn get_module_health(
&self,
module_id: &str,
) -> Result<Option<crate::module::process::monitor::ModuleHealth>, ModuleError>;
async fn get_all_module_health(
&self,
) -> Result<Vec<(String, crate::module::process::monitor::ModuleHealth)>, ModuleError>;
async fn report_module_health(
&self,
health: crate::module::process::monitor::ModuleHealth,
) -> Result<(), ModuleError>;
async fn get_block_template(
&self,
rules: Vec<String>,
coinbase_script: Option<Vec<u8>>,
coinbase_address: Option<String>,
) -> Result<blvm_protocol::mining::BlockTemplate, ModuleError>;
async fn submit_block(&self, block: Block) -> Result<SubmitBlockResult, ModuleError>;
async fn merge_block_serve_denylist(&self, block_hashes: &[Hash]) -> Result<(), ModuleError>;
async fn get_block_serve_denylist_snapshot(
&self,
) -> Result<BlockServeDenylistSnapshot, ModuleError>;
async fn clear_block_serve_denylist(&self) -> Result<(), ModuleError>;
async fn replace_block_serve_denylist(&self, block_hashes: &[Hash]) -> Result<(), ModuleError>;
async fn merge_tx_serve_denylist(&self, tx_hashes: &[Hash]) -> Result<(), ModuleError>;
async fn get_tx_serve_denylist_snapshot(&self) -> Result<TxServeDenylistSnapshot, ModuleError>;
async fn clear_tx_serve_denylist(&self) -> Result<(), ModuleError>;
async fn replace_tx_serve_denylist(&self, tx_hashes: &[Hash]) -> Result<(), ModuleError>;
async fn get_sync_status(&self) -> Result<SyncStatus, ModuleError>;
async fn ban_peer(
&self,
peer_addr: &str,
ban_duration_seconds: Option<u64>,
) -> Result<(), ModuleError>;
async fn set_block_serve_maintenance_mode(&self, enabled: bool) -> Result<(), ModuleError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EventType {
NewBlock,
NewTransaction,
BlockDisconnected,
ChainReorg,
PaymentRequestCreated,
PaymentSettled,
PaymentFailed,
PaymentVerified,
PaymentRouteFound,
PaymentRouteFailed,
ChannelOpened,
ChannelClosed,
BlockMined,
BlockTemplateUpdated,
MiningDifficultyChanged,
MiningJobCreated,
MeshPacketReceived,
StratumV2MessageReceived,
ShareSubmitted,
MergeMiningReward,
MiningPoolConnected,
MiningPoolDisconnected,
GovernanceProposalCreated,
GovernanceProposalVoted,
GovernanceProposalMerged,
WebhookSent,
WebhookFailed,
GovernanceForkDetected,
PeerConnected,
PeerDisconnected,
PeerBanned,
PeerUnbanned,
MessageReceived,
MessageSent,
BroadcastStarted,
BroadcastCompleted,
RouteDiscovered,
RouteFailed,
ConnectionAttempt,
AddressDiscovered,
AddressExpired,
NetworkPartition,
NetworkReconnected,
DoSAttackDetected,
RateLimitExceeded,
BlockValidationStarted,
BlockValidationCompleted,
ScriptVerificationStarted,
ScriptVerificationCompleted,
UTXOValidationStarted,
UTXOValidationCompleted,
DifficultyAdjusted,
SoftForkActivated,
SoftForkLockedIn,
ConsensusRuleViolation,
HeadersSyncStarted,
HeadersSyncProgress,
HeadersSyncCompleted,
BlockSyncStarted,
BlockSyncProgress,
BlockSyncCompleted,
SyncStateChanged,
MempoolTransactionAdded,
MempoolTransactionRemoved,
MempoolThresholdExceeded,
FeeRateChanged,
MempoolCleared,
StorageRead,
StorageWrite,
StorageQuery,
DatabaseBackupStarted,
DatabaseBackupCompleted,
ModuleLoaded,
ModuleUnloaded,
ModuleReloaded,
ModuleStarted,
ModuleStopped,
ModuleCrashed,
ModuleHealthChanged,
ModuleStateChanged,
ConfigLoaded,
NodeShutdown,
NodeShutdownCompleted,
NodeStartupCompleted,
MaintenanceStarted,
MaintenanceCompleted,
DataMaintenance,
HealthCheck,
DiskSpaceLow,
ResourceLimitWarning,
DandelionStemStarted,
DandelionStemAdvanced,
DandelionFluffed,
DandelionStemPathExpired,
CompactBlockReceived,
BlockReconstructionStarted,
BlockReconstructionCompleted,
FibreBlockEncoded,
FibreBlockSent,
FibrePeerRegistered,
PackageReceived,
PackageRejected,
UtxoCommitmentReceived,
UtxoCommitmentVerified,
BanListShared,
BanListReceived,
SelectiveSyncPolicyApplied,
ActionExecuted,
ModulePurchaseCompleted,
StratumClientConnected,
StratumClientDisconnected,
IBDBlockFiltered,
ModuleDiscovered,
ModuleInstalled,
ModuleUpdated,
ModuleRemoved,
}
#[derive(Debug, Error)]
pub enum ModuleError {
#[error("IPC communication error: {0}")]
IpcError(String),
#[error("Module initialization failed: {0}")]
InitializationError(String),
#[error("Module operation failed: {0}")]
OperationError(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("Module not found: {0}")]
ModuleNotFound(String),
#[error("Module dependency missing: {0}")]
DependencyMissing(String),
#[error("Invalid module manifest: {0}")]
InvalidManifest(String),
#[error("Module version incompatible: {0}")]
VersionIncompatible(String),
#[error("Module crashed: {0}")]
ModuleCrashed(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("Timeout waiting for module response")]
Timeout,
#[error("Resource limit exceeded: {0}")]
ResourceLimitExceeded(String),
#[error("Cryptographic error: {0}")]
CryptoError(String),
#[error("Config error: {0}")]
Config(String),
#[error("RPC error: {0}")]
Rpc(String),
#[error("Migration error: {0}")]
Migration(String),
#[error("CLI error: {0}")]
Cli(String),
#[error("{0}")]
Other(String),
}
impl From<serde_json::Error> for ModuleError {
fn from(e: serde_json::Error) -> Self {
ModuleError::SerializationError(e.to_string())
}
}
impl From<bincode::Error> for ModuleError {
fn from(e: bincode::Error) -> Self {
ModuleError::SerializationError(e.to_string())
}
}
impl From<anyhow::Error> for ModuleError {
fn from(e: anyhow::Error) -> Self {
ModuleError::OperationError(e.to_string())
}
}
impl ModuleError {
pub fn op_err<E: std::fmt::Display>(msg: &str, e: E) -> Self {
ModuleError::OperationError(format!("{msg}: {e}"))
}
}
impl From<String> for ModuleError {
fn from(s: String) -> Self {
ModuleError::Other(s)
}
}
impl From<&str> for ModuleError {
fn from(s: &str) -> Self {
ModuleError::Other(s.to_string())
}
}
pub mod module_error_msg {
pub const CHAIN_NOT_INITIALIZED: &str = "Chain not initialized";
pub const CHAIN_NOT_YET_INITIALIZED: &str = "Chain not yet initialized";
pub const EVENT_MANAGER_NOT_AVAILABLE: &str = "Event manager not available";
pub const IPC_SERVER_NOT_AVAILABLE: &str = "IPC server not available";
pub const MEMPOOL_MANAGER_NOT_AVAILABLE: &str = "Mempool manager not available";
pub const METRICS_MANAGER_NOT_AVAILABLE: &str = "Metrics manager not available";
pub const MODULE_API_REGISTRY_NOT_AVAILABLE: &str = "Module API registry not available";
pub const MODULE_ID_NOT_SET: &str = "Module ID not set";
pub const MODULE_ID_NOT_SET_FOR_METRICS: &str = "Module ID not set for metrics reporting";
pub const MODULE_ID_NOT_SET_FOR_TASK: &str = "Module ID not set for task scheduling";
pub const MODULE_ID_NOT_SET_FOR_TIMER: &str = "Module ID not set for timer registration";
pub const MODULE_MANAGER_NOT_AVAILABLE: &str = "Module manager not available";
pub const MODULE_REGISTRY_NOT_AVAILABLE: &str = "Module registry not available";
pub const MODULE_ROUTER_NOT_AVAILABLE: &str = "Module router not available";
pub const NETWORK_MANAGER_NOT_AVAILABLE: &str = "Network manager not available";
pub const NO_CHAIN_TIP: &str = "No chain tip";
pub const PAYMENT_STATE_MACHINE_NOT_AVAILABLE: &str = "Payment state machine not available";
pub const RPC_SERVER_NOT_AVAILABLE: &str = "RPC server not available";
pub const TIMER_MANAGER_NOT_AVAILABLE: &str = "Timer manager not available";
pub const INVOCATION_RESPONSE_CHANNEL_CLOSED: &str = "Invocation response channel closed";
pub const MODULE_CONNECTION_CLOSED: &str = "Module connection closed";
pub const MODULE_DID_NOT_RESPOND_CLI_60S: &str =
"Module did not respond to CLI invocation within 60 seconds";
pub const MODULE_DID_NOT_RESPOND_RPC_60S: &str =
"Module did not respond to RPC invocation within 60 seconds";
pub const MODULE_RETURNED_SUCCESS_BUT_NO_PAYLOAD: &str =
"Module returned success but no payload";
pub const MODULE_RETURNED_WRONG_PAYLOAD_TYPE_RPC: &str =
"Module returned wrong payload type for RPC";
pub const NO_MODULE_WITH_CLI_NAME_LOADED: &str = "No module with CLI name '{}' is loaded";
pub const TASK_SCHEDULING_REQUIRES_CALLBACK_IPC: &str =
"Task scheduling requires callback which cannot be serialized over IPC. Use module-side task management.";
pub const TIMER_CANCELLATION_NOT_SUPPORTED_IPC: &str =
"Timer cancellation not supported over IPC. Use module-side timer management.";
pub const TIMER_REGISTRATION_REQUIRES_CALLBACK_IPC: &str =
"Timer registration requires callback which cannot be serialized over IPC. Use module-side timer management.";
pub const BINARY_HASH_MUST_BE_32_BYTES: &str = "Binary hash must be 32 bytes";
pub const EXPECTED_MODULE_MESSAGE: &str = "Expected Module message";
pub const HASH_MUST_BE_32_BYTES: &str = "Hash must be 32 bytes";
pub const IROH_TRANSPORT_NOT_SUPPORTED_MODULE_FETCHING: &str =
"Iroh transport not yet supported for module fetching";
pub const MANIFEST_HASH_MUST_BE_32_BYTES: &str = "Manifest hash must be 32 bytes";
pub const MISSING_BINARY_HASH_FIELD: &str = "Missing 'binary_hash' field";
pub const MISSING_HASH_FIELD: &str = "Missing 'hash' field";
pub const MISSING_MANIFEST_FIELD: &str = "Missing 'manifest' field";
pub const MISSING_MANIFEST_HASH_FIELD: &str = "Missing 'manifest_hash' field";
pub const MISSING_NAME_FIELD: &str = "Missing 'name' field";
pub const MISSING_VERSION_FIELD: &str = "Missing 'version' field";
pub const NETWORK_MANAGER_NOT_SET_P2P_FETCHING: &str =
"Network manager not set for P2P fetching";
pub const REQUEST_ID_MISMATCH: &str = "Request ID mismatch";
pub const RESPONSE_CHANNEL_CLOSED: &str = "Response channel closed";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MempoolSize {
pub transaction_count: usize,
pub size_bytes: usize,
pub total_fee_sats: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkStats {
pub peer_count: usize,
pub hash_rate: f64,
pub bytes_sent: u64,
pub bytes_received: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerInfo {
pub addr: String,
pub transport_type: String,
pub services: u64,
pub version: u32,
pub connected_since: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainInfo {
pub tip_hash: Hash,
pub height: u64,
pub difficulty: u32,
pub chain_work: u64,
pub is_synced: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockServeDenylistSnapshot {
pub total_count: u64,
pub truncated: bool,
pub hashes: Vec<Hash>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TxServeDenylistSnapshot {
pub total_count: u64,
pub truncated: bool,
pub hashes: Vec<Hash>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SyncStatus {
pub phase: String,
pub progress: f64,
pub is_synced: bool,
pub error_message: Option<String>,
}
pub const SERVE_DENYLIST_SNAPSHOT_MAX_HASHES: usize = 4096;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LightningInfo {
pub node_url: String,
pub node_pubkey: Vec<u8>,
pub channel_count: usize,
pub total_capacity_sats: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentState {
pub payment_id: String,
pub status: String, pub amount_sats: u64,
pub tx_hash: Option<Hash>,
pub confirmations: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SubmitBlockResult {
Accepted,
Rejected(String),
Duplicate,
}