use crate::utils::current_timestamp;
use anyhow::Result;
use blvm_protocol::{Block, BlockHeader, Transaction};
use std::collections::HashMap;
use tracing::{debug, info, warn};
pub trait MempoolProvider: Send + Sync {
fn get_transactions(&self) -> Vec<Transaction>;
fn get_transaction(&self, hash: &[u8; 32]) -> Option<Transaction>;
fn get_mempool_size(&self) -> usize;
fn get_prioritized_transactions(
&self,
limit: usize,
utxo_set: &blvm_protocol::UtxoSet,
) -> Vec<Transaction>;
fn remove_transaction(&mut self, hash: &[u8; 32]) -> bool;
}
pub struct TransactionSelector {
max_block_size: usize,
max_block_weight: u64,
min_fee_rate: u64,
}
impl Default for TransactionSelector {
fn default() -> Self {
Self::new()
}
}
impl TransactionSelector {
pub fn new() -> Self {
Self {
max_block_size: 1_000_000, max_block_weight: 4_000_000, min_fee_rate: 1, }
}
pub fn with_params(max_block_size: usize, max_block_weight: u64, min_fee_rate: u64) -> Self {
Self {
max_block_size,
max_block_weight,
min_fee_rate,
}
}
pub fn select_transactions(
&self,
mempool: &dyn MempoolProvider,
utxo_set: &blvm_protocol::UtxoSet,
) -> Vec<Transaction> {
let mut selected = Vec::new();
let mut current_size = 0;
let mut current_weight = 0;
let transactions = mempool.get_prioritized_transactions(1000, utxo_set);
for tx in transactions {
let tx_size = self.calculate_transaction_size(&tx);
let tx_weight = self.calculate_transaction_weight(&tx);
if current_size + tx_size > self.max_block_size
|| current_weight + tx_weight > self.max_block_weight
{
break;
}
let fee_rate = self.calculate_fee_rate_with_utxo(&tx, utxo_set);
if fee_rate < self.min_fee_rate {
continue;
}
selected.push(tx);
current_size += tx_size;
current_weight += tx_weight;
}
selected
}
fn calculate_transaction_size(&self, tx: &Transaction) -> usize {
tx.inputs.len() * 148 + tx.outputs.len() * 34 + 10
}
fn calculate_transaction_weight(&self, tx: &Transaction) -> u64 {
self.calculate_transaction_size(tx) as u64 * 4
}
fn calculate_fee_rate_with_utxo(
&self,
tx: &Transaction,
utxo_set: &blvm_protocol::UtxoSet,
) -> u64 {
let size = self.calculate_transaction_size(tx);
if size == 0 {
return 0;
}
let mut input_total = 0u64;
for input in &tx.inputs {
if let Some(utxo) = utxo_set.get(&input.prevout) {
input_total += utxo.value as u64;
}
}
let output_total: u64 = tx.outputs.iter().map(|out| out.value as u64).sum();
let fee = input_total.saturating_sub(output_total);
fee / size as u64
}
pub fn max_block_size(&self) -> usize {
self.max_block_size
}
pub fn max_block_weight(&self) -> u64 {
self.max_block_weight
}
pub fn min_fee_rate(&self) -> u64 {
self.min_fee_rate
}
}
pub struct MiningEngine {
mining_enabled: bool,
mining_threads: u32,
block_template: Option<Block>,
stats: MiningStats,
}
#[derive(Debug, Clone)]
pub struct MiningStats {
pub blocks_mined: u64,
pub total_hashrate: f64,
pub average_block_time: f64,
pub last_block_time: Option<u64>,
}
impl Default for MiningEngine {
fn default() -> Self {
Self::new()
}
}
impl MiningEngine {
pub fn new() -> Self {
Self {
mining_enabled: false,
mining_threads: 1,
block_template: None,
stats: MiningStats {
blocks_mined: 0,
total_hashrate: 0.0,
average_block_time: 0.0,
last_block_time: None,
},
}
}
pub fn with_threads(threads: u32) -> Self {
Self {
mining_enabled: false,
mining_threads: threads,
block_template: None,
stats: MiningStats {
blocks_mined: 0,
total_hashrate: 0.0,
average_block_time: 0.0,
last_block_time: None,
},
}
}
pub fn enable_mining(&mut self) {
self.mining_enabled = true;
info!("Mining enabled with {} threads", self.mining_threads);
}
pub fn disable_mining(&mut self) {
self.mining_enabled = false;
info!("Mining disabled");
}
pub fn is_mining_enabled(&self) -> bool {
self.mining_enabled
}
pub fn get_stats(&self) -> &MiningStats {
&self.stats
}
pub fn get_threads(&self) -> u32 {
self.mining_threads
}
pub fn set_threads(&mut self, threads: u32) {
self.mining_threads = threads;
}
pub async fn mine_template(&mut self, template: Block) -> Result<Block> {
debug!("Mining block template with {} threads", self.mining_threads);
self.block_template = Some(template.clone());
use blvm_protocol::ConsensusProof;
let consensus = ConsensusProof::new();
let max_attempts_per_thread = 1_000_000u64;
if self.mining_threads > 1 {
self.mine_template_multithreaded(template, max_attempts_per_thread, &consensus)
.await
} else {
let template_clone = template.clone();
let (mined_block, result) = tokio::task::spawn_blocking(move || {
consensus.mine_block(template_clone, max_attempts_per_thread)
})
.await
.map_err(|e| anyhow::anyhow!("Mining task panicked: {}", e))?
.map_err(|e| anyhow::anyhow!("Mining failed: {}", e))?;
self.handle_mining_result(mined_block, result)
}
}
async fn mine_template_multithreaded(
&mut self,
template: Block,
max_attempts_per_thread: u64,
_consensus: &blvm_protocol::ConsensusProof,
) -> Result<Block> {
use blvm_protocol::mining::MiningResult;
use blvm_protocol::pow::check_proof_of_work;
use tokio::sync::oneshot;
let nonces_per_thread = max_attempts_per_thread;
let total_threads = self.mining_threads as u64;
let mut handles = Vec::new();
for thread_id in 0..total_threads {
let template_clone = template.clone();
let start_nonce = thread_id * nonces_per_thread;
let end_nonce = start_nonce + nonces_per_thread;
let (tx, rx) = oneshot::channel();
let handle = tokio::task::spawn_blocking(move || {
for nonce in start_nonce..end_nonce {
let mut block = template_clone.clone();
block.header.nonce = nonce;
if let Ok(valid) = check_proof_of_work(&block.header) {
if valid {
let _ = tx.send(Ok((block, MiningResult::Success)));
return;
}
}
}
let mut block = template_clone;
block.header.nonce = start_nonce; let _ = tx.send(Ok((block, MiningResult::Failure)));
});
handles.push((handle, rx));
}
let mut results = Vec::new();
for (handle, rx) in handles {
handle
.await
.map_err(|e| anyhow::anyhow!("Mining task panicked: {}", e))?;
match rx.await {
Ok(Ok((block, result))) => {
if matches!(result, MiningResult::Success) {
return self.handle_mining_result(block, result);
}
results.push((block, result));
}
Ok(Err(e)) => return Err(e),
Err(_) => {
continue;
}
}
}
if let Some((block, _)) = results.first() {
self.handle_mining_result(block.clone(), MiningResult::Failure)
} else {
Err(anyhow::anyhow!("Mining failed: all threads exhausted"))
}
}
fn handle_mining_result(
&mut self,
mined_block: Block,
result: blvm_protocol::mining::MiningResult,
) -> Result<Block> {
use blvm_protocol::mining::MiningResult;
match result {
MiningResult::Success => {
info!(
"Successfully mined block with nonce {}",
mined_block.header.nonce
);
self.stats.blocks_mined += 1;
self.stats.last_block_time = Some(current_timestamp());
Ok(mined_block)
}
MiningResult::Failure => {
warn!("Could not find valid nonce (difficulty may be too high)");
Err(anyhow::anyhow!("Mining failed: could not find valid nonce"))
}
}
}
pub fn get_block_template(&self) -> Option<&Block> {
self.block_template.as_ref()
}
pub fn clear_template(&mut self) {
self.block_template = None;
}
pub fn update_hashrate(&mut self, hashrate: f64) {
self.stats.total_hashrate = hashrate;
}
pub fn update_average_block_time(&mut self, block_time: f64) {
self.stats.average_block_time = block_time;
}
}
pub struct MiningCoordinator {
mining_engine: MiningEngine,
transaction_selector: TransactionSelector,
mempool: std::sync::Arc<crate::node::mempool::MempoolManager>,
storage: Option<std::sync::Arc<crate::storage::Storage>>,
#[cfg(feature = "stratum-v2")]
stratum_v2_client: Option<crate::network::stratum_v2::client::StratumV2Client>,
}
impl MiningCoordinator {
pub fn new(
mempool: std::sync::Arc<crate::node::mempool::MempoolManager>,
storage: Option<std::sync::Arc<crate::storage::Storage>>,
) -> Self {
Self {
mining_engine: MiningEngine::new(),
transaction_selector: TransactionSelector::new(),
mempool,
storage,
#[cfg(feature = "stratum-v2")]
stratum_v2_client: None,
}
}
pub fn with_params(
mempool: std::sync::Arc<crate::node::mempool::MempoolManager>,
storage: Option<std::sync::Arc<crate::storage::Storage>>,
threads: u32,
max_block_size: usize,
max_block_weight: u64,
min_fee_rate: u64,
) -> Self {
Self {
mining_engine: MiningEngine::with_threads(threads),
transaction_selector: TransactionSelector::with_params(
max_block_size,
max_block_weight,
min_fee_rate,
),
mempool,
storage,
#[cfg(feature = "stratum-v2")]
stratum_v2_client: None,
}
}
#[cfg(feature = "stratum-v2")]
pub fn set_stratum_v2_client(
&mut self,
client: crate::network::stratum_v2::client::StratumV2Client,
) {
self.stratum_v2_client = Some(client);
}
#[cfg(feature = "stratum-v2")]
pub fn stratum_v2_client(
&self,
) -> Option<&crate::network::stratum_v2::client::StratumV2Client> {
self.stratum_v2_client.as_ref()
}
pub async fn start(&mut self) -> Result<()> {
info!("Starting mining coordinator");
self.initialize_mining().await?;
self.mining_loop().await?;
Ok(())
}
async fn initialize_mining(&mut self) -> Result<()> {
debug!("Initializing mining");
Ok(())
}
async fn mining_loop(&mut self) -> Result<()> {
loop {
if self.mining_engine.is_mining_enabled() {
self.mine_block().await?;
} else {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
}
async fn mine_block(&mut self) -> Result<()> {
debug!("Mining block");
let template = self.generate_block_template().await?;
let mined_block = self.mining_engine.mine_template(template).await?;
self.submit_block(mined_block).await?;
Ok(())
}
pub async fn generate_block_template(&mut self) -> Result<Block> {
debug!("Generating block template");
let (prev_block_hash, bits, height) = if let Some(ref storage) = self.storage {
if let Some(tip_header) = storage
.chain()
.get_tip_header()
.map_err(|e| anyhow::anyhow!("Failed to get tip header: {}", e))?
{
let tip_hash = storage
.chain()
.get_tip_hash()
.map_err(|e| anyhow::anyhow!("Failed to get tip hash: {}", e))?
.unwrap_or([0u8; 32]);
let chain_height = storage
.chain()
.get_height()
.map_err(|e| anyhow::anyhow!("Failed to get chain height: {}", e))?
.unwrap_or(0);
(tip_hash, tip_header.bits, chain_height)
} else {
([0u8; 32], 0x1d00ffff, 0)
}
} else {
([0u8; 32], 0x1d00ffff, 0)
};
let utxo_set = if let Some(ref storage) = self.storage {
storage
.utxos()
.get_all_utxos()
.map_err(|e| anyhow::anyhow!("Failed to get UTXO set: {}", e))?
} else {
blvm_protocol::UtxoSet::default()
};
let transactions = self
.transaction_selector
.select_transactions(&*self.mempool as &dyn MempoolProvider, &utxo_set);
let coinbase_tx = self
.create_coinbase_transaction(height + 1, &transactions, &utxo_set)
.await?;
let mut all_transactions = vec![coinbase_tx];
all_transactions.extend(transactions);
use blvm_protocol::mining::calculate_merkle_root;
let merkle_root = calculate_merkle_root(&all_transactions)
.map_err(|e| anyhow::anyhow!("Failed to calculate merkle root: {}", e))?;
let timestamp = current_timestamp();
let template = Block {
header: BlockHeader {
version: 1,
prev_block_hash,
merkle_root,
timestamp,
bits,
nonce: 0,
},
transactions: all_transactions.into_boxed_slice(),
};
debug!("Generated block template: height={}, prev_hash={:?}, {} transactions, merkle_root={:?}",
height + 1, prev_block_hash, template.transactions.len(), merkle_root);
Ok(template)
}
async fn create_coinbase_transaction(
&self,
height: u64,
selected_transactions: &[Transaction],
utxo_set: &blvm_protocol::UtxoSet,
) -> Result<Transaction> {
use blvm_protocol::ConsensusProof;
let consensus = ConsensusProof::new();
let subsidy = consensus.get_block_subsidy(height) as u64;
let total_fees: u64 = selected_transactions
.iter()
.map(|tx| self.mempool.calculate_transaction_fee(tx, utxo_set))
.sum();
let coinbase_value = subsidy.checked_add(total_fees).ok_or_else(|| {
anyhow::anyhow!(
"Coinbase value overflow: subsidy {} + fees {}",
subsidy,
total_fees
)
})?;
debug!(
"Creating coinbase: height={}, subsidy={}, fees={}, total={}",
height, subsidy, total_fees, coinbase_value
);
let lock_time = height.saturating_sub(13);
let h = height.min(u64::from(u32::MAX));
let mut height_bytes = Vec::new();
let mut x = h;
while x > 0 {
height_bytes.push((x & 0xff) as u8);
x >>= 8;
}
if height_bytes.is_empty() {
height_bytes.push(0);
}
let mut script_sig = vec![height_bytes.len() as u8];
script_sig.extend(height_bytes);
if script_sig.len() < 2 {
script_sig = vec![0x01, 0x00];
}
Ok(Transaction {
version: 1,
inputs: vec![blvm_protocol::TransactionInput {
prevout: blvm_protocol::OutPoint {
hash: [0u8; 32],
index: 0xffffffff,
},
script_sig,
sequence: 0xfffffffe,
}]
.into(),
outputs: crate::tx_outputs![blvm_protocol::TransactionOutput {
value: coinbase_value as i64,
script_pubkey: vec![
blvm_protocol::opcodes::OP_DUP,
blvm_protocol::opcodes::OP_HASH160,
blvm_protocol::opcodes::PUSH_20_BYTES,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
blvm_protocol::opcodes::OP_EQUALVERIFY,
blvm_protocol::opcodes::OP_CHECKSIG,
],
}],
lock_time,
})
}
async fn submit_block(&self, _block: Block) -> Result<()> {
debug!("Submitting mined block");
Ok(())
}
pub fn enable_mining(&mut self) {
self.mining_engine.enable_mining();
}
pub fn disable_mining(&mut self) {
self.mining_engine.disable_mining();
}
pub fn is_mining_enabled(&self) -> bool {
self.mining_engine.is_mining_enabled()
}
pub fn get_mining_info(&self) -> MiningInfo {
MiningInfo {
enabled: self.mining_engine.is_mining_enabled(),
threads: self.mining_engine.get_threads(),
has_template: self.mining_engine.get_block_template().is_some(),
}
}
pub fn get_mining_stats(&self) -> &MiningStats {
self.mining_engine.get_stats()
}
pub fn mining_engine(&self) -> &MiningEngine {
&self.mining_engine
}
pub fn mining_engine_mut(&mut self) -> &mut MiningEngine {
&mut self.mining_engine
}
pub fn transaction_selector(&self) -> &TransactionSelector {
&self.transaction_selector
}
pub fn transaction_selector_mut(&mut self) -> &mut TransactionSelector {
&mut self.transaction_selector
}
pub fn get_mempool_size(&self) -> usize {
self.mempool.size()
}
}
#[derive(Debug, Clone)]
pub struct MiningInfo {
pub enabled: bool,
pub threads: u32,
pub has_template: bool,
}
pub struct MockMempoolProvider {
transactions: HashMap<[u8; 32], Transaction>,
prioritized_transactions: Vec<(Transaction, u64)>,
}
impl Default for MockMempoolProvider {
fn default() -> Self {
Self::new()
}
}
impl MockMempoolProvider {
pub fn new() -> Self {
Self {
transactions: HashMap::new(),
prioritized_transactions: Vec::new(),
}
}
pub fn add_transaction(&mut self, tx: Transaction) {
let hash = self.calculate_tx_hash(&tx);
let fee_rate = self.calculate_fee_rate(&tx);
self.transactions.insert(hash, tx.clone());
self.prioritized_transactions.push((tx, fee_rate));
self.prioritized_transactions.sort_by(|a, b| b.1.cmp(&a.1));
}
pub fn clear(&mut self) {
self.transactions.clear();
self.prioritized_transactions.clear();
}
fn calculate_tx_hash(&self, tx: &Transaction) -> [u8; 32] {
let mut hash = [0u8; 32];
hash[0] = tx.version as u8;
hash[1] = tx.inputs.len() as u8;
hash[2] = tx.outputs.len() as u8;
hash
}
fn calculate_fee_rate(&self, tx: &Transaction) -> u64 {
let total_output_value: u64 = tx.outputs.iter().map(|out| out.value as u64).sum();
let total_input_value = total_output_value + (tx.version * 1000); let fee = total_input_value - total_output_value;
let size = tx.inputs.len() * 148 + tx.outputs.len() * 34 + 10;
if size == 0 {
return 0;
}
fee / size as u64
}
}
impl MempoolProvider for MockMempoolProvider {
fn get_transactions(&self) -> Vec<Transaction> {
self.transactions.values().cloned().collect()
}
fn get_transaction(&self, hash: &[u8; 32]) -> Option<Transaction> {
self.transactions.get(hash).cloned()
}
fn get_mempool_size(&self) -> usize {
self.transactions.len()
}
fn get_prioritized_transactions(
&self,
limit: usize,
_utxo_set: &blvm_protocol::UtxoSet,
) -> Vec<Transaction> {
self.prioritized_transactions
.iter()
.take(limit)
.map(|(tx, _)| tx.clone())
.collect()
}
fn remove_transaction(&mut self, hash: &[u8; 32]) -> bool {
if let Some(tx) = self.transactions.remove(hash) {
self.prioritized_transactions.retain(|(t, _)| t != &tx);
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use blvm_protocol::TransactionOutput;
#[test]
fn test_transaction_selector_creation() {
let selector = TransactionSelector::new();
assert_eq!(selector.max_block_size(), 1_000_000);
assert_eq!(selector.max_block_weight(), 4_000_000);
assert_eq!(selector.min_fee_rate(), 1);
}
#[test]
fn test_transaction_selector_with_params() {
let selector = TransactionSelector::with_params(2_000_000, 8_000_000, 5);
assert_eq!(selector.max_block_size(), 2_000_000);
assert_eq!(selector.max_block_weight(), 8_000_000);
assert_eq!(selector.min_fee_rate(), 5);
}
#[test]
fn test_transaction_selector_transaction_selection() {
let selector = TransactionSelector::new();
let mut mempool = MockMempoolProvider::new();
let tx1 = create_test_transaction(1, 1000);
let tx2 = create_test_transaction(2, 2000);
let tx3 = create_test_transaction(3, 500);
mempool.add_transaction(tx1);
mempool.add_transaction(tx2);
mempool.add_transaction(tx3);
let mut utxo_set = blvm_protocol::UtxoSet::default();
let outpoint = blvm_protocol::OutPoint {
hash: [0u8; 32],
index: 0,
};
utxo_set.insert(
outpoint,
std::sync::Arc::new(blvm_protocol::UTXO {
value: 100_000_000, script_pubkey: vec![blvm_protocol::opcodes::OP_1].into(),
height: 0,
is_coinbase: false,
}),
);
let selected = selector.select_transactions(&mempool, &utxo_set);
assert!(!selected.is_empty());
assert!(selected.len() <= 3);
}
#[test]
fn test_transaction_selector_size_calculation() {
let selector = TransactionSelector::new();
let tx = create_test_transaction(1, 1000);
let size = selector.calculate_transaction_size(&tx);
assert!(size > 0);
let weight = selector.calculate_transaction_weight(&tx);
assert!(weight > 0);
let mut utxo_set = blvm_protocol::UtxoSet::default();
let outpoint = blvm_protocol::OutPoint {
hash: [0u8; 32],
index: 0,
};
utxo_set.insert(
outpoint,
std::sync::Arc::new(blvm_protocol::UTXO {
value: 10000,
script_pubkey: vec![blvm_protocol::opcodes::OP_1].into(),
height: 0,
is_coinbase: false,
}),
);
let fee_rate = selector.calculate_fee_rate_with_utxo(&tx, &utxo_set);
assert!(fee_rate > 0);
}
#[test]
fn test_mining_engine_creation() {
let engine = MiningEngine::new();
assert!(!engine.is_mining_enabled());
assert_eq!(engine.get_threads(), 1);
assert!(engine.get_block_template().is_none());
assert_eq!(engine.get_stats().blocks_mined, 0);
}
#[test]
fn test_mining_engine_with_threads() {
let engine = MiningEngine::with_threads(4);
assert!(!engine.is_mining_enabled());
assert_eq!(engine.get_threads(), 4);
}
#[test]
fn test_mining_engine_enable_disable() {
let mut engine = MiningEngine::new();
assert!(!engine.is_mining_enabled());
engine.enable_mining();
assert!(engine.is_mining_enabled());
engine.disable_mining();
assert!(!engine.is_mining_enabled());
}
#[test]
fn test_mining_engine_thread_management() {
let mut engine = MiningEngine::new();
assert_eq!(engine.get_threads(), 1);
engine.set_threads(8);
assert_eq!(engine.get_threads(), 8);
}
#[tokio::test]
async fn test_mining_engine_mine_template() {
let mut engine = MiningEngine::new();
let template = create_test_block();
let result = engine.mine_template(template.clone()).await;
if let Ok(mined_block) = result {
assert_eq!(mined_block.header.version, template.header.version);
assert_ne!(mined_block.header.nonce, template.header.nonce);
use blvm_protocol::pow::check_proof_of_work;
let pow_valid = check_proof_of_work(&mined_block.header).unwrap();
assert!(pow_valid, "Mined block should have valid proof of work");
assert!(engine.get_block_template().is_some());
assert_eq!(engine.get_stats().blocks_mined, 1);
} else {
assert!(engine.get_block_template().is_some());
}
}
#[tokio::test]
async fn test_mining_engine_mine_template_multithreaded() {
let mut engine = MiningEngine::with_threads(4);
let template = create_test_block();
let result = engine.mine_template(template.clone()).await;
if let Ok(mined_block) = result {
assert_eq!(mined_block.header.version, template.header.version);
use blvm_protocol::pow::check_proof_of_work;
let pow_valid = check_proof_of_work(&mined_block.header).unwrap();
assert!(pow_valid, "Mined block should have valid proof of work");
assert!(engine.get_block_template().is_some());
assert_eq!(engine.get_stats().blocks_mined, 1);
} else {
assert!(engine.get_block_template().is_some());
}
}
#[tokio::test]
async fn test_mining_engine_mine_template_regtest_difficulty() {
let mut engine = MiningEngine::new();
let template = create_test_block();
let result = engine.mine_template(template.clone()).await;
if let Ok(mined_block) = result {
assert_eq!(mined_block.header.version, template.header.version);
assert_ne!(mined_block.header.nonce, template.header.nonce);
use blvm_protocol::pow::check_proof_of_work;
let pow_valid = check_proof_of_work(&mined_block.header).unwrap();
assert!(pow_valid, "Mined block should have valid proof of work");
assert_eq!(engine.get_stats().blocks_mined, 1);
} else {
assert!(engine.get_block_template().is_some());
}
}
#[test]
fn test_mining_engine_template_management() {
let mut engine = MiningEngine::new();
assert!(engine.get_block_template().is_none());
let template = create_test_block();
engine.block_template = Some(template.clone());
assert!(engine.get_block_template().is_some());
assert_eq!(
engine.get_block_template().unwrap().header.version,
template.header.version
);
engine.clear_template();
assert!(engine.get_block_template().is_none());
}
#[test]
fn test_mining_engine_statistics() {
let mut engine = MiningEngine::new();
let stats = engine.get_stats();
assert_eq!(stats.blocks_mined, 0);
assert_eq!(stats.total_hashrate, 0.0);
assert_eq!(stats.average_block_time, 0.0);
assert!(stats.last_block_time.is_none());
engine.update_hashrate(1000.0);
assert_eq!(engine.get_stats().total_hashrate, 1000.0);
engine.update_average_block_time(600.0);
assert_eq!(engine.get_stats().average_block_time, 600.0);
}
#[test]
fn test_mock_mempool_provider_creation() {
let mempool = MockMempoolProvider::new();
assert_eq!(mempool.get_mempool_size(), 0);
assert!(mempool.get_transactions().is_empty());
let empty_utxo_set = blvm_protocol::UtxoSet::default();
assert!(mempool
.get_prioritized_transactions(10, &empty_utxo_set)
.is_empty());
}
#[test]
fn test_mock_mempool_provider_transaction_management() {
let mut mempool = MockMempoolProvider::new();
let tx1 = create_test_transaction(1, 1000);
let tx2 = create_test_transaction(2, 2000);
mempool.add_transaction(tx1.clone());
mempool.add_transaction(tx2.clone());
assert_eq!(mempool.get_mempool_size(), 2);
assert_eq!(mempool.get_transactions().len(), 2);
let empty_utxo_set = blvm_protocol::UtxoSet::default();
let prioritized = mempool.get_prioritized_transactions(10, &empty_utxo_set);
assert_eq!(prioritized.len(), 2);
let hash = mempool.calculate_tx_hash(&tx1);
assert!(mempool.remove_transaction(&hash));
assert_eq!(mempool.get_mempool_size(), 1);
let fake_hash = [0u8; 32];
assert!(!mempool.remove_transaction(&fake_hash));
}
#[test]
fn test_mock_mempool_provider_prioritization() {
let mut mempool = MockMempoolProvider::new();
let tx_low_fee = create_test_transaction(1, 100); let tx_high_fee = create_test_transaction(2, 5000); let tx_medium_fee = create_test_transaction(3, 1000);
mempool.add_transaction(tx_low_fee);
mempool.add_transaction(tx_high_fee);
mempool.add_transaction(tx_medium_fee);
let empty_utxo_set = blvm_protocol::UtxoSet::default();
let prioritized = mempool.get_prioritized_transactions(10, &empty_utxo_set);
assert_eq!(prioritized.len(), 3);
assert_eq!(prioritized[0].version, 3);
assert_eq!(prioritized[1].version, 2);
assert_eq!(prioritized[2].version, 1);
}
#[test]
fn test_mock_mempool_provider_clear() {
let mut mempool = MockMempoolProvider::new();
let tx = create_test_transaction(1, 1000);
mempool.add_transaction(tx);
assert_eq!(mempool.get_mempool_size(), 1);
mempool.clear();
assert_eq!(mempool.get_mempool_size(), 0);
assert!(mempool.get_transactions().is_empty());
}
#[test]
fn test_mining_coordinator_creation() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let coordinator = MiningCoordinator::new(mempool, None);
assert!(!coordinator.is_mining_enabled());
assert_eq!(coordinator.get_mempool_size(), 0);
assert_eq!(coordinator.mining_engine().get_threads(), 1);
assert_eq!(
coordinator.transaction_selector().max_block_size(),
1_000_000
);
}
#[test]
fn test_mining_coordinator_with_params() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let coordinator = MiningCoordinator::with_params(mempool, None, 4, 2_000_000, 8_000_000, 5);
assert_eq!(coordinator.mining_engine().get_threads(), 4);
assert_eq!(
coordinator.transaction_selector().max_block_size(),
2_000_000
);
assert_eq!(
coordinator.transaction_selector().max_block_weight(),
8_000_000
);
assert_eq!(coordinator.transaction_selector().min_fee_rate(), 5);
}
#[test]
fn test_mining_coordinator_enable_disable() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let mut coordinator = MiningCoordinator::new(mempool, None);
assert!(!coordinator.is_mining_enabled());
coordinator.enable_mining();
assert!(coordinator.is_mining_enabled());
coordinator.disable_mining();
assert!(!coordinator.is_mining_enabled());
}
#[test]
fn test_mining_coordinator_info() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let coordinator = MiningCoordinator::new(mempool, None);
let info = coordinator.get_mining_info();
assert!(!info.enabled);
assert_eq!(info.threads, 1);
assert!(!info.has_template);
}
#[test]
fn test_mining_coordinator_statistics() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let coordinator = MiningCoordinator::new(mempool, None);
let stats = coordinator.get_mining_stats();
assert_eq!(stats.blocks_mined, 0);
assert_eq!(stats.total_hashrate, 0.0);
assert_eq!(stats.average_block_time, 0.0);
assert!(stats.last_block_time.is_none());
}
#[test]
fn test_mining_coordinator_accessors() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let coordinator = MiningCoordinator::new(mempool, None);
let engine = coordinator.mining_engine();
assert_eq!(engine.get_threads(), 1);
let selector = coordinator.transaction_selector();
assert_eq!(selector.max_block_size(), 1_000_000);
let mut coordinator = coordinator;
let engine_mut = coordinator.mining_engine_mut();
engine_mut.set_threads(4);
assert_eq!(coordinator.mining_engine().get_threads(), 4);
let selector_mut = coordinator.transaction_selector_mut();
assert_eq!(selector_mut.max_block_size(), 1_000_000);
}
#[tokio::test]
async fn test_mining_coordinator_mempool_operations() {
use std::sync::Arc;
let mut mempool_manager = crate::node::mempool::MempoolManager::new();
let tx = create_test_transaction(1, 1000);
let _ = mempool_manager.add_transaction(tx);
let mempool = Arc::new(mempool_manager);
let coordinator = MiningCoordinator::new(mempool, None);
assert_eq!(coordinator.get_mempool_size(), 1);
}
#[tokio::test]
async fn test_mining_coordinator_block_template_generation() {
use std::sync::Arc;
let mut mempool_manager = crate::node::mempool::MempoolManager::new();
let tx = create_test_transaction(1, 1000);
let _ = mempool_manager.add_transaction(tx);
let mempool = Arc::new(mempool_manager);
let mut coordinator = MiningCoordinator::new(mempool, None);
let template = coordinator.generate_block_template().await;
assert!(template.is_ok());
let block = template.unwrap();
assert_eq!(block.header.version, 1);
assert!(!block.transactions.is_empty()); }
#[tokio::test]
async fn test_mining_coordinator_coinbase_creation() {
use std::sync::Arc;
let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
let coordinator = MiningCoordinator::new(mempool, None);
let empty_utxo_set = blvm_protocol::UtxoSet::default();
let coinbase = coordinator
.create_coinbase_transaction(0, &[], &empty_utxo_set)
.await;
assert!(coinbase.is_ok());
let tx = coinbase.unwrap();
assert_eq!(tx.version, 1);
assert_eq!(tx.inputs.len(), 1);
assert_eq!(tx.inputs[0].prevout.hash, [0u8; 32]);
assert_eq!(tx.inputs[0].prevout.index, 0xffff_ffff);
assert_eq!(tx.outputs.len(), 1);
assert_eq!(tx.outputs[0].value, 5000000000); assert_eq!(tx.lock_time, 0);
}
fn create_test_transaction(version: i32, output_value: u64) -> Transaction {
use blvm_protocol::{OutPoint, TransactionInput};
Transaction {
version: version as u64,
inputs: blvm_protocol::tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0,
},
script_sig: vec![
blvm_protocol::opcodes::OP_DUP,
blvm_protocol::opcodes::OP_HASH160,
blvm_protocol::opcodes::PUSH_20_BYTES,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
blvm_protocol::opcodes::OP_EQUALVERIFY,
blvm_protocol::opcodes::OP_CHECKSIG,
],
sequence: 0xffffffff,
}],
outputs: blvm_protocol::tx_outputs![TransactionOutput {
value: output_value as i64,
script_pubkey: vec![
blvm_protocol::opcodes::OP_DUP,
blvm_protocol::opcodes::OP_HASH160,
blvm_protocol::opcodes::PUSH_20_BYTES,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
blvm_protocol::opcodes::OP_EQUALVERIFY,
blvm_protocol::opcodes::OP_CHECKSIG,
],
}],
lock_time: 0,
}
}
fn create_test_block() -> Block {
Block {
header: BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
},
transactions: vec![create_test_transaction(1, 1000)].into_boxed_slice(),
}
}
}