use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path}; use blockchain::common::network_startup::get_connections; use blockchain::env; use blockchain::json; use blockchain::records::memory::response_channels::generate_uid; use blockchain::standalone_tools::connections::handshake; use blockchain::to_string_pretty; const NETWORK_NAME_BYTES: usize = 7; const NETWORK_INFO_FIXED_BYTES_WITHOUT_PREFIX: usize = 40; fn read_u32(response: &[u8], offset: &mut usize) -> Option { let bytes: [u8; 4] = response.get(*offset..*offset + 4)?.try_into().ok()?; *offset += 4; Some(u32::from_le_bytes(bytes)) } fn read_u64(response: &[u8], offset: &mut usize) -> Option { let bytes: [u8; 8] = response.get(*offset..*offset + 8)?.try_into().ok()?; *offset += 8; Some(u64::from_le_bytes(bytes)) } fn decode_network_info(response: &[u8]) -> Option { // Network-info responses are binary, with one variable-width field: // the wallet prefix is whatever remains after the fixed fields. if response.len() <= NETWORK_INFO_FIXED_BYTES_WITHOUT_PREFIX { return None; } let wallet_prefix_len = response.len() - NETWORK_INFO_FIXED_BYTES_WITHOUT_PREFIX; let mut offset = 0; let version = *response.get(offset)?; offset += 1; // The network name is the fixed 7-byte value from network settings: // "mainnet", "testnet", or "Invalid". let network = String::from_utf8_lossy(response.get(offset..offset + NETWORK_NAME_BYTES)?) .trim() .to_string(); offset += NETWORK_NAME_BYTES; let time = read_u32(response, &mut offset)?; // Mainnet uses CLC and testnet uses CLTC, so the prefix length is // inferred from the total payload size instead of hard-coded. let wallet_prefix = String::from_utf8_lossy(response.get(offset..offset + wallet_prefix_len)?) .trim() .to_string(); offset += wallet_prefix_len; let height = read_u32(response, &mut offset)?; let next_block_difficulty = read_u64(response, &mut offset)?; let total_block_transactions = read_u32(response, &mut offset)?; let total_mempool_transactions = read_u32(response, &mut offset)?; let largest_tx_fee = read_u64(response, &mut offset)?; // Print JSON for the CLI user, but this is decoded from the binary // RPC payload and no JSON crossed the TCP stream. let output = json!({ "version": version, "network": network, "time": time, "wallet_prefix": wallet_prefix, "height": height, "next_block_difficulty": next_block_difficulty, "total_block_transactions": total_block_transactions, "total_mempool_transactions": total_mempool_transactions, "largest_tx_fee": largest_tx_fee, }); to_string_pretty(&output).ok() } #[tokio::main] async fn main() { // Command 1 asks a peer for its current network-info snapshot. let hashmap_key = generate_uid(); let rpc_command = 1; // This lookup takes no arguments; the wallet key is only used for // the authenticated handshake before the RPC command is sent. let args: Vec = env::args().collect(); if args.len() != 1 { println!("Usage: ./lookup_network_info"); return; } let wallet_path = prompt_wallet_path().await; let encryption_key = prompt_hidden_nonempty( "What is your wallet decryption key? ", "Wallet key cannot be empty. Please try again.", ) .await; // Try configured peers in order and stop at the first readable // network-info response or text error. let connections = get_connections().await; let mut connected = false; for conn in connections { if connected { break; } let socket_address = conn.parse().expect("Failed to parse the socket address"); let result = handshake::connect_and_handshake( socket_address, "".to_string(), rpc_command, handshake::HandshakeWallet::WalletKey { encryption_key: encryption_key.clone(), wallet_path: wallet_path.clone(), }, hashmap_key, ) .await; match result { Ok(response) => { // Prefer binary network-info decoding; if that fails, // print a plain text error returned by the peer. if let Some(output) = decode_network_info(&response) { println!("{output}"); connected = true; } else { let response_text = String::from_utf8_lossy(&response); let trimmed = response_text.trim(); if !trimmed.is_empty() { println!("{trimmed}"); connected = true; } } } Err(_) => { connected = false; } } } if !connected { eprintln!("failed to connect"); } }