diff --git a/src/blocks/block.rs b/src/blocks/block.rs index d661541..ccf1b83 100644 --- a/src/blocks/block.rs +++ b/src/blocks/block.rs @@ -1,7 +1,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_anchor, update_block_data}; +use crate::records::memory::averages::asert_genesis_anchor; use crate::sled::Db; use crate::to_string; use crate::wallets::structures::Wallet; @@ -11,7 +11,7 @@ use crate::{decode, encode}; use crate::{AsyncReadExt, AsyncWriteExt}; const TARGET_BLOCK_SECONDS: i128 = 15; -const ASERT_HALF_LIFE_SECONDS: i128 = 1_800; +const ASERT_HALF_LIFE_SECONDS: i128 = 300; const ASERT_RADIX_BITS: i128 = 16; const ASERT_FIXED_ONE: i128 = 1 << ASERT_RADIX_BITS; @@ -172,7 +172,7 @@ impl UnminedBlock { raw_target.clamp(lower_bound, upper_bound) } - // Adjust difficulty based on ASERT drift from the oldest cached canonical block. + // Adjust difficulty based on ASERT drift from the genesis anchor. pub async fn adjust_difficulty( current_timestamp: u32, db: &Db, @@ -181,10 +181,8 @@ impl UnminedBlock { let block_number = get_height(db); let candidate_height = block_number + 1; - // Refresh cached canonical block data before reading the ASERT anchor. - update_block_data(block_number).await; - - let Some((anchor_height, anchor_timestamp, anchor_difficulty)) = asert_anchor().await + let Some((anchor_height, anchor_timestamp, anchor_difficulty)) = + asert_genesis_anchor().await else { return current_difficulty; }; diff --git a/src/orphans/undo_block.rs b/src/orphans/undo_block.rs index 968c92a..0396c13 100644 --- a/src/orphans/undo_block.rs +++ b/src/orphans/undo_block.rs @@ -1,5 +1,4 @@ use crate::records::block_height::decrease_block_height::decrease_height; -use crate::records::memory::averages::{load_initial_blocks, DIFFICULTY_AVERAGE_WINDOW}; use crate::remove_file; use crate::sled::Db; @@ -21,11 +20,6 @@ pub async fn undo_block( } pub async fn finalize_undo_height(final_height: u32, db: &Db) { - // once rollback is complete, lower the recorded chain - // height and refresh the rolling averages cache + // once rollback is complete, lower the recorded chain height decrease_height(final_height, db); - // Difficulty averages are cached from recent blocks, so they must be - // rebuilt after removing block files. - let start_block = final_height.saturating_sub(DIFFICULTY_AVERAGE_WINDOW.saturating_sub(1)); - load_initial_blocks(start_block, final_height).await; } diff --git a/src/records/memory/averages.rs b/src/records/memory/averages.rs index 5864a34..cd509ac 100644 --- a/src/records/memory/averages.rs +++ b/src/records/memory/averages.rs @@ -1,126 +1,14 @@ -use crate::blocks::block::DIFFICULTY_OFFSET; -use crate::common::network_paths_and_settings::block_extension_and_paths; -use crate::lazy_static; -use crate::HashMap; -use crate::Mutex; -use crate::PathBuf; - -pub const DIFFICULTY_AVERAGE_WINDOW: u32 = 50; - -lazy_static! { - static ref AVERAGE_DATA: Mutex> = Mutex::new(HashMap::new()); -} - -pub async fn load_initial_blocks(start: u32, stop: u32) { - // Rebuild the rolling average cache from disk, keeping only the - // most recent rolling difficulty window needed by the algorithm. - let mut cache = AVERAGE_DATA.lock().await; - *cache = HashMap::new(); // Clear and reset the cache - let ( - _network_name, - _padded_base_coin, - file_ext, - _torrent_path, - _wallet_path, - block_path, - _db_path, - _balance_path, - _log_path, - ) = block_extension_and_paths(); - - for block_num in start..=stop { - let file_path = PathBuf::from(&block_path).join(format!("{block_num}.{file_ext}")); - - if let Ok(file_content) = tokio::fs::read(file_path).await { - let timestamp = if file_content.len() >= 4 { - u32::from_le_bytes(file_content[0..4].try_into().unwrap_or_default()) - } else { - 0 - }; - - let difficulty = if file_content.len() >= DIFFICULTY_OFFSET + 8 { - u64::from_le_bytes( - file_content[DIFFICULTY_OFFSET..DIFFICULTY_OFFSET + 8] - .try_into() - .unwrap_or_default(), - ) - } else { - 0 - }; - cache.insert(block_num, (timestamp, difficulty)); - - // Ensure only the configured rolling window is kept if starting from a larger range. - if cache.len() > DIFFICULTY_AVERAGE_WINDOW as usize { - let oldest_block = cache.keys().min().copied(); - if let Some(oldest) = oldest_block { - cache.remove(&oldest); - } - } - } - } -} - -pub async fn update_block_data(block_num: u32) { - let ( - _network_name, - _padded_base_coin, - file_ext, - _torrent_path, - _wallet_path, - block_path, - _db_path, - _balance_path, - _log_path, - ) = block_extension_and_paths(); - - // Avoid re-reading blocks that are already present in the rolling cache. - let cache = AVERAGE_DATA.lock().await; - if cache.contains_key(&block_num) { - return; - } - - drop(cache); - - let file_path = PathBuf::from(&block_path).join(format!("{block_num}.{file_ext}")); - - if let Ok(file_content) = tokio::fs::read(file_path).await { - let timestamp = if file_content.len() >= 4 { - u32::from_le_bytes(file_content[0..4].try_into().unwrap_or_default()) - } else { - 0 - }; - - let difficulty = if file_content.len() >= DIFFICULTY_OFFSET + 8 { - u64::from_le_bytes( - file_content[DIFFICULTY_OFFSET..DIFFICULTY_OFFSET + 8] - .try_into() - .unwrap_or_default(), - ) - } else { - 0 - }; - - // Reinsert under the cache lock and trim back to the rolling window. - let mut cache = AVERAGE_DATA.lock().await; - cache.insert(block_num, (timestamp, difficulty)); - - if cache.len() > DIFFICULTY_AVERAGE_WINDOW as usize { - let oldest_block = cache.keys().min().copied(); - if let Some(oldest) = oldest_block { - cache.remove(&oldest); - } - } - } -} - -pub async fn asert_anchor() -> Option<(u32, u32, u64)> { - // ASERT uses the oldest cached canonical block as its reference point. - // The cache is already rebuilt after startup and rollback, and it is - // trimmed to the active difficulty window. - let cache = AVERAGE_DATA.lock().await; - cache - .iter() - .filter(|(_, (_, difficulty))| *difficulty > 0) - .min_by_key(|(height, _)| *height) - .map(|(height, (timestamp, difficulty))| (*height, *timestamp, *difficulty)) +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. + match load_block_header(0).await { + Ok(header) => Some(( + 0, + header.unmined_block.timestamp, + header.unmined_block.next_block_difficulty, + )), + Err(_) => None, + } } diff --git a/src/records/record_chain/save.rs b/src/records/record_chain/save.rs index bf3f275..14c5d91 100644 --- a/src/records/record_chain/save.rs +++ b/src/records/record_chain/save.rs @@ -9,7 +9,7 @@ use crate::miner::flag::{ 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_anchor, update_block_data}; +use crate::records::memory::averages::asert_genesis_anchor; use crate::records::memory::mempool::{ apply_selected_transaction_math, mark_processed_by_signatures, mark_selected_transactions_processed, restore_processed_by_signatures, @@ -211,14 +211,13 @@ async fn log_saved_block_difficulty( current_difficulty: u64, new_difficulty: u64, ) { - // Skip genesis because there is no prior rolling-average context to - // compare against for a difficulty adjustment log line. + // Skip genesis because it is the ASERT anchor itself. if block_number == 0 { return; } - update_block_data(block_number - 1).await; - let Some((anchor_height, anchor_timestamp, anchor_difficulty)) = asert_anchor().await else { + let Some((anchor_height, anchor_timestamp, anchor_difficulty)) = asert_genesis_anchor().await + else { info!( "[difficulty] saved_block={block_number} timestamp={timestamp} current_difficulty={current_difficulty} new_difficulty={new_difficulty} asert_anchor=missing" ); diff --git a/src/startup/initialize_startup.rs b/src/startup/initialize_startup.rs index 56cb4bd..164ee4d 100644 --- a/src/startup/initialize_startup.rs +++ b/src/startup/initialize_startup.rs @@ -6,8 +6,6 @@ use crate::log::error; use crate::miner::flag::{ request_mining_stop, set_mining_state, set_node_mode, MiningState, NodeMode, }; -use crate::records::block_height::get_block_height::get_height; -use crate::records::memory::averages::{load_initial_blocks, DIFFICULTY_AVERAGE_WINDOW}; use crate::records::memory::connections::initialize_connection; use crate::wallets::structures::Wallet; use crate::Path; @@ -82,8 +80,7 @@ pub async fn obtain_startup_wallet() -> Wallet { } pub async fn open_chain_state() -> sled::Db { - // Open the sled state database and warm the rolling averages cache - // once the process is fully ready to continue startup. + // Open the sled state database once the process is fully ready to continue startup. let ( _network_name, _padded_base_coin, @@ -100,10 +97,5 @@ pub async fn open_chain_state() -> sled::Db { .open() .expect("Failed to open the database"); - let latest_block = get_height(&db); - let start_block = latest_block.saturating_sub(DIFFICULTY_AVERAGE_WINDOW.saturating_sub(1)); - // Warm the rolling difficulty cache from the newest window before mining starts. - load_initial_blocks(start_block, latest_block).await; - db } diff --git a/src/verifications/async_funcs/verify_block.rs b/src/verifications/async_funcs/verify_block.rs index 4bf7b23..e38965f 100644 --- a/src/verifications/async_funcs/verify_block.rs +++ b/src/verifications/async_funcs/verify_block.rs @@ -14,6 +14,8 @@ use crate::wallets::structures::Wallet; use crate::Arc; use crate::Utc; +const ALLOWED_FUTURE_BLOCK_SECONDS: u32 = 1; + impl Block { pub async fn verify( &self, @@ -77,8 +79,9 @@ impl Block { // validate hash Self::validate_hash_difficulty(db, hash).await?; - // verify timestamp is not in the future - if timestamp > current_timestamp { + // Allow the same one-second clock skew tolerated during handshake, + // while the parent timestamp check below still enforces block spacing. + if timestamp > current_timestamp.saturating_add(ALLOWED_FUTURE_BLOCK_SECONDS) { return Err("Timestamp in the block is in the future.".to_string()); }