sybil detection fixes
This commit is contained in:
parent
04b93275fa
commit
13ae207739
|
|
@ -2,7 +2,8 @@ use crate::orphans::replay_errors::staged_candidate_status_for_error;
|
|||
use crate::orphans::structs::UndoTransactions;
|
||||
use crate::orphans::torrent_candidates::hydrate_torrent_candidates;
|
||||
use crate::records::block_height::get_block_height::get_height;
|
||||
use crate::records::memory::response_channels::reserve_entry;
|
||||
use crate::records::memory::response_channels::reserve_entry_with_context;
|
||||
use crate::rpc::command_maps::RPC_TORRENT_BY_HEIGHT;
|
||||
use crate::records::memory::torrent_status::{
|
||||
get_torrent_status, set_torrent_status, TorrentStatus,
|
||||
};
|
||||
|
|
@ -137,7 +138,12 @@ pub async fn save_new_blocks(
|
|||
|
||||
// No staged candidate worked, so request the replacement torrent
|
||||
// directly from the connected peer.
|
||||
let (hashmap_key, _save_tx, save_rx) = reserve_entry(params.map.clone()).await;
|
||||
let (hashmap_key, _save_tx, save_rx) = reserve_entry_with_context(
|
||||
params.map.clone(),
|
||||
Some(RPC_TORRENT_BY_HEIGHT),
|
||||
Some(params.connections_key.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
send_request_torrent_message(
|
||||
params.stream.clone(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||
use crate::records::memory::response_channels::{reserve_entry_with_context, Command};
|
||||
use crate::rpc::command_maps::RPC_TORRENT_CANDIDATES;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::torrent::torrenting_system::save_torrent::save_staged_torrent;
|
||||
|
|
@ -11,7 +11,12 @@ pub async fn hydrate_torrent_candidates(
|
|||
) -> 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 (hashmap_key, _tx, rx) = reserve_entry_with_context(
|
||||
map.clone(),
|
||||
Some(RPC_TORRENT_CANDIDATES),
|
||||
Some(connections_key.clone()),
|
||||
)
|
||||
.await;
|
||||
let mut message = Vec::with_capacity(4);
|
||||
message.push(RPC_TORRENT_CANDIDATES);
|
||||
message.extend_from_slice(&hashmap_key);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ use crate::records::memory::enums::{ClientType, ConnectionType};
|
|||
use crate::records::memory::network_mapping::monitor::{MONITOR_ACTION_ADD, MONITOR_ACTION_REMOVE};
|
||||
use crate::records::memory::network_mapping::structs::{MonitorAddressParams, SignedMonitorEdit};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::response_channels::{delete_entry, reserve_entry, Command};
|
||||
use crate::records::memory::response_channels::{
|
||||
delete_entry, reserve_entry_with_context, Command,
|
||||
};
|
||||
use crate::records::memory::structs::{Connection, StoreConnectionParams};
|
||||
use crate::rpc::client::handshake::connect_and_handshake;
|
||||
use crate::rpc::client::handshake_processing::{bootstrap_peer_discovery, BootstrapParams};
|
||||
|
|
@ -350,7 +352,12 @@ impl Connection {
|
|||
|
||||
let message_type = RPC_BLOCK_HEIGHT; // Block-height request used as a lightweight checkup ping.
|
||||
let (checkup_key, _checkup_tx, checkup_rx_mutex) =
|
||||
reserve_entry(command_map.clone()).await;
|
||||
reserve_entry_with_context(
|
||||
command_map.clone(),
|
||||
Some(RPC_BLOCK_HEIGHT),
|
||||
Some(format!("{ip}:{port}")),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Send a lightweight ping message and wait for the reply
|
||||
// routed back through the shared response hashmap.
|
||||
|
|
@ -776,7 +783,7 @@ fn spawn_monitor_update(ip: String, action: u8, peer_public_key: Vec<u8>, port:
|
|||
.await;
|
||||
let edit = SignedMonitorEdit {
|
||||
action,
|
||||
monitored_address,
|
||||
monitored_address: monitored_address.clone(),
|
||||
monitoring_address,
|
||||
target_ip: ip.clone(),
|
||||
modified_timestamp: timestamp,
|
||||
|
|
@ -795,6 +802,13 @@ fn spawn_monitor_update(ip: String, action: u8, peer_public_key: Vec<u8>, port:
|
|||
} else {
|
||||
NodeInfo::remove_monitor(params).await
|
||||
};
|
||||
NodeInfo::broadcast_address_state(
|
||||
context.map.clone(),
|
||||
&monitored_address,
|
||||
"",
|
||||
&format!("{ip}:{port}"),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
use super::*;
|
||||
|
||||
impl NodeInfo {
|
||||
pub async fn broadcast_address_state(
|
||||
map: Arc<Mutex<Command>>,
|
||||
address: &str,
|
||||
remote_ip: &str,
|
||||
connections_key: &str,
|
||||
) {
|
||||
let edit = {
|
||||
let address_map = ADDRESS_MAP.lock().await;
|
||||
let Some(node) = address_map.get(address) else {
|
||||
return;
|
||||
};
|
||||
SignedNodeEdit {
|
||||
address: address.to_string(),
|
||||
ip: node.ip.clone(),
|
||||
modified_by: node.added_by.clone(),
|
||||
modified_timestamp: node.added_timestamp,
|
||||
modified_signature: node.added_signature.clone(),
|
||||
}
|
||||
};
|
||||
Self::broadcast_node(map, &edit, remote_ip, NodeEditType::Add, connections_key).await;
|
||||
}
|
||||
|
||||
pub async fn broadcast_node(
|
||||
map: Arc<Mutex<Command>>,
|
||||
edit: &SignedNodeEdit,
|
||||
|
|
@ -28,6 +50,20 @@ impl NodeInfo {
|
|||
};
|
||||
let modified_timestamp_bytes = edit.modified_timestamp.to_le_bytes();
|
||||
let modified_signature_bytes = decode(&edit.modified_signature).unwrap();
|
||||
let monitor_bytes = {
|
||||
let address_map = ADDRESS_MAP.lock().await;
|
||||
address_map
|
||||
.get(&edit.address)
|
||||
.map(|node| {
|
||||
node.monitoring
|
||||
.iter()
|
||||
.filter_map(|monitor| Wallet::short_address_to_bytes(monitor))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let monitor_count = monitor_bytes.len().min(u16::MAX as usize) as u16;
|
||||
let monitor_count_bytes = monitor_count.to_le_bytes();
|
||||
let streams = {
|
||||
let connections_lock = CONNECTIONS.read().await;
|
||||
connections_lock
|
||||
|
|
@ -47,6 +83,10 @@ impl NodeInfo {
|
|||
message.extend_from_slice(&modified_by_bytes);
|
||||
message.extend_from_slice(&modified_timestamp_bytes);
|
||||
message.extend_from_slice(&modified_signature_bytes);
|
||||
message.extend_from_slice(&monitor_count_bytes);
|
||||
for monitor in monitor_bytes.iter().take(monitor_count as usize) {
|
||||
message.extend_from_slice(monitor);
|
||||
}
|
||||
let Some((peer_ip, _)) = peer_key.rsplit_once(':') else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -68,6 +108,7 @@ impl NodeInfo {
|
|||
let AddAddressParams {
|
||||
map,
|
||||
mut edit,
|
||||
monitors,
|
||||
mut blocks_mined,
|
||||
remote_ip,
|
||||
db,
|
||||
|
|
@ -118,6 +159,7 @@ impl NodeInfo {
|
|||
}
|
||||
|
||||
let mut penalize_duplicate_ip = false;
|
||||
let mut accepted_existing_node = false;
|
||||
|
||||
{
|
||||
let mut address_map = ADDRESS_MAP.lock().await;
|
||||
|
|
@ -168,6 +210,7 @@ impl NodeInfo {
|
|||
// same IP are still rejected, but deleted records remain in place
|
||||
// for historical validation.
|
||||
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
||||
existing_node.monitoring = monitors.clone();
|
||||
if existing_node.deleted_timestamp > 0 {
|
||||
if existing_node.ip == edit.ip {
|
||||
existing_node.deleted_timestamp = 0_u64;
|
||||
|
|
@ -186,29 +229,35 @@ impl NodeInfo {
|
|||
edit.modified_signature.clone(),
|
||||
);
|
||||
}
|
||||
return RpcResponse::Binary(b"Success".to_vec());
|
||||
accepted_existing_node = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(existing_node) = address_map.values_mut().find(|node| node.ip == edit.ip) {
|
||||
if existing_node.deleted_timestamp == 0 && edit.ip != GENESIS_IP {
|
||||
penalize_duplicate_ip = true;
|
||||
if !accepted_existing_node {
|
||||
if let Some(existing_node) = address_map.values_mut().find(|node| node.ip == edit.ip) {
|
||||
if existing_node.deleted_timestamp == 0 && edit.ip != GENESIS_IP {
|
||||
penalize_duplicate_ip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !penalize_duplicate_ip {
|
||||
// Persist the new node locally. Network-map entries are bare
|
||||
// IP membership records, separate from live socket keys.
|
||||
address_map.insert(
|
||||
edit.address.clone(),
|
||||
NodeInfo::new(
|
||||
edit.ip.clone(),
|
||||
blocks_mined,
|
||||
edit.modified_by.clone(),
|
||||
edit.modified_timestamp,
|
||||
edit.modified_signature.clone(),
|
||||
),
|
||||
);
|
||||
if !penalize_duplicate_ip {
|
||||
// Persist the new node locally. Network-map entries are bare
|
||||
// IP membership records, separate from live socket keys.
|
||||
address_map.insert(
|
||||
edit.address.clone(),
|
||||
{
|
||||
let mut node = NodeInfo::new(
|
||||
edit.ip.clone(),
|
||||
blocks_mined,
|
||||
edit.modified_by.clone(),
|
||||
edit.modified_timestamp,
|
||||
edit.modified_signature.clone(),
|
||||
);
|
||||
node.monitoring = monitors.clone();
|
||||
node
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
use crate::rpc::command_maps::{
|
||||
RPC_ADD_NETWORK_NODE, RPC_NETWORK_MONITOR_ADD, RPC_NETWORK_MONITOR_REMOVE,
|
||||
};
|
||||
use crate::rpc::command_maps::RPC_ADD_NETWORK_NODE;
|
||||
|
||||
// NodeEditType keeps the network membership update type explicit at the call sites.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum NodeEditType {
|
||||
Add,
|
||||
MonitorAdd,
|
||||
MonitorRemove,
|
||||
}
|
||||
|
||||
impl NodeEditType {
|
||||
|
|
@ -15,8 +11,6 @@ impl NodeEditType {
|
|||
pub fn message_type(self) -> u8 {
|
||||
match self {
|
||||
NodeEditType::Add => RPC_ADD_NETWORK_NODE,
|
||||
NodeEditType::MonitorAdd => RPC_NETWORK_MONITOR_ADD,
|
||||
NodeEditType::MonitorRemove => RPC_NETWORK_MONITOR_REMOVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,62 +21,6 @@ impl NodeInfo {
|
|||
Wallet::sign_transaction(&hashed_data, private_key).await
|
||||
}
|
||||
|
||||
pub async fn broadcast_monitor(
|
||||
map: Arc<Mutex<Command>>,
|
||||
edit: &SignedMonitorEdit,
|
||||
remote_ip: &str,
|
||||
edittype: NodeEditType,
|
||||
_connections_key: &str,
|
||||
) {
|
||||
let monitored_bytes = match Wallet::short_address_to_bytes(&edit.monitored_address) {
|
||||
Some(bytes) => bytes,
|
||||
None => return,
|
||||
};
|
||||
let monitoring_bytes = match Wallet::short_address_to_bytes(&edit.monitoring_address) {
|
||||
Some(bytes) => bytes,
|
||||
None => return,
|
||||
};
|
||||
let target_ip_bytes = ip_to_binary(&edit.target_ip);
|
||||
let timestamp_bytes = edit.modified_timestamp.to_le_bytes();
|
||||
let signature_bytes = match decode(&edit.modified_signature) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let streams = {
|
||||
let connections_lock = CONNECTIONS.read().await;
|
||||
connections_lock
|
||||
.as_ref()
|
||||
.map(|connection| connection.get_all_ready_peer_streams_with_keys())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
if !streams.is_empty() {
|
||||
for (peer_key, unlocked_stream) in streams {
|
||||
let Some((peer_ip, _)) = peer_key.rsplit_once(':') else {
|
||||
continue;
|
||||
};
|
||||
if !remote_ip.is_empty() && peer_ip == remote_ip {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (hashmap_key, _hashmap_tx, hashmap_rx) = reserve_entry(map.clone()).await;
|
||||
let mut message = Vec::new();
|
||||
message.push(edittype.message_type());
|
||||
message.extend_from_slice(&hashmap_key);
|
||||
message.push(edit.action);
|
||||
message.extend_from_slice(&monitored_bytes);
|
||||
message.extend_from_slice(&monitoring_bytes);
|
||||
message.extend_from_slice(&target_ip_bytes);
|
||||
message.extend_from_slice(×tamp_bytes);
|
||||
message.extend_from_slice(&signature_bytes);
|
||||
RpcResponse::send_raw(&unlocked_stream, Some(&peer_key), &message).await;
|
||||
let mut rx = hashmap_rx.lock().await;
|
||||
let _ = timeout(Duration::from_secs(5), rx.recv()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_deleted_and_cascade(
|
||||
address_map: &mut HashMap<String, NodeInfo>,
|
||||
deleted_address: &str,
|
||||
|
|
@ -155,12 +99,11 @@ impl NodeInfo {
|
|||
|
||||
async fn apply_monitor(params: MonitorAddressParams, action: u8) -> RpcResponse {
|
||||
let MonitorAddressParams {
|
||||
map,
|
||||
mut edit,
|
||||
remote_ip,
|
||||
db,
|
||||
wallet,
|
||||
connections_key,
|
||||
..
|
||||
} = params;
|
||||
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
||||
|
||||
|
|
@ -221,13 +164,6 @@ impl NodeInfo {
|
|||
}
|
||||
}
|
||||
|
||||
let broadcast_type = if action == MONITOR_ACTION_ADD {
|
||||
NodeEditType::MonitorAdd
|
||||
} else {
|
||||
NodeEditType::MonitorRemove
|
||||
};
|
||||
Self::broadcast_monitor(map, &edit, &remote_ip, broadcast_type, &connections_key).await;
|
||||
|
||||
RpcResponse::Binary(b"Success".to_vec())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ pub struct SignedMonitorEdit {
|
|||
pub struct AddAddressParams {
|
||||
pub map: Arc<Mutex<Command>>,
|
||||
pub edit: SignedNodeEdit,
|
||||
pub monitors: Vec<String>,
|
||||
pub blocks_mined: u8,
|
||||
pub remote_ip: String,
|
||||
pub db: Db,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::log::warn;
|
||||
use crate::mpsc;
|
||||
use crate::Arc;
|
||||
use crate::Duration;
|
||||
|
|
@ -10,19 +11,24 @@ pub struct ChannelPair {
|
|||
pub tx: mpsc::Sender<Vec<u8>>,
|
||||
pub rx: Arc<Mutex<mpsc::Receiver<Vec<u8>>>>,
|
||||
pub expires_at: Option<Instant>,
|
||||
pub created_at: Instant,
|
||||
pub command: Option<u8>,
|
||||
pub peer: Option<String>,
|
||||
}
|
||||
|
||||
pub type Byte3 = [u8; 3];
|
||||
|
||||
pub type Command = HashMap<Byte3, ChannelPair>;
|
||||
|
||||
const SLOW_RPC_TRACE_MS: u128 = 1_000;
|
||||
|
||||
fn random_3_byte_number() -> [u8; 3] {
|
||||
let mut rng = rand::thread_rng();
|
||||
let num: u32 = rng.gen_range(0..=0xFFFFFF);
|
||||
// The protocol UID is three bytes on the wire, so the random u32 is sliced
|
||||
// down to the same fixed-width little-endian layout used by requests.
|
||||
|
||||
num.to_le_bytes()[1..4].try_into().unwrap()
|
||||
num.to_le_bytes()[0..3].try_into().unwrap()
|
||||
}
|
||||
|
||||
// Generate an untracked three-byte UID for fire-and-forget messages
|
||||
|
|
@ -46,6 +52,18 @@ pub async fn reserve_entry(
|
|||
Byte3,
|
||||
mpsc::Sender<Vec<u8>>,
|
||||
Arc<Mutex<mpsc::Receiver<Vec<u8>>>>,
|
||||
) {
|
||||
reserve_entry_with_context(map, None, None).await
|
||||
}
|
||||
|
||||
pub async fn reserve_entry_with_context(
|
||||
map: Arc<Mutex<Command>>,
|
||||
command: Option<u8>,
|
||||
peer: Option<String>,
|
||||
) -> (
|
||||
Byte3,
|
||||
mpsc::Sender<Vec<u8>>,
|
||||
Arc<Mutex<mpsc::Receiver<Vec<u8>>>>,
|
||||
) {
|
||||
loop {
|
||||
let key = random_3_byte_number();
|
||||
|
|
@ -74,6 +92,9 @@ pub async fn reserve_entry(
|
|||
tx: tx.clone(),
|
||||
rx: rx.clone(),
|
||||
expires_at: None,
|
||||
created_at: now,
|
||||
command,
|
||||
peer: peer.clone(),
|
||||
},
|
||||
);
|
||||
(tx, rx)
|
||||
|
|
@ -82,6 +103,21 @@ pub async fn reserve_entry(
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ReplyTrace {
|
||||
pub age_ms: u128,
|
||||
pub command: Option<u8>,
|
||||
pub peer: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn trace_entry(map: Arc<Mutex<Command>>, key: Byte3) -> Option<ReplyTrace> {
|
||||
let map = map.lock().await;
|
||||
map.get(&key).map(|channel_pair| ReplyTrace {
|
||||
age_ms: channel_pair.created_at.elapsed().as_millis(),
|
||||
command: channel_pair.command,
|
||||
peer: channel_pair.peer.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_entry(map: Arc<Mutex<Command>>, key: Byte3) -> Option<mpsc::Sender<Vec<u8>>> {
|
||||
let map = map.lock().await;
|
||||
if let Some(channel_pair) = map.get(&key) {
|
||||
|
|
@ -110,6 +146,14 @@ pub async fn is_retired_entry(map: Arc<Mutex<Command>>, key: Byte3) -> bool {
|
|||
pub async fn delete_entry(map: Arc<Mutex<Command>>, key: Byte3) {
|
||||
let mut map = map.lock().await;
|
||||
if let Some(channel_pair) = map.get_mut(&key) {
|
||||
let age_ms = channel_pair.created_at.elapsed().as_millis();
|
||||
if age_ms >= SLOW_RPC_TRACE_MS && channel_pair.expires_at.is_none() {
|
||||
warn!(
|
||||
"[rpc_trace] retiring slow uid: uid={key:?} cmd={:?} peer={} age_ms={age_ms}",
|
||||
channel_pair.command,
|
||||
channel_pair.peer.as_deref().unwrap_or("unknown")
|
||||
);
|
||||
}
|
||||
// Keep the UID reserved briefly after completion so a late duplicate
|
||||
// reply cannot be mistaken for a fresh request.
|
||||
channel_pair.expires_at = Some(Instant::now() + Duration::from_secs(30));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::encode;
|
||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||
use crate::records::memory::response_channels::{reserve_entry_with_context, Command};
|
||||
use crate::rpc::command_maps::RPC_BLOCK_HASH_AT_HEIGHT;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::{timeout, Arc, Duration, Mutex, TcpStream};
|
||||
|
|
@ -10,7 +10,12 @@ pub async fn request_block_hash_at_height(
|
|||
connections_key: String,
|
||||
height: u32,
|
||||
) -> Result<String, String> {
|
||||
let (hashmap_key, _tx, rx) = reserve_entry(map).await;
|
||||
let (hashmap_key, _tx, rx) = reserve_entry_with_context(
|
||||
map,
|
||||
Some(RPC_BLOCK_HASH_AT_HEIGHT),
|
||||
Some(connections_key.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut message = vec![RPC_BLOCK_HASH_AT_HEIGHT];
|
||||
message.extend_from_slice(&hashmap_key);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ use crate::log::{error, info, warn};
|
|||
use crate::orphans::structs::OrphanCheckup2;
|
||||
use crate::orphans::sync_check::sync_checkup;
|
||||
use crate::records::block_height::get_block_height::get_height;
|
||||
use crate::records::memory::response_channels::reserve_entry;
|
||||
use crate::records::memory::response_channels::reserve_entry_with_context;
|
||||
use crate::records::memory::response_channels::Command;
|
||||
use crate::rpc::command_maps::RPC_TORRENT_BY_HEIGHT;
|
||||
use crate::sled::Db;
|
||||
use crate::timeout;
|
||||
use crate::torrent::structs::Torrent;
|
||||
|
|
@ -37,7 +38,12 @@ pub async fn node_syncing(
|
|||
// Walk forward block-by-block until the local chain catches up to
|
||||
// the advertised remote height.
|
||||
while remote_height >= local_height {
|
||||
let (hashmap_key, _torrent_tx, torrent_rx) = reserve_entry(map.clone()).await;
|
||||
let (hashmap_key, _torrent_tx, torrent_rx) = reserve_entry_with_context(
|
||||
map.clone(),
|
||||
Some(RPC_TORRENT_BY_HEIGHT),
|
||||
Some(connections_key.clone()),
|
||||
)
|
||||
.await;
|
||||
send_request_torrent_message(
|
||||
stream.clone(),
|
||||
local_height,
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ pub const RPC_WALLET_REGISTRY_SYNC: u8 = 39;
|
|||
pub const RPC_VANITY_LOOKUP: u8 = 40;
|
||||
pub const RPC_TORRENT_CANDIDATES: u8 = 41;
|
||||
pub const RPC_BLOCK_HASH_AT_HEIGHT: u8 = 42;
|
||||
pub const RPC_NETWORK_MONITOR_ADD: u8 = 43;
|
||||
pub const RPC_NETWORK_MONITOR_REMOVE: u8 = 44;
|
||||
pub const RPC_VANITY_OWNER_LOOKUP: u8 = 45;
|
||||
pub const RPC_REPLY: u8 = 255;
|
||||
pub const MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,20 @@ pub async fn add_network_node(
|
|||
let added_signature =
|
||||
read_bytes_from_stream::read_signature_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let monitor_count =
|
||||
read_bytes_from_stream::read_u16_from_stream(connections_key, stream_locked.clone())
|
||||
.await? as usize;
|
||||
let mut monitors = Vec::with_capacity(monitor_count);
|
||||
for _ in 0..monitor_count {
|
||||
let monitor_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||
connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
if let Some(monitor) = Wallet::bytes_to_short_address(&monitor_bytes) {
|
||||
monitors.push(monitor);
|
||||
}
|
||||
}
|
||||
let remote_ip = read_bytes_from_stream::read_caller_ip(stream_locked).await?;
|
||||
|
||||
// NodeInfo owns the signature checks, local re-signing rules, and
|
||||
|
|
@ -60,6 +74,7 @@ pub async fn add_network_node(
|
|||
modified_timestamp: added_timestamp,
|
||||
modified_signature: added_signature,
|
||||
},
|
||||
monitors,
|
||||
blocks_mined: 0_u8,
|
||||
remote_ip,
|
||||
db: db.clone(),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ pub mod largest_tx_fee;
|
|||
pub mod latest_block;
|
||||
pub mod memory_by_signature;
|
||||
pub mod network_info;
|
||||
pub mod network_monitor_add;
|
||||
pub mod network_monitor_remove;
|
||||
pub mod nft_list;
|
||||
pub mod nft_lookup;
|
||||
pub mod random_node;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::log::warn;
|
||||
use crate::records::memory::enums::ClientType;
|
||||
use crate::records::memory::response_channels::{
|
||||
delete_entry, get_entry, is_retired_entry, Command,
|
||||
delete_entry, get_entry, is_retired_entry, trace_entry, Command,
|
||||
};
|
||||
use crate::rpc::command_maps::MAX_RPC_REPLY_BYTES;
|
||||
use crate::rpc::commands::bad_rpc_call;
|
||||
|
|
@ -38,6 +38,7 @@ pub async fn route_reply(
|
|||
let tx_option = get_entry(map.clone(), uid).await;
|
||||
|
||||
if let Some(tx) = tx_option {
|
||||
let trace = trace_entry(map.clone(), uid).await;
|
||||
// Replies are payload-only after the UID and length prefix; the
|
||||
// waiting request task already knows how to interpret the bytes.
|
||||
let buffer = read_bytes_from_stream::read_usize_from_stream(
|
||||
|
|
@ -49,6 +50,16 @@ pub async fn route_reply(
|
|||
if tx.send(buffer).await.is_err() {
|
||||
warn!("[rpc] reply receiver dropped before payload delivery: {uid:?}");
|
||||
}
|
||||
if let Some(trace) = trace {
|
||||
if trace.age_ms >= 1_000 {
|
||||
warn!(
|
||||
"[rpc_trace] slow reply routed: uid={uid:?} cmd={:?} peer={} age_ms={} payload_bytes={message_length}",
|
||||
trace.command,
|
||||
trace.peer.as_deref().unwrap_or(connections_key),
|
||||
trace.age_ms
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
delete_entry(map, uid).await;
|
||||
return Ok(());
|
||||
|
|
@ -59,7 +70,17 @@ pub async fn route_reply(
|
|||
// Retired UIDs are normal timeout fallout: the requester gave up,
|
||||
// but the peer eventually answered. Drain without penalizing so
|
||||
// startup/sync latency does not poison an otherwise valid peer.
|
||||
warn!("[rpc] late reply arrived for retired uid: {uid:?}");
|
||||
let trace = trace_entry(map.clone(), uid).await;
|
||||
if let Some(trace) = trace {
|
||||
warn!(
|
||||
"[rpc] late reply arrived for retired uid: {uid:?} cmd={:?} peer={} age_ms={} payload_bytes={message_length}",
|
||||
trace.command,
|
||||
trace.peer.as_deref().unwrap_or(connections_key),
|
||||
trace.age_ms
|
||||
);
|
||||
} else {
|
||||
warn!("[rpc] late reply arrived for retired uid: {uid:?}");
|
||||
}
|
||||
} else {
|
||||
// Unknown, never-reserved UIDs can still indicate malformed or
|
||||
// forged traffic, so those keep counting as bad RPC behavior.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::common::binary_conversions::binary_to_string;
|
||||
use crate::encode;
|
||||
use crate::log::warn;
|
||||
use crate::records::memory::enums::ClientType;
|
||||
use crate::records::memory::response_channels::Command;
|
||||
use crate::rpc::server::command_loop_state::next_incoming_command;
|
||||
|
|
@ -9,6 +10,7 @@ use crate::sled::Db;
|
|||
use crate::wallets::structures::Wallet;
|
||||
use crate::Arc;
|
||||
use crate::AsyncWriteExt;
|
||||
use crate::Instant;
|
||||
use crate::Mutex;
|
||||
use crate::TcpStream;
|
||||
|
||||
|
|
@ -29,6 +31,7 @@ pub async fn start_loop(
|
|||
let command = incoming_command.command;
|
||||
let ip = incoming_command.ip;
|
||||
let client_type = incoming_command.client_type;
|
||||
let command_started = Instant::now();
|
||||
|
||||
match command {
|
||||
1 => {
|
||||
|
|
@ -813,34 +816,6 @@ pub async fn start_loop(
|
|||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
43 => {
|
||||
// add a monitor edge in the network map
|
||||
let (uid, result) = commands::network_monitor_add::network_monitor_add(
|
||||
&connections_key,
|
||||
stream_locked.clone(),
|
||||
&db,
|
||||
wallet.clone(),
|
||||
map.clone(),
|
||||
)
|
||||
.await?;
|
||||
result
|
||||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
44 => {
|
||||
// remove a monitor edge in the network map
|
||||
let (uid, result) = commands::network_monitor_remove::network_monitor_remove(
|
||||
&connections_key,
|
||||
stream_locked.clone(),
|
||||
&db,
|
||||
wallet.clone(),
|
||||
map.clone(),
|
||||
)
|
||||
.await?;
|
||||
result
|
||||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
45 => {
|
||||
// request the canonical short address that owns a registered vanity address
|
||||
let (uid, _) = read_bytes_from_stream::read_uid_from_stream(
|
||||
|
|
@ -893,6 +868,13 @@ pub async fn start_loop(
|
|||
}
|
||||
}
|
||||
|
||||
let elapsed_ms = command_started.elapsed().as_millis();
|
||||
if elapsed_ms >= 1_000 {
|
||||
warn!(
|
||||
"[rpc_trace] slow handler: cmd={command} peer={connections_key} elapsed_ms={elapsed_ms}"
|
||||
);
|
||||
}
|
||||
|
||||
// Wallet-backed client sessions are short-lived request/response
|
||||
// connections, so close them after each non-reply command.
|
||||
if client_type == ClientType::Client && command != command_maps::RPC_REPLY {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
use crate::common::binary_conversions::{binary_to_ip, binary_to_string, ip_to_binary};
|
||||
use crate::common::network_startup::get_ip_and_port;
|
||||
use crate::records::memory::network_mapping::monitor::MONITOR_ACTION_ADD;
|
||||
use crate::records::memory::network_mapping::structs::{
|
||||
AddAddressParams, MonitorAddressParams, SignedMonitorEdit, SignedNodeEdit,
|
||||
NODE_ADDED_BY_OFFSET, NODE_ADDED_SIGNATURE_OFFSET, NODE_ADDED_TIMESTAMP_OFFSET,
|
||||
NODE_BLOCKS_MINED_OFFSET, NODE_DELETED_BLOCK_OFFSET, NODE_DELETED_TIMESTAMP_OFFSET,
|
||||
NODE_IP_OFFSET, NODE_MONITOR_COUNT_OFFSET, NODE_RECORD_FIXED_BYTES,
|
||||
AddAddressParams, SignedNodeEdit, NODE_ADDED_BY_OFFSET, NODE_ADDED_SIGNATURE_OFFSET,
|
||||
NODE_ADDED_TIMESTAMP_OFFSET, NODE_BLOCKS_MINED_OFFSET, NODE_DELETED_BLOCK_OFFSET,
|
||||
NODE_DELETED_TIMESTAMP_OFFSET, NODE_IP_OFFSET, NODE_MONITOR_COUNT_OFFSET,
|
||||
NODE_RECORD_FIXED_BYTES,
|
||||
};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||
use crate::records::memory::structs::Connection;
|
||||
use crate::rpc::command_maps::{RPC_ADD_NETWORK_NODE, RPC_REQUEST_NODE_LIST};
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
|
|
@ -21,55 +19,6 @@ use crate::Mutex;
|
|||
use crate::TcpStream;
|
||||
use crate::Utc;
|
||||
|
||||
async fn record_local_monitor_for_peer(
|
||||
connections_key: &str,
|
||||
command_map: Arc<Mutex<Command>>,
|
||||
db: &Db,
|
||||
wallet: Arc<Wallet>,
|
||||
) {
|
||||
let Some(peer_wallet_bytes) = Connection::get_wallet_for_connection_key(connections_key).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(monitored_address) = Wallet::public_key_bytes_to_short_address(&peer_wallet_bytes)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let monitoring_address = wallet.saved.short_address.clone();
|
||||
if monitored_address == monitoring_address {
|
||||
return;
|
||||
}
|
||||
let Some((target_ip, _)) = connections_key.rsplit_once(':') else {
|
||||
return;
|
||||
};
|
||||
let timestamp = Utc::now().timestamp_millis() as u64;
|
||||
let signature = NodeInfo::monitor_signature(
|
||||
MONITOR_ACTION_ADD,
|
||||
&monitored_address,
|
||||
&monitoring_address,
|
||||
target_ip,
|
||||
timestamp,
|
||||
&wallet,
|
||||
)
|
||||
.await;
|
||||
let _ = NodeInfo::add_monitor(MonitorAddressParams {
|
||||
map: command_map,
|
||||
edit: SignedMonitorEdit {
|
||||
action: MONITOR_ACTION_ADD,
|
||||
monitored_address,
|
||||
monitoring_address,
|
||||
target_ip: target_ip.to_string(),
|
||||
modified_timestamp: timestamp,
|
||||
modified_signature: signature,
|
||||
},
|
||||
remote_ip: String::new(),
|
||||
db: db.clone(),
|
||||
wallet,
|
||||
connections_key: connections_key.to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn announce_self_to_network(
|
||||
unlocked_stream: Arc<Mutex<TcpStream>>,
|
||||
address: &str,
|
||||
|
|
@ -114,6 +63,7 @@ pub async fn announce_self_to_network(
|
|||
message.extend_from_slice(&modified_by_bytes);
|
||||
message.extend_from_slice(&modified_timestamp_bytes);
|
||||
message.extend_from_slice(&modified_signature_bytes);
|
||||
message.extend_from_slice(&0u16.to_le_bytes());
|
||||
|
||||
RpcResponse::send_raw(&unlocked_stream, Some(connections_key), &message).await;
|
||||
|
||||
|
|
@ -141,7 +91,6 @@ pub async fn announce_self_to_network(
|
|||
connections_key,
|
||||
)
|
||||
.await?;
|
||||
record_local_monitor_for_peer(connections_key, command_map, db, wallet).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -215,6 +164,16 @@ pub async fn get_network_mapping(
|
|||
.unwrap(),
|
||||
);
|
||||
let remote_ip = "";
|
||||
let mut monitors = Vec::with_capacity(monitor_count);
|
||||
for monitor_index in 0..monitor_count {
|
||||
let start =
|
||||
NODE_RECORD_FIXED_BYTES + monitor_index * Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
let end = start + Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
if let Some(monitor) = Wallet::bytes_to_short_address(&chunk[start..end]) {
|
||||
monitors.push(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
// Add records are imported through NodeInfo so local validation/signing rules stay central.
|
||||
NodeInfo::add_address(AddAddressParams {
|
||||
map: command_map.clone(),
|
||||
|
|
@ -225,6 +184,7 @@ pub async fn get_network_mapping(
|
|||
modified_timestamp: added_timestamp,
|
||||
modified_signature: added_signature,
|
||||
},
|
||||
monitors,
|
||||
blocks_mined,
|
||||
remote_ip: remote_ip.to_string(),
|
||||
db: db.clone(),
|
||||
|
|
@ -233,17 +193,6 @@ pub async fn get_network_mapping(
|
|||
})
|
||||
.await;
|
||||
|
||||
let mut monitors = Vec::with_capacity(monitor_count);
|
||||
for monitor_index in 0..monitor_count {
|
||||
let start =
|
||||
NODE_RECORD_FIXED_BYTES + monitor_index * Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
let end = start + Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
if let Some(monitor) = Wallet::bytes_to_short_address(&chunk[start..end]) {
|
||||
monitors.push(monitor);
|
||||
}
|
||||
}
|
||||
NodeInfo::set_monitors_from_mapping(&address, monitors).await;
|
||||
|
||||
if deleted_timestamp > 0 {
|
||||
NodeInfo::set_deleted_metadata_from_mapping(&address, deleted_timestamp, deleted_block)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||
use crate::records::memory::response_channels::{reserve_entry_with_context, Command};
|
||||
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::Arc;
|
||||
|
|
@ -13,7 +13,9 @@ pub async fn request_remote_height(
|
|||
) -> Result<u32, String> {
|
||||
// request the remote node's current chain height using
|
||||
// the standard reply-channel request/response flow
|
||||
let (hashmap_key, _tx, rx) = reserve_entry(map.clone()).await;
|
||||
let (hashmap_key, _tx, rx) =
|
||||
reserve_entry_with_context(map.clone(), Some(RPC_BLOCK_HEIGHT), Some(connections_key.clone()))
|
||||
.await;
|
||||
|
||||
// message format is the height command plus the unique
|
||||
// reply key used to route the response back here
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::records::memory::response_channels::{delete_entry, reserve_entry};
|
||||
use crate::records::memory::response_channels::{delete_entry, reserve_entry_with_context};
|
||||
use crate::rpc::command_maps::RPC_BLOCK_PIECE;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::torrent::structs::RequestPiece;
|
||||
|
|
@ -9,7 +9,12 @@ use crate::{timeout, Duration};
|
|||
pub async fn request_piece_from_node(params: RequestPiece) -> Result<Vec<u8>, String> {
|
||||
// Reserve a response slot in the shared hashmap so the incoming
|
||||
// piece bytes can be routed back to this request.
|
||||
let (hashmap_key, _block_tx, block_rx) = reserve_entry(params.map.clone()).await;
|
||||
let (hashmap_key, _block_tx, block_rx) = reserve_entry_with_context(
|
||||
params.map.clone(),
|
||||
Some(RPC_BLOCK_PIECE),
|
||||
Some(params.connections_key.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Compute the exact piece length, including the shortened final
|
||||
// piece when the block length is not an exact multiple.
|
||||
|
|
|
|||
Loading…
Reference in New Issue