use super::manager::{RdfEventsStore, RdfStoreError};
use super::prelude::*;
use super::util::{subl, subnn};
use crate::niri::{FromNamedNode, ToNamedNode};
use crate::querydb::nrq_get;
use nostr::PublicKey;
use oxigraph::model::vocab::rdf;
use rand_09::seq::IndexedRandom;
use std::collections::HashSet;
use std::sync::Arc;
use std::thread::sleep;
use std::time::Duration;
pub enum TrustLevel {
DontKnow,
NoTrust,
Marginal,
Full,
}
#[derive(PartialEq)]
pub enum TrustType {
Direct,
Indirect,
}
#[derive(PartialEq)]
pub enum TrustRingRadiusAlgo {
Linear,
Double,
Power(u8),
}
pub const PRED_WOT_TRUSTS: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/trusts");
pub const PRED_WOT_TRUSTS_INDIRECT: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/trusts_indirect");
pub const PRED_WOT_TRUST_LEVEL: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/trust_level");
pub const PRED_WOT_TRUST_SCORE: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/trust_score");
pub const PRED_WOT_RING: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/wot_ring");
pub const RING_RDF_TYPE: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing");
pub const PRED_RING_CREATOR: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#creator");
pub const PRED_RING_CENTER: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#center");
pub const PRED_RING_RADIUS: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#radius");
pub const PRED_RING_CREATED_AT: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#created_at");
pub const PRED_RING_COLOR_HEX: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#color_hex");
pub const PRED_RING_KIND: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#kind");
pub const PRED_RING_PASSES: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#passes");
pub const PRED_RING_FROZEN: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#frozen");
pub const PRED_RING_FROZEN_AT: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#frozen_at");
pub const PRED_RING_PCL: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#pcl");
pub const PRED_RING_BELONGS_TO: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#belongs_to");
pub const PRED_RING_PART_OF: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#part_of");
pub const PRED_RING_MEMBER_OF: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#member_of");
pub const PRED_IN_RING: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#in_ring");
pub const PRED_RING_CONTAINS: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#contains");
pub const PRED_RING_SCORED: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRing#scored");
pub const PRED_CCIRCLE_RADIUS: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRingCircle#radius");
pub const PRED_CCIRCLE_INDEX: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRingCircle#index");
pub const PRED_CCIRCLE_WEIGHT: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked("http://nostralink.org/TrustRingCircle#weight");
pub const PRED_CCIRCLE_COLOR_HEX: NamedNodeRef<'static> =
NamedNodeRef::new_unchecked(
"http://nostralink.org/TrustRingCircle#color_hex",
);
#[derive(Debug, Clone)]
pub struct QueryRings;
#[derive(Debug, Clone)]
pub struct QueryRingCircles;
#[derive(Debug, Clone)]
pub struct QueryRingWeb;
#[derive(Debug, Clone)]
pub struct QueryRingNet;
pub trait WebOfTrust {
fn wot_trust(
&self,
truster: &PublicKey,
trusted: &PublicKey,
ttype: TrustType,
) -> Result<(), RdfStoreError>;
fn wot_score_change(
&self,
trusted: &PublicKey,
score: f32,
ring_node: Option<NamedNode>,
) -> Result<(), RdfStoreError>;
fn wot_does_trust(
&self,
truster: &PublicKey,
trusted: &PublicKey,
) -> Result<bool, RdfStoreError>;
fn wot_trusted_list(
&self,
truster: &PublicKey,
) -> Result<Vec<PublicKey>, RdfStoreError>;
fn wot_ring_create(
&self,
my_pubk: &PublicKey,
public_key: &PublicKey,
ring_radius: i32,
ring_color_hex: &str,
radius_algo: Option<TrustRingRadiusAlgo>,
kind: Option<i32>,
) -> Result<(), RdfStoreError>;
fn wot_rings_created_by(
&self,
pubk: &PublicKey,
) -> Result<Arc<TRdfResultSet<QueryRings>>, RdfStoreError>;
fn wot_rings_process(
&self,
my_pubk: &PublicKey,
) -> Result<HashSet<PublicKey>, RdfStoreError>;
fn wot_ring_extend(
&self,
ring_node: NamedNodeRef,
center_pubk: &PublicKey,
my_pubk: &PublicKey,
public_key: &PublicKey,
ring_radius: i32,
ring_ccircle_num: i32,
contacts_set: &mut HashSet<PublicKey>,
processed_keys: &mut Vec<String>,
max_contacts: usize
) -> Result<(), RdfStoreError>;
fn wot_ring_radius(
&self,
my_pubk: &PublicKey,
public_key: &PublicKey,
) -> Result<i32, RdfStoreError>;
fn wot_ring_members_from_pubk(
&self,
my_pubk: &PublicKey,
public_key: &PublicKey,
depth: u32,
) -> Result<Vec<PublicKey>, RdfStoreError>;
fn wot_ring_members(
&self,
ring_node: NamedNodeRef,
depth: u32,
) -> Result<Vec<PublicKey>, RdfStoreError>;
}
fn ring_ccircle_pred(depth: i32) -> NamedNode {
NamedNode::new_unchecked(format!(
"http://nostralink.org/TrustRing#ccircle_{}",
depth
))
}
fn ring_main_node(public_key: &PublicKey) -> Result<NamedNode, LDError> {
public_key.named_node_with("/ring", None)
}
fn ring_ccircle_node(
public_key: &PublicKey,
ccircle: i32,
) -> Result<NamedNode, LDError> {
public_key.named_node_with("/ring", Some(&ccircle.to_string()))
}
fn ring_ccircle_weight(ccircle_idx: i32) -> f32 {
(50 / (ccircle_idx + 1)) as f32
}
fn comp_score(
ccircle_radius: i32,
ccircle_weight: i32,
decay_factor: f32,
) -> f32 {
let radius_factor = ccircle_radius as f32;
let weight_factor = ccircle_weight as f32;
weight_factor * (-decay_factor * radius_factor).exp()
}
fn rand_color() -> Literal {
let colors = ["#7FFFD4", "#6495ED", "#DC143C", "#8FBC8F", "#008000", "#FF4500"];
match colors.choose(&mut rand_09::rng()) {
Some(c) => Literal::from(*c),
None => Literal::from("#000000"),
}
}
impl WebOfTrust for RdfEventsStore {
fn wot_rings_created_by(
&self,
creator_pubk: &PublicKey,
) -> Result<Arc<TRdfResultSet<QueryRings>>, RdfStoreError> {
let subs = [subnn("ring_creator", creator_pubk.named_node()?)?];
self.run_query_typed(&nrq_get("wot_rings").unwrap(), None, subs, None)
}
fn wot_rings_process(
&self,
my_pubk: &PublicKey,
) -> Result<HashSet<PublicKey>, RdfStoreError> {
let mut contacts_set = HashSet::new();
let mut processed = vec![];
let rings = self.wot_rings_created_by(my_pubk)?;
for ring in &rings.rows {
if let Some(_frozen) = ring.get("frozen") {
continue;
}
let ring_node = ring.get_node("ring").unwrap();
let creator = ring.get_node("ring_creator").unwrap();
let center = ring.get_node("ring_center").unwrap();
let radius = ring
.get_string("ring_radius")
.unwrap()
.parse::<i32>()
.map_err(|_| RdfStoreError::QueryError)?;
self.wot_ring_extend(
ring_node.as_ref(),
¢er.from_named_node()?,
&creator.from_named_node()?,
¢er.from_named_node()?,
radius,
0,
&mut contacts_set,
&mut processed,
64
)?;
}
Ok(contacts_set)
}
fn wot_does_trust(
&self,
truster: &PublicKey,
trusted: &PublicKey,
) -> Result<bool, RdfStoreError> {
let truster_node = truster.named_node()?;
let trusted_node = trusted.named_node()?;
let results = self
.store
.quads_for_pattern(
Some((&truster_node).into()),
Some(PRED_WOT_TRUSTS),
Some((&trusted_node).into()),
None,
)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| RdfStoreError::QueryError)?;
Ok(!results.is_empty())
}
fn wot_trust(
&self,
truster: &PublicKey,
trusted: &PublicKey,
ttype: TrustType,
) -> Result<(), RdfStoreError> {
let truster_node = truster.named_node()?;
let trusted_node = trusted.named_node()?;
let pred = match ttype {
TrustType::Direct => PRED_WOT_TRUSTS,
TrustType::Indirect => PRED_WOT_TRUSTS_INDIRECT,
};
if ttype == TrustType::Direct {
self.store.remove(QuadRef::new(
&truster_node,
PRED_WOT_TRUSTS_INDIRECT,
&trusted_node,
&GraphName::DefaultGraph,
))?;
}
let trust_quad = QuadRef::new(
&truster_node,
pred,
&trusted_node,
&GraphName::DefaultGraph,
);
self.store
.insert(trust_quad)
.map_err(|_| RdfStoreError::QuadInsertError)?;
Ok(())
}
fn wot_trusted_list(
&self,
truster: &PublicKey,
) -> Result<Vec<PublicKey>, RdfStoreError> {
let truster_node = truster.named_node()?;
let results = self
.store
.quads_for_pattern(
Some((&truster_node).into()),
Some(PRED_WOT_TRUSTS),
None,
None,
)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| RdfStoreError::QueryError)?;
let trusted_keys: Vec<PublicKey> = results
.iter()
.filter_map(|quad| match &quad.object {
Term::NamedNode(node) => node.from_named_node().ok(),
_ => None,
})
.collect();
Ok(trusted_keys)
}
fn wot_score_change(
&self,
trusted: &PublicKey,
score: f32,
ring_node: Option<NamedNode>,
) -> Result<(), RdfStoreError> {
let trusted_node = trusted.named_node()?;
if let Some(node) = ring_node {
if self
.store
.quads_for_pattern(
Some((&node).into()),
Some(PRED_RING_SCORED),
Some((&trusted_node).into()),
None,
)
.count()
> 0
{
return Ok(());
}
self.store.insert(QuadRef::new(
&node,
PRED_RING_SCORED,
&trusted_node,
&GraphName::DefaultGraph,
))?;
}
let mut cur_score = self
.store
.quads_for_pattern(
Some((&trusted_node).into()),
Some(PRED_WOT_TRUST_SCORE),
None,
None,
)
.filter_map(|quad| match &quad.ok()?.object {
Term::Literal(lit) => lit.value().parse::<f32>().ok(),
_ => None,
})
.next()
.unwrap_or(0.0);
self.store.remove(QuadRef::new(
&trusted_node,
PRED_WOT_TRUST_SCORE,
&Literal::from(cur_score),
&GraphName::DefaultGraph,
))?;
cur_score += score;
self.store
.insert(QuadRef::new(
&trusted_node,
PRED_WOT_TRUST_SCORE,
&Literal::from(cur_score),
&GraphName::DefaultGraph,
))
.map_err(|_| RdfStoreError::QuadInsertError)?;
Ok(())
}
fn wot_ring_create(
&self,
my_pubk: &PublicKey,
public_key: &PublicKey,
ring_radius: i32,
ring_color_hex: &str,
radius_algo: Option<TrustRingRadiusAlgo>,
kind: Option<i32>,
) -> Result<(), RdfStoreError> {
if !(0..=10).contains(&ring_radius) {
return Err(RdfStoreError::InvalidFilterValue);
}
let ring_radius_lit = Literal::from(ring_radius);
let public_key_node = public_key.named_node()?;
let ring_node = ring_main_node(public_key)?;
if self
.store
.quads_for_pattern(Some((&ring_node).into()), None, None, None)
.count()
!= 0
{
let _previous_radius = self
.literal_for_subject_predicate(
(&ring_node).into(),
PRED_RING_RADIUS,
)?
.value()
.parse::<i32>()
.unwrap_or(0);
self.rm_quads_pattern(Some((&ring_node).into()), None, None, None)?;
}
self.wot_trust(my_pubk, public_key, TrustType::Direct)?;
self.store.insert(QuadRef::new(
&ring_node,
rdf::TYPE,
RING_RDF_TYPE,
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ring_node,
PRED_RING_CREATOR,
&my_pubk.named_node()?,
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ring_node,
PRED_RING_COLOR_HEX,
&Literal::from(ring_color_hex),
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ring_node,
PRED_RING_CENTER,
&public_key_node,
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ring_node,
PRED_RING_RADIUS,
&ring_radius_lit,
&GraphName::DefaultGraph,
))?;
if let Some(rk) = kind {
self.store.insert(QuadRef::new(
&ring_node,
PRED_RING_KIND,
&Literal::from(rk),
&GraphName::DefaultGraph,
))?;
}
for idx in 0..ring_radius {
let ccircle_node = ring_ccircle_node(public_key, idx)?;
self.store.insert(QuadRef::new(
&ring_node,
&ring_ccircle_pred(idx),
&ccircle_node,
&GraphName::DefaultGraph,
))?;
let cradius = match radius_algo {
Some(TrustRingRadiusAlgo::Linear) | None => idx + 1,
Some(TrustRingRadiusAlgo::Double) => (idx + 1) * 2,
Some(TrustRingRadiusAlgo::Power(power)) => {
(idx + 1).pow(power.into())
}
};
self.store.insert(QuadRef::new(
&ccircle_node,
PRED_CCIRCLE_RADIUS,
&Literal::from(cradius),
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ccircle_node,
PRED_CCIRCLE_WEIGHT,
&Literal::from(ring_ccircle_weight(idx)),
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ccircle_node,
PRED_CCIRCLE_INDEX,
&Literal::from(idx),
&GraphName::DefaultGraph,
))?;
self.store.insert(QuadRef::new(
&ccircle_node,
PRED_CCIRCLE_COLOR_HEX,
&rand_color(),
&GraphName::DefaultGraph,
))?;
}
Ok(())
}
fn wot_ring_extend(
&self,
ring_node: NamedNodeRef,
center_pubk: &PublicKey,
my_pubk: &PublicKey,
public_key: &PublicKey,
ring_radius: i32,
ring_ccircle_num: i32,
contacts_set: &mut HashSet<PublicKey>,
processed_keys: &mut Vec<String>,
max_contacts: usize
) -> Result<(), RdfStoreError> {
if !(0..=10).contains(&ring_radius) {
return Err(RdfStoreError::InvalidFilterValue);
}
let public_key_node = public_key.named_node()?;
let ccircle_node = ring_ccircle_node(center_pubk, ring_ccircle_num)?;
let ccircle_radius = self
.literal_for_subject_predicate(
(&ccircle_node).into(),
PRED_CCIRCLE_RADIUS,
)?
.value()
.parse::<i32>()
.unwrap_or(1);
let ccircle_weight = self
.literal_for_subject_predicate(
(&ccircle_node).into(),
PRED_CCIRCLE_WEIGHT,
)?
.value()
.parse::<i32>()
.unwrap_or(1);
let mut contained = false;
for cidx in 0..ring_ccircle_num {
if self
.store
.quads_for_pattern(
Some((&ring_ccircle_node(center_pubk, cidx)?).into()),
Some(PRED_RING_CONTAINS),
Some((&public_key_node).into()),
None,
)
.count()
> 0
{
contained = true;
}
}
if !contained {
self.store.insert(QuadRef::new(
&ccircle_node,
PRED_RING_CONTAINS,
&public_key_node,
&GraphName::DefaultGraph,
))?;
}
self.wot_score_change(
public_key,
comp_score(ccircle_radius, ccircle_weight, 0.5),
Some(ring_node.into()),
)?;
if self
.store
.quads_for_pattern(
Some((ring_node).into()),
Some(PRED_RING_PCL),
Some((&public_key_node).into()),
None,
)
.count()
== 0
{
contacts_set.insert(*public_key);
}
if ring_ccircle_num < ring_radius {
if let Ok((_event_id, contacts)) =
self.get_contacts_w_event_id(public_key, max_contacts)
{
if !contacts.is_empty() {
for contact in contacts {
self.wot_trust(my_pubk, &contact, TrustType::Indirect)?;
if processed_keys.contains(&contact.to_hex()) {
continue;
}
processed_keys.push(contact.to_hex());
sleep(Duration::from_millis(50));
self.wot_ring_extend(
ring_node,
center_pubk,
my_pubk,
&contact,
ring_radius,
ring_ccircle_num + 1,
contacts_set,
processed_keys,
max_contacts / 2
)?;
}
self.store.insert(QuadRef::new(
ring_node,
PRED_RING_PCL,
&public_key_node,
&GraphName::DefaultGraph,
))?;
}
}
}
processed_keys.push(public_key.to_hex());
Ok(())
}
fn wot_ring_radius(
&self,
my_pubk: &PublicKey,
public_key: &PublicKey,
) -> Result<i32, RdfStoreError> {
let subs = [
subnn("ring_creator", my_pubk.named_node()?)?,
subnn("ring_center", public_key.named_node()?)?,
];
match self
.run_query(&nrq_get("wot_rings").unwrap(), subs, None)?
.rows
.first()
{
Some(row) => match row.get("ring_radius") {
Some(cell) => cell.to_int().ok_or(RdfStoreError::QueryError),
None => Err(RdfStoreError::QueryError),
},
None => Err(RdfStoreError::QueryError),
}
}
fn wot_ring_members(
&self,
ring_node: NamedNodeRef,
depth: u32,
) -> Result<Vec<PublicKey>, RdfStoreError> {
let mut subs = vec![
subnn("ring", ring_node.into())?,
subl("max_depth", Literal::from(depth))?,
];
for idx in 0..depth {
subs.push(subl(
&format!("include_ccircle_{idx}"),
Literal::from(1),
)?);
}
Ok(self
.run_query_typed(
&nrq_get("wot_ring_web_nometa")?,
None,
subs,
None,
)?
.rows
.clone()
.into_iter()
.map(|row: TRdfResultRow<QueryRings>| {
row.get_node("trusted_nip21")
.unwrap()
.from_named_node()
.unwrap()
})
.collect())
}
fn wot_ring_members_from_pubk(
&self,
my_pubk: &PublicKey,
public_key: &PublicKey,
depth: u32,
) -> Result<Vec<PublicKey>, RdfStoreError> {
let mut subs = vec![
subnn("ring_creator", my_pubk.named_node()?)?,
subnn("ring_center", public_key.named_node()?)?,
subl("max_depth", Literal::from(depth))?,
];
for idx in 0..depth {
subs.push(subl(
&format!("include_ccircle_{idx}"),
Literal::from(1),
)?);
}
Ok(self
.run_query_typed(
&nrq_get("wot_ring_web_nometa")?,
None,
subs,
None,
)?
.rows
.clone()
.into_iter()
.map(|row: TRdfResultRow<QueryRings>| {
row.get_node("trusted_nip21")
.unwrap()
.from_named_node()
.unwrap()
})
.collect())
}
}
impl RdfEventsStore {
#[allow(dead_code)]
fn get_contacts(
&self,
public_key: &PublicKey,
) -> Result<Vec<PublicKey>, RdfStoreError> {
let user_pk_hex = public_key.to_hex();
let subs = [subl("pubk", Literal::from(user_pk_hex))?];
Ok(self
.run_query(&nrq_get("contact_list").unwrap(), subs, None)?
.rows
.iter()
.filter_map(|row| {
row.get("contact_pubk").unwrap().value.to_public_key().ok()
})
.collect::<Vec<_>>())
}
fn get_contacts_w_event_id(
&self,
public_key: &PublicKey,
max_contacts: usize,
) -> Result<(String, Vec<PublicKey>), RdfStoreError> {
let user_pk_hex = public_key.to_hex();
let subs = [subl("pubk", Literal::from(user_pk_hex))?];
let results =
self.run_query(&nrq_get("contact_list_latest")?, subs, Some(format!("cl_{}", public_key.to_hex())))?;
match results.first() {
Some(row) => {
let event_id = row.get("event_id").unwrap().to_string();
let mut contacts = results
.rows
.iter()
.filter_map(|row| {
row.get("contact_pubk")
.unwrap()
.value
.to_public_key()
.ok()
})
.take(max_contacts)
.collect::<Vec<_>>();
contacts.dedup();
Ok((event_id, contacts))
}
None => Err(RdfStoreError::QueryError),
}
}
}