fixed mining io issues

This commit is contained in:
viraladmin 2026-06-13 13:51:54 -06:00
parent f651fda2dd
commit b8c8e47b69
14 changed files with 248 additions and 43 deletions

View File

@ -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)) =

View File

@ -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

View File

@ -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;

View File

@ -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<u32> = None;
let mut registration_paused_height: Option<u32> = 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,

View File

@ -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(());
}

View File

@ -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, &params.db).await;
rebuild_chain_state_cache(&params.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(&params.db).await?;
save_new_blocks(&params, replay_to_height, wallet, true_start_height).await?;
rebuild_chain_state_cache(&params.db).await?;
NodeInfo::rebuild_mined_counts_from_chain(&params.db).await?;
Ok(())
}

View File

@ -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,

View File

@ -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<u32>,
tip_header: Option<VrfBlock>,
tip_hash: Option<String>,
genesis_anchor: Option<(u32, u32, u64)>,
recent_headers: HashMap<u32, VrfBlock>,
}
lazy_static! {
static ref CHAIN_STATE: Mutex<ChainStateCache> = 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<u32> {
CHAIN_STATE.lock().await.height
}
pub async fn cached_tip_header(expected_height: u32) -> Option<VrfBlock> {
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<String> {
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<VrfBlock> {
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
}

View File

@ -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;

View File

@ -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 {

View File

@ -402,8 +402,10 @@ 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: peer={connections_key} height={block_number}"
"[broadcast] ignored torrent from non-operational peer outside orphan window: peer={connections_key} local_height={local_height} height={block_number}"
);
return Ok((
uid,
@ -414,6 +416,10 @@ pub async fn receive_torrent(
),
));
}
warn!(
"[broadcast] accepting torrent from non-operational peer within orphan window: peer={connections_key} local_height={local_height} height={block_number}"
);
}
let outcome = torrent_submission(
block_number,

View File

@ -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<Wallet>, 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.

View File

@ -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))
}

View File

@ -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