sybil detection fixes

This commit is contained in:
viraladmin 2026-06-05 21:33:37 -06:00
parent 04b93275fa
commit 13ae207739
38 changed files with 2223 additions and 2193 deletions

View File

@ -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(),

View File

@ -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);

View File

@ -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;
});
}

View File

@ -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
},
);
}
}
}

View File

@ -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,
}
}
}

View File

@ -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(&timestamp_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())
}

View File

@ -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,

View File

@ -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));

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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(),

View File

@ -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;

View File

@ -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.

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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.