146 lines
5.0 KiB
Rust
146 lines
5.0 KiB
Rust
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<u32> {
|
|
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<u64> {
|
|
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<String> {
|
|
// 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<String> = 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");
|
|
}
|
|
}
|