172 lines
6.4 KiB
Rust
172 lines
6.4 KiB
Rust
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<Vec<u8>, 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<HashMap<u8, String>> = 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<dyn std::error::Error>> {
|
|
// 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<Mutex<Command>>,
|
|
) {
|
|
// 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;
|
|
}
|
|
}
|