diff --git a/src/bin/create_transfer_tx.rs b/src/bin/create_transfer_tx.rs index 7873df3..054b83c 100644 --- a/src/bin/create_transfer_tx.rs +++ b/src/bin/create_transfer_tx.rs @@ -67,8 +67,47 @@ async fn main() { }; let receiver = match normalize_short_address_input(&receiver_input) { Ok(address) => address, - Err(_) => { - println!("reciver wallet is not valid"); + Err(err) => { + let trimmed_receiver = receiver_input.trim(); + let escaped_receiver = receiver_input + .chars() + .flat_map(char::escape_default) + .collect::(); + let escaped_trimmed_receiver = trimmed_receiver + .chars() + .flat_map(char::escape_default) + .collect::(); + let dot_count = trimmed_receiver.matches('.').count(); + let (payload_chars, payload_bytes, suffix, payload_is_hex, payload_is_ascii) = + match trimmed_receiver.rsplit_once('.') { + Some((payload, suffix)) => ( + payload.chars().count(), + payload.len(), + suffix, + payload.chars().all(|ch| ch.is_ascii_hexdigit()), + payload.is_ascii(), + ), + None => (0, 0, "", false, trimmed_receiver.is_ascii()), + }; + + println!("receiver wallet is not valid: {err}"); + println!( + "receiver diagnostics: raw_chars={} raw_bytes={} trimmed_chars={} trimmed_bytes={} dot_count={} suffix={} payload_chars={} payload_bytes={} payload_hex={} payload_ascii={} raw_ascii={} raw_control_chars={}", + receiver_input.chars().count(), + receiver_input.len(), + trimmed_receiver.chars().count(), + trimmed_receiver.len(), + dot_count, + suffix, + payload_chars, + payload_bytes, + payload_is_hex, + payload_is_ascii, + receiver_input.is_ascii(), + receiver_input.chars().any(char::is_control) + ); + println!("receiver escaped raw: {escaped_receiver}"); + println!("receiver escaped trimmed: {escaped_trimmed_receiver}"); return; } }; diff --git a/src/orphans/snapshot_check.rs b/src/orphans/snapshot_check.rs index 3aa56e7..7cfb964 100644 --- a/src/orphans/snapshot_check.rs +++ b/src/orphans/snapshot_check.rs @@ -1,6 +1,6 @@ use crate::common::binary_conversions::binary_to_string; use crate::log::{error, info, warn}; -use crate::miner::flag::begin_reorg_lock; +use crate::miner::flag::{begin_reorg_lock, is_syncing_mode}; use crate::orphans::structs::UndoTransactions; use crate::orphans::undo_block_transactions::undo_transactions; use crate::records::memory::connections::live_miner_peer_streams; @@ -35,22 +35,24 @@ fn required_snapshot_votes(total_voters: usize) -> usize { (total_voters * 2).div_ceil(3).max(2) } +fn peer_is_syncing_vote_error(error: &str) -> bool { + error.trim() == "error: Node is syncing" +} + async fn snapshot_has_peer_quorum( snapshot_height: u32, local_hash: &str, map: Arc>, ) -> bool { let peers = live_miner_peer_streams().await; - let total_voters = peers.len() + 1; - if total_voters < 2 { + if peers.is_empty() { warn!( "[snapshot] not advancing snapshot at height {snapshot_height}: no connected miner peers" ); return false; } - let required_votes = required_snapshot_votes(total_voters); let mut handles = Vec::with_capacity(peers.len()); for (connections_key, stream) in peers { @@ -68,10 +70,12 @@ async fn snapshot_has_peer_quorum( } let mut matching_votes = 1usize; + let mut eligible_peer_votes = 0usize; for handle in handles { match handle.await { Ok((connections_key, Ok(peer_hash))) => { + eligible_peer_votes += 1; if peer_hash == local_hash { matching_votes += 1; } else { @@ -81,9 +85,15 @@ async fn snapshot_has_peer_quorum( } } Ok((connections_key, Err(err))) => { - warn!( - "[snapshot] peer vote failed: height={snapshot_height} peer={connections_key} err={err}" - ); + if peer_is_syncing_vote_error(&err) { + info!( + "[snapshot] skipping syncing peer vote: height={snapshot_height} peer={connections_key}" + ); + } else { + warn!( + "[snapshot] peer vote failed: height={snapshot_height} peer={connections_key} err={err}" + ); + } } Err(err) => { warn!("[snapshot] peer vote task failed at height {snapshot_height}: {err}"); @@ -91,6 +101,15 @@ async fn snapshot_has_peer_quorum( } } + let total_voters = eligible_peer_votes + 1; + if total_voters < 2 { + warn!( + "[snapshot] not advancing snapshot at height {snapshot_height}: no eligible non-syncing miner peers" + ); + return false; + } + + let required_votes = required_snapshot_votes(total_voters); if matching_votes >= required_votes { info!( "[snapshot] consensus reached: height={snapshot_height} hash={local_hash} votes={matching_votes}/{total_voters} required={required_votes}" @@ -117,6 +136,10 @@ pub async fn update_snapshot( current_height: u32, map: Arc>, ) -> Result<(), String> { + if is_syncing_mode() { + return Ok(()); + } + // Genesis is always a valid snapshot, then later snapshots lag the tip // so normal orphan correction still has room to operate. let snapshot_height = if current_height == 0 { diff --git a/src/records/unpack_block/load_by_block_number.rs b/src/records/unpack_block/load_by_block_number.rs index b8d393a..1bd6239 100644 --- a/src/records/unpack_block/load_by_block_number.rs +++ b/src/records/unpack_block/load_by_block_number.rs @@ -67,14 +67,12 @@ pub async fn load_block(block_number: u32) -> Result { // Load the full block because this path reconstructs both the header and // every transaction for validation or inspection. - let binary_data = match fs::read(&file_name) { - Ok(data) => data, - Err(err) => { - println!("{}", &file_name); - eprintln!("Error reading file load_block: {err:?}"); - return Err(format!("Unable to read binary file: {err:?}")); - } - }; + let binary_data = match fs::read(&file_name) { + Ok(data) => data, + Err(err) => { + return Err(format!("Unable to read block {block_number}: {err:?}")); + } + }; if binary_data.len() < VRF_BLOCK_BYTES { return Err("Unable to load block: binary data shorter than VrfBlock header".to_string()); diff --git a/src/records/unpack_block/unpack_header.rs b/src/records/unpack_block/unpack_header.rs index 8572aed..8aa2f35 100644 --- a/src/records/unpack_block/unpack_header.rs +++ b/src/records/unpack_block/unpack_header.rs @@ -22,12 +22,14 @@ pub async fn load_block_header(block_number: u32) -> Result { .join(format!("{block_number}.{block_ext}")) .to_string_lossy() .into_owned(); - let file = match File::open(&file_name).await { - Ok(file) => file, - Err(err) => { - return Err(format!("Error opening file {}: {:?}", &file_name, err)); - } - }; + let file = match File::open(&file_name).await { + Ok(file) => file, + Err(err) => { + return Err(format!( + "Error opening block file for height {block_number}: {err:?}" + )); + } + }; let mut binary_data = Vec::with_capacity(VRF_BLOCK_BYTES); // Only read the fixed VrfBlock prefix from the block file. @@ -35,12 +37,11 @@ pub async fn load_block_header(block_number: u32) -> Result { .take(VRF_BLOCK_BYTES as u64) .read_to_end(&mut binary_data) .await - { - return Err(format!( - "Error reading file load_block_head {}: {:?}", - &file_name, err - )); - } + { + return Err(format!( + "Error reading block header for height {block_number}: {err:?}" + )); + } // The stored header format is the same VrfBlock prefix used at the // beginning of every block file. diff --git a/src/rpc/commands/block_by_hash.rs b/src/rpc/commands/block_by_hash.rs index 68759cf..6df880f 100644 --- a/src/rpc/commands/block_by_hash.rs +++ b/src/rpc/commands/block_by_hash.rs @@ -51,10 +51,8 @@ pub async fn request_block(db: &Db, hash: &str) -> RpcResponse { match load_block(block_number).await { Ok(block) => match block.to_bytes().await { Ok(block_bytes) => RpcResponse::Binary(block_bytes), - Err(err) => RpcResponse::Binary( - format!("error: Failed to convert block to bytes: {err}").into_bytes(), - ), + Err(_) => RpcResponse::Binary(b"error: Unable to serialize block".to_vec()), }, - Err(err) => RpcResponse::Binary(format!("error: Failed to load block: {err}").into_bytes()), + Err(_) => RpcResponse::Binary(b"error: Block not found".to_vec()), } } diff --git a/src/rpc/commands/block_by_height.rs b/src/rpc/commands/block_by_height.rs index c6c0d6a..2edc738 100644 --- a/src/rpc/commands/block_by_height.rs +++ b/src/rpc/commands/block_by_height.rs @@ -9,15 +9,9 @@ pub async fn request_block(height: u32) -> RpcResponse { let block_bytes = block.to_bytes().await; match block_bytes { Ok(result) => RpcResponse::Binary(result), - Err(err) => { - let msg = format!("error: {err}").to_string().as_bytes().to_vec(); - RpcResponse::Binary(msg) - } + Err(_) => RpcResponse::Binary(b"error: Unable to serialize block".to_vec()), } } - Err(err) => { - let msg = format!("error: {err}").to_string().as_bytes().to_vec(); - RpcResponse::Binary(msg) - } + Err(_) => RpcResponse::Binary(format!("error: Block {height} not found").into_bytes()), } } diff --git a/src/rpc/commands/block_hash_at_height.rs b/src/rpc/commands/block_hash_at_height.rs index 6bdfcc1..2797749 100644 --- a/src/rpc/commands/block_hash_at_height.rs +++ b/src/rpc/commands/block_hash_at_height.rs @@ -1,8 +1,13 @@ use crate::decode; +use crate::miner::flag::is_syncing_mode; use crate::records::unpack_block::unpack_header::load_block_header; use crate::rpc::responses::RpcResponse; pub async fn request_block_hash_at_height(block_number: u32) -> RpcResponse { + if is_syncing_mode() { + return RpcResponse::Binary(b"error: Node is syncing".to_vec()); + } + match load_block_header(block_number).await { Ok(header) => { let hash = header.hash().await; @@ -15,10 +20,8 @@ pub async fn request_block_hash_at_height(block_number: u32) -> RpcResponse { ), } } - Err(err) => RpcResponse::Binary( - format!("error: Failed to load block header at height {block_number}: {err}") - .as_bytes() - .to_vec(), - ), + Err(_) => { + RpcResponse::Binary(format!("error: Block {block_number} not found").into_bytes()) + } } } diff --git a/src/rpc/commands/latest_block.rs b/src/rpc/commands/latest_block.rs index 48f0d4d..a76b2b9 100644 --- a/src/rpc/commands/latest_block.rs +++ b/src/rpc/commands/latest_block.rs @@ -13,15 +13,9 @@ pub async fn request_latest_block(db: &Db) -> RpcResponse { let block_bytes = block.to_bytes().await; match block_bytes { Ok(block_bytes) => RpcResponse::Binary(block_bytes), - Err(err) => { - let msg = format!("error: {err}").to_string().as_bytes().to_vec(); - RpcResponse::Binary(msg) - } + Err(_) => RpcResponse::Binary(b"error: Unable to serialize block".to_vec()), } } - Err(err) => { - let msg = format!("error: {err}").to_string().as_bytes().to_vec(); - RpcResponse::Binary(msg) - } + Err(_) => RpcResponse::Binary(format!("error: Block {height} not found").into_bytes()), } } diff --git a/src/rpc/commands/receive_torrent.rs b/src/rpc/commands/receive_torrent.rs index e13aa48..500ffa9 100644 --- a/src/rpc/commands/receive_torrent.rs +++ b/src/rpc/commands/receive_torrent.rs @@ -33,7 +33,7 @@ pub fn should_trigger_orphan_check(error: &str) -> bool { || error.contains("Candidate parent is not current chain parent.") || error.contains("Difficulty mismatch with the blockchain data.") || error.contains("Incoming block is no longer the next expected height.") - || error.contains("Error opening file ./testnet_blocks/") + || error.contains("Unable to read block") } async fn next_expected_height(db: &Db) -> u32 {