Contractless/src/orphans/torrent_candidates.rs

79 lines
2.9 KiB
Rust

use crate::records::memory::response_channels::{reserve_entry, Command};
use crate::rpc::command_maps::RPC_TORRENT_CANDIDATES;
use crate::rpc::responses::RpcResponse;
use crate::torrent::torrenting_system::save_torrent::save_staged_torrent;
use crate::{timeout, Arc, Duration, Mutex, TcpStream};
pub async fn hydrate_torrent_candidates(
stream: Arc<Mutex<TcpStream>>,
map: Arc<Mutex<Command>>,
connections_key: String,
) -> Result<usize, String> {
// Reserve a reply slot and send a small request packet asking the peer for
// its staged/local torrent candidates.
let (hashmap_key, _tx, rx) = reserve_entry(map.clone()).await;
let mut message = Vec::with_capacity(4);
message.push(RPC_TORRENT_CANDIDATES);
message.extend_from_slice(&hashmap_key);
RpcResponse::send_raw(&stream, Some(&connections_key), &message).await;
let response = {
let mut rx = rx.lock().await;
timeout(Duration::from_secs(30), rx.recv())
.await
.map_err(|_| "Timed out waiting for torrent candidates".to_string())?
.ok_or_else(|| "Torrent candidate response channel closed".to_string())?
};
if response.len() < 4 {
return Err("Torrent candidate response was too short".to_string());
}
// The response starts with the number of entries, then each entry is
// height + byte length + raw torrent bytes.
let mut offset = 0_usize;
let candidate_count = u32::from_le_bytes(
response[offset..offset + 4]
.try_into()
.map_err(|_| "Failed to read torrent candidate count".to_string())?,
);
offset += 4;
let mut imported = 0_usize;
for _ in 0..candidate_count {
// Refuse truncated entries rather than trying to save partial torrent
// bytes into staging.
if response.len().saturating_sub(offset) < 8 {
return Err("Torrent candidate entry was truncated".to_string());
}
let height = u32::from_le_bytes(
response[offset..offset + 4]
.try_into()
.map_err(|_| "Failed to read torrent candidate height".to_string())?,
);
offset += 4;
let torrent_len = u32::from_le_bytes(
response[offset..offset + 4]
.try_into()
.map_err(|_| "Failed to read torrent candidate length".to_string())?,
) as usize;
offset += 4;
if response.len().saturating_sub(offset) < torrent_len {
return Err("Torrent candidate bytes were truncated".to_string());
}
let torrent_bytes = &response[offset..offset + torrent_len];
// Imported candidates are staged locally; normal orphan replay decides
// later whether any of them should become canonical blocks.
save_staged_torrent(height, torrent_bytes).await?;
imported += 1;
offset += torrent_len;
}
Ok(imported)
}