difficulty adjectment

This commit is contained in:
viraladmin 2026-06-01 13:51:23 -06:00
parent 72b894d6cb
commit 88b043cf41
6 changed files with 29 additions and 155 deletions

View File

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

View File

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

View File

@ -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<HashMap<u32, (u32, u64)>> = 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,
}
}

View File

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

View File

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

View File

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