syncing fixes

This commit is contained in:
viraladmin 2026-06-28 11:04:51 -06:00
parent eef98c4a00
commit dbea91ee65
7 changed files with 275 additions and 164 deletions

View File

@ -129,6 +129,7 @@ async fn candidate_attaches_before_rollback(
in_memory_pieces: Arc::new(Mutex::new(crate::HashMap::new())),
use_in_memory_pieces: false,
torrent: torrent.clone(),
torrent_bytes: None,
staged_path: String::new(),
block_number: height,
allow_during_reorg: true,

View File

@ -12,7 +12,6 @@ use crate::sled::Db;
use crate::timeout;
use crate::torrent::structs::Torrent;
use crate::torrent::torrenting_system::save_block::verify_and_save_downloaded_block;
use crate::torrent::torrenting_system::save_torrent::save_staged_torrent;
use crate::torrent::torrenting_system::setup_block_download::download_sync_candidate;
use crate::torrent::torrenting_system::torrent_requests::send_request_torrent_message;
use crate::verifications::verification_service::global_verification_service;
@ -24,6 +23,18 @@ use crate::TcpStream;
use std::collections::BTreeMap;
const SYNC_PREFETCH_WINDOW: usize = 100;
const SYNC_TRANSIENT_RETRY_LIMIT: u8 = 3;
fn is_transient_sync_error(err: &str) -> bool {
err.contains("No available peer could provide remaining pieces")
|| err.contains("Timed out waiting for piece reply")
|| err.contains("Piece reply channel closed")
|| err.contains("Peer could not provide piece")
|| err.contains("Requested candidate not found")
|| err.contains("piece not found")
|| err.contains("Candidate download already active")
|| err.contains("Timed out waiting for active candidate download")
}
fn abort_pending_downloads(
pending: &mut BTreeMap<
@ -76,9 +87,6 @@ fn spawn_sync_download(
let torrent = Torrent::from_bytes(&torrent_bytes)
.await
.map_err(|err| err.to_string())?;
let staged_path = save_staged_torrent(height, &torrent_bytes)
.await
.map_err(|err| format!("Failed to save staged torrent: {err}"))?;
set_torrent_status(height, &torrent.info.info_hash, TorrentStatus::Pending).await;
let verification_service = global_verification_service()
@ -86,7 +94,8 @@ fn spawn_sync_download(
download_sync_candidate(
height,
torrent,
staged_path,
String::new(),
torrent_bytes,
allow_startup_peers,
db,
Arc::new(verification_service),
@ -160,6 +169,7 @@ pub async fn node_syncing(
let mut next_to_schedule = local_height;
let mut next_to_save = local_height;
let mut pending = BTreeMap::new();
let mut retry_counts = BTreeMap::<u32, u8>::new();
// Download a small window of candidates ahead, but validate/save only
// the exact next height so parent, difficulty, and balance rules still
@ -198,6 +208,18 @@ pub async fn node_syncing(
}
break;
}
if is_transient_sync_error(&err) {
let retries = retry_counts.entry(next_to_save).or_insert(0);
if *retries < SYNC_TRANSIENT_RETRY_LIMIT {
*retries += 1;
warn!(
"[sync] transient download error at height={next_to_save}; retry {}/{} before orphan recovery: {err}",
*retries, SYNC_TRANSIENT_RETRY_LIMIT
);
next_to_schedule = next_to_save;
continue;
}
}
handle_sync_error(
&err,
next_to_save,
@ -228,6 +250,18 @@ pub async fn node_syncing(
if let Err(err) = verify_and_save_downloaded_block(download, wallet.clone()).await {
abort_pending_downloads(&mut pending);
if is_transient_sync_error(&err) {
let retries = retry_counts.entry(next_to_save).or_insert(0);
if *retries < SYNC_TRANSIENT_RETRY_LIMIT {
*retries += 1;
warn!(
"[sync] transient save/download error at height={next_to_save}; retry {}/{} before orphan recovery: {err}",
*retries, SYNC_TRANSIENT_RETRY_LIMIT
);
next_to_schedule = next_to_save;
continue;
}
}
handle_sync_error(
&err,
next_to_save,
@ -249,6 +283,7 @@ pub async fn node_syncing(
}
next_to_save += 1;
retry_counts.remove(&(next_to_save - 1));
}
abort_pending_downloads(&mut pending);
info!("[sync] node syncing complete, awaiting post-sync checks before mining resumes");

View File

@ -309,10 +309,13 @@ pub async fn token_catalog(db: &Db) -> RpcResponse {
const BURN_COUNT_BYTES: usize = 4;
let mut rows = Vec::new();
let RpcResponse::Binary(token_list_bytes) = crate::rpc::commands::token_list::get_tokens(db).await;
let RpcResponse::Binary(token_list_bytes) =
crate::rpc::commands::token_list::get_tokens(db).await;
if token_list_bytes.len() % TOKEN_LIST_ROW_BYTES != 0 {
return RpcResponse::Binary(b"error: Token list response had an unexpected byte length".to_vec());
return RpcResponse::Binary(
b"error: Token list response had an unexpected byte length".to_vec(),
);
}
for token_row in token_list_bytes.chunks_exact(TOKEN_LIST_ROW_BYTES) {

View File

@ -52,6 +52,7 @@ pub struct DownloadSave {
pub in_memory_pieces: Arc<Mutex<HashMap<u8, Vec<u8>>>>,
pub use_in_memory_pieces: bool,
pub torrent: Torrent,
pub torrent_bytes: Option<Vec<u8>>,
pub staged_path: String,
pub block_number: u32,
pub allow_during_reorg: bool,

View File

@ -8,7 +8,9 @@ use crate::records::record_chain::structs::{SaveBlockParams, SaveType};
use crate::records::unpack_block::load_by_binary_data::load_block_from_binary;
use crate::torrent::structs::DownloadSave;
use crate::torrent::torrenting_system::create_file::combine_pieces;
use crate::torrent::torrenting_system::save_torrent::promote_staged_torrent;
use crate::torrent::torrenting_system::save_torrent::{
promote_staged_torrent, save_canonical_torrent_bytes,
};
use crate::torrent::torrenting_system::temp_database_storage::remove_block_pieces_from_db;
use crate::wallets::structures::Wallet;
use crate::Arc;
@ -134,8 +136,15 @@ async fn verify_and_save_block_payload(
return Err(err);
}
// Once the block is saved successfully, move the staged torrent into its canonical path.
match promote_staged_torrent(Path::new(&params.staged_path), params.block_number) {
// Once the block is saved successfully, publish its torrent metadata.
// Startup sync keeps torrent bytes in memory to avoid exposing partial staged files.
let promote_result = if let Some(torrent_bytes) = params.torrent_bytes.as_deref() {
save_canonical_torrent_bytes(params.block_number, torrent_bytes).await
} else {
promote_staged_torrent(Path::new(&params.staged_path), params.block_number)
};
match promote_result {
Ok(_) => {
// The saved block and canonical torrent can now serve future
// piece requests, so this node no longer needs its temporary

View File

@ -2,6 +2,7 @@ use crate::common::network_paths_and_settings::block_extension_and_paths;
use crate::torrent::structs::Torrent;
use crate::{create_dir_all, fs, read, read_dir, remove_file, AsyncWriteExt, Path};
use std::io::ErrorKind;
use std::time::{SystemTime, UNIX_EPOCH};
fn staged_torrent_dir() -> Result<std::path::PathBuf, String> {
// Keep staged torrents under a dedicated subdirectory so incoming
@ -260,3 +261,61 @@ pub fn promote_staged_torrent(staged_path: &Path, height: u32) -> Result<String,
Ok(canonical_path.to_string_lossy().to_string())
}
pub async fn save_canonical_torrent_bytes(
height: u32,
torrent_bytes: &[u8],
) -> Result<String, String> {
let canonical_path = canonical_torrent_path(height);
if torrent_bytes_match(&canonical_path, torrent_bytes).await? {
return Ok(canonical_path.to_string_lossy().to_string());
}
if canonical_path.exists() {
return Err(format!(
"Canonical torrent collision for height {height} at {}",
canonical_path.display()
));
}
let parent = canonical_path.parent().ok_or_else(|| {
format!(
"Canonical torrent path has no parent: {}",
canonical_path.display()
)
})?;
create_dir_all(parent)
.await
.map_err(|e| format!("Failed to create canonical torrent directory: {e}"))?;
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| format!("System clock error while saving torrent: {e}"))?
.as_nanos();
let temp_path = parent.join(format!("{height}.torrent.sync-{unique}.tmp"));
let mut temp_file = crate::tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&temp_path)
.await
.map_err(|e| format!("Failed to create temporary canonical torrent file: {e}"))?;
temp_file
.write_all(torrent_bytes)
.await
.map_err(|e| format!("Failed to write temporary canonical torrent file: {e}"))?;
temp_file
.flush()
.await
.map_err(|e| format!("Failed to flush temporary canonical torrent file: {e}"))?;
drop(temp_file);
if let Err(err) = crate::tokio::fs::rename(&temp_path, &canonical_path).await {
let _ = remove_file(&temp_path).await;
return Err(format!(
"Failed to promote temporary canonical torrent: {err}"
));
}
Ok(canonical_path.to_string_lossy().to_string())
}

View File

@ -38,6 +38,7 @@ pub async fn setup_download(
in_memory_pieces: Arc::new(Mutex::new(HashMap::new())),
use_in_memory_pieces,
torrent,
torrent_bytes: None,
staged_path,
block_number,
allow_during_reorg,
@ -57,6 +58,7 @@ pub async fn download_sync_candidate(
block_number: u32,
torrent: Torrent,
staged_path: String,
torrent_bytes: Vec<u8>,
allow_startup_peers: bool,
db: Db,
verification_service: Arc<VerificationService>,
@ -71,6 +73,7 @@ pub async fn download_sync_candidate(
in_memory_pieces: Arc::new(Mutex::new(HashMap::new())),
use_in_memory_pieces: true,
torrent,
torrent_bytes: Some(torrent_bytes),
staged_path,
block_number,
allow_during_reorg: false,