Skip to content
This repository has been archived by the owner on Sep 21, 2024. It is now read-only.

Commit

Permalink
Refactor internal record representation as a wrapper around a UCAN au…
Browse files Browse the repository at this point in the history
…th token representing permission to publish.
  • Loading branch information
jsantell committed Nov 3, 2022
1 parent a040d24 commit ccc027f
Show file tree
Hide file tree
Showing 12 changed files with 823 additions and 558 deletions.
203 changes: 80 additions & 123 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion rust/noosphere-core/src/authority/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl TryFrom<String> for SphereAction {
}
}

#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SphereReference {
pub did: String,
}
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-core/src/data/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl DelegationIpld {
}
}

/// See https://github.com/ucan-wg/spec#66-revocation
/// See <https://github.com/ucan-wg/spec#66-revocation>
/// TODO(ucan-wg/spec#112): Verify the form of this
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Hash)]
pub struct RevocationIpld {
Expand Down
4 changes: 4 additions & 0 deletions rust/noosphere-ns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ tracing = "0.1"
cid = "~0.8"
serde = "^1"
serde_json = "^1"
ucan = { version = "0.7.0-alpha.1" }
ucan-key-support = { version = "0.7.0-alpha.1" }
noosphere-storage = { version = "0.1.0-alpha.1", path = "../noosphere-storage" }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
noosphere-core = { version = "0.1.0-alpha.1", path = "../noosphere-core" }
Expand All @@ -40,3 +43,4 @@ ucan = { version = "0.7.0-alpha.1" }
rand = { version = "0.8.5" }
test-log = { version = "0.2.11", default-features = false, features = ["trace"] }
tracing-subscriber = { version = "~0.3", features = ["env-filter"] }
libipld-cbor = "~0.14"
117 changes: 81 additions & 36 deletions rust/noosphere-ns/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
use crate::{dht::DHTConfig, name_system::NameSystem};
use anyhow::{anyhow, Result};
use libp2p::{self, Multiaddr};
use std::collections::HashMap;
use noosphere_storage::{db::SphereDb, interface::Store};
use std::net::Ipv4Addr;
use ucan_key_support::ed25519::Ed25519KeyMaterial;

/// [NameSystemBuilder] is the primary external interface for
/// creating a new [NameSystem].
/// creating a new [NameSystem]. `key_material` and `store`
/// must be provided.
///
/// # Examples
///
/// ```
/// use noosphere_core::authority::generate_ed25519_key;
/// use noosphere_storage::{db::SphereDb, memory::{MemoryStore, MemoryStorageProvider}};
/// use noosphere_ns::{NameSystem, NameSystemBuilder};
/// use tokio;
///
/// let key_material = generate_ed25519_key();
/// let ns = NameSystemBuilder::default()
/// .key_material(&key_material)
/// .listening_port(30000)
/// .build().expect("valid config");
/// #[tokio::main]
/// async fn main() {
/// let key_material = generate_ed25519_key();
/// let store = SphereDb::new(&MemoryStorageProvider::default()).await.unwrap();
///
/// let ns = NameSystemBuilder::default()
/// .key_material(&key_material)
/// .store(&store)
/// .listening_port(30000)
/// .build().expect("valid config");
///
/// assert!(NameSystemBuilder::<MemoryStore>::default().build().is_err(), "key_material and store must be provided.");
/// }
/// ```
pub struct NameSystemBuilder<'a> {
bootstrap_peers: Option<&'a Vec<Multiaddr>>,
pub struct NameSystemBuilder<S>
where
S: Store,
{
bootstrap_peers: Option<Vec<Multiaddr>>,
dht_config: DHTConfig,
key_material: Option<&'a Ed25519KeyMaterial>,
ttl: u64,
key_material: Option<Ed25519KeyMaterial>,
store: Option<SphereDb<S>>,
propagation_interval: u64,
}

impl<'a> NameSystemBuilder<'a> {
impl<S> NameSystemBuilder<S>
where
S: Store,
{
/// If bootstrap peers are provided, how often,
/// in seconds, should the bootstrap process execute
/// to keep routing tables fresh.
Expand All @@ -40,18 +57,19 @@ impl<'a> NameSystemBuilder<'a> {
/// Peer addresses to query to update routing tables
/// during bootstrap. A standalone bootstrap node would
/// have this field empty.
pub fn bootstrap_peers(mut self, peers: &'a Vec<Multiaddr>) -> Self {
self.bootstrap_peers = Some(peers);
pub fn bootstrap_peers(mut self, peers: &Vec<Multiaddr>) -> Self {
self.bootstrap_peers = Some(peers.to_owned());
self
}

/// Public/private keypair for DHT node.
pub fn key_material(mut self, key_material: &'a Ed25519KeyMaterial) -> Self {
self.key_material = Some(key_material);
pub fn key_material(mut self, key_material: &Ed25519KeyMaterial) -> Self {
self.key_material = Some(key_material.to_owned());
self
}

/// Port to listen for incoming TCP connections.
/// Port to listen for incoming TCP connections. If not specified,
/// an open port is automatically chosen.
pub fn listening_port(mut self, port: u16) -> Self {
let mut address = Multiaddr::empty();
address.push(libp2p::multiaddr::Protocol::Ip4(Ipv4Addr::new(
Expand All @@ -76,37 +94,49 @@ impl<'a> NameSystemBuilder<'a> {
self
}

/// Default Time To Live (TTL) for records propagated to the network.
pub fn ttl(mut self, ttl: u64) -> Self {
self.ttl = ttl;
/// The Noosphere Store to use for reading and writing sphere data.
pub fn store(mut self, store: &SphereDb<S>) -> Self {
self.store = Some(store.to_owned());
self
}

/// Default interval for hosted records to be propagated to the network.
pub fn propagation_interval(mut self, propagation_interval: u64) -> Self {
self.propagation_interval = propagation_interval;
self
}

/// Build a [NameSystem] based off of the provided configuration.
pub fn build(mut self) -> Result<NameSystem<'a>> {
pub fn build(mut self) -> Result<NameSystem<S>> {
let key_material = self
.key_material
.take()
.ok_or_else(|| anyhow!("key_material required."))?;
Ok(NameSystem {
bootstrap_peers: self.bootstrap_peers.take(),
dht: None,
dht_config: self.dht_config,
let store = self
.store
.take()
.ok_or_else(|| anyhow!("store required."))?;
Ok(NameSystem::new(
key_material,
ttl: self.ttl,
hosted_records: HashMap::new(),
resolved_records: HashMap::new(),
})
store,
self.bootstrap_peers.take(),
self.dht_config,
self.propagation_interval,
))
}
}

impl<'a> Default for NameSystemBuilder<'a> {
impl<S> Default for NameSystemBuilder<S>
where
S: Store,
{
fn default() -> Self {
Self {
bootstrap_peers: None,
ttl: 60 * 60 * 24, // 1 day
dht_config: DHTConfig::default(),
key_material: None,
store: None,
propagation_interval: 60 * 60 * 24, // 1 day
}
}
}
Expand All @@ -115,10 +145,18 @@ impl<'a> Default for NameSystemBuilder<'a> {
mod tests {
use super::*;
use noosphere_core::authority::generate_ed25519_key;
use noosphere_storage::{
db::SphereDb,
memory::{MemoryStorageProvider, MemoryStore},
};
use tokio;

#[test]
fn test_name_system_builder() -> Result<(), anyhow::Error> {
#[tokio::test]
async fn test_name_system_builder() -> Result<(), anyhow::Error> {
let key_material = generate_ed25519_key();
let store = SphereDb::new(&MemoryStorageProvider::default())
.await
.unwrap();
let bootstrap_peers: Vec<Multiaddr> = vec![
"/ip4/127.0.0.50/tcp/33333/p2p/12D3KooWH8WgH9mgbMXrKX4veokUznvEn6Ycwg4qaGNi83nLkoUK"
.parse()?,
Expand All @@ -129,15 +167,16 @@ mod tests {
let ns = NameSystemBuilder::default()
.listening_port(30000)
.key_material(&key_material)
.store(&store)
.bootstrap_peers(&bootstrap_peers)
.bootstrap_interval(33)
.peer_dialing_interval(11)
.query_timeout(22)
.ttl(3600)
.propagation_interval(3600)
.build()?;

assert_eq!(ns.key_material.0.as_ref(), key_material.0.as_ref());
assert_eq!(ns.ttl, 3600);
assert_eq!(ns._propagation_interval, 3600);
assert_eq!(ns.bootstrap_peers.as_ref().unwrap().len(), 2);
assert_eq!(ns.bootstrap_peers.as_ref().unwrap()[0], bootstrap_peers[0],);
assert_eq!(ns.bootstrap_peers.as_ref().unwrap()[1], bootstrap_peers[1]);
Expand All @@ -149,9 +188,15 @@ mod tests {
assert_eq!(ns.dht_config.peer_dialing_interval, 11);
assert_eq!(ns.dht_config.query_timeout, 22);

if let Ok(_) = NameSystemBuilder::default().build() {
if let Ok(_) = NameSystemBuilder::default().store(&store).build() {
panic!("key_material required.");
}
if let Ok(_) = NameSystemBuilder::<MemoryStore>::default()
.key_material(&key_material)
.build()
{
panic!("store required.");
}
Ok(())
}
}
25 changes: 16 additions & 9 deletions rust/noosphere-ns/src/dht/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,17 @@ impl DHTProcessor {
}
QueryResult::GetRecord(Err(e)) => {
if let Some(message) = self.requests.remove(&id) {
message.respond(Err(DHTError::from(e)));
match e {
kad::GetRecordError::NotFound { key, .. } => {
// Not finding a record is not an `Err` response,
// but simply a successful query with a `None` result.
message.respond(Ok(DHTResponse::GetRecord {
name: key.to_vec(),
value: None,
}))
}
e @ _ => message.respond(Err(DHTError::from(e))),
};
}
}
QueryResult::PutRecord(Ok(kad::PutRecordOk { key })) => {
Expand Down Expand Up @@ -348,15 +358,12 @@ impl DHTProcessor {
} => {}
kad::InboundRequest::PutRecord { source, record, .. } => match record {
Some(rec) => {
if self
.swarm
.behaviour_mut()
.kad
.store_mut()
.put(rec.clone())
.is_err()
if let Err(e) = self.swarm.behaviour_mut().kad.store_mut().put(rec.clone())
{
warn!("InboundRequest::PutRecord failed: {:?} {:?}", rec, source);
warn!(
"InboundRequest::PutRecord failed: {:?} {:?}, {}",
rec, source, e
);
}
}
None => warn!("InboundRequest::PutRecord failed; empty record"),
Expand Down
4 changes: 4 additions & 0 deletions rust/noosphere-ns/src/dht/swarm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ impl DHTBehaviour {
let kad = {
let mut cfg = KademliaConfig::default();
cfg.set_query_timeout(Duration::from_secs(config.query_timeout.into()));
// By default, all records from peers are automatically stored.
// `FilterBoth` means it's the Kademlia behaviour handler's responsibility
// to determine whether or not Provider records and KV records ("both") get stored,
// where we implement logic to validate/prune incoming records.
cfg.set_record_filtering(KademliaStoreInserts::FilterBoth);

// TODO(#99): Use SphereFS storage
Expand Down
19 changes: 5 additions & 14 deletions rust/noosphere-ns/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
#[cfg(not(target_arch = "wasm32"))]
#![cfg(not(target_arch = "wasm32"))]

#[macro_use]
extern crate tracing;

#[cfg(not(target_arch = "wasm32"))]
pub mod dht;

#[cfg(not(target_arch = "wasm32"))]
mod builder;
#[cfg(not(target_arch = "wasm32"))]
pub mod dht;
mod name_system;
#[cfg(not(target_arch = "wasm32"))]
mod records;
pub mod utils;

#[cfg(not(target_arch = "wasm32"))]
pub use builder::NameSystemBuilder;
#[cfg(not(target_arch = "wasm32"))]
pub use cid::Cid;
#[cfg(not(target_arch = "wasm32"))]
pub use libp2p::Multiaddr;
#[cfg(not(target_arch = "wasm32"))]
pub use libp2p::multiaddr;
pub use name_system::NameSystem;
#[cfg(not(target_arch = "wasm32"))]
pub use records::NSRecord;
Loading

0 comments on commit ccc027f

Please sign in to comment.