syncing fixes
This commit is contained in:
parent
eef98c4a00
commit
dbea91ee65
|
|
@ -129,6 +129,7 @@ async fn candidate_attaches_before_rollback(
|
||||||
in_memory_pieces: Arc::new(Mutex::new(crate::HashMap::new())),
|
in_memory_pieces: Arc::new(Mutex::new(crate::HashMap::new())),
|
||||||
use_in_memory_pieces: false,
|
use_in_memory_pieces: false,
|
||||||
torrent: torrent.clone(),
|
torrent: torrent.clone(),
|
||||||
|
torrent_bytes: None,
|
||||||
staged_path: String::new(),
|
staged_path: String::new(),
|
||||||
block_number: height,
|
block_number: height,
|
||||||
allow_during_reorg: true,
|
allow_during_reorg: true,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ use crate::sled::Db;
|
||||||
use crate::timeout;
|
use crate::timeout;
|
||||||
use crate::torrent::structs::Torrent;
|
use crate::torrent::structs::Torrent;
|
||||||
use crate::torrent::torrenting_system::save_block::verify_and_save_downloaded_block;
|
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::setup_block_download::download_sync_candidate;
|
||||||
use crate::torrent::torrenting_system::torrent_requests::send_request_torrent_message;
|
use crate::torrent::torrenting_system::torrent_requests::send_request_torrent_message;
|
||||||
use crate::verifications::verification_service::global_verification_service;
|
use crate::verifications::verification_service::global_verification_service;
|
||||||
|
|
@ -24,6 +23,18 @@ use crate::TcpStream;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
const SYNC_PREFETCH_WINDOW: usize = 100;
|
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(
|
fn abort_pending_downloads(
|
||||||
pending: &mut BTreeMap<
|
pending: &mut BTreeMap<
|
||||||
|
|
@ -76,9 +87,6 @@ fn spawn_sync_download(
|
||||||
let torrent = Torrent::from_bytes(&torrent_bytes)
|
let torrent = Torrent::from_bytes(&torrent_bytes)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?;
|
.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;
|
set_torrent_status(height, &torrent.info.info_hash, TorrentStatus::Pending).await;
|
||||||
|
|
||||||
let verification_service = global_verification_service()
|
let verification_service = global_verification_service()
|
||||||
|
|
@ -86,7 +94,8 @@ fn spawn_sync_download(
|
||||||
download_sync_candidate(
|
download_sync_candidate(
|
||||||
height,
|
height,
|
||||||
torrent,
|
torrent,
|
||||||
staged_path,
|
String::new(),
|
||||||
|
torrent_bytes,
|
||||||
allow_startup_peers,
|
allow_startup_peers,
|
||||||
db,
|
db,
|
||||||
Arc::new(verification_service),
|
Arc::new(verification_service),
|
||||||
|
|
@ -160,6 +169,7 @@ pub async fn node_syncing(
|
||||||
let mut next_to_schedule = local_height;
|
let mut next_to_schedule = local_height;
|
||||||
let mut next_to_save = local_height;
|
let mut next_to_save = local_height;
|
||||||
let mut pending = BTreeMap::new();
|
let mut pending = BTreeMap::new();
|
||||||
|
let mut retry_counts = BTreeMap::<u32, u8>::new();
|
||||||
|
|
||||||
// Download a small window of candidates ahead, but validate/save only
|
// Download a small window of candidates ahead, but validate/save only
|
||||||
// the exact next height so parent, difficulty, and balance rules still
|
// the exact next height so parent, difficulty, and balance rules still
|
||||||
|
|
@ -198,6 +208,18 @@ pub async fn node_syncing(
|
||||||
}
|
}
|
||||||
break;
|
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(
|
handle_sync_error(
|
||||||
&err,
|
&err,
|
||||||
next_to_save,
|
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 {
|
if let Err(err) = verify_and_save_downloaded_block(download, wallet.clone()).await {
|
||||||
abort_pending_downloads(&mut pending);
|
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(
|
handle_sync_error(
|
||||||
&err,
|
&err,
|
||||||
next_to_save,
|
next_to_save,
|
||||||
|
|
@ -249,6 +283,7 @@ pub async fn node_syncing(
|
||||||
}
|
}
|
||||||
|
|
||||||
next_to_save += 1;
|
next_to_save += 1;
|
||||||
|
retry_counts.remove(&(next_to_save - 1));
|
||||||
}
|
}
|
||||||
abort_pending_downloads(&mut pending);
|
abort_pending_downloads(&mut pending);
|
||||||
info!("[sync] node syncing complete, awaiting post-sync checks before mining resumes");
|
info!("[sync] node syncing complete, awaiting post-sync checks before mining resumes");
|
||||||
|
|
|
||||||
|
|
@ -309,10 +309,13 @@ pub async fn token_catalog(db: &Db) -> RpcResponse {
|
||||||
const BURN_COUNT_BYTES: usize = 4;
|
const BURN_COUNT_BYTES: usize = 4;
|
||||||
|
|
||||||
let mut rows = Vec::new();
|
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 {
|
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) {
|
for token_row in token_list_bytes.chunks_exact(TOKEN_LIST_ROW_BYTES) {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ pub struct DownloadSave {
|
||||||
pub in_memory_pieces: Arc<Mutex<HashMap<u8, Vec<u8>>>>,
|
pub in_memory_pieces: Arc<Mutex<HashMap<u8, Vec<u8>>>>,
|
||||||
pub use_in_memory_pieces: bool,
|
pub use_in_memory_pieces: bool,
|
||||||
pub torrent: Torrent,
|
pub torrent: Torrent,
|
||||||
|
pub torrent_bytes: Option<Vec<u8>>,
|
||||||
pub staged_path: String,
|
pub staged_path: String,
|
||||||
pub block_number: u32,
|
pub block_number: u32,
|
||||||
pub allow_during_reorg: bool,
|
pub allow_during_reorg: bool,
|
||||||
|
|
|
||||||
|
|
@ -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::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||||
use crate::torrent::structs::DownloadSave;
|
use crate::torrent::structs::DownloadSave;
|
||||||
use crate::torrent::torrenting_system::create_file::combine_pieces;
|
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::torrent::torrenting_system::temp_database_storage::remove_block_pieces_from_db;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
|
|
@ -134,8 +136,15 @@ async fn verify_and_save_block_payload(
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once the block is saved successfully, move the staged torrent into its canonical path.
|
// Once the block is saved successfully, publish its torrent metadata.
|
||||||
match promote_staged_torrent(Path::new(¶ms.staged_path), params.block_number) {
|
// 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(¶ms.staged_path), params.block_number)
|
||||||
|
};
|
||||||
|
|
||||||
|
match promote_result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// The saved block and canonical torrent can now serve future
|
// The saved block and canonical torrent can now serve future
|
||||||
// piece requests, so this node no longer needs its temporary
|
// piece requests, so this node no longer needs its temporary
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::torrent::structs::Torrent;
|
use crate::torrent::structs::Torrent;
|
||||||
use crate::{create_dir_all, fs, read, read_dir, remove_file, AsyncWriteExt, Path};
|
use crate::{create_dir_all, fs, read, read_dir, remove_file, AsyncWriteExt, Path};
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
fn staged_torrent_dir() -> Result<std::path::PathBuf, String> {
|
fn staged_torrent_dir() -> Result<std::path::PathBuf, String> {
|
||||||
// Keep staged torrents under a dedicated subdirectory so incoming
|
// 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())
|
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())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ pub async fn setup_download(
|
||||||
in_memory_pieces: Arc::new(Mutex::new(HashMap::new())),
|
in_memory_pieces: Arc::new(Mutex::new(HashMap::new())),
|
||||||
use_in_memory_pieces,
|
use_in_memory_pieces,
|
||||||
torrent,
|
torrent,
|
||||||
|
torrent_bytes: None,
|
||||||
staged_path,
|
staged_path,
|
||||||
block_number,
|
block_number,
|
||||||
allow_during_reorg,
|
allow_during_reorg,
|
||||||
|
|
@ -57,6 +58,7 @@ pub async fn download_sync_candidate(
|
||||||
block_number: u32,
|
block_number: u32,
|
||||||
torrent: Torrent,
|
torrent: Torrent,
|
||||||
staged_path: String,
|
staged_path: String,
|
||||||
|
torrent_bytes: Vec<u8>,
|
||||||
allow_startup_peers: bool,
|
allow_startup_peers: bool,
|
||||||
db: Db,
|
db: Db,
|
||||||
verification_service: Arc<VerificationService>,
|
verification_service: Arc<VerificationService>,
|
||||||
|
|
@ -71,6 +73,7 @@ pub async fn download_sync_candidate(
|
||||||
in_memory_pieces: Arc::new(Mutex::new(HashMap::new())),
|
in_memory_pieces: Arc::new(Mutex::new(HashMap::new())),
|
||||||
use_in_memory_pieces: true,
|
use_in_memory_pieces: true,
|
||||||
torrent,
|
torrent,
|
||||||
|
torrent_bytes: Some(torrent_bytes),
|
||||||
staged_path,
|
staged_path,
|
||||||
block_number,
|
block_number,
|
||||||
allow_during_reorg: false,
|
allow_during_reorg: false,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue