use crate::blocks::block::{NONCE_OFFSET, VRF_OFFSET}; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::skein::skein_128_hash_bytes; use crate::log::error; use crate::records::memory::response_channels::{reserve_entry, Command}; use crate::rpc::command_maps::RPC_SUBMIT_TORRENT; use crate::rpc::responses::RpcResponse; use crate::torrent::structs::{Info, Torrent}; use crate::torrent::torrenting_system::get_nodes::get_torrent_broadcast_nodes_from_memory; use crate::torrent::torrenting_system::torrent_cache::should_broadcast_torrent; use crate::Arc; use crate::File; use crate::HashMap; use crate::Mutex; use crate::{AsyncReadExt, AsyncWriteExt}; pub async fn metadata_from_file( file_path: &str, block_height: u32, difficulty: u64, timestamp: u32, block_hash: &str, previous_hash: &str, miner_wallet: String, ) -> Result, String> { // The torrent is built from the mined block file and saved under the network torrent path. let ( _network_name, _padded_base_coin, _block_ext, out_path, _wallet_path, _block_path, _db_path, _balance_path, _log_path, ) = block_extension_and_paths(); let mut file = File::open(file_path) .await .map_err(|err| format!("Failed to open saved block file for torrent metadata: {err}"))?; let mut content = Vec::new(); if let Err(err) = file.read_to_end(&mut content).await { error!("Failed to read file content: {err}"); return Err(format!( "Failed to read saved block file for torrent metadata: {err}" )); } // Pick larger piece sizes for larger blocks so torrents do not exceed the u8 piece index limit. let mut piece_length: u32; if !content.is_empty() && content.len() < 1000 { piece_length = 500_u32; } else if content.len() >= 1000 && content.len() < 10000 { piece_length = 1000_u32; } else if content.len() >= 10000 && content.len() < 100000 { piece_length = 10000_u32; } else if content.len() >= 100000 && content.len() < 1000000 { piece_length = 100000_u32; } else if content.len() >= 1000000 && content.len() < 10000000 { piece_length = 1000000_u32; } else { piece_length = 2000000_u32; } while !content.is_empty() && content.chunks(piece_length as usize).len() > u8::MAX as usize { // If the rough size bucket still creates too many pieces, keep doubling until it fits. piece_length = piece_length.saturating_mul(2); } // The info hash is the 128-bit hash of the full block bytes. let block_hashed = skein_128_hash_bytes(&content); // Nonce and VRF are copied from the serialized block header at fixed offsets. let nonce = content[NONCE_OFFSET]; let vrf_bytes = &content[VRF_OFFSET..VRF_OFFSET + 16]; let vrf = u128::from_le_bytes( vrf_bytes .try_into() .expect("Slice must be exactly 16 bytes"), ); // Hash each piece separately so peers can verify downloaded chunks before assembly. let mut piece_hashes: Vec> = Vec::new(); for (index, piece) in content.chunks(piece_length as usize).enumerate() { let hash = skein_128_hash_bytes(piece); let piece_number = u8::try_from(index + 1) .map_err(|_| "Piece count exceeds u8 limit while creating torrent metadata")?; let mut map = HashMap::new(); map.insert(piece_number, hash); piece_hashes.push(map); } // Torrent info mirrors the data needed to verify the downloaded block later. let info = Info { length: content.len() as u64, this_block_difficulty: difficulty, timestamp, nonce, vrf, block_hash: block_hash.to_string(), previous_hash: previous_hash.to_string(), piece_length, info_hash: block_hashed, pieces: piece_hashes, }; let torrent = Torrent { info, mined_by: miner_wallet, }; // Save the compact binary torrent metadata so the save path can // broadcast it only after the block height has committed. let torrent_bytes = torrent.to_bytes().await; create_torrent_file(out_path, block_height, &torrent_bytes) .await .map_err(|err| format!("Failed to create torrent metadata file: {err}"))?; Ok(torrent_bytes) } async fn create_torrent_file( out_path: String, block_height: u32, metadata: &[u8], ) -> Result<(), Box> { // The torrent filename follows the committed block height. let torrent_file_path = crate::Path::new(&out_path).join(format!("{block_height}.torrent")); // Write the canonical local .torrent file through a temporary path // so peers never see partial metadata for a committed block. let temp_torrent_file_path = torrent_file_path.with_extension("torrent.tmp"); let mut torrent_file = File::create(&temp_torrent_file_path).await?; torrent_file.write_all(metadata).await?; torrent_file.flush().await?; crate::fs::rename(&temp_torrent_file_path, &torrent_file_path)?; Ok(()) } pub async fn broadcast_new_torrent_to_peers( block_height: u32, torrent_bytes: &[u8], map: Arc>, ) { // Command byte for "submit torrent". let command: u8 = RPC_SUBMIT_TORRENT; // The cache suppresses repeated broadcasts of identical torrent bytes. let torrent_hash = skein_128_hash_bytes(torrent_bytes); if !should_broadcast_torrent(&torrent_hash, block_height).await { return; } let torrent_len = 4 + torrent_bytes.len() as u32; // Send the torrent to all currently connected miner peers. let peers = get_torrent_broadcast_nodes_from_memory().await; for (connections_key, stream) in peers { // Each peer needs its own reply mapping entry and UID. let (uid_bytes, _tx, _rx) = reserve_entry(map.clone()).await; let mut message = Vec::with_capacity(1 + 3 + 4 + 4 + torrent_bytes.len()); message.push(command); // Command byte message.extend_from_slice(&uid_bytes); // UID message.extend_from_slice(&torrent_len.to_le_bytes()); // Torrent byte size message.extend_from_slice(&block_height.to_le_bytes()); // Block height message.extend_from_slice(torrent_bytes); // Torrent file contents RpcResponse::send_raw(&stream, Some(&connections_key), &message).await; } }