bootstrap

This commit is contained in:
John Smith 2023-02-13 16:12:46 -05:00
parent 5fd0684ae7
commit 1d8e2d3fda
19 changed files with 428 additions and 471 deletions

7
Cargo.lock generated
View File

@ -6003,6 +6003,7 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-bindgen-test", "wasm-bindgen-test",
"wasm-logger", "wasm-logger",
"weak-table",
"web-sys", "web-sys",
"webpki 0.22.0", "webpki 0.22.0",
"webpki-roots 0.22.6", "webpki-roots 0.22.6",
@ -6327,6 +6328,12 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "weak-table"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.60" version = "0.3.60"

View File

@ -50,7 +50,6 @@ core:
node_id: '' node_id: ''
node_id_secret: '' node_id_secret: ''
bootstrap: ['bootstrap.dev.veilid.net'] bootstrap: ['bootstrap.dev.veilid.net']
bootstrap_nodes: []
routing_table: routing_table:
limit_over_attached: 64 limit_over_attached: 64
limit_fully_attached: 32 limit_fully_attached: 32

View File

@ -14,14 +14,14 @@ and the `veilid-server.conf` file.
## Global Directives ## Global Directives
| Directive | Description | | Directive | Description |
|-------------------------------|-----------------------------------------| | ---------------------------- | ------------------------------------- |
| [daemon](#daemon) | Run `veilid-server` in the background | | [daemon](#daemon) | Run `veilid-server` in the background |
| [client\_api](#client_api) || | [client\_api](#client_api) | |
| [auto\_attach](#auto_attach) || | [auto\_attach](#auto_attach) | |
| [logging](#logging) || | [logging](#logging) | |
| [testing](#testing) || | [testing](#testing) | |
| [core](#core) || | [core](#core) | |
### daemon ### daemon
@ -39,10 +39,10 @@ client_api:
listen_address: 'localhost:5959' listen_address: 'localhost:5959'
``` ```
| Parameter | Description | | Parameter | Description |
|-----------------------------------------------|-------------| | -------------------------------------------- | ----------- |
| [enabled](#client_apienabled) || | [enabled](#client_apienabled) | |
| [listen\_address](#client_apilisten_address) || | [listen\_address](#client_apilisten_address) | |
#### client\_api:enabled #### client\_api:enabled
@ -82,13 +82,13 @@ logging:
grpc_endpoint: 'localhost:4317' grpc_endpoint: 'localhost:4317'
``` ```
| Parameter | Description | | Parameter | Description |
|-------------------------------|-------------| | ---------------------------- | ----------- |
| [system](#loggingsystem) || | [system](#loggingsystem) | |
| [terminal](#loggingterminal) || | [terminal](#loggingterminal) | |
| [file](#loggingfile) || | [file](#loggingfile) | |
| [api](#loggingapi) || | [api](#loggingapi) | |
| [otlp](#loggingotlp) || | [otlp](#loggingotlp) | |
#### logging:system #### logging:system
@ -142,12 +142,12 @@ testing:
### core ### core
| Parameter | Description | | Parameter | Description |
|-------------------------------------------|-------------| | ---------------------------------------- | ----------- |
| [protected\_store](#coreprotected_store) || | [protected\_store](#coreprotected_store) | |
| [table\_store](#coretable_store) || | [table\_store](#coretable_store) | |
| [block\_store](#block_store) || | [block\_store](#block_store) | |
| [network](#corenetwork) || | [network](#corenetwork) | |
#### core:protected\_store #### core:protected\_store
@ -191,7 +191,6 @@ network:
node_id: '' node_id: ''
node_id_secret: '' node_id_secret: ''
bootstrap: ['bootstrap.dev.veilid.net'] bootstrap: ['bootstrap.dev.veilid.net']
bootstrap_nodes: []
upnp: true upnp: true
detect_address_changes: true detect_address_changes: true
enable_local_peer_scope: false enable_local_peer_scope: false
@ -199,13 +198,13 @@ network:
``` ```
| Parameter | Description | | Parameter | Description |
|---------------------------------------------|-------------| | ------------------------------------------- | ----------- |
| [routing\_table](#corenetworkrouting_table) || | [routing\_table](#corenetworkrouting_table) | |
| [rpc](#corenetworkrpc) || | [rpc](#corenetworkrpc) | |
| [dht](#corenetworkdht) || | [dht](#corenetworkdht) | |
| [tls](#corenetworktls) || | [tls](#corenetworktls) | |
| [application](#corenetworkapplication) || | [application](#corenetworkapplication) | |
| [protocol](#corenetworkprotocol) || | [protocol](#corenetworkprotocol) | |
#### core:network:routing\_table #### core:network:routing\_table

View File

@ -11,11 +11,11 @@ crate-type = ["cdylib", "staticlib", "rlib"]
[features] [features]
default = [] default = []
rt-async-std = [ "async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std" ] rt-async-std = ["async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std"]
rt-tokio = [ "tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket", "veilid-tools/rt-tokio" ] rt-tokio = ["tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket", "veilid-tools/rt-tokio"]
veilid_core_android_tests = [ "dep:paranoid-android" ] veilid_core_android_tests = ["dep:paranoid-android"]
veilid_core_ios_tests = [ "dep:tracing-oslog" ] veilid_core_ios_tests = ["dep:tracing-oslog"]
tracking = [] tracking = []
[dependencies] [dependencies]
@ -65,6 +65,7 @@ keyvaluedb = { path = "../external/keyvaluedb/keyvaluedb" }
rkyv = { git = "https://github.com/rkyv/rkyv.git", rev = "57e2a8d", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] } rkyv = { git = "https://github.com/rkyv/rkyv.git", rev = "57e2a8d", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] }
bytecheck = "^0" bytecheck = "^0"
data-encoding = { version = "^2" } data-encoding = { version = "^2" }
weak-table = "0.3.2"
# Dependencies for native builds only # Dependencies for native builds only
# Linux, Windows, Mac, iOS, Android # Linux, Windows, Mac, iOS, Android

View File

@ -11,9 +11,9 @@ use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as
pub type CryptoKind = FourCC; pub type CryptoKind = FourCC;
/// Sort best crypto kinds first /// Sort best crypto kinds first
pub fn compare_crypto_kind(a: CryptoKind, b: CryptoKind) -> cmp::Ordering { pub fn compare_crypto_kind(a: &CryptoKind, b: &CryptoKind) -> cmp::Ordering {
let a_idx = VALID_CRYPTO_KINDS.iter().position(|&k| k == a); let a_idx = VALID_CRYPTO_KINDS.iter().position(|k| k == a);
let b_idx = VALID_CRYPTO_KINDS.iter().position(|&k| k == b); let b_idx = VALID_CRYPTO_KINDS.iter().position(|k| k == b);
if let Some(a_idx) = a_idx { if let Some(a_idx) = a_idx {
if let Some(b_idx) = b_idx { if let Some(b_idx) = b_idx {
a_idx.cmp(&b_idx) a_idx.cmp(&b_idx)
@ -23,7 +23,7 @@ pub fn compare_crypto_kind(a: CryptoKind, b: CryptoKind) -> cmp::Ordering {
} else if let Some(b_idx) = b_idx { } else if let Some(b_idx) = b_idx {
cmp::Ordering::Greater cmp::Ordering::Greater
} else { } else {
a.cmp(&b) a.cmp(b)
} }
} }
@ -86,7 +86,7 @@ impl PartialOrd for TypedKey {
impl Ord for TypedKey { impl Ord for TypedKey {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
let x = compare_crypto_kind(self.kind, other.kind); let x = compare_crypto_kind(&self.kind, &other.kind);
if x != cmp::Ordering::Equal { if x != cmp::Ordering::Equal {
return x; return x;
} }
@ -140,6 +140,14 @@ impl TypedKeySet {
items: Vec::with_capacity(cap), items: Vec::with_capacity(cap),
} }
} }
pub fn kinds(&self) -> Vec<CryptoKind> {
let mut out = Vec::new();
for tk in &self.items {
out.push(tk.kind);
}
out.sort_by(compare_crypto_kind);
out
}
pub fn get(&self, kind: CryptoKind) -> Option<TypedKey> { pub fn get(&self, kind: CryptoKind) -> Option<TypedKey> {
self.items.iter().find(|x| x.kind == kind).copied() self.items.iter().find(|x| x.kind == kind).copied()
} }
@ -153,6 +161,18 @@ impl TypedKeySet {
self.items.push(typed_key); self.items.push(typed_key);
self.items.sort() self.items.sort()
} }
pub fn add_all(&mut self, typed_keys: &[TypedKey]) {
'outer: for typed_key in typed_keys {
for x in &mut self.items {
if x.kind == typed_key.kind {
*x = *typed_key;
continue 'outer;
}
}
self.items.push(*typed_key);
}
self.items.sort()
}
pub fn remove(&self, kind: CryptoKind) { pub fn remove(&self, kind: CryptoKind) {
if let Some(idx) = self.items.iter().position(|x| x.kind == kind) { if let Some(idx) = self.items.iter().position(|x| x.kind == kind) {
self.items.remove(idx); self.items.remove(idx);
@ -256,7 +276,7 @@ impl PartialOrd for TypedKeyPair {
impl Ord for TypedKeyPair { impl Ord for TypedKeyPair {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
let x = compare_crypto_kind(self.kind, other.kind); let x = compare_crypto_kind(&self.kind, &other.kind);
if x != cmp::Ordering::Equal { if x != cmp::Ordering::Equal {
return x; return x;
} }
@ -340,7 +360,7 @@ impl PartialOrd for TypedSignature {
impl Ord for TypedSignature { impl Ord for TypedSignature {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
let x = compare_crypto_kind(self.kind, other.kind); let x = compare_crypto_kind(&self.kind, &other.kind);
if x != cmp::Ordering::Equal { if x != cmp::Ordering::Equal {
return x; return x;
} }
@ -410,7 +430,7 @@ impl PartialOrd for TypedKeySignature {
impl Ord for TypedKeySignature { impl Ord for TypedKeySignature {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
let x = compare_crypto_kind(self.kind, other.kind); let x = compare_crypto_kind(&self.kind, &other.kind);
if x != cmp::Ordering::Equal { if x != cmp::Ordering::Equal {
return x; return x;
} }

View File

@ -1327,7 +1327,7 @@ impl NetworkManager {
} }
// Is this an out-of-band receipt instead of an envelope? // Is this an out-of-band receipt instead of an envelope?
if data[0..4] == *RECEIPT_MAGIC { if data[0..3] == *RECEIPT_MAGIC {
network_result_value_or_log!(self.handle_out_of_band_receipt(data).await => {}); network_result_value_or_log!(self.handle_out_of_band_receipt(data).await => {});
return Ok(true); return Ok(true);
} }

View File

@ -11,19 +11,19 @@ pub struct Bucket {
/// handle to the routing table /// handle to the routing table
routing_table: RoutingTable, routing_table: RoutingTable,
/// Map of keys to entries for this bucket /// Map of keys to entries for this bucket
entries: BTreeMap<TypedKey, Arc<BucketEntry>>, entries: BTreeMap<PublicKey, Arc<BucketEntry>>,
/// The most recent entry in this bucket /// The most recent entry in this bucket
newest_entry: Option<TypedKey>, newest_entry: Option<PublicKey>,
/// The crypto kind in use for the public keys in this bucket /// The crypto kind in use for the public keys in this bucket
kind: CryptoKind, kind: CryptoKind,
} }
pub(super) type EntriesIter<'a> = pub(super) type EntriesIter<'a> =
alloc::collections::btree_map::Iter<'a, TypedKey, Arc<BucketEntry>>; alloc::collections::btree_map::Iter<'a, PublicKey, Arc<BucketEntry>>;
#[derive(Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)] #[derive(Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
#[archive_attr(repr(C), derive(CheckBytes))] #[archive_attr(repr(C), derive(CheckBytes))]
struct SerializedBucketEntryData { struct SerializedBucketEntryData {
key: TypedKey, key: PublicKey,
value: u32, // index into serialized entries list value: u32, // index into serialized entries list
} }
@ -31,7 +31,7 @@ struct SerializedBucketEntryData {
#[archive_attr(repr(C), derive(CheckBytes))] #[archive_attr(repr(C), derive(CheckBytes))]
struct SerializedBucketData { struct SerializedBucketData {
entries: Vec<SerializedBucketEntryData>, entries: Vec<SerializedBucketEntryData>,
newest_entry: Option<TypedKey>, newest_entry: Option<PublicKey>,
} }
fn state_ordering(state: BucketEntryState) -> usize { fn state_ordering(state: BucketEntryState) -> usize {
@ -95,49 +95,45 @@ impl Bucket {
} }
/// Create a new entry with a node_id of this crypto kind and return it /// Create a new entry with a node_id of this crypto kind and return it
pub(super) fn add_entry(&mut self, node_id: TypedKey) -> NodeRef { pub(super) fn add_entry(&mut self, node_id_key: PublicKey) -> NodeRef {
assert_eq!(node_id.kind, self.kind); log_rtab!("Node added: {}:{}", self.kind, node_id_key);
log_rtab!("Node added: {}", node_id);
// Add new entry // Add new entry
let entry = Arc::new(BucketEntry::new(node_id)); let entry = Arc::new(BucketEntry::new(TypedKey::new(self.kind, node_id_key)));
self.entries.insert(node_id.key, entry.clone()); self.entries.insert(node_id_key, entry.clone());
// This is now the newest bucket entry // This is now the newest bucket entry
self.newest_entry = Some(node_id.key); self.newest_entry = Some(node_id_key);
// Get a node ref to return since this is new // Get a node ref to return since this is new
NodeRef::new(self.routing_table.clone(), entry, None) NodeRef::new(self.routing_table.clone(), entry, None)
} }
/// Add an existing entry with a new node_id for this crypto kind /// Add an existing entry with a new node_id for this crypto kind
pub(super) fn add_existing_entry(&mut self, node_id: TypedKey, entry: Arc<BucketEntry>) { pub(super) fn add_existing_entry(&mut self, node_id_key: PublicKey, entry: Arc<BucketEntry>) {
assert_eq!(node_id.kind, self.kind); log_rtab!("Existing node added: {}:{}", self.kind, node_id_key);
log_rtab!("Existing node added: {}", node_id);
// Add existing entry // Add existing entry
entry.with_mut_inner(|e| e.add_node_id(node_id)); entry.with_mut_inner(|e| e.add_node_id(TypedKey::new(self.kind, node_id_key)));
self.entries.insert(node_id.key, entry); self.entries.insert(node_id_key, entry);
// This is now the newest bucket entry // This is now the newest bucket entry
self.newest_entry = Some(node_id.key); self.newest_entry = Some(node_id_key);
// No need to return a noderef here because the noderef will already exist in the caller // No need to return a noderef here because the noderef will already exist in the caller
} }
/// Remove an entry with a node_id for this crypto kind from the bucket /// Remove an entry with a node_id for this crypto kind from the bucket
fn remove_entry(&mut self, node_id: &TypedKey) { fn remove_entry(&mut self, node_id_key: &PublicKey) {
log_rtab!("Node removed: {}:{}", self.kind, node_id); log_rtab!("Node removed: {}:{}", self.kind, node_id_key);
// Remove the entry // Remove the entry
self.entries.remove(node_id); self.entries.remove(node_id_key);
// newest_entry is updated by kick_bucket() // newest_entry is updated by kick_bucket()
} }
pub(super) fn entry(&self, key: &TypedKey) -> Option<Arc<BucketEntry>> { pub(super) fn entry(&self, key: &PublicKey) -> Option<Arc<BucketEntry>> {
self.entries.get(key).cloned() self.entries.get(key).cloned()
} }
@ -145,7 +141,7 @@ impl Bucket {
self.entries.iter() self.entries.iter()
} }
pub(super) fn kick(&mut self, bucket_depth: usize) -> Option<BTreeSet<TypedKey>> { pub(super) fn kick(&mut self, bucket_depth: usize) -> Option<BTreeSet<PublicKey>> {
// Get number of entries to attempt to purge from bucket // Get number of entries to attempt to purge from bucket
let bucket_len = self.entries.len(); let bucket_len = self.entries.len();
@ -155,11 +151,11 @@ impl Bucket {
} }
// Try to purge the newest entries that overflow the bucket // Try to purge the newest entries that overflow the bucket
let mut dead_node_ids: BTreeSet<TypedKey> = BTreeSet::new(); let mut dead_node_ids: BTreeSet<PublicKey> = BTreeSet::new();
let mut extra_entries = bucket_len - bucket_depth; let mut extra_entries = bucket_len - bucket_depth;
// Get the sorted list of entries by their kick order // Get the sorted list of entries by their kick order
let mut sorted_entries: Vec<(TypedKey, Arc<BucketEntry>)> = self let mut sorted_entries: Vec<(PublicKey, Arc<BucketEntry>)> = self
.entries .entries
.iter() .iter()
.map(|(k, v)| (k.clone(), v.clone())) .map(|(k, v)| (k.clone(), v.clone()))

View File

@ -7,7 +7,7 @@ impl RoutingTable {
let inner = self.inner.read(); let inner = self.inner.read();
out += "Routing Table Info:\n"; out += "Routing Table Info:\n";
out += &format!(" Node Id: {}\n", self.unlocked_inner.node_id.encode()); out += &format!(" Node Ids: {}\n", self.unlocked_inner.node_ids());
out += &format!( out += &format!(
" Self Latency Stats Accounting: {:#?}\n\n", " Self Latency Stats Accounting: {:#?}\n\n",
inner.self_latency_stats_accounting inner.self_latency_stats_accounting
@ -55,13 +55,20 @@ impl RoutingTable {
short_urls.sort(); short_urls.sort();
short_urls.dedup(); short_urls.dedup();
let valid_envelope_versions = VALID_ENVELOPE_VERSIONS.map(|x| x.to_string()).join(",");
let node_ids = self
.unlocked_inner
.node_ids()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join(",");
out += "TXT Record:\n"; out += "TXT Record:\n";
out += &format!( out += &format!(
"{},{},{},{},{}", "{}|{}|{}|{}|",
BOOTSTRAP_TXT_VERSION, BOOTSTRAP_TXT_VERSION,
MIN_CRYPTO_VERSION, valid_envelope_versions,
MAX_CRYPTO_VERSION, node_ids,
self.node_id().encode(),
some_hostname.unwrap() some_hostname.unwrap()
); );
for short_url in short_urls { for short_url in short_urls {

View File

@ -111,7 +111,15 @@ impl RoutingTableUnlockedInner {
} }
pub fn node_id(&self, kind: CryptoKind) -> TypedKey { pub fn node_id(&self, kind: CryptoKind) -> TypedKey {
self.node_id_keypairs.get(&kind).unwrap().key TypedKey::new(kind, self.node_id_keypairs.get(&kind).unwrap().key)
}
pub fn node_ids(&self) -> TypedKeySet {
let mut tks = TypedKeySet::new();
for x in &self.node_id_keypairs {
tks.add(TypedKey::new(*x.0, x.1.key));
}
tks
} }
pub fn node_id_secret(&self, kind: CryptoKind) -> SecretKey { pub fn node_id_secret(&self, kind: CryptoKind) -> SecretKey {
@ -890,6 +898,7 @@ impl RoutingTable {
pub fn find_closest_nodes<'a, T, O>( pub fn find_closest_nodes<'a, T, O>(
&self, &self,
node_count: usize,
node_id: TypedKey, node_id: TypedKey,
filters: VecDeque<RoutingTableEntryFilter>, filters: VecDeque<RoutingTableEntryFilter>,
transform: T, transform: T,
@ -899,7 +908,7 @@ impl RoutingTable {
{ {
self.inner self.inner
.read() .read()
.find_closest_nodes(node_id, filters, transform) .find_closest_nodes(node_count, node_id, filters, transform)
} }
#[instrument(level = "trace", skip(self), ret)] #[instrument(level = "trace", skip(self), ret)]
@ -940,14 +949,14 @@ impl RoutingTable {
#[instrument(level = "trace", skip(self), ret, err)] #[instrument(level = "trace", skip(self), ret, err)]
pub async fn find_self(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> { pub async fn find_self(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
let node_id = self.node_id(); let self_node_id = self.node_id();
self.find_node(node_ref, node_id).await self.find_node(node_ref, self_node_id).await
} }
#[instrument(level = "trace", skip(self), ret, err)] #[instrument(level = "trace", skip(self), ret, err)]
pub async fn find_target(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> { pub async fn find_target(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
let node_id = node_ref.node_id(); let target_node_id = node_ref.node_id();
self.find_node(node_ref, node_id).await self.find_node(node_ref, target_node_id).await
} }
#[instrument(level = "trace", skip(self))] #[instrument(level = "trace", skip(self))]
@ -1047,12 +1056,12 @@ impl RoutingTable {
// Go through all entries and find fastest entry that matches filter function // Go through all entries and find fastest entry that matches filter function
let inner = self.inner.read(); let inner = self.inner.read();
let inner = &*inner; let inner = &*inner;
let mut best_inbound_relay: Option<(TypedKey, Arc<BucketEntry>)> = None; let mut best_inbound_relay: Option<Arc<BucketEntry>> = None;
// Iterate all known nodes for candidates // Iterate all known nodes for candidates
inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| { inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, entry| {
let v2 = v.clone(); let entry2 = entry.clone();
v.with(rti, |rti, e| { entry.with(rti, |rti, e| {
// Ensure we have the node's status // Ensure we have the node's status
if let Some(node_status) = e.node_status(routing_domain) { if let Some(node_status) = e.node_status(routing_domain) {
// Ensure the node will relay // Ensure the node will relay
@ -1060,18 +1069,18 @@ impl RoutingTable {
// Compare against previous candidate // Compare against previous candidate
if let Some(best_inbound_relay) = best_inbound_relay.as_mut() { if let Some(best_inbound_relay) = best_inbound_relay.as_mut() {
// Less is faster // Less is faster
let better = best_inbound_relay.1.with(rti, |_rti, best| { let better = best_inbound_relay.with(rti, |_rti, best| {
// choose low latency stability for relays // choose low latency stability for relays
BucketEntryInner::cmp_fastest_reliable(cur_ts, e, best) BucketEntryInner::cmp_fastest_reliable(cur_ts, e, best)
== std::cmp::Ordering::Less == std::cmp::Ordering::Less
}); });
// Now apply filter function and see if this node should be included // Now apply filter function and see if this node should be included
if better && relay_node_filter(e) { if better && relay_node_filter(e) {
*best_inbound_relay = (k, v2); *best_inbound_relay = entry2;
} }
} else if relay_node_filter(e) { } else if relay_node_filter(e) {
// Always store the first candidate // Always store the first candidate
best_inbound_relay = Some((k, v2)); best_inbound_relay = Some(entry2);
} }
} }
} }
@ -1080,6 +1089,6 @@ impl RoutingTable {
Option::<()>::None Option::<()>::None
}); });
// Return the best inbound relay noderef // Return the best inbound relay noderef
best_inbound_relay.map(|(k, e)| NodeRef::new(self.clone(), e, None)) best_inbound_relay.map(|e| NodeRef::new(self.clone(), e, None))
} }
} }

View File

@ -1,4 +1,5 @@
use super::*; use super::*;
use weak_table::PtrWeakHashSet;
const RECENT_PEERS_TABLE_SIZE: usize = 64; const RECENT_PEERS_TABLE_SIZE: usize = 64;
@ -15,8 +16,8 @@ pub struct RoutingTableInner {
pub(super) unlocked_inner: Arc<RoutingTableUnlockedInner>, pub(super) unlocked_inner: Arc<RoutingTableUnlockedInner>,
/// Routing table buckets that hold references to entries, per crypto kind /// Routing table buckets that hold references to entries, per crypto kind
pub(super) buckets: BTreeMap<CryptoKind, Vec<Bucket>>, pub(super) buckets: BTreeMap<CryptoKind, Vec<Bucket>>,
/// A fast counter for the number of entries in the table, total /// A weak set of all the entries we have in the buckets for faster iteration
pub(super) bucket_entry_count: usize, pub(super) all_entries: PtrWeakHashSet<Weak<BucketEntry>>,
/// The public internet routing domain /// The public internet routing domain
pub(super) public_internet_routing_domain: PublicInternetRoutingDomainDetail, pub(super) public_internet_routing_domain: PublicInternetRoutingDomainDetail,
/// The dial info we use on the local network /// The dial info we use on the local network
@ -40,7 +41,7 @@ impl RoutingTableInner {
buckets: BTreeMap::new(), buckets: BTreeMap::new(),
public_internet_routing_domain: PublicInternetRoutingDomainDetail::default(), public_internet_routing_domain: PublicInternetRoutingDomainDetail::default(),
local_network_routing_domain: LocalNetworkRoutingDomainDetail::default(), local_network_routing_domain: LocalNetworkRoutingDomainDetail::default(),
bucket_entry_count: 0, all_entries: PtrWeakHashSet::new(),
self_latency_stats_accounting: LatencyStatsAccounting::new(), self_latency_stats_accounting: LatencyStatsAccounting::new(),
self_transfer_stats_accounting: TransferStatsAccounting::new(), self_transfer_stats_accounting: TransferStatsAccounting::new(),
self_transfer_stats: TransferStatsDownUp::default(), self_transfer_stats: TransferStatsDownUp::default(),
@ -49,6 +50,10 @@ impl RoutingTableInner {
} }
} }
pub fn bucket_entry_count(&self) -> usize {
self.all_entries.len()
}
pub fn transfer_stats_accounting(&mut self) -> &mut TransferStatsAccounting { pub fn transfer_stats_accounting(&mut self) -> &mut TransferStatsAccounting {
&mut self.self_transfer_stats_accounting &mut self.self_transfer_stats_accounting
} }
@ -209,7 +214,7 @@ impl RoutingTableInner {
pub fn reset_all_updated_since_last_network_change(&mut self) { pub fn reset_all_updated_since_last_network_change(&mut self) {
let cur_ts = get_aligned_timestamp(); let cur_ts = get_aligned_timestamp();
self.with_entries_mut(cur_ts, BucketEntryState::Dead, |rti, _, v| { self.with_entries_mut(cur_ts, BucketEntryState::Dead, |rti, v| {
v.with_mut(rti, |_rti, e| { v.with_mut(rti, |_rti, e| {
e.set_updated_since_last_network_change(false) e.set_updated_since_last_network_change(false)
}); });
@ -310,7 +315,7 @@ impl RoutingTableInner {
for ck in VALID_CRYPTO_KINDS { for ck in VALID_CRYPTO_KINDS {
let ckbuckets = Vec::with_capacity(PUBLIC_KEY_LENGTH * 8); let ckbuckets = Vec::with_capacity(PUBLIC_KEY_LENGTH * 8);
for _ in 0..PUBLIC_KEY_LENGTH * 8 { for _ in 0..PUBLIC_KEY_LENGTH * 8 {
let bucket = Bucket::new(routing_table.clone()); let bucket = Bucket::new(routing_table.clone(), ck);
ckbuckets.push(bucket); ckbuckets.push(bucket);
} }
self.buckets.insert(ck, ckbuckets); self.buckets.insert(ck, ckbuckets);
@ -345,14 +350,18 @@ impl RoutingTableInner {
pub fn purge_buckets(&mut self) { pub fn purge_buckets(&mut self) {
log_rtab!( log_rtab!(
"Starting routing table buckets purge. Table currently has {} nodes", "Starting routing table buckets purge. Table currently has {} nodes",
self.bucket_entry_count self.bucket_entry_count()
); );
for bucket in &mut self.buckets { for ck in VALID_CRYPTO_KINDS {
bucket.kick(0); for bucket in &mut self.buckets[&ck] {
bucket.kick(0);
}
} }
self.all_entries.remove_expired();
log_rtab!(debug log_rtab!(debug
"Routing table buckets purge complete. Routing table now has {} nodes", "Routing table buckets purge complete. Routing table now has {} nodes",
self.bucket_entry_count self.bucket_entry_count()
); );
} }
@ -360,32 +369,36 @@ impl RoutingTableInner {
pub fn purge_last_connections(&mut self) { pub fn purge_last_connections(&mut self) {
log_rtab!( log_rtab!(
"Starting routing table last_connections purge. Table currently has {} nodes", "Starting routing table last_connections purge. Table currently has {} nodes",
self.bucket_entry_count self.bucket_entry_count()
); );
for bucket in &self.buckets { for ck in VALID_CRYPTO_KINDS {
for entry in bucket.entries() { for bucket in &self.buckets[&ck] {
entry.1.with_mut_inner(|e| { for entry in bucket.entries() {
e.clear_last_connections(); entry.1.with_mut_inner(|e| {
}); e.clear_last_connections();
});
}
} }
} }
self.all_entries.remove_expired();
log_rtab!(debug log_rtab!(debug
"Routing table last_connections purge complete. Routing table now has {} nodes", "Routing table last_connections purge complete. Routing table now has {} nodes",
self.bucket_entry_count self.bucket_entry_count()
); );
} }
/// Attempt to settle buckets and remove entries down to the desired number /// Attempt to settle buckets and remove entries down to the desired number
/// which may not be possible due extant NodeRefs /// which may not be possible due extant NodeRefs
pub fn kick_bucket(&mut self, idx: usize) { pub fn kick_bucket(&mut self, kind: CryptoKind, idx: usize) {
let bucket = &mut self.buckets[idx]; let bucket = &mut self.buckets[&kind][idx];
let bucket_depth = Self::bucket_depth(idx); let bucket_depth = Self::bucket_depth(idx);
if let Some(dead_node_ids) = bucket.kick(bucket_depth) { if let Some(dead_node_ids) = bucket.kick(bucket_depth) {
// Remove counts // Remove expired entries
self.bucket_entry_count -= dead_node_ids.len(); self.all_entries.remove_expired();
log_rtab!(debug "Routing table now has {} nodes", self.bucket_entry_count);
log_rtab!(debug "Bucket {}:{} kicked Routing table now has {} nodes", kind, idx, self.bucket_entry_count());
// Now purge the routing table inner vectors // Now purge the routing table inner vectors
//let filter = |k: &DHTKey| dead_node_ids.contains(k); //let filter = |k: &DHTKey| dead_node_ids.contains(k);
@ -403,7 +416,7 @@ impl RoutingTableInner {
) -> usize { ) -> usize {
let mut count = 0usize; let mut count = 0usize;
let cur_ts = get_aligned_timestamp(); let cur_ts = get_aligned_timestamp();
self.with_entries(cur_ts, min_state, |rti, _, e| { self.with_entries(cur_ts, min_state, |rti, e| {
if e.with(rti, |rti, e| e.best_routing_domain(rti, routing_domain_set)) if e.with(rti, |rti, e| e.best_routing_domain(rti, routing_domain_set))
.is_some() .is_some()
{ {
@ -414,54 +427,36 @@ impl RoutingTableInner {
count count
} }
pub fn with_entries< pub fn with_entries<T, F: FnMut(&RoutingTableInner, Arc<BucketEntry>) -> Option<T>>(
T,
F: FnMut(&RoutingTableInner, TypedKey, Arc<BucketEntry>) -> Option<T>,
>(
&self, &self,
cur_ts: Timestamp, cur_ts: Timestamp,
min_state: BucketEntryState, min_state: BucketEntryState,
mut f: F, mut f: F,
) -> Option<T> { ) -> Option<T> {
let mut entryvec = Vec::with_capacity(self.bucket_entry_count); for entry in self.all_entries {
for bucket in &self.buckets { if entry.with(self, |_rti, e| e.state(cur_ts) >= min_state) {
for entry in bucket.entries() { if let Some(out) = f(self, entry) {
if entry.1.with(self, |_rti, e| e.state(cur_ts) >= min_state) { return Some(out);
entryvec.push((*entry.0, entry.1.clone()));
} }
} }
} }
for entry in entryvec {
if let Some(out) = f(self, entry.0, entry.1) {
return Some(out);
}
}
None None
} }
pub fn with_entries_mut< pub fn with_entries_mut<T, F: FnMut(&mut RoutingTableInner, Arc<BucketEntry>) -> Option<T>>(
T,
F: FnMut(&mut RoutingTableInner, TypedKey, Arc<BucketEntry>) -> Option<T>,
>(
&mut self, &mut self,
cur_ts: Timestamp, cur_ts: Timestamp,
min_state: BucketEntryState, min_state: BucketEntryState,
mut f: F, mut f: F,
) -> Option<T> { ) -> Option<T> {
let mut entryvec = Vec::with_capacity(self.bucket_entry_count); for entry in self.all_entries {
for bucket in &self.buckets { if entry.with(self, |_rti, e| e.state(cur_ts) >= min_state) {
for entry in bucket.entries() { if let Some(out) = f(self, entry) {
if entry.1.with(self, |_rti, e| e.state(cur_ts) >= min_state) { return Some(out);
entryvec.push((*entry.0, entry.1.clone()));
} }
} }
} }
for entry in entryvec {
if let Some(out) = f(self, entry.0, entry.1) {
return Some(out);
}
}
None None
} }
@ -728,18 +723,16 @@ impl RoutingTableInner {
let mut dead_entry_count: usize = 0; let mut dead_entry_count: usize = 0;
let cur_ts = get_aligned_timestamp(); let cur_ts = get_aligned_timestamp();
for bucket in &self.buckets { for entry in self.all_entries {
for (_, v) in bucket.entries() { match entry.with(self, |_rti, e| e.state(cur_ts)) {
match v.with(self, |_rti, e| e.state(cur_ts)) { BucketEntryState::Reliable => {
BucketEntryState::Reliable => { reliable_entry_count += 1;
reliable_entry_count += 1; }
} BucketEntryState::Unreliable => {
BucketEntryState::Unreliable => { unreliable_entry_count += 1;
unreliable_entry_count += 1; }
} BucketEntryState::Dead => {
BucketEntryState::Dead => { dead_entry_count += 1;
dead_entry_count += 1;
}
} }
} }
} }
@ -798,7 +791,7 @@ impl RoutingTableInner {
self.find_fastest_nodes( self.find_fastest_nodes(
node_count, node_count,
filters, filters,
|_rti: &RoutingTableInner, k: TypedKey, v: Option<Arc<BucketEntry>>| { |_rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| {
NodeRef::new(outer_self.clone(), v.unwrap().clone(), None) NodeRef::new(outer_self.clone(), v.unwrap().clone(), None)
}, },
) )
@ -846,30 +839,30 @@ impl RoutingTableInner {
&'b Option<Arc<BucketEntry>>, &'b Option<Arc<BucketEntry>>,
&'b Option<Arc<BucketEntry>>, &'b Option<Arc<BucketEntry>>,
) -> core::cmp::Ordering, ) -> core::cmp::Ordering,
T: for<'r> FnMut(&'r RoutingTableInner, TypedKey, Option<Arc<BucketEntry>>) -> O, T: for<'r> FnMut(&'r RoutingTableInner, Option<Arc<BucketEntry>>) -> O,
{ {
// collect all the nodes for sorting // collect all the nodes for sorting
let mut nodes = let mut nodes =
Vec::<(TypedKey, Option<Arc<BucketEntry>>)>::with_capacity(self.bucket_entry_count + 1); Vec::<Option<Arc<BucketEntry>>>::with_capacity(self.bucket_entry_count() + 1);
// add our own node (only one of there with the None entry) // add our own node (only one of there with the None entry)
let mut filtered = false; let mut filtered = false;
for filter in &mut filters { for filter in &mut filters {
if !filter(self, self.unlocked_inner.node_id, None) { if !filter(self, None) {
filtered = true; filtered = true;
break; break;
} }
} }
if !filtered { if !filtered {
nodes.push((self.unlocked_inner.node_id, None)); nodes.push(None);
} }
// add all nodes from buckets // add all nodes that match filter
self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| { self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, v| {
// Apply filter // Apply filter
for filter in &mut filters { for filter in &mut filters {
if filter(rti, k, Some(v.clone())) { if filter(rti, Some(v.clone())) {
nodes.push((k, Some(v.clone()))); nodes.push(Some(v.clone()));
break; break;
} }
} }
@ -883,7 +876,7 @@ impl RoutingTableInner {
let cnt = usize::min(node_count, nodes.len()); let cnt = usize::min(node_count, nodes.len());
let mut out = Vec::<O>::with_capacity(cnt); let mut out = Vec::<O>::with_capacity(cnt);
for node in nodes { for node in nodes {
let val = transform(self, node.0, node.1); let val = transform(self, node);
out.push(val); out.push(val);
} }
@ -921,12 +914,19 @@ impl RoutingTableInner {
// Fastest sort // Fastest sort
let sort = |rti: &RoutingTableInner, let sort = |rti: &RoutingTableInner,
(a_key, a_entry): &(TypedKey, Option<Arc<BucketEntry>>), a_entry: &Option<Arc<BucketEntry>>,
(b_key, b_entry): &(TypedKey, Option<Arc<BucketEntry>>)| { b_entry: &Option<Arc<BucketEntry>>| {
// same nodes are always the same // same nodes are always the same
if a_key == b_key { if let Some(a_entry) = a_entry {
if let Some(b_entry) = b_entry {
if Arc::ptr_eq(&a_entry, &b_entry) {
return core::cmp::Ordering::Equal;
}
}
} else if b_entry.is_none() {
return core::cmp::Ordering::Equal; return core::cmp::Ordering::Equal;
} }
// our own node always comes last (should not happen, here for completeness) // our own node always comes last (should not happen, here for completeness)
if a_entry.is_none() { if a_entry.is_none() {
return core::cmp::Ordering::Greater; return core::cmp::Ordering::Greater;
@ -977,26 +977,28 @@ impl RoutingTableInner {
pub fn find_closest_nodes<T, O>( pub fn find_closest_nodes<T, O>(
&self, &self,
node_count: usize,
node_id: TypedKey, node_id: TypedKey,
filters: VecDeque<RoutingTableEntryFilter>, filters: VecDeque<RoutingTableEntryFilter>,
transform: T, transform: T,
) -> Vec<O> ) -> Vec<O>
where where
T: for<'r> FnMut(&'r RoutingTableInner, TypedKey, Option<Arc<BucketEntry>>) -> O, T: for<'r> FnMut(&'r RoutingTableInner, Option<Arc<BucketEntry>>) -> O,
{ {
let cur_ts = get_aligned_timestamp(); let cur_ts = get_aligned_timestamp();
let node_count = {
let config = self.config();
let c = config.get();
c.network.dht.max_find_node_count as usize
};
// closest sort // closest sort
let sort = |rti: &RoutingTableInner, let sort = |rti: &RoutingTableInner,
(a_key, a_entry): &(TypedKey, Option<Arc<BucketEntry>>), a_entry: &Option<Arc<BucketEntry>>,
(b_key, b_entry): &(TypedKey, Option<Arc<BucketEntry>>)| { b_entry: &Option<Arc<BucketEntry>>| {
// same nodes are always the same // same nodes are always the same
if a_key == b_key { if let Some(a_entry) = a_entry {
if let Some(b_entry) = b_entry {
if Arc::ptr_eq(&a_entry, &b_entry) {
return core::cmp::Ordering::Equal;
}
}
} else if b_entry.is_none() {
return core::cmp::Ordering::Equal; return core::cmp::Ordering::Equal;
} }

View File

@ -3,23 +3,122 @@ use super::*;
use futures_util::stream::{FuturesUnordered, StreamExt}; use futures_util::stream::{FuturesUnordered, StreamExt};
use stop_token::future::FutureExt as StopFutureExt; use stop_token::future::FutureExt as StopFutureExt;
pub const BOOTSTRAP_TXT_VERSION: u8 = 0; pub const BOOTSTRAP_TXT_VERSION_0: u8 = 0;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BootstrapRecord { pub struct BootstrapRecord {
min_version: u8, node_ids: TypedKeySet,
max_version: u8, envelope_support: Vec<u8>,
dial_info_details: Vec<DialInfoDetail>, dial_info_details: Vec<DialInfoDetail>,
} }
pub type BootstrapRecordMap = BTreeMap<PublicKey, BootstrapRecord>; impl BootstrapRecord {
pub fn merge(&mut self, other: &BootstrapRecord) {
self.node_ids.add_all(&other.node_ids);
for x in other.envelope_support {
if !self.envelope_support.contains(&x) {
self.envelope_support.push(x);
self.envelope_support.sort();
}
}
for did in &other.dial_info_details {
if !self.dial_info_details.contains(did) {
self.dial_info_details.push(did.clone());
}
}
}
}
impl RoutingTable { impl RoutingTable {
/// Process bootstrap version 0
async fn process_bootstrap_records_v0(
&self,
records: Vec<String>,
) -> EyreResult<Option<BootstrapRecord>> {
// Bootstrap TXT Record Format Version 0:
// txt_version|envelope_support|node_ids|hostname|dialinfoshort*
//
// Split bootstrap node record by '|' and then lists by ','. Example:
// 0|0|VLD0:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ|bootstrap-1.dev.veilid.net|T5150,U5150,W5150/ws
if records.len() != 5 {
bail!("invalid number of fields in bootstrap v0 txt record");
}
// Envelope support
let mut envelope_support = Vec::new();
for ess in records[1].split(",") {
let ess = ess.trim();
let es = match records[1].parse::<u8>() {
Ok(v) => v,
Err(e) => {
bail!(
"invalid envelope version specified in bootstrap node txt record: {}",
e
);
}
};
envelope_support.push(es);
}
envelope_support.dedup();
envelope_support.sort();
// Node Id
let node_ids = TypedKeySet::new();
for node_id_str in records[2].split(",") {
let node_id_str = node_id_str.trim();
let node_id = match TypedKey::from_str(&node_id_str) {
Ok(v) => v,
Err(e) => {
bail!(
"Invalid node id in bootstrap node record {}: {}",
node_id_str,
e
);
}
};
}
// If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node
if self.unlocked_inner.matches_own_node_id(&node_ids) {
return Ok(None);
}
// Hostname
let hostname_str = records[3].trim();
// Resolve each record and store in node dial infos list
let mut dial_info_details = Vec::new();
for rec in records[4].split(",") {
let rec = rec.trim();
let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) {
Ok(dis) => dis,
Err(e) => {
warn!("Couldn't resolve bootstrap node dial info {}: {}", rec, e);
continue;
}
};
for di in dial_infos {
dial_info_details.push(DialInfoDetail {
dial_info: di,
class: DialInfoClass::Direct,
});
}
}
Ok(Some(BootstrapRecord {
node_ids,
envelope_support,
dial_info_details,
}))
}
// Bootstrap lookup process // Bootstrap lookup process
#[instrument(level = "trace", skip(self), ret, err)] #[instrument(level = "trace", skip(self), ret, err)]
pub(crate) async fn resolve_bootstrap( pub(crate) async fn resolve_bootstrap(
&self, &self,
bootstrap: Vec<String>, bootstrap: Vec<String>,
) -> EyreResult<BootstrapRecordMap> { ) -> EyreResult<Vec<BootstrapRecord>> {
// Resolve from bootstrap root to bootstrap hostnames // Resolve from bootstrap root to bootstrap hostnames
let mut bsnames = Vec::<String>::new(); let mut bsnames = Vec::<String>::new();
for bh in bootstrap { for bh in bootstrap {
@ -58,22 +157,14 @@ impl RoutingTable {
Ok(v) => v, Ok(v) => v,
}; };
// for each record resolve into key/bootstraprecord pairs // for each record resolve into key/bootstraprecord pairs
let mut bootstrap_records: Vec<(PublicKey, BootstrapRecord)> = Vec::new(); let mut bootstrap_records: Vec<BootstrapRecord> = Vec::new();
for bsnirecord in bsnirecords { for bsnirecord in bsnirecords {
// Bootstrap TXT Record Format Version 0: // All formats split on '|' character
// txt_version,min_version,max_version,nodeid,hostname,dialinfoshort*
//
// Split bootstrap node record by commas. Example:
// 0,0,0,7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ,bootstrap-1.dev.veilid.net,T5150,U5150,W5150/ws
let records: Vec<String> = bsnirecord let records: Vec<String> = bsnirecord
.trim() .trim()
.split(',') .split('|')
.map(|x| x.trim().to_owned()) .map(|x| x.trim().to_owned())
.collect(); .collect();
if records.len() < 6 {
warn!("invalid number of fields in bootstrap txt record");
continue;
}
// Bootstrap TXT record version // Bootstrap TXT record version
let txt_version: u8 = match records[0].parse::<u8>() { let txt_version: u8 = match records[0].parse::<u8>() {
@ -86,81 +177,30 @@ impl RoutingTable {
continue; continue;
} }
}; };
if txt_version != BOOTSTRAP_TXT_VERSION { let bootstrap_record = match txt_version {
warn!("unsupported bootstrap txt record version"); BOOTSTRAP_TXT_VERSION_0 => {
continue; match self.process_bootstrap_records_v0(records).await {
} Err(e) => {
warn!(
// Min/Max wire protocol version "couldn't process v0 bootstrap records from {}: {}",
let min_version: u8 = match records[1].parse::<u8>() { bsname, e
Ok(v) => v, );
Err(e) => { continue;
warn!( }
"invalid min_version specified in bootstrap node txt record: {}", Ok(Some(v)) => v,
e Ok(None) => {
); // skipping
continue; continue;
} }
};
let max_version: u8 = match records[2].parse::<u8>() {
Ok(v) => v,
Err(e) => {
warn!(
"invalid max_version specified in bootstrap node txt record: {}",
e
);
continue;
}
};
// Node Id
let node_id_str = &records[3];
let node_id_key = match PublicKey::try_decode(node_id_str) {
Ok(v) => v,
Err(e) => {
warn!(
"Invalid node id in bootstrap node record {}: {}",
node_id_str, e
);
continue;
}
};
// Hostname
let hostname_str = &records[4];
// If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node
if self.node_id() == node_id_key {
continue;
}
// Resolve each record and store in node dial infos list
let mut bootstrap_record = BootstrapRecord {
min_version,
max_version,
dial_info_details: Vec::new(),
};
for rec in &records[5..] {
let rec = rec.trim();
let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) {
Ok(dis) => dis,
Err(e) => {
warn!(
"Couldn't resolve bootstrap node dial info {}: {}",
rec, e
);
continue;
} }
};
for di in dial_infos {
bootstrap_record.dial_info_details.push(DialInfoDetail {
dial_info: di,
class: DialInfoClass::Direct,
});
} }
} _ => {
bootstrap_records.push((node_id_key, bootstrap_record)); warn!("unsupported bootstrap txt record version");
continue;
}
};
bootstrap_records.push(bootstrap_record);
} }
Some(bootstrap_records) Some(bootstrap_records)
} }
@ -168,21 +208,35 @@ impl RoutingTable {
); );
} }
let mut bsmap = BootstrapRecordMap::new(); let mut merged_bootstrap_records: Vec<BootstrapRecord> = Vec::new();
while let Some(bootstrap_records) = unord.next().await { while let Some(bootstrap_records) = unord.next().await {
if let Some(bootstrap_records) = bootstrap_records { let Some(bootstrap_records) = bootstrap_records else {
for (bskey, mut bsrec) in bootstrap_records { continue;
let rec = bsmap.entry(bskey).or_insert_with(|| BootstrapRecord { };
min_version: bsrec.min_version, for mut bsrec in bootstrap_records {
max_version: bsrec.max_version, let mut mbi = 0;
dial_info_details: Vec::new(), while mbi < merged_bootstrap_records.len() {
}); let mbr = &mut merged_bootstrap_records[mbi];
rec.dial_info_details.append(&mut bsrec.dial_info_details); if mbr.node_ids.contains_any(&bsrec.node_ids) {
// Merge record, pop this one out
let mbr = merged_bootstrap_records.remove(mbi);
bsrec.merge(&mbr);
} else {
// No overlap, go to next record
mbi += 1;
}
} }
// Append merged record
merged_bootstrap_records.push(bsrec);
} }
} }
Ok(bsmap) // ensure dial infos are sorted
for mbr in &mut merged_bootstrap_records {
mbr.dial_info_details.sort();
}
Ok(merged_bootstrap_records)
} }
// 'direct' bootstrap task routine for systems incapable of resolving TXT records, such as browser WASM // 'direct' bootstrap task routine for systems incapable of resolving TXT records, such as browser WASM
@ -203,14 +257,10 @@ impl RoutingTable {
// Got peer info, let's add it to the routing table // Got peer info, let's add it to the routing table
for pi in peer_info { for pi in peer_info {
let k = pi.node_id.key;
// Register the node // Register the node
if let Some(nr) = self.register_node_with_signed_node_info( if let Some(nr) =
RoutingDomain::PublicInternet, self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, false)
k, {
pi.signed_node_info,
false,
) {
// Add this our futures to process in parallel // Add this our futures to process in parallel
let routing_table = self.clone(); let routing_table = self.clone();
unord.push( unord.push(
@ -230,90 +280,62 @@ impl RoutingTable {
#[instrument(level = "trace", skip(self), err)] #[instrument(level = "trace", skip(self), err)]
pub(crate) async fn bootstrap_task_routine(self, stop_token: StopToken) -> EyreResult<()> { pub(crate) async fn bootstrap_task_routine(self, stop_token: StopToken) -> EyreResult<()> {
let (bootstrap, bootstrap_nodes) = self.with_config(|c| { let bootstrap = self
( .unlocked_inner
c.network.bootstrap.clone(), .with_config(|c| c.network.routing_table.bootstrap.clone());
c.network.bootstrap_nodes.clone(),
) // Don't bother if bootstraps aren't configured
}); if bootstrap.is_empty() {
return Ok(());
}
log_rtab!(debug "--- bootstrap_task"); log_rtab!(debug "--- bootstrap_task");
// See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism // See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism
if !bootstrap.is_empty() && bootstrap_nodes.is_empty() { let mut bootstrap_dialinfos = Vec::<DialInfo>::new();
let mut bootstrap_dialinfos = Vec::<DialInfo>::new(); for b in &bootstrap {
for b in &bootstrap { if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) {
if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) { for bootstrap_di in bootstrap_di_vec {
for bootstrap_di in bootstrap_di_vec { bootstrap_dialinfos.push(bootstrap_di);
bootstrap_dialinfos.push(bootstrap_di);
}
} }
} }
if bootstrap_dialinfos.len() > 0 { }
return self if bootstrap_dialinfos.len() > 0 {
.direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos) return self
.await; .direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos)
} .await;
} }
// If we aren't specifying a bootstrap node list explicitly, then pull from the bootstrap server(s) // If not direct, resolve bootstrap servers and recurse their TXT entries
let bsmap: BootstrapRecordMap = if !bootstrap_nodes.is_empty() { let bsrecs = self.resolve_bootstrap(bootstrap).await?;
let mut bsmap = BootstrapRecordMap::new();
let mut bootstrap_node_dial_infos = Vec::new();
for b in bootstrap_nodes {
let (id_str, di_str) = b
.split_once('@')
.ok_or_else(|| eyre!("Invalid node dial info in bootstrap entry"))?;
let node_id =
NodeId::from_str(id_str).wrap_err("Invalid node id in bootstrap entry")?;
let dial_info =
DialInfo::from_str(di_str).wrap_err("Invalid dial info in bootstrap entry")?;
bootstrap_node_dial_infos.push((node_id, dial_info));
}
for (node_id, dial_info) in bootstrap_node_dial_infos {
bsmap
.entry(node_id.key)
.or_insert_with(|| BootstrapRecord {
min_version: MIN_CRYPTO_VERSION,
max_version: MAX_CRYPTO_VERSION,
dial_info_details: Vec::new(),
})
.dial_info_details
.push(DialInfoDetail {
dial_info,
class: DialInfoClass::Direct, // Bootstraps are always directly reachable
});
}
bsmap
} else {
// Resolve bootstrap servers and recurse their TXT entries
self.resolve_bootstrap(bootstrap).await?
};
// Map all bootstrap entries to a single key with multiple dialinfo
// Run all bootstrap operations concurrently // Run all bootstrap operations concurrently
let mut unord = FuturesUnordered::new(); let mut unord = FuturesUnordered::new();
for (k, mut v) in bsmap { for bsrec in bsrecs {
// Sort dial info so we get the preferred order correct log_rtab!(
v.dial_info_details.sort(); "--- bootstrapping {} with {:?}",
&bsrec.node_ids,
&bsrec.dial_info_details
);
log_rtab!("--- bootstrapping {} with {:?}", k.encode(), &v); // Get crypto support from list of node ids
let crypto_support = bsrec.node_ids.kinds();
// Make invalid signed node info (no signature) // Make unsigned SignedNodeInfo
if let Some(nr) = self.register_node_with_signed_node_info( let sni = SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo {
RoutingDomain::PublicInternet, network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable
k, outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled
SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo { address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable
network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable envelope_support: bsrec.envelope_support, // Envelope support is as specified in the bootstrap list
outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled crypto_support, // Crypto support is derived from list of node ids
address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable dial_info_detail_list: bsrec.dial_info_details, // Dial info is as specified in the bootstrap list
min_version: v.min_version, // Minimum crypto version specified in txt record }));
max_version: v.max_version, // Maximum crypto version specified in txt record
dial_info_detail_list: v.dial_info_details, // Dial info is as specified in the bootstrap list let pi = PeerInfo::new(bsrec.node_ids, sni);
})),
true, if let Some(nr) =
) { self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, true)
{
// Add this our futures to process in parallel // Add this our futures to process in parallel
let routing_table = self.clone(); let routing_table = self.clone();
unord.push( unord.push(

View File

@ -10,12 +10,13 @@ impl RoutingTable {
_last_ts: Timestamp, _last_ts: Timestamp,
cur_ts: Timestamp, cur_ts: Timestamp,
) -> EyreResult<()> { ) -> EyreResult<()> {
let kick_queue: Vec<usize> = core::mem::take(&mut *self.unlocked_inner.kick_queue.lock()) let kick_queue: Vec<(CryptoKind, usize)> =
.into_iter() core::mem::take(&mut *self.unlocked_inner.kick_queue.lock())
.collect(); .into_iter()
.collect();
let mut inner = self.inner.write(); let mut inner = self.inner.write();
for idx in kick_queue { for (ck, idx) in kick_queue {
inner.kick_bucket(idx) inner.kick_bucket(ck, idx)
} }
Ok(()) Ok(())
} }

View File

@ -106,7 +106,14 @@ impl RPCProcessor {
) as RoutingTableEntryFilter; ) as RoutingTableEntryFilter;
let filters = VecDeque::from([filter]); let filters = VecDeque::from([filter]);
let node_count = {
let config = self.config();
let c = config.get();
c.network.dht.max_find_node_count as usize
};
let closest_nodes = routing_table.find_closest_nodes( let closest_nodes = routing_table.find_closest_nodes(
node_count,
find_node_q.node_id, find_node_q.node_id,
filters, filters,
// transform // transform

View File

@ -195,7 +195,6 @@ fn config_callback(key: String) -> ConfigCallbackReturn {
"network.node_id" => Ok(Box::new(Option::<TypedKey>::None)), "network.node_id" => Ok(Box::new(Option::<TypedKey>::None)),
"network.node_id_secret" => Ok(Box::new(Option::<SecretKey>::None)), "network.node_id_secret" => Ok(Box::new(Option::<SecretKey>::None)),
"network.bootstrap" => Ok(Box::new(Vec::<String>::new())), "network.bootstrap" => Ok(Box::new(Vec::<String>::new())),
"network.bootstrap_nodes" => Ok(Box::new(Vec::<String>::new())),
"network.routing_table.limit_over_attached" => Ok(Box::new(64u32)), "network.routing_table.limit_over_attached" => Ok(Box::new(64u32)),
"network.routing_table.limit_fully_attached" => Ok(Box::new(32u32)), "network.routing_table.limit_fully_attached" => Ok(Box::new(32u32)),
"network.routing_table.limit_attached_strong" => Ok(Box::new(16u32)), "network.routing_table.limit_attached_strong" => Ok(Box::new(16u32)),
@ -319,7 +318,6 @@ pub async fn test_config() {
assert!(inner.network.node_id.is_none()); assert!(inner.network.node_id.is_none());
assert!(inner.network.node_id_secret.is_none()); assert!(inner.network.node_id_secret.is_none());
assert_eq!(inner.network.bootstrap, Vec::<String>::new()); assert_eq!(inner.network.bootstrap, Vec::<String>::new());
assert_eq!(inner.network.bootstrap_nodes, Vec::<String>::new());
assert_eq!(inner.network.rpc.concurrency, 2u32); assert_eq!(inner.network.rpc.concurrency, 2u32);
assert_eq!(inner.network.rpc.queue_size, 1024u32); assert_eq!(inner.network.rpc.queue_size, 1024u32);
assert_eq!(inner.network.rpc.timeout_ms, 10_000u32); assert_eq!(inner.network.rpc.timeout_ms, 10_000u32);

View File

@ -352,7 +352,6 @@ pub struct VeilidConfigNodeId {
pub struct VeilidConfigRoutingTable { pub struct VeilidConfigRoutingTable {
pub node_ids: BTreeMap<CryptoKind, VeilidConfigNodeId>, pub node_ids: BTreeMap<CryptoKind, VeilidConfigNodeId>,
pub bootstrap: Vec<String>, pub bootstrap: Vec<String>,
pub bootstrap_nodes: Vec<String>,
pub limit_over_attached: u32, pub limit_over_attached: u32,
pub limit_fully_attached: u32, pub limit_fully_attached: u32,
pub limit_attached_strong: u32, pub limit_attached_strong: u32,
@ -677,7 +676,6 @@ impl VeilidConfig {
get_config_indexed!(inner.network.routing_table.node_ids, ck, node_id_secret); get_config_indexed!(inner.network.routing_table.node_ids, ck, node_id_secret);
} }
get_config!(inner.network.routing_table.bootstrap); get_config!(inner.network.routing_table.bootstrap);
get_config!(inner.network.routing_table.bootstrap_nodes);
get_config!(inner.network.routing_table.limit_over_attached); get_config!(inner.network.routing_table.limit_over_attached);
get_config!(inner.network.routing_table.limit_fully_attached); get_config!(inner.network.routing_table.limit_fully_attached);
get_config!(inner.network.routing_table.limit_attached_strong); get_config!(inner.network.routing_table.limit_attached_strong);

View File

@ -51,7 +51,6 @@ Future<VeilidConfig> getDefaultVeilidConfig(String programName) async {
bootstrap: kIsWeb bootstrap: kIsWeb
? ["ws://bootstrap.dev.veilid.net:5150/ws"] ? ["ws://bootstrap.dev.veilid.net:5150/ws"]
: ["bootstrap.dev.veilid.net"], : ["bootstrap.dev.veilid.net"],
bootstrapNodes: [],
routingTable: VeilidConfigRoutingTable( routingTable: VeilidConfigRoutingTable(
limitOverAttached: 64, limitOverAttached: 64,
limitFullyAttached: 32, limitFullyAttached: 32,

View File

@ -744,7 +744,6 @@ class VeilidConfigNetwork {
String? nodeId; String? nodeId;
String? nodeIdSecret; String? nodeIdSecret;
List<String> bootstrap; List<String> bootstrap;
List<String> bootstrapNodes;
VeilidConfigRoutingTable routingTable; VeilidConfigRoutingTable routingTable;
VeilidConfigRPC rpc; VeilidConfigRPC rpc;
VeilidConfigDHT dht; VeilidConfigDHT dht;
@ -768,7 +767,6 @@ class VeilidConfigNetwork {
required this.nodeId, required this.nodeId,
required this.nodeIdSecret, required this.nodeIdSecret,
required this.bootstrap, required this.bootstrap,
required this.bootstrapNodes,
required this.routingTable, required this.routingTable,
required this.rpc, required this.rpc,
required this.dht, required this.dht,
@ -794,7 +792,6 @@ class VeilidConfigNetwork {
'node_id': nodeId, 'node_id': nodeId,
'node_id_secret': nodeIdSecret, 'node_id_secret': nodeIdSecret,
'bootstrap': bootstrap, 'bootstrap': bootstrap,
'bootstrap_nodes': bootstrapNodes,
'routing_table': routingTable.json, 'routing_table': routingTable.json,
'rpc': rpc.json, 'rpc': rpc.json,
'dht': dht.json, 'dht': dht.json,
@ -823,7 +820,6 @@ class VeilidConfigNetwork {
nodeId = json['node_id'], nodeId = json['node_id'],
nodeIdSecret = json['node_id_secret'], nodeIdSecret = json['node_id_secret'],
bootstrap = json['bootstrap'], bootstrap = json['bootstrap'],
bootstrapNodes = json['bootstrap_nodes'],
routingTable = VeilidConfigRoutingTable.fromJson(json['routing_table']), routingTable = VeilidConfigRoutingTable.fromJson(json['routing_table']),
rpc = VeilidConfigRPC.fromJson(json['rpc']), rpc = VeilidConfigRPC.fromJson(json['rpc']),
dht = VeilidConfigDHT.fromJson(json['dht']), dht = VeilidConfigDHT.fromJson(json['dht']),

View File

@ -122,14 +122,6 @@ fn do_clap_matches(default_config_path: &OsStr) -> Result<clap::ArgMatches, clap
.value_name("BOOTSTRAP_LIST") .value_name("BOOTSTRAP_LIST")
.help("Specify a list of bootstrap hostnames to use") .help("Specify a list of bootstrap hostnames to use")
) )
.arg(
Arg::new("bootstrap-nodes")
.conflicts_with("bootstrap")
.long("bootstrap-nodes")
.takes_value(true)
.value_name("BOOTSTRAP_NODE_LIST")
.help("Specify a list of bootstrap node dialinfos to use"),
)
.arg( .arg(
Arg::new("panic") Arg::new("panic")
.long("panic") .long("panic")
@ -280,28 +272,6 @@ pub fn process_command_line() -> EyreResult<(Settings, ArgMatches)> {
settingsrw.core.network.bootstrap = bootstrap_list; settingsrw.core.network.bootstrap = bootstrap_list;
} }
if matches.occurrences_of("bootstrap-nodes") != 0 {
let bootstrap_list = match matches.value_of("bootstrap-nodes") {
Some(x) => {
println!("Overriding bootstrap node list with: ");
let mut out: Vec<ParsedNodeDialInfo> = Vec::new();
for x in x.split(',') {
let x = x.trim();
println!(" {}", x);
out.push(
ParsedNodeDialInfo::from_str(x)
.wrap_err("unable to parse dial info in bootstrap node list")?,
);
}
out
}
None => {
bail!("value not specified for bootstrap node list");
}
};
settingsrw.core.network.bootstrap_nodes = bootstrap_list;
}
#[cfg(feature = "rt-tokio")] #[cfg(feature = "rt-tokio")]
if matches.occurrences_of("console") != 0 { if matches.occurrences_of("console") != 0 {
settingsrw.logging.console.enabled = true; settingsrw.logging.console.enabled = true;

View File

@ -67,7 +67,6 @@ core:
node_id: null node_id: null
node_id_secret: null node_id_secret: null
bootstrap: ['bootstrap.dev.veilid.net'] bootstrap: ['bootstrap.dev.veilid.net']
bootstrap_nodes: []
routing_table: routing_table:
limit_over_attached: 64 limit_over_attached: 64
limit_fully_attached: 32 limit_fully_attached: 32
@ -300,66 +299,6 @@ impl serde::Serialize for ParsedUrl {
} }
} }
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedNodeDialInfo {
pub node_dial_info_string: String,
pub node_id: NodeId,
pub dial_info: DialInfo,
}
// impl ParsedNodeDialInfo {
// pub fn offset_port(&mut self, offset: u16) -> Result<(), ()> {
// // Bump port on dial_info
// self.dial_info
// .set_port(self.dial_info.port() + 1);
// self.node_dial_info_string = format!("{}@{}",self.node_id, self.dial_info);
// Ok(())
// }
// }
impl FromStr for ParsedNodeDialInfo {
type Err = veilid_core::VeilidAPIError;
fn from_str(
node_dial_info_string: &str,
) -> Result<ParsedNodeDialInfo, veilid_core::VeilidAPIError> {
let (id_str, di_str) = node_dial_info_string.split_once('@').ok_or_else(|| {
VeilidAPIError::invalid_argument(
"Invalid node dial info in bootstrap entry",
"node_dial_info_string",
node_dial_info_string,
)
})?;
let node_id = NodeId::from_str(id_str)
.map_err(|e| VeilidAPIError::invalid_argument(e, "node_id", id_str))?;
let dial_info = DialInfo::from_str(di_str)
.map_err(|e| VeilidAPIError::invalid_argument(e, "dial_info", id_str))?;
Ok(Self {
node_dial_info_string: node_dial_info_string.to_owned(),
node_id,
dial_info,
})
}
}
impl<'de> serde::Deserialize<'de> for ParsedNodeDialInfo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
ParsedNodeDialInfo::from_str(s.as_str()).map_err(serde::de::Error::custom)
}
}
impl serde::Serialize for ParsedNodeDialInfo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.node_dial_info_string.serialize(serializer)
}
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct NamedSocketAddrs { pub struct NamedSocketAddrs {
pub name: String, pub name: String,
@ -598,7 +537,6 @@ pub struct Network {
pub node_id: Option<veilid_core::TypedKey>, pub node_id: Option<veilid_core::TypedKey>,
pub node_id_secret: Option<veilid_core::SecretKey>, pub node_id_secret: Option<veilid_core::SecretKey>,
pub bootstrap: Vec<String>, pub bootstrap: Vec<String>,
pub bootstrap_nodes: Vec<ParsedNodeDialInfo>,
pub routing_table: RoutingTable, pub routing_table: RoutingTable,
pub rpc: Rpc, pub rpc: Rpc,
pub dht: Dht, pub dht: Dht,
@ -967,7 +905,6 @@ impl Settings {
set_config_value!(inner.core.network.node_id, value); set_config_value!(inner.core.network.node_id, value);
set_config_value!(inner.core.network.node_id_secret, value); set_config_value!(inner.core.network.node_id_secret, value);
set_config_value!(inner.core.network.bootstrap, value); set_config_value!(inner.core.network.bootstrap, value);
set_config_value!(inner.core.network.bootstrap_nodes, value);
set_config_value!(inner.core.network.routing_table.limit_over_attached, value); set_config_value!(inner.core.network.routing_table.limit_over_attached, value);
set_config_value!(inner.core.network.routing_table.limit_fully_attached, value); set_config_value!(inner.core.network.routing_table.limit_fully_attached, value);
set_config_value!( set_config_value!(
@ -1122,16 +1059,6 @@ impl Settings {
"network.node_id" => Ok(Box::new(inner.core.network.node_id)), "network.node_id" => Ok(Box::new(inner.core.network.node_id)),
"network.node_id_secret" => Ok(Box::new(inner.core.network.node_id_secret)), "network.node_id_secret" => Ok(Box::new(inner.core.network.node_id_secret)),
"network.bootstrap" => Ok(Box::new(inner.core.network.bootstrap.clone())), "network.bootstrap" => Ok(Box::new(inner.core.network.bootstrap.clone())),
"network.bootstrap_nodes" => Ok(Box::new(
inner
.core
.network
.bootstrap_nodes
.clone()
.into_iter()
.map(|e| e.node_dial_info_string)
.collect::<Vec<String>>(),
)),
"network.routing_table.limit_over_attached" => Ok(Box::new( "network.routing_table.limit_over_attached" => Ok(Box::new(
inner.core.network.routing_table.limit_over_attached, inner.core.network.routing_table.limit_over_attached,
)), )),
@ -1495,7 +1422,6 @@ mod tests {
s.core.network.bootstrap, s.core.network.bootstrap,
vec!["bootstrap.dev.veilid.net".to_owned()] vec!["bootstrap.dev.veilid.net".to_owned()]
); );
assert_eq!(s.core.network.bootstrap_nodes, vec![]);
// //
assert_eq!(s.core.network.rpc.concurrency, 0); assert_eq!(s.core.network.rpc.concurrency, 0);
assert_eq!(s.core.network.rpc.queue_size, 1024); assert_eq!(s.core.network.rpc.queue_size, 1024);