From b8c8e47b69d928cd8b83218d650ac1469f7bd8c7 Mon Sep 17 00:00:00 2001 From: viraladmin <00purple@gmail.com> Date: Sat, 13 Jun 2026 13:51:54 -0600 Subject: [PATCH] fixed mining io issues --- src/blocks/block.rs | 5 +- src/miner/block_rewards.rs | 15 +- src/miner/fairness.rs | 9 +- src/miner/mining.rs | 20 ++- src/miner/nonce.rs | 6 +- src/orphans/undo_block_transactions.rs | 3 + src/records/memory/averages.rs | 5 + src/records/memory/chain_state.rs | 133 ++++++++++++++++++ src/records/memory/mod.rs | 1 + src/records/record_chain/save.rs | 28 +++- src/rpc/commands/receive_torrent.rs | 24 ++-- src/startup/node_runtime.rs | 4 + src/verifications/async_funcs/verify_block.rs | 30 +++- .../async_funcs/verify_rewards.rs | 8 +- 14 files changed, 248 insertions(+), 43 deletions(-) create mode 100644 src/records/memory/chain_state.rs diff --git a/src/blocks/block.rs b/src/blocks/block.rs index c120f2f..5bc329b 100644 --- a/src/blocks/block.rs +++ b/src/blocks/block.rs @@ -2,6 +2,7 @@ use crate::common::skein::{skein_256_hash_data, skein_512_hash_data}; use crate::common::types::Transaction; use crate::records::block_height::get_block_height::get_height; use crate::records::memory::averages::asert_genesis_anchor; +use crate::records::memory::chain_state::cached_chain_height; use crate::sled::Db; use crate::to_string; use crate::wallets::structures::Wallet; @@ -178,7 +179,9 @@ impl UnminedBlock { db: &Db, current_difficulty: u64, ) -> u64 { - let block_number = get_height(db); + let block_number = cached_chain_height() + .await + .unwrap_or_else(|| get_height(db)); let candidate_height = block_number + 1; let Some((anchor_height, anchor_timestamp, anchor_difficulty)) = diff --git a/src/miner/block_rewards.rs b/src/miner/block_rewards.rs index e3de3d1..99b7d31 100644 --- a/src/miner/block_rewards.rs +++ b/src/miner/block_rewards.rs @@ -1,6 +1,7 @@ use crate::blocks::rewards::{RewardsTransaction, UnsignedRewardsTransaction}; use crate::common::types::BLOCKS_PER_HALVING; use crate::records::block_height::get_block_height::get_height; +use crate::records::memory::chain_state::cached_chain_height; use crate::records::memory::network_mapping::NodeInfo; use crate::sled::Db; @@ -35,18 +36,14 @@ pub async fn create_rewards_transaction( let txtype = 1; // The reward belongs to the block being created, not the current tip. - let block_height = get_height(db) + 1; + let block_height = cached_chain_height() + .await + .unwrap_or_else(|| get_height(db)) + + 1; // New miners must first prove participation before receiving // the block subsidy, so early mined blocks pay a zero reward. - let value = if !NodeInfo::chain_mined_count_at_least( - short_address, - get_height(db), - REWARD_MATURITY_BLOCKS, - ) - .await - .unwrap_or(false) - { + let value = if NodeInfo::get_mined_count(short_address).await < REWARD_MATURITY_BLOCKS { 0_u64 } else { calculate_block_reward(block_height).await diff --git a/src/miner/fairness.rs b/src/miner/fairness.rs index 9da27c2..41c9021 100644 --- a/src/miner/fairness.rs +++ b/src/miner/fairness.rs @@ -1,6 +1,7 @@ use crate::log::info; use crate::miner::flag::{is_mining_stop_requested, is_normal_mode, set_mining_state, MiningState}; use crate::records::block_height::get_block_height::get_height; +use crate::records::memory::chain_state::cached_header; use crate::records::unpack_block::unpack_header::load_block_header; use crate::sled::Db; use crate::sleep; @@ -21,8 +22,12 @@ pub async fn fairness_difficulty(block_height: u32, miner_wallet: &str) -> bool // Walk backward through the recent headers and count how many // consecutive blocks were mined by this same miner. for i in (start_block..=block_height).rev() { - // Load the saved header for this height. - let block = load_block_header(i).await.unwrap(); + // Use the in-memory recent-header cache during mining, falling + // back to disk only if the cache is not populated yet. + let block = match cached_header(i).await { + Some(block) => block, + None => load_block_header(i).await.unwrap(), + }; // The header stores the miner short address directly. let mined_by_miner = block.unmined_block.miner; diff --git a/src/miner/mining.rs b/src/miner/mining.rs index 1b853e8..41bb396 100644 --- a/src/miner/mining.rs +++ b/src/miner/mining.rs @@ -8,6 +8,7 @@ use crate::miner::nonce::run_nonce_round; use crate::miner::structs::MiningAttemptContext; use crate::miner::winner::{handle_mining_winner, verify_and_save_block}; use crate::records::block_height::get_block_height::get_height; +use crate::records::memory::chain_state::{cached_chain_height, cached_tip_header}; use crate::records::memory::connections::peer_connection_count; use crate::records::memory::network_mapping::NodeInfo; use crate::records::memory::response_channels::Command; @@ -46,7 +47,7 @@ pub async fn mine_block( // Track the height this miner expects to produce next so nonce workers // can stop quickly when another peer advances the chain. - let mut expected_block_height = get_height(db) + 1; + let mut expected_block_height = current_chain_height(db).await + 1; let mut fairness_paused_height: Option = None; let mut registration_paused_height: Option = None; let mut was_stopped = true; @@ -58,7 +59,7 @@ pub async fn mine_block( // Re-read height each round because peers may have saved a block // while this miner was paused or waiting for the next second. - let current_block_number = get_height(db) + 1; + let current_block_number = current_chain_height(db).await + 1; if current_block_number != expected_block_height { expected_block_height = current_block_number; } @@ -191,14 +192,14 @@ async fn wait_for_next_second_or_chain_change( // space unless the chain tip or node mode changes first. if !(is_normal_mode() && !is_mining_stop_requested() - && get_height(db) + 1 == expected_block_height) + && current_chain_height(db).await + 1 == expected_block_height) { return false; } while is_normal_mode() && !is_mining_stop_requested() - && get_height(db) + 1 == expected_block_height + && current_chain_height(db).await + 1 == expected_block_height { let now_second = Utc::now().timestamp() as u32; if now_second != round_second { @@ -222,7 +223,10 @@ async fn build_attempt_context( return Err("Mining paused before loading previous block header".into()); } let previous_block_height = current_block_number - 1; - let previous_block = load_block_header(previous_block_height).await?; + let previous_block = match cached_tip_header(previous_block_height).await { + Some(header) => header, + None => load_block_header(previous_block_height).await?, + }; let previous_hash = previous_block.hash().await; let previous_difficulty = previous_block.unmined_block.next_block_difficulty; @@ -237,6 +241,12 @@ async fn build_attempt_context( }) } +async fn current_chain_height(db: &Db) -> u32 { + cached_chain_height() + .await + .unwrap_or_else(|| get_height(db)) +} + pub async fn mine_block_internal( ctx: &MiningAttemptContext, nonce: u8, diff --git a/src/miner/nonce.rs b/src/miner/nonce.rs index b7e705a..cfa9143 100644 --- a/src/miner/nonce.rs +++ b/src/miner/nonce.rs @@ -5,6 +5,7 @@ use crate::miner::flag::{is_mining_stop_requested, is_normal_mode}; use crate::miner::mining::mine_block_internal; use crate::miner::structs::MiningAttemptContext; use crate::records::block_height::get_block_height::get_height; +use crate::records::memory::chain_state::cached_chain_height; use crate::task; use crate::Arc; use crate::AtomicBool; @@ -92,7 +93,10 @@ async fn nonce_range( } // If the chain tip changed, this round is stale for every worker. - if get_height(&ctx.db) + 1 != ctx.current_block_number { + let current_height = cached_chain_height() + .await + .unwrap_or_else(|| get_height(&ctx.db)); + if current_height + 1 != ctx.current_block_number { stop_flag.store(true, AtomicOrdering::SeqCst); return Ok(()); } diff --git a/src/orphans/undo_block_transactions.rs b/src/orphans/undo_block_transactions.rs index 6aa4ab5..81e22ec 100644 --- a/src/orphans/undo_block_transactions.rs +++ b/src/orphans/undo_block_transactions.rs @@ -17,6 +17,7 @@ use crate::orphans::undo_transactions::undo_swap::undo_swap_transaction; use crate::orphans::undo_transactions::undo_transfer::undo_transfer_transaction; use crate::orphans::undo_transactions::undo_vanity::undo_vanity_transaction; use crate::records::block_height::get_block_height::get_height; +use crate::records::memory::chain_state::rebuild_chain_state_cache; use crate::records::memory::network_mapping::NodeInfo; use crate::records::memory::torrent_status::reset_all_torrent_statuses; use crate::records::unpack_block::load_by_block_number::load_block; @@ -154,6 +155,7 @@ pub async fn undo_transactions( let final_height = true_start_height.saturating_sub(1); crate::orphans::undo_block::finalize_undo_height(final_height, ¶ms.db).await; + rebuild_chain_state_cache(¶ms.db).await?; // Only now that every rolled-back block has been unwound do we test // whether its former transactions are still spendable on the new base. @@ -169,6 +171,7 @@ pub async fn undo_transactions( // replacement blocks, and finally rebuild mined counts again NodeInfo::rebuild_mined_counts_from_chain(¶ms.db).await?; save_new_blocks(¶ms, replay_to_height, wallet, true_start_height).await?; + rebuild_chain_state_cache(¶ms.db).await?; NodeInfo::rebuild_mined_counts_from_chain(¶ms.db).await?; Ok(()) } diff --git a/src/records/memory/averages.rs b/src/records/memory/averages.rs index cd509ac..4004090 100644 --- a/src/records/memory/averages.rs +++ b/src/records/memory/averages.rs @@ -1,8 +1,13 @@ +use crate::records::memory::chain_state::cached_asert_genesis_anchor; use crate::records::unpack_block::unpack_header::load_block_header; pub async fn asert_genesis_anchor() -> Option<(u32, u32, u64)> { // ASERT uses genesis as a fixed consensus anchor, so long-term drift // toward the 15-second schedule cannot be forgotten by a rolling cache. + if let Some(anchor) = cached_asert_genesis_anchor().await { + return Some(anchor); + } + match load_block_header(0).await { Ok(header) => Some(( 0, diff --git a/src/records/memory/chain_state.rs b/src/records/memory/chain_state.rs new file mode 100644 index 0000000..087542e --- /dev/null +++ b/src/records/memory/chain_state.rs @@ -0,0 +1,133 @@ +use crate::blocks::block::VrfBlock; +use crate::records::block_height::get_block_height::get_height; +use crate::records::unpack_block::unpack_header::load_block_header; +use crate::{lazy_static, HashMap, Mutex}; + +const RECENT_HEADER_CACHE_DEPTH: u32 = 32; + +#[derive(Clone, Default)] +struct ChainStateCache { + height: Option, + tip_header: Option, + tip_hash: Option, + genesis_anchor: Option<(u32, u32, u64)>, + recent_headers: HashMap, +} + +lazy_static! { + static ref CHAIN_STATE: Mutex = Mutex::new(ChainStateCache::default()); +} + +fn recent_floor(height: u32) -> u32 { + height.saturating_sub(RECENT_HEADER_CACHE_DEPTH) +} + +fn cache_genesis_anchor(cache: &mut ChainStateCache, height: u32, header: &VrfBlock) { + if height == 0 { + cache.genesis_anchor = Some(( + 0, + header.unmined_block.timestamp, + header.unmined_block.next_block_difficulty, + )); + } +} + +fn prune_recent_headers(cache: &mut ChainStateCache, height: u32) { + let floor = recent_floor(height); + cache + .recent_headers + .retain(|cached_height, _| *cached_height == 0 || *cached_height >= floor); +} + +pub async fn rebuild_chain_state_cache(db: &crate::sled::Db) -> Result<(), String> { + let height = get_height(db); + let mut loaded_headers = HashMap::new(); + let mut genesis_anchor = None; + let mut tip_header = None; + let mut tip_hash = None; + + if let Ok(genesis_header) = load_block_header(0).await { + genesis_anchor = Some(( + 0, + genesis_header.unmined_block.timestamp, + genesis_header.unmined_block.next_block_difficulty, + )); + loaded_headers.insert(0, genesis_header); + } + + let floor = recent_floor(height); + for block_height in floor..=height { + if block_height == 0 && loaded_headers.contains_key(&0) { + continue; + } + if let Ok(header) = load_block_header(block_height).await { + if block_height == height { + tip_hash = Some(header.hash().await); + tip_header = Some(header.clone()); + } + loaded_headers.insert(block_height, header); + } + } + + if height == 0 { + if let Some(header) = loaded_headers.get(&0) { + tip_hash = Some(header.hash().await); + tip_header = Some(header.clone()); + } + } + + let mut cache = CHAIN_STATE.lock().await; + *cache = ChainStateCache { + height: Some(height), + tip_header, + tip_hash, + genesis_anchor, + recent_headers: loaded_headers, + }; + Ok(()) +} + +pub async fn update_chain_state_after_save(height: u32, header: VrfBlock, header_hash: String) { + let mut cache = CHAIN_STATE.lock().await; + cache.height = Some(height); + cache.tip_header = Some(header.clone()); + cache.tip_hash = Some(header_hash); + cache.recent_headers.insert(height, header.clone()); + cache_genesis_anchor(&mut cache, height, &header); + prune_recent_headers(&mut cache, height); +} + +pub async fn cached_chain_height() -> Option { + CHAIN_STATE.lock().await.height +} + +pub async fn cached_tip_header(expected_height: u32) -> Option { + let cache = CHAIN_STATE.lock().await; + if cache.height == Some(expected_height) { + cache.tip_header.clone() + } else { + None + } +} + +pub async fn cached_tip_hash(expected_height: u32) -> Option { + let cache = CHAIN_STATE.lock().await; + if cache.height == Some(expected_height) { + cache.tip_hash.clone() + } else { + None + } +} + +pub async fn cached_header(block_height: u32) -> Option { + CHAIN_STATE + .lock() + .await + .recent_headers + .get(&block_height) + .cloned() +} + +pub async fn cached_asert_genesis_anchor() -> Option<(u32, u32, u64)> { + CHAIN_STATE.lock().await.genesis_anchor +} diff --git a/src/records/memory/mod.rs b/src/records/memory/mod.rs index e5813e1..2503ddf 100644 --- a/src/records/memory/mod.rs +++ b/src/records/memory/mod.rs @@ -1,6 +1,7 @@ // The memory module holds the runtime-only structures used for active connections, // mempool ordering, torrent state, and other transient node state. pub mod averages; +pub mod chain_state; pub mod connections; pub mod enums; pub mod mempool; diff --git a/src/records/record_chain/save.rs b/src/records/record_chain/save.rs index 31712ce..ed89888 100644 --- a/src/records/record_chain/save.rs +++ b/src/records/record_chain/save.rs @@ -10,6 +10,9 @@ use crate::orphans::snapshot_check::{snapshot_height, update_snapshot}; use crate::records::block_height::get_block_height::get_height; use crate::records::block_height::increase_block_height::increase_height; use crate::records::memory::averages::asert_genesis_anchor; +use crate::records::memory::chain_state::{ + cached_tip_hash, cached_tip_header, update_chain_state_after_save, +}; use crate::records::memory::mempool::{ apply_selected_transaction_math, ensure_db_connection, mark_processed_by_signatures, mark_selected_transactions_processed, restore_processed_by_signatures, @@ -89,8 +92,10 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> { // before any headers, files, or mempool effects are written. let current_height = get_height(&db); if current_height > 0 { - let current_block = load_block_header(current_height).await?; - let current_hash = current_block.hash().await; + let current_hash = match cached_tip_hash(current_height).await { + Some(hash) => hash, + None => load_block_header(current_height).await?.hash().await, + }; if current_hash != *previous_hash { if save_type == SaveType::Mining { return Err(format!( @@ -113,7 +118,10 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> { // so the saved-block diagnostic log reflects the live adjustment input. let mut previous_difficulty = 0_u64; if block_header_number > 0 { - previous_difficulty = previous_block_difficulty(block_header_number).await?; + previous_difficulty = match cached_tip_header(block_header_number - 1).await { + Some(header) => header.unmined_block.next_block_difficulty, + None => previous_block_difficulty(block_header_number).await?, + }; } log_saved_block_difficulty( @@ -366,6 +374,13 @@ async fn save_binary_data_with_mempool_stream( broadcast_new_torrent_to_peers(next_number, &torrent_bytes, map).await; } + match crate::blocks::block::VrfBlock::from_bytes(header_bytes).await { + Ok(saved_header) => { + update_chain_state_after_save(next_number, saved_header, header_hash.to_string()).await; + } + Err(err) => error!("Failed to cache saved block header: {err}"), + } + // Only advance mined-count tracking when this save actually moved // the persisted chain height forward. if get_height(db) > previous_height { @@ -497,6 +512,13 @@ async fn save_binary_data(params: SaveBinaryDataParams<'_>) -> Result<(), String broadcast_new_torrent_to_peers(next_number, &torrent_bytes, map).await; } + match crate::blocks::block::VrfBlock::from_bytes(header_bytes).await { + Ok(saved_header) => { + update_chain_state_after_save(next_number, saved_header, header_hash.to_string()).await; + } + Err(err) => error!("Failed to cache saved block header: {err}"), + } + // Only advance mined-count tracking when this save actually moved // the persisted chain height forward. if get_height(db) > previous_height { diff --git a/src/rpc/commands/receive_torrent.rs b/src/rpc/commands/receive_torrent.rs index d012ed1..9044a58 100644 --- a/src/rpc/commands/receive_torrent.rs +++ b/src/rpc/commands/receive_torrent.rs @@ -402,17 +402,23 @@ pub async fn receive_torrent( if get_client_type_from_memory(connections_key).await == Some(ClientType::Miner) && !peer_is_operational(connections_key).await { + let local_height = get_height(db); + if !within_orphan_window(local_height, block_number) { + warn!( + "[broadcast] ignored torrent from non-operational peer outside orphan window: peer={connections_key} local_height={local_height} height={block_number}" + ); + return Ok(( + uid, + RpcResponse::Binary( + "Torrent ignored from non-operational peer." + .as_bytes() + .to_vec(), + ), + )); + } warn!( - "[broadcast] ignored torrent from non-operational peer: peer={connections_key} height={block_number}" + "[broadcast] accepting torrent from non-operational peer within orphan window: peer={connections_key} local_height={local_height} height={block_number}" ); - return Ok(( - uid, - RpcResponse::Binary( - "Torrent ignored from non-operational peer." - .as_bytes() - .to_vec(), - ), - )); } let outcome = torrent_submission( diff --git a/src/startup/node_runtime.rs b/src/startup/node_runtime.rs index 9150905..ef573cc 100644 --- a/src/startup/node_runtime.rs +++ b/src/startup/node_runtime.rs @@ -9,6 +9,7 @@ use crate::log::{error, info}; use crate::miner::genesis::create_genesis_transaction; use crate::miner::mining::mine_block; use crate::panic; +use crate::records::memory::chain_state::rebuild_chain_state_cache; use crate::records::memory::mempool::{init_db, setup_mempool}; use crate::records::memory::network_mapping::NodeInfo; use crate::records::memory::response_channels::Command; @@ -108,6 +109,9 @@ pub async fn run_unlocked_node(wallet: Arc, install_shutdown: bool) -> R // Open sled after Postgres is ready so block state and mempool state start together. let db = open_chain_state().await; + if let Err(err) = rebuild_chain_state_cache(&db).await { + error!("[chain_state] failed to rebuild startup chain state cache: {err}"); + } if install_shutdown { // Console/daemon mode owns signal cleanup; Windows service shutdown is handled separately. diff --git a/src/verifications/async_funcs/verify_block.rs b/src/verifications/async_funcs/verify_block.rs index 4791d20..a545a5b 100644 --- a/src/verifications/async_funcs/verify_block.rs +++ b/src/verifications/async_funcs/verify_block.rs @@ -5,6 +5,9 @@ use crate::common::types::Transaction; use crate::encode; use crate::miner::fairness::fairness_difficulty; use crate::records::block_height::get_block_height::get_height; +use crate::records::memory::chain_state::{ + cached_chain_height, cached_tip_hash, cached_tip_header, +}; use crate::records::memory::network_mapping::NodeInfo; use crate::records::unpack_block::unpack_header::load_block_header; use crate::records::wallet_registry::resolve_pubkey_from_short_address; @@ -47,7 +50,7 @@ impl Block { .map_err(|e| e.to_string())? .ok_or_else(|| "This miner address is not registered".to_string())?; let miner_pubkey_hex = encode(&miner_pubkey); - let block_number = get_height(db) + 1; + let block_number = current_chain_height(db).await + 1; if !NodeInfo::address_checkup(miner, block_number).await { return Err("This address is not eligable to mine".to_string()); @@ -117,8 +120,11 @@ impl Block { // load the last block this is the current height // as we are always validating a height higher than // what is recorded. - let previous_height = get_height(db); - let previous_block = load_block_header(previous_height).await?; + let previous_height = current_chain_height(db).await; + let previous_block = match cached_tip_header(previous_height).await { + Some(header) => header, + None => load_block_header(previous_height).await?, + }; // check if miner is eligible based on // fairness difficulty checker @@ -130,7 +136,10 @@ impl Block { } // get previous block hash - let calculated_previous_hash = previous_block.hash().await; + let calculated_previous_hash = match cached_tip_hash(previous_height).await { + Some(hash) => hash, + None => previous_block.hash().await, + }; // Validate recorded previous_hash is equal to // the previous block hash @@ -195,9 +204,12 @@ impl Block { ); } } - let current_block_number = get_height(db) + 1; + let current_block_number = current_chain_height(db).await + 1; let previous_block_height = current_block_number - 1; - let previous_block = load_block_header(previous_block_height).await?; + let previous_block = match cached_tip_header(previous_block_height).await { + Some(header) => header, + None => load_block_header(previous_block_height).await?, + }; let difficulty_target = previous_block.unmined_block.next_block_difficulty; if hash >= difficulty_target { Err(format!( @@ -208,3 +220,9 @@ impl Block { } } } + +async fn current_chain_height(db: &Db) -> u32 { + cached_chain_height() + .await + .unwrap_or_else(|| get_height(db)) +} diff --git a/src/verifications/async_funcs/verify_rewards.rs b/src/verifications/async_funcs/verify_rewards.rs index 15f5e31..9ad7c2c 100644 --- a/src/verifications/async_funcs/verify_rewards.rs +++ b/src/verifications/async_funcs/verify_rewards.rs @@ -25,13 +25,7 @@ impl RewardsTransaction { // New miners receive zero reward until their mined-count history // reaches the maturity threshold. - let reward_value = if !NodeInfo::chain_mined_count_at_least( - &miner, - previous_height, - REWARD_MATURITY_BLOCKS, - ) - .await? - { + let reward_value = if NodeInfo::get_mined_count(&miner).await < REWARD_MATURITY_BLOCKS { 0_u64 } else { calculate_block_reward(previous_height + 1).await