bootstrap
This commit is contained in:
parent
5fd0684ae7
commit
1d8e2d3fda
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -6003,6 +6003,7 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test",
|
||||
"wasm-logger",
|
||||
"weak-table",
|
||||
"web-sys",
|
||||
"webpki 0.22.0",
|
||||
"webpki-roots 0.22.6",
|
||||
@ -6327,6 +6328,12 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weak-table"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.60"
|
||||
|
@ -50,7 +50,6 @@ core:
|
||||
node_id: ''
|
||||
node_id_secret: ''
|
||||
bootstrap: ['bootstrap.dev.veilid.net']
|
||||
bootstrap_nodes: []
|
||||
routing_table:
|
||||
limit_over_attached: 64
|
||||
limit_fully_attached: 32
|
||||
|
@ -15,7 +15,7 @@ and the `veilid-server.conf` file.
|
||||
## Global Directives
|
||||
|
||||
| Directive | Description |
|
||||
|-------------------------------|-----------------------------------------|
|
||||
| ---------------------------- | ------------------------------------- |
|
||||
| [daemon](#daemon) | Run `veilid-server` in the background |
|
||||
| [client\_api](#client_api) | |
|
||||
| [auto\_attach](#auto_attach) | |
|
||||
@ -40,7 +40,7 @@ client_api:
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------------------------------------------|-------------|
|
||||
| -------------------------------------------- | ----------- |
|
||||
| [enabled](#client_apienabled) | |
|
||||
| [listen\_address](#client_apilisten_address) | |
|
||||
|
||||
@ -83,7 +83,7 @@ logging:
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|-------------------------------|-------------|
|
||||
| ---------------------------- | ----------- |
|
||||
| [system](#loggingsystem) | |
|
||||
| [terminal](#loggingterminal) | |
|
||||
| [file](#loggingfile) | |
|
||||
@ -143,7 +143,7 @@ testing:
|
||||
### core
|
||||
|
||||
| Parameter | Description |
|
||||
|-------------------------------------------|-------------|
|
||||
| ---------------------------------------- | ----------- |
|
||||
| [protected\_store](#coreprotected_store) | |
|
||||
| [table\_store](#coretable_store) | |
|
||||
| [block\_store](#block_store) | |
|
||||
@ -191,7 +191,6 @@ network:
|
||||
node_id: ''
|
||||
node_id_secret: ''
|
||||
bootstrap: ['bootstrap.dev.veilid.net']
|
||||
bootstrap_nodes: []
|
||||
upnp: true
|
||||
detect_address_changes: true
|
||||
enable_local_peer_scope: false
|
||||
@ -199,7 +198,7 @@ network:
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---------------------------------------------|-------------|
|
||||
| ------------------------------------------- | ----------- |
|
||||
| [routing\_table](#corenetworkrouting_table) | |
|
||||
| [rpc](#corenetworkrpc) | |
|
||||
| [dht](#corenetworkdht) | |
|
||||
|
@ -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"] }
|
||||
bytecheck = "^0"
|
||||
data-encoding = { version = "^2" }
|
||||
weak-table = "0.3.2"
|
||||
|
||||
# Dependencies for native builds only
|
||||
# Linux, Windows, Mac, iOS, Android
|
||||
|
@ -11,9 +11,9 @@ use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as
|
||||
pub type CryptoKind = FourCC;
|
||||
|
||||
/// Sort best crypto kinds first
|
||||
pub fn compare_crypto_kind(a: CryptoKind, b: CryptoKind) -> cmp::Ordering {
|
||||
let a_idx = VALID_CRYPTO_KINDS.iter().position(|&k| k == a);
|
||||
let b_idx = VALID_CRYPTO_KINDS.iter().position(|&k| k == b);
|
||||
pub fn compare_crypto_kind(a: &CryptoKind, b: &CryptoKind) -> cmp::Ordering {
|
||||
let a_idx = VALID_CRYPTO_KINDS.iter().position(|k| k == a);
|
||||
let b_idx = VALID_CRYPTO_KINDS.iter().position(|k| k == b);
|
||||
if let Some(a_idx) = a_idx {
|
||||
if let Some(b_idx) = 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 {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
a.cmp(&b)
|
||||
a.cmp(b)
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ impl PartialOrd for TypedKey {
|
||||
|
||||
impl Ord for TypedKey {
|
||||
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 {
|
||||
return x;
|
||||
}
|
||||
@ -140,6 +140,14 @@ impl TypedKeySet {
|
||||
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> {
|
||||
self.items.iter().find(|x| x.kind == kind).copied()
|
||||
}
|
||||
@ -153,6 +161,18 @@ impl TypedKeySet {
|
||||
self.items.push(typed_key);
|
||||
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) {
|
||||
if let Some(idx) = self.items.iter().position(|x| x.kind == kind) {
|
||||
self.items.remove(idx);
|
||||
@ -256,7 +276,7 @@ impl PartialOrd for TypedKeyPair {
|
||||
|
||||
impl Ord for TypedKeyPair {
|
||||
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 {
|
||||
return x;
|
||||
}
|
||||
@ -340,7 +360,7 @@ impl PartialOrd for TypedSignature {
|
||||
|
||||
impl Ord for TypedSignature {
|
||||
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 {
|
||||
return x;
|
||||
}
|
||||
@ -410,7 +430,7 @@ impl PartialOrd for TypedKeySignature {
|
||||
|
||||
impl Ord for TypedKeySignature {
|
||||
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 {
|
||||
return x;
|
||||
}
|
||||
|
@ -1327,7 +1327,7 @@ impl NetworkManager {
|
||||
}
|
||||
|
||||
// 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 => {});
|
||||
return Ok(true);
|
||||
}
|
||||
|
@ -11,19 +11,19 @@ pub struct Bucket {
|
||||
/// handle to the routing table
|
||||
routing_table: RoutingTable,
|
||||
/// 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
|
||||
newest_entry: Option<TypedKey>,
|
||||
newest_entry: Option<PublicKey>,
|
||||
/// The crypto kind in use for the public keys in this bucket
|
||||
kind: CryptoKind,
|
||||
}
|
||||
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)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
struct SerializedBucketEntryData {
|
||||
key: TypedKey,
|
||||
key: PublicKey,
|
||||
value: u32, // index into serialized entries list
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ struct SerializedBucketEntryData {
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
struct SerializedBucketData {
|
||||
entries: Vec<SerializedBucketEntryData>,
|
||||
newest_entry: Option<TypedKey>,
|
||||
newest_entry: Option<PublicKey>,
|
||||
}
|
||||
|
||||
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
|
||||
pub(super) fn add_entry(&mut self, node_id: TypedKey) -> NodeRef {
|
||||
assert_eq!(node_id.kind, self.kind);
|
||||
|
||||
log_rtab!("Node added: {}", node_id);
|
||||
pub(super) fn add_entry(&mut self, node_id_key: PublicKey) -> NodeRef {
|
||||
log_rtab!("Node added: {}:{}", self.kind, node_id_key);
|
||||
|
||||
// Add new entry
|
||||
let entry = Arc::new(BucketEntry::new(node_id));
|
||||
self.entries.insert(node_id.key, entry.clone());
|
||||
let entry = Arc::new(BucketEntry::new(TypedKey::new(self.kind, node_id_key)));
|
||||
self.entries.insert(node_id_key, entry.clone());
|
||||
|
||||
// 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
|
||||
NodeRef::new(self.routing_table.clone(), entry, None)
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
assert_eq!(node_id.kind, self.kind);
|
||||
|
||||
log_rtab!("Existing node added: {}", node_id);
|
||||
pub(super) fn add_existing_entry(&mut self, node_id_key: PublicKey, entry: Arc<BucketEntry>) {
|
||||
log_rtab!("Existing node added: {}:{}", self.kind, node_id_key);
|
||||
|
||||
// Add existing entry
|
||||
entry.with_mut_inner(|e| e.add_node_id(node_id));
|
||||
self.entries.insert(node_id.key, entry);
|
||||
entry.with_mut_inner(|e| e.add_node_id(TypedKey::new(self.kind, node_id_key)));
|
||||
self.entries.insert(node_id_key, 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
|
||||
}
|
||||
|
||||
/// Remove an entry with a node_id for this crypto kind from the bucket
|
||||
fn remove_entry(&mut self, node_id: &TypedKey) {
|
||||
log_rtab!("Node removed: {}:{}", self.kind, node_id);
|
||||
fn remove_entry(&mut self, node_id_key: &PublicKey) {
|
||||
log_rtab!("Node removed: {}:{}", self.kind, node_id_key);
|
||||
|
||||
// Remove the entry
|
||||
self.entries.remove(node_id);
|
||||
self.entries.remove(node_id_key);
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@ -145,7 +141,7 @@ impl Bucket {
|
||||
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
|
||||
let bucket_len = self.entries.len();
|
||||
|
||||
@ -155,11 +151,11 @@ impl 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;
|
||||
|
||||
// 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
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
|
@ -7,7 +7,7 @@ impl RoutingTable {
|
||||
let inner = self.inner.read();
|
||||
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!(
|
||||
" Self Latency Stats Accounting: {:#?}\n\n",
|
||||
inner.self_latency_stats_accounting
|
||||
@ -55,13 +55,20 @@ impl RoutingTable {
|
||||
short_urls.sort();
|
||||
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 += &format!(
|
||||
"{},{},{},{},{}",
|
||||
"{}|{}|{}|{}|",
|
||||
BOOTSTRAP_TXT_VERSION,
|
||||
MIN_CRYPTO_VERSION,
|
||||
MAX_CRYPTO_VERSION,
|
||||
self.node_id().encode(),
|
||||
valid_envelope_versions,
|
||||
node_ids,
|
||||
some_hostname.unwrap()
|
||||
);
|
||||
for short_url in short_urls {
|
||||
|
@ -111,7 +111,15 @@ impl RoutingTableUnlockedInner {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -890,6 +898,7 @@ impl RoutingTable {
|
||||
|
||||
pub fn find_closest_nodes<'a, T, O>(
|
||||
&self,
|
||||
node_count: usize,
|
||||
node_id: TypedKey,
|
||||
filters: VecDeque<RoutingTableEntryFilter>,
|
||||
transform: T,
|
||||
@ -899,7 +908,7 @@ impl RoutingTable {
|
||||
{
|
||||
self.inner
|
||||
.read()
|
||||
.find_closest_nodes(node_id, filters, transform)
|
||||
.find_closest_nodes(node_count, node_id, filters, transform)
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret)]
|
||||
@ -940,14 +949,14 @@ impl RoutingTable {
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub async fn find_self(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let node_id = self.node_id();
|
||||
self.find_node(node_ref, node_id).await
|
||||
let self_node_id = self.node_id();
|
||||
self.find_node(node_ref, self_node_id).await
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub async fn find_target(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let node_id = node_ref.node_id();
|
||||
self.find_node(node_ref, node_id).await
|
||||
let target_node_id = node_ref.node_id();
|
||||
self.find_node(node_ref, target_node_id).await
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
@ -1047,12 +1056,12 @@ impl RoutingTable {
|
||||
// Go through all entries and find fastest entry that matches filter function
|
||||
let inner = self.inner.read();
|
||||
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
|
||||
inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| {
|
||||
let v2 = v.clone();
|
||||
v.with(rti, |rti, e| {
|
||||
inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, entry| {
|
||||
let entry2 = entry.clone();
|
||||
entry.with(rti, |rti, e| {
|
||||
// Ensure we have the node's status
|
||||
if let Some(node_status) = e.node_status(routing_domain) {
|
||||
// Ensure the node will relay
|
||||
@ -1060,18 +1069,18 @@ impl RoutingTable {
|
||||
// Compare against previous candidate
|
||||
if let Some(best_inbound_relay) = best_inbound_relay.as_mut() {
|
||||
// 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
|
||||
BucketEntryInner::cmp_fastest_reliable(cur_ts, e, best)
|
||||
== std::cmp::Ordering::Less
|
||||
});
|
||||
// Now apply filter function and see if this node should be included
|
||||
if better && relay_node_filter(e) {
|
||||
*best_inbound_relay = (k, v2);
|
||||
*best_inbound_relay = entry2;
|
||||
}
|
||||
} else if relay_node_filter(e) {
|
||||
// Always store the first candidate
|
||||
best_inbound_relay = Some((k, v2));
|
||||
best_inbound_relay = Some(entry2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1080,6 +1089,6 @@ impl RoutingTable {
|
||||
Option::<()>::None
|
||||
});
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use weak_table::PtrWeakHashSet;
|
||||
|
||||
const RECENT_PEERS_TABLE_SIZE: usize = 64;
|
||||
|
||||
@ -15,8 +16,8 @@ pub struct RoutingTableInner {
|
||||
pub(super) unlocked_inner: Arc<RoutingTableUnlockedInner>,
|
||||
/// Routing table buckets that hold references to entries, per crypto kind
|
||||
pub(super) buckets: BTreeMap<CryptoKind, Vec<Bucket>>,
|
||||
/// A fast counter for the number of entries in the table, total
|
||||
pub(super) bucket_entry_count: usize,
|
||||
/// A weak set of all the entries we have in the buckets for faster iteration
|
||||
pub(super) all_entries: PtrWeakHashSet<Weak<BucketEntry>>,
|
||||
/// The public internet routing domain
|
||||
pub(super) public_internet_routing_domain: PublicInternetRoutingDomainDetail,
|
||||
/// The dial info we use on the local network
|
||||
@ -40,7 +41,7 @@ impl RoutingTableInner {
|
||||
buckets: BTreeMap::new(),
|
||||
public_internet_routing_domain: PublicInternetRoutingDomainDetail::default(),
|
||||
local_network_routing_domain: LocalNetworkRoutingDomainDetail::default(),
|
||||
bucket_entry_count: 0,
|
||||
all_entries: PtrWeakHashSet::new(),
|
||||
self_latency_stats_accounting: LatencyStatsAccounting::new(),
|
||||
self_transfer_stats_accounting: TransferStatsAccounting::new(),
|
||||
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 {
|
||||
&mut self.self_transfer_stats_accounting
|
||||
}
|
||||
@ -209,7 +214,7 @@ impl RoutingTableInner {
|
||||
|
||||
pub fn reset_all_updated_since_last_network_change(&mut self) {
|
||||
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| {
|
||||
e.set_updated_since_last_network_change(false)
|
||||
});
|
||||
@ -310,7 +315,7 @@ impl RoutingTableInner {
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
let ckbuckets = Vec::with_capacity(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);
|
||||
}
|
||||
self.buckets.insert(ck, ckbuckets);
|
||||
@ -345,14 +350,18 @@ impl RoutingTableInner {
|
||||
pub fn purge_buckets(&mut self) {
|
||||
log_rtab!(
|
||||
"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 {
|
||||
for bucket in &mut self.buckets[&ck] {
|
||||
bucket.kick(0);
|
||||
}
|
||||
}
|
||||
self.all_entries.remove_expired();
|
||||
|
||||
log_rtab!(debug
|
||||
"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) {
|
||||
log_rtab!(
|
||||
"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 bucket in &self.buckets[&ck] {
|
||||
for entry in bucket.entries() {
|
||||
entry.1.with_mut_inner(|e| {
|
||||
e.clear_last_connections();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
self.all_entries.remove_expired();
|
||||
|
||||
log_rtab!(debug
|
||||
"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
|
||||
/// which may not be possible due extant NodeRefs
|
||||
pub fn kick_bucket(&mut self, idx: usize) {
|
||||
let bucket = &mut self.buckets[idx];
|
||||
pub fn kick_bucket(&mut self, kind: CryptoKind, idx: usize) {
|
||||
let bucket = &mut self.buckets[&kind][idx];
|
||||
let bucket_depth = Self::bucket_depth(idx);
|
||||
|
||||
if let Some(dead_node_ids) = bucket.kick(bucket_depth) {
|
||||
// Remove counts
|
||||
self.bucket_entry_count -= dead_node_ids.len();
|
||||
log_rtab!(debug "Routing table now has {} nodes", self.bucket_entry_count);
|
||||
// Remove expired entries
|
||||
self.all_entries.remove_expired();
|
||||
|
||||
log_rtab!(debug "Bucket {}:{} kicked Routing table now has {} nodes", kind, idx, self.bucket_entry_count());
|
||||
|
||||
// Now purge the routing table inner vectors
|
||||
//let filter = |k: &DHTKey| dead_node_ids.contains(k);
|
||||
@ -403,7 +416,7 @@ impl RoutingTableInner {
|
||||
) -> usize {
|
||||
let mut count = 0usize;
|
||||
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))
|
||||
.is_some()
|
||||
{
|
||||
@ -414,54 +427,36 @@ impl RoutingTableInner {
|
||||
count
|
||||
}
|
||||
|
||||
pub fn with_entries<
|
||||
T,
|
||||
F: FnMut(&RoutingTableInner, TypedKey, Arc<BucketEntry>) -> Option<T>,
|
||||
>(
|
||||
pub fn with_entries<T, F: FnMut(&RoutingTableInner, Arc<BucketEntry>) -> Option<T>>(
|
||||
&self,
|
||||
cur_ts: Timestamp,
|
||||
min_state: BucketEntryState,
|
||||
mut f: F,
|
||||
) -> Option<T> {
|
||||
let mut entryvec = Vec::with_capacity(self.bucket_entry_count);
|
||||
for bucket in &self.buckets {
|
||||
for entry in bucket.entries() {
|
||||
if entry.1.with(self, |_rti, e| e.state(cur_ts) >= min_state) {
|
||||
entryvec.push((*entry.0, entry.1.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
for entry in entryvec {
|
||||
if let Some(out) = f(self, entry.0, entry.1) {
|
||||
for entry in self.all_entries {
|
||||
if entry.with(self, |_rti, e| e.state(cur_ts) >= min_state) {
|
||||
if let Some(out) = f(self, entry) {
|
||||
return Some(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn with_entries_mut<
|
||||
T,
|
||||
F: FnMut(&mut RoutingTableInner, TypedKey, Arc<BucketEntry>) -> Option<T>,
|
||||
>(
|
||||
pub fn with_entries_mut<T, F: FnMut(&mut RoutingTableInner, Arc<BucketEntry>) -> Option<T>>(
|
||||
&mut self,
|
||||
cur_ts: Timestamp,
|
||||
min_state: BucketEntryState,
|
||||
mut f: F,
|
||||
) -> Option<T> {
|
||||
let mut entryvec = Vec::with_capacity(self.bucket_entry_count);
|
||||
for bucket in &self.buckets {
|
||||
for entry in bucket.entries() {
|
||||
if entry.1.with(self, |_rti, e| e.state(cur_ts) >= min_state) {
|
||||
entryvec.push((*entry.0, entry.1.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
for entry in entryvec {
|
||||
if let Some(out) = f(self, entry.0, entry.1) {
|
||||
for entry in self.all_entries {
|
||||
if entry.with(self, |_rti, e| e.state(cur_ts) >= min_state) {
|
||||
if let Some(out) = f(self, entry) {
|
||||
return Some(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@ -728,9 +723,8 @@ impl RoutingTableInner {
|
||||
let mut dead_entry_count: usize = 0;
|
||||
|
||||
let cur_ts = get_aligned_timestamp();
|
||||
for bucket in &self.buckets {
|
||||
for (_, v) in bucket.entries() {
|
||||
match v.with(self, |_rti, e| e.state(cur_ts)) {
|
||||
for entry in self.all_entries {
|
||||
match entry.with(self, |_rti, e| e.state(cur_ts)) {
|
||||
BucketEntryState::Reliable => {
|
||||
reliable_entry_count += 1;
|
||||
}
|
||||
@ -742,7 +736,6 @@ impl RoutingTableInner {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let public_internet_ready = !matches!(
|
||||
self.get_network_class(RoutingDomain::PublicInternet)
|
||||
@ -798,7 +791,7 @@ impl RoutingTableInner {
|
||||
self.find_fastest_nodes(
|
||||
node_count,
|
||||
filters,
|
||||
|_rti: &RoutingTableInner, k: TypedKey, v: Option<Arc<BucketEntry>>| {
|
||||
|_rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| {
|
||||
NodeRef::new(outer_self.clone(), v.unwrap().clone(), None)
|
||||
},
|
||||
)
|
||||
@ -846,30 +839,30 @@ impl RoutingTableInner {
|
||||
&'b Option<Arc<BucketEntry>>,
|
||||
&'b Option<Arc<BucketEntry>>,
|
||||
) -> 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
|
||||
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)
|
||||
let mut filtered = false;
|
||||
for filter in &mut filters {
|
||||
if !filter(self, self.unlocked_inner.node_id, None) {
|
||||
if !filter(self, None) {
|
||||
filtered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !filtered {
|
||||
nodes.push((self.unlocked_inner.node_id, None));
|
||||
nodes.push(None);
|
||||
}
|
||||
|
||||
// add all nodes from buckets
|
||||
self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| {
|
||||
// add all nodes that match filter
|
||||
self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, v| {
|
||||
// Apply filter
|
||||
for filter in &mut filters {
|
||||
if filter(rti, k, Some(v.clone())) {
|
||||
nodes.push((k, Some(v.clone())));
|
||||
if filter(rti, Some(v.clone())) {
|
||||
nodes.push(Some(v.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -883,7 +876,7 @@ impl RoutingTableInner {
|
||||
let cnt = usize::min(node_count, nodes.len());
|
||||
let mut out = Vec::<O>::with_capacity(cnt);
|
||||
for node in nodes {
|
||||
let val = transform(self, node.0, node.1);
|
||||
let val = transform(self, node);
|
||||
out.push(val);
|
||||
}
|
||||
|
||||
@ -921,12 +914,19 @@ impl RoutingTableInner {
|
||||
|
||||
// Fastest sort
|
||||
let sort = |rti: &RoutingTableInner,
|
||||
(a_key, a_entry): &(TypedKey, Option<Arc<BucketEntry>>),
|
||||
(b_key, b_entry): &(TypedKey, Option<Arc<BucketEntry>>)| {
|
||||
a_entry: &Option<Arc<BucketEntry>>,
|
||||
b_entry: &Option<Arc<BucketEntry>>| {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// our own node always comes last (should not happen, here for completeness)
|
||||
if a_entry.is_none() {
|
||||
return core::cmp::Ordering::Greater;
|
||||
@ -977,26 +977,28 @@ impl RoutingTableInner {
|
||||
|
||||
pub fn find_closest_nodes<T, O>(
|
||||
&self,
|
||||
node_count: usize,
|
||||
node_id: TypedKey,
|
||||
filters: VecDeque<RoutingTableEntryFilter>,
|
||||
transform: T,
|
||||
) -> Vec<O>
|
||||
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 node_count = {
|
||||
let config = self.config();
|
||||
let c = config.get();
|
||||
c.network.dht.max_find_node_count as usize
|
||||
};
|
||||
|
||||
// closest sort
|
||||
let sort = |rti: &RoutingTableInner,
|
||||
(a_key, a_entry): &(TypedKey, Option<Arc<BucketEntry>>),
|
||||
(b_key, b_entry): &(TypedKey, Option<Arc<BucketEntry>>)| {
|
||||
a_entry: &Option<Arc<BucketEntry>>,
|
||||
b_entry: &Option<Arc<BucketEntry>>| {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -3,23 +3,122 @@ use super::*;
|
||||
use futures_util::stream::{FuturesUnordered, StreamExt};
|
||||
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)]
|
||||
pub struct BootstrapRecord {
|
||||
min_version: u8,
|
||||
max_version: u8,
|
||||
node_ids: TypedKeySet,
|
||||
envelope_support: Vec<u8>,
|
||||
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 {
|
||||
/// 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
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub(crate) async fn resolve_bootstrap(
|
||||
&self,
|
||||
bootstrap: Vec<String>,
|
||||
) -> EyreResult<BootstrapRecordMap> {
|
||||
) -> EyreResult<Vec<BootstrapRecord>> {
|
||||
// Resolve from bootstrap root to bootstrap hostnames
|
||||
let mut bsnames = Vec::<String>::new();
|
||||
for bh in bootstrap {
|
||||
@ -58,22 +157,14 @@ impl RoutingTable {
|
||||
Ok(v) => v,
|
||||
};
|
||||
// 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 {
|
||||
// Bootstrap TXT Record Format Version 0:
|
||||
// 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
|
||||
// All formats split on '|' character
|
||||
let records: Vec<String> = bsnirecord
|
||||
.trim()
|
||||
.split(',')
|
||||
.split('|')
|
||||
.map(|x| x.trim().to_owned())
|
||||
.collect();
|
||||
if records.len() < 6 {
|
||||
warn!("invalid number of fields in bootstrap txt record");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bootstrap TXT record version
|
||||
let txt_version: u8 = match records[0].parse::<u8>() {
|
||||
@ -86,81 +177,30 @@ impl RoutingTable {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if txt_version != BOOTSTRAP_TXT_VERSION {
|
||||
let bootstrap_record = match txt_version {
|
||||
BOOTSTRAP_TXT_VERSION_0 => {
|
||||
match self.process_bootstrap_records_v0(records).await {
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"couldn't process v0 bootstrap records from {}: {}",
|
||||
bsname, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => {
|
||||
// skipping
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!("unsupported bootstrap txt record version");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Min/Max wire protocol version
|
||||
let min_version: u8 = match records[1].parse::<u8>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"invalid min_version specified in bootstrap node txt record: {}",
|
||||
e
|
||||
);
|
||||
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));
|
||||
bootstrap_records.push(bootstrap_record);
|
||||
}
|
||||
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 {
|
||||
if let Some(bootstrap_records) = bootstrap_records {
|
||||
for (bskey, mut bsrec) in bootstrap_records {
|
||||
let rec = bsmap.entry(bskey).or_insert_with(|| BootstrapRecord {
|
||||
min_version: bsrec.min_version,
|
||||
max_version: bsrec.max_version,
|
||||
dial_info_details: Vec::new(),
|
||||
});
|
||||
rec.dial_info_details.append(&mut bsrec.dial_info_details);
|
||||
let Some(bootstrap_records) = bootstrap_records else {
|
||||
continue;
|
||||
};
|
||||
for mut bsrec in bootstrap_records {
|
||||
let mut mbi = 0;
|
||||
while mbi < merged_bootstrap_records.len() {
|
||||
let mbr = &mut merged_bootstrap_records[mbi];
|
||||
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
|
||||
@ -203,14 +257,10 @@ impl RoutingTable {
|
||||
|
||||
// Got peer info, let's add it to the routing table
|
||||
for pi in peer_info {
|
||||
let k = pi.node_id.key;
|
||||
// Register the node
|
||||
if let Some(nr) = self.register_node_with_signed_node_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
k,
|
||||
pi.signed_node_info,
|
||||
false,
|
||||
) {
|
||||
if let Some(nr) =
|
||||
self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, false)
|
||||
{
|
||||
// Add this our futures to process in parallel
|
||||
let routing_table = self.clone();
|
||||
unord.push(
|
||||
@ -230,17 +280,18 @@ impl RoutingTable {
|
||||
|
||||
#[instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) async fn bootstrap_task_routine(self, stop_token: StopToken) -> EyreResult<()> {
|
||||
let (bootstrap, bootstrap_nodes) = self.with_config(|c| {
|
||||
(
|
||||
c.network.bootstrap.clone(),
|
||||
c.network.bootstrap_nodes.clone(),
|
||||
)
|
||||
});
|
||||
let bootstrap = self
|
||||
.unlocked_inner
|
||||
.with_config(|c| c.network.routing_table.bootstrap.clone());
|
||||
|
||||
// Don't bother if bootstraps aren't configured
|
||||
if bootstrap.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log_rtab!(debug "--- bootstrap_task");
|
||||
|
||||
// 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();
|
||||
for b in &bootstrap {
|
||||
if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) {
|
||||
@ -254,66 +305,37 @@ impl RoutingTable {
|
||||
.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)
|
||||
let bsmap: BootstrapRecordMap = if !bootstrap_nodes.is_empty() {
|
||||
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
|
||||
// If not direct, resolve bootstrap servers and recurse their TXT entries
|
||||
let bsrecs = self.resolve_bootstrap(bootstrap).await?;
|
||||
|
||||
// Run all bootstrap operations concurrently
|
||||
let mut unord = FuturesUnordered::new();
|
||||
for (k, mut v) in bsmap {
|
||||
// Sort dial info so we get the preferred order correct
|
||||
v.dial_info_details.sort();
|
||||
for bsrec in bsrecs {
|
||||
log_rtab!(
|
||||
"--- 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)
|
||||
if let Some(nr) = self.register_node_with_signed_node_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
k,
|
||||
SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo {
|
||||
// Make unsigned SignedNodeInfo
|
||||
let sni = SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo {
|
||||
network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable
|
||||
outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled
|
||||
address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable
|
||||
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
|
||||
})),
|
||||
true,
|
||||
) {
|
||||
envelope_support: bsrec.envelope_support, // Envelope support is as specified in the bootstrap list
|
||||
crypto_support, // Crypto support is derived from list of node ids
|
||||
dial_info_detail_list: bsrec.dial_info_details, // Dial info is as specified in the bootstrap list
|
||||
}));
|
||||
|
||||
let pi = PeerInfo::new(bsrec.node_ids, sni);
|
||||
|
||||
if let Some(nr) =
|
||||
self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, true)
|
||||
{
|
||||
// Add this our futures to process in parallel
|
||||
let routing_table = self.clone();
|
||||
unord.push(
|
||||
|
@ -10,12 +10,13 @@ impl RoutingTable {
|
||||
_last_ts: Timestamp,
|
||||
cur_ts: Timestamp,
|
||||
) -> EyreResult<()> {
|
||||
let kick_queue: Vec<usize> = core::mem::take(&mut *self.unlocked_inner.kick_queue.lock())
|
||||
let kick_queue: Vec<(CryptoKind, usize)> =
|
||||
core::mem::take(&mut *self.unlocked_inner.kick_queue.lock())
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut inner = self.inner.write();
|
||||
for idx in kick_queue {
|
||||
inner.kick_bucket(idx)
|
||||
for (ck, idx) in kick_queue {
|
||||
inner.kick_bucket(ck, idx)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -106,7 +106,14 @@ impl RPCProcessor {
|
||||
) as RoutingTableEntryFilter;
|
||||
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(
|
||||
node_count,
|
||||
find_node_q.node_id,
|
||||
filters,
|
||||
// transform
|
||||
|
@ -195,7 +195,6 @@ fn config_callback(key: String) -> ConfigCallbackReturn {
|
||||
"network.node_id" => Ok(Box::new(Option::<TypedKey>::None)),
|
||||
"network.node_id_secret" => Ok(Box::new(Option::<SecretKey>::None)),
|
||||
"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_fully_attached" => Ok(Box::new(32u32)),
|
||||
"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_secret.is_none());
|
||||
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.queue_size, 1024u32);
|
||||
assert_eq!(inner.network.rpc.timeout_ms, 10_000u32);
|
||||
|
@ -352,7 +352,6 @@ pub struct VeilidConfigNodeId {
|
||||
pub struct VeilidConfigRoutingTable {
|
||||
pub node_ids: BTreeMap<CryptoKind, VeilidConfigNodeId>,
|
||||
pub bootstrap: Vec<String>,
|
||||
pub bootstrap_nodes: Vec<String>,
|
||||
pub limit_over_attached: u32,
|
||||
pub limit_fully_attached: 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!(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_fully_attached);
|
||||
get_config!(inner.network.routing_table.limit_attached_strong);
|
||||
|
@ -51,7 +51,6 @@ Future<VeilidConfig> getDefaultVeilidConfig(String programName) async {
|
||||
bootstrap: kIsWeb
|
||||
? ["ws://bootstrap.dev.veilid.net:5150/ws"]
|
||||
: ["bootstrap.dev.veilid.net"],
|
||||
bootstrapNodes: [],
|
||||
routingTable: VeilidConfigRoutingTable(
|
||||
limitOverAttached: 64,
|
||||
limitFullyAttached: 32,
|
||||
|
@ -744,7 +744,6 @@ class VeilidConfigNetwork {
|
||||
String? nodeId;
|
||||
String? nodeIdSecret;
|
||||
List<String> bootstrap;
|
||||
List<String> bootstrapNodes;
|
||||
VeilidConfigRoutingTable routingTable;
|
||||
VeilidConfigRPC rpc;
|
||||
VeilidConfigDHT dht;
|
||||
@ -768,7 +767,6 @@ class VeilidConfigNetwork {
|
||||
required this.nodeId,
|
||||
required this.nodeIdSecret,
|
||||
required this.bootstrap,
|
||||
required this.bootstrapNodes,
|
||||
required this.routingTable,
|
||||
required this.rpc,
|
||||
required this.dht,
|
||||
@ -794,7 +792,6 @@ class VeilidConfigNetwork {
|
||||
'node_id': nodeId,
|
||||
'node_id_secret': nodeIdSecret,
|
||||
'bootstrap': bootstrap,
|
||||
'bootstrap_nodes': bootstrapNodes,
|
||||
'routing_table': routingTable.json,
|
||||
'rpc': rpc.json,
|
||||
'dht': dht.json,
|
||||
@ -823,7 +820,6 @@ class VeilidConfigNetwork {
|
||||
nodeId = json['node_id'],
|
||||
nodeIdSecret = json['node_id_secret'],
|
||||
bootstrap = json['bootstrap'],
|
||||
bootstrapNodes = json['bootstrap_nodes'],
|
||||
routingTable = VeilidConfigRoutingTable.fromJson(json['routing_table']),
|
||||
rpc = VeilidConfigRPC.fromJson(json['rpc']),
|
||||
dht = VeilidConfigDHT.fromJson(json['dht']),
|
||||
|
@ -122,14 +122,6 @@ fn do_clap_matches(default_config_path: &OsStr) -> Result<clap::ArgMatches, clap
|
||||
.value_name("BOOTSTRAP_LIST")
|
||||
.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::new("panic")
|
||||
.long("panic")
|
||||
@ -280,28 +272,6 @@ pub fn process_command_line() -> EyreResult<(Settings, ArgMatches)> {
|
||||
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")]
|
||||
if matches.occurrences_of("console") != 0 {
|
||||
settingsrw.logging.console.enabled = true;
|
||||
|
@ -67,7 +67,6 @@ core:
|
||||
node_id: null
|
||||
node_id_secret: null
|
||||
bootstrap: ['bootstrap.dev.veilid.net']
|
||||
bootstrap_nodes: []
|
||||
routing_table:
|
||||
limit_over_attached: 64
|
||||
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)]
|
||||
pub struct NamedSocketAddrs {
|
||||
pub name: String,
|
||||
@ -598,7 +537,6 @@ pub struct Network {
|
||||
pub node_id: Option<veilid_core::TypedKey>,
|
||||
pub node_id_secret: Option<veilid_core::SecretKey>,
|
||||
pub bootstrap: Vec<String>,
|
||||
pub bootstrap_nodes: Vec<ParsedNodeDialInfo>,
|
||||
pub routing_table: RoutingTable,
|
||||
pub rpc: Rpc,
|
||||
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_secret, 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_fully_attached, value);
|
||||
set_config_value!(
|
||||
@ -1122,16 +1059,6 @@ impl Settings {
|
||||
"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.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(
|
||||
inner.core.network.routing_table.limit_over_attached,
|
||||
)),
|
||||
@ -1495,7 +1422,6 @@ mod tests {
|
||||
s.core.network.bootstrap,
|
||||
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.queue_size, 1024);
|
||||
|
Loading…
Reference in New Issue
Block a user