From 74d8411d55e4878f0fac13fd91980d5b4b3548a8 Mon Sep 17 00:00:00 2001 From: Iris Date: Mon, 15 Apr 2024 10:13:19 +0200 Subject: [PATCH 1/6] feat: add ccip support to domain_to_addr --- src/config.rs | 68 +++++++ src/endpoints/domain_to_addr.rs | 328 ++++++++++++++++++++------------ src/endpoints/mod.rs | 2 +- src/models.rs | 8 + 4 files changed, 282 insertions(+), 124 deletions(-) diff --git a/src/config.rs b/src/config.rs index ad6f38c..b1901d1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use serde::de::{MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; use starknet::core::types::FieldElement; use std::collections::HashMap; @@ -66,26 +67,49 @@ pub_struct!(Clone, Debug; Altcoins { data: HashMap, }); +pub_struct!(Clone, Debug, Deserialize; Rpc { + url: String, +}); + +#[derive(Deserialize)] +struct TempOffchainResolver { + root_domain: String, + resolver_address: String, + uri: Vec, +} + +pub_struct!(Clone, Debug, Deserialize; OffchainResolver { + resolver_address: String, + uri: Vec, +}); + +#[derive(Debug, Clone)] +pub struct OffchainResolvers(HashMap); + #[derive(Deserialize)] struct RawConfig { server: Server, databases: Databases, + rpc: Rpc, contracts: Contracts, starkscan: Starkscan, custom_resolvers: HashMap>, solana: Solana, altcoins: Altcoins, + offchain_resolvers: OffchainResolvers, } pub_struct!(Clone, Deserialize; Config { server: Server, databases: Databases, + rpc: Rpc, contracts: Contracts, starkscan: Starkscan, custom_resolvers: HashMap>, reversed_resolvers: HashMap, solana: Solana, altcoins: Altcoins, + offchain_resolvers: OffchainResolvers, }); impl Altcoins { @@ -123,6 +147,48 @@ impl<'de> Deserialize<'de> for Altcoins { } } +impl OffchainResolvers { + pub fn get(&self, key: &str) -> Option<&OffchainResolver> { + self.0.get(key) + } +} + +impl<'de> Deserialize<'de> for OffchainResolvers { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct OffchainResolversVisitor; + + impl<'de> Visitor<'de> for OffchainResolversVisitor { + type Value = OffchainResolvers; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map of resolver addresses to OffchainResolvers") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut hash_map = HashMap::new(); + while let Some((_, temp_resolver)) = + map.next_entry::()? + { + let resolver = OffchainResolver { + resolver_address: temp_resolver.resolver_address, + uri: temp_resolver.uri, + }; + hash_map.insert(temp_resolver.root_domain, resolver); + } + Ok(OffchainResolvers(hash_map)) + } + } + + deserializer.deserialize_map(OffchainResolversVisitor) + } +} + impl From for Config { fn from(raw: RawConfig) -> Self { let mut reversed_resolvers = HashMap::new(); @@ -134,12 +200,14 @@ impl From for Config { Config { server: raw.server, databases: raw.databases, + rpc: raw.rpc, contracts: raw.contracts, starkscan: raw.starkscan, custom_resolvers: raw.custom_resolvers, reversed_resolvers, solana: raw.solana, altcoins: raw.altcoins, + offchain_resolvers: raw.offchain_resolvers, } } } diff --git a/src/endpoints/domain_to_addr.rs b/src/endpoints/domain_to_addr.rs index 06da9b1..c6f0aec 100644 --- a/src/endpoints/domain_to_addr.rs +++ b/src/endpoints/domain_to_addr.rs @@ -1,6 +1,6 @@ use crate::{ - models::AppState, - utils::{extract_prefix_and_root, get_error}, + models::{AppState, OffchainResolverHint}, + utils::{extract_prefix_and_root, get_error, to_hex}, }; use axum::{ extract::{Query, State}, @@ -10,7 +10,14 @@ use axum::{ use axum_auto_routes::route; use futures::StreamExt; use mongodb::{bson::doc, options::AggregateOptions}; +use reqwest::Url; use serde::{Deserialize, Serialize}; +use starknet::{ + core::types::{BlockId, BlockTag, FieldElement, FunctionCall}, + macros::selector, + providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider}, +}; +use starknet_id::encode; use std::sync::Arc; #[derive(Serialize)] @@ -62,152 +69,227 @@ pub async fn handler( } } - // native resolver None => { - let domains = state - .starknetid_db - .collection::("domains"); + match (&state.conf).offchain_resolvers.get(&root_domain) { + // offchain resolver + Some(offchain_resolver) => { + // query offchain_resolver uri + let url = format!("{}{}", offchain_resolver.uri[0], query.domain.clone()); + let client = reqwest::Client::new(); + match client + .get(&url) + .header("accept", "application/json") + .send() + .await + { + Ok(response) => match response.text().await { + Ok(text) => match serde_json::from_str::(&text) { + Ok(hints) => { + // Call the naming contract with the hints + let provider = JsonRpcClient::new(HttpTransport::new( + Url::parse(&state.conf.rpc.url).unwrap(), + )); + //encode domain + let trimmed_domain = query.domain.strip_suffix(".stark").unwrap_or(&query.domain); + let splitted_domain = trimmed_domain.split('.').collect::>(); + let encoded_domain : Vec = splitted_domain.iter().map(|part| encode(part).unwrap()).collect(); - let pipeline = [ - doc! { - "$match": doc! { - "_cursor.to": null, - "domain": query.domain.clone() - } - }, - doc! { - "$lookup": doc! { - "from": "id_user_data", - "let": doc! { - "userId": "$id" - }, - "pipeline": [ - doc! { - "$match": doc! { - "_cursor.to": doc! { - "$exists": false - }, - "field": "0x000000000000000000000000000000000000000000000000737461726b6e6574", - "$expr": doc! { - "$eq": [ - "$id", - "$$userId" - ] + // build calldata + let mut calldata : Vec = vec![ + FieldElement::from(splitted_domain.len()), + ]; + calldata.extend(encoded_domain); + // add hint in calldata + calldata.push(FieldElement::from(4_u64)); + calldata.push(hints.address); + calldata.push(hints.r); + calldata.push(hints.s); + calldata.push(FieldElement::from(hints.max_validity)); + + let call_result = provider + .call( + FunctionCall { + contract_address: state.conf.contracts.naming, + entry_point_selector: selector!("domain_to_address"), + calldata, + }, + BlockId::Tag(BlockTag::Latest), + ) + .await; + + match call_result { + Ok(result) => { + // if call is successful we return the address + (StatusCode::OK, Json(DomainToAddrData { + addr: to_hex(&result[0]), + domain_expiry: None + })).into_response() + } + Err(e) => get_error(format!("{}", e)), } - } - } - ], - "as": "userData" - } - }, - doc! { - "$unwind": doc! { - "path": "$userData", - "preserveNullAndEmptyArrays": true + }, + Err(e) => get_error(format!( + "Failed to deserialize result from Starkscan API: {} for response: {}", + e, text + )), + }, + Err(e) => get_error(format!( + "Failed to get JSON response while fetching offchain resolver api: {}", + e + )), + }, + Err(e) => get_error(format!("Failed to fetch offchain resolver api: {}", e)), } - }, - doc! { - "$lookup": doc! { - "from": "id_owners", - "let": doc! { - "userId": "$id" + } + None => { + // native resolver + let domains = state + .starknetid_db + .collection::("domains"); + + let pipeline = [ + doc! { + "$match": doc! { + "_cursor.to": null, + "domain": query.domain.clone() + } }, - "pipeline": [ - doc! { - "$match": doc! { - "$or": [ - doc! { + doc! { + "$lookup": doc! { + "from": "id_user_data", + "let": doc! { + "userId": "$id" + }, + "pipeline": [ + doc! { + "$match": doc! { "_cursor.to": doc! { "$exists": false + }, + "field": "0x000000000000000000000000000000000000000000000000737461726b6e6574", + "$expr": doc! { + "$eq": [ + "$id", + "$$userId" + ] } - }, - doc! { - "_cursor.to": null } - ], - "$expr": doc! { - "$eq": [ - "$id", - "$$userId" - ] } - } + ], + "as": "userData" } - ], - "as": "ownerData" - } - }, - doc! { - "$unwind": doc! { - "path": "$ownerData", - "preserveNullAndEmptyArrays": true - } - }, - doc! { - "$project": doc! { - "addr": doc! { - "$cond": doc! { - "if": doc! { - "$and": [ - doc! { - "$ifNull": [ - "$legacy_address", - false - ] - }, - doc! { - "$ne": [ - "$legacy_address", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ] - } - ] + }, + doc! { + "$unwind": doc! { + "path": "$userData", + "preserveNullAndEmptyArrays": true + } + }, + doc! { + "$lookup": doc! { + "from": "id_owners", + "let": doc! { + "userId": "$id" }, - "then": "$legacy_address", - "else": doc! { + "pipeline": [ + doc! { + "$match": doc! { + "$or": [ + doc! { + "_cursor.to": doc! { + "$exists": false + } + }, + doc! { + "_cursor.to": null + } + ], + "$expr": doc! { + "$eq": [ + "$id", + "$$userId" + ] + } + } + } + ], + "as": "ownerData" + } + }, + doc! { + "$unwind": doc! { + "path": "$ownerData", + "preserveNullAndEmptyArrays": true + } + }, + doc! { + "$project": doc! { + "addr": doc! { "$cond": doc! { "if": doc! { - "$ifNull": [ - "$userData.data", - false + "$and": [ + doc! { + "$ifNull": [ + "$legacy_address", + false + ] + }, + doc! { + "$ne": [ + "$legacy_address", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } ] }, - "then": "$userData.data", - "else": "$ownerData.owner" + "then": "$legacy_address", + "else": doc! { + "$cond": doc! { + "if": doc! { + "$ifNull": [ + "$userData.data", + false + ] + }, + "then": "$userData.data", + "else": "$ownerData.owner" + } + } } - } + }, + "domain_expiry": "$expiry" } }, - "domain_expiry": "$expiry" - } - }, - ]; + ]; - // Execute the aggregation pipeline - let cursor: Result, &str> = domains - .aggregate(pipeline, AggregateOptions::default()) - .await - .map_err(|_| "Error while executing aggregation pipeline"); + // Execute the aggregation pipeline + let cursor: Result, &str> = domains + .aggregate(pipeline, AggregateOptions::default()) + .await + .map_err(|_| "Error while executing aggregation pipeline"); - match cursor { - Ok(mut cursor) => { - while let Some(result) = cursor.next().await { - return match result { - Ok(doc) => { - let addr = doc.get_str("addr").unwrap_or_default().to_owned(); - let domain_expiry = doc.get_i64("domain_expiry").ok(); - let data = DomainToAddrData { - addr, - domain_expiry, + match cursor { + Ok(mut cursor) => { + while let Some(result) = cursor.next().await { + return match result { + Ok(doc) => { + let addr = + doc.get_str("addr").unwrap_or_default().to_owned(); + let domain_expiry = doc.get_i64("domain_expiry").ok(); + let data = DomainToAddrData { + addr, + domain_expiry, + }; + (StatusCode::OK, Json(data)).into_response() + } + Err(e) => get_error(format!("Error calling the db: {}", e)), }; - (StatusCode::OK, Json(data)).into_response() } - Err(e) => get_error(format!("Error calling the db: {}", e)), - }; + return get_error("No document found for the given domain".to_string()); + } + Err(e) => get_error(format!("Error accessing the database: {}", e)), } - return get_error("No document found for the given domain".to_string()); } - Err(e) => return get_error(format!("Error accessing the database: {}", e)), } } } diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 42d14df..4d599a6 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -5,12 +5,12 @@ pub mod addr_to_external_domains; pub mod addr_to_full_ids; pub mod addr_to_token_id; pub mod addrs_to_domains; -pub mod get_altcoin_quote; pub mod crosschain; pub mod data_to_ids; pub mod domain_to_addr; pub mod domain_to_data; pub mod galxe; +pub mod get_altcoin_quote; pub mod id_to_data; pub mod referral; pub mod renewal; diff --git a/src/models.rs b/src/models.rs index 9117769..3a5ae76 100644 --- a/src/models.rs +++ b/src/models.rs @@ -146,3 +146,11 @@ pub struct State { pub struct States { pub states: HashMap, } + +#[derive(Deserialize, Debug)] +pub struct OffchainResolverHint { + pub address: FieldElement, + pub r: FieldElement, + pub s: FieldElement, + pub max_validity: u64, +} From e93c2315fbb0104a6537f9804c0bf0dbe17ee7af Mon Sep 17 00:00:00 2001 From: Iris Date: Mon, 15 Apr 2024 10:14:12 +0200 Subject: [PATCH 2/6] fix: update config template --- config.template.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.template.toml b/config.template.toml index b84f1d2..6c2ca46 100644 --- a/config.template.toml +++ b/config.template.toml @@ -17,6 +17,9 @@ old_verifier = "0xXXXXXXXXXXXX" pop_verifier = "0xXXXXXXXXXXXX" pp_verifier = "0xXXXXXXXXXXXX" +[rpc] +url = "xxxxxx" + [starkscan] api_url = "https://api-testnet.starkscan.co/api/v0" api_key = "xxxxxx" From d64a6e7442d5a14f39e2b07c20f90caf29c3a309 Mon Sep 17 00:00:00 2001 From: Iris Date: Mon, 15 Apr 2024 10:15:08 +0200 Subject: [PATCH 3/6] fix: update config template with offchain resolvers --- config.template.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config.template.toml b/config.template.toml index 6c2ca46..49d0416 100644 --- a/config.template.toml +++ b/config.template.toml @@ -63,3 +63,12 @@ min_price = 2000 max_price = 10000 decimals = 18 max_quote_validity = 600 + +[offchain_resolvers] + +[offchain_resolvers.NOTION] +root_domain = "notion.stark" +resolver_address = "0x153be68cf8fc71138610811dd2b4fa481eb99f3eedcb3fce7369569055be275" +uri = [ + "https://sepolia.api.ccip-demo.starknet.id/resolve?domain=", +] \ No newline at end of file From 9784388cdbe55959fa3eccde56c904e19342493f Mon Sep 17 00:00:00 2001 From: Iris Date: Mon, 15 Apr 2024 14:48:27 +0200 Subject: [PATCH 4/6] fix: result for a root domain that has an offchain resolver enabled --- src/endpoints/domain_to_addr.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/endpoints/domain_to_addr.rs b/src/endpoints/domain_to_addr.rs index c6f0aec..dd1f1dd 100644 --- a/src/endpoints/domain_to_addr.rs +++ b/src/endpoints/domain_to_addr.rs @@ -70,9 +70,12 @@ pub async fn handler( } None => { - match (&state.conf).offchain_resolvers.get(&root_domain) { + match ( + !prefix.is_empty(), + (&state.conf).offchain_resolvers.get(&root_domain), + ) { // offchain resolver - Some(offchain_resolver) => { + (true, Some(offchain_resolver)) => { // query offchain_resolver uri let url = format!("{}{}", offchain_resolver.uri[0], query.domain.clone()); let client = reqwest::Client::new(); @@ -128,10 +131,7 @@ pub async fn handler( Err(e) => get_error(format!("{}", e)), } }, - Err(e) => get_error(format!( - "Failed to deserialize result from Starkscan API: {} for response: {}", - e, text - )), + Err(_) => get_error(text.to_string()), }, Err(e) => get_error(format!( "Failed to get JSON response while fetching offchain resolver api: {}", @@ -141,7 +141,7 @@ pub async fn handler( Err(e) => get_error(format!("Failed to fetch offchain resolver api: {}", e)), } } - None => { + _ => { // native resolver let domains = state .starknetid_db From 5c04076f791ef9f0ddc1e43f74b41826d2b0346b Mon Sep 17 00:00:00 2001 From: Iris Date: Tue, 16 Apr 2024 17:26:53 +0200 Subject: [PATCH 5/6] feat: fetch offchain_resolvers indexed --- config.template.toml | 5 +- src/config.rs | 11 +-- src/endpoints/domain_to_addr.rs | 13 ++-- src/main.rs | 17 +++++ src/models.rs | 11 ++- src/resolving.rs | 118 +++++++++++++++++++++++++++++++- src/utils.rs | 4 ++ 7 files changed, 162 insertions(+), 17 deletions(-) diff --git a/config.template.toml b/config.template.toml index 49d0416..4324f07 100644 --- a/config.template.toml +++ b/config.template.toml @@ -17,8 +17,9 @@ old_verifier = "0xXXXXXXXXXXXX" pop_verifier = "0xXXXXXXXXXXXX" pp_verifier = "0xXXXXXXXXXXXX" -[rpc] -url = "xxxxxx" +[variables] +rpc_url = "xxxxxx" +refresh_delay = 60 # in seconds [starkscan] api_url = "https://api-testnet.starkscan.co/api/v0" diff --git a/src/config.rs b/src/config.rs index b1901d1..ec99064 100644 --- a/src/config.rs +++ b/src/config.rs @@ -67,8 +67,9 @@ pub_struct!(Clone, Debug; Altcoins { data: HashMap, }); -pub_struct!(Clone, Debug, Deserialize; Rpc { - url: String, +pub_struct!(Clone, Debug, Deserialize; Variables { + rpc_url: String, + refresh_delay: f64, }); #[derive(Deserialize)] @@ -90,7 +91,7 @@ pub struct OffchainResolvers(HashMap); struct RawConfig { server: Server, databases: Databases, - rpc: Rpc, + variables: Variables, contracts: Contracts, starkscan: Starkscan, custom_resolvers: HashMap>, @@ -102,7 +103,7 @@ struct RawConfig { pub_struct!(Clone, Deserialize; Config { server: Server, databases: Databases, - rpc: Rpc, + variables: Variables, contracts: Contracts, starkscan: Starkscan, custom_resolvers: HashMap>, @@ -200,7 +201,7 @@ impl From for Config { Config { server: raw.server, databases: raw.databases, - rpc: raw.rpc, + variables: raw.variables, contracts: raw.contracts, starkscan: raw.starkscan, custom_resolvers: raw.custom_resolvers, diff --git a/src/endpoints/domain_to_addr.rs b/src/endpoints/domain_to_addr.rs index dd1f1dd..b6c6a2f 100644 --- a/src/endpoints/domain_to_addr.rs +++ b/src/endpoints/domain_to_addr.rs @@ -1,5 +1,7 @@ use crate::{ models::{AppState, OffchainResolverHint}, + resolving::is_offchain_resolver, + root, utils::{extract_prefix_and_root, get_error, to_hex}, }; use axum::{ @@ -70,12 +72,9 @@ pub async fn handler( } None => { - match ( - !prefix.is_empty(), - (&state.conf).offchain_resolvers.get(&root_domain), - ) { + match is_offchain_resolver(prefix, root_domain, &state) { // offchain resolver - (true, Some(offchain_resolver)) => { + Some(offchain_resolver) => { // query offchain_resolver uri let url = format!("{}{}", offchain_resolver.uri[0], query.domain.clone()); let client = reqwest::Client::new(); @@ -90,7 +89,7 @@ pub async fn handler( Ok(hints) => { // Call the naming contract with the hints let provider = JsonRpcClient::new(HttpTransport::new( - Url::parse(&state.conf.rpc.url).unwrap(), + Url::parse(&state.conf.variables.rpc_url).unwrap(), )); //encode domain let trimmed_domain = query.domain.strip_suffix(".stark").unwrap_or(&query.domain); @@ -141,7 +140,7 @@ pub async fn handler( Err(e) => get_error(format!("Failed to fetch offchain resolver api: {}", e)), } } - _ => { + None => { // native resolver let domains = state .starknetid_db diff --git a/src/main.rs b/src/main.rs index f240bd3..a51061d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,12 +9,16 @@ mod utils; use axum::{http::StatusCode, Router}; use axum_auto_routes::route; use mongodb::{bson::doc, options::ClientOptions, Client}; +use std::collections::HashMap; use std::sync::Arc; use std::{net::SocketAddr, sync::Mutex}; +use tokio::time::{sleep, Duration}; use utils::WithState; use tower_http::cors::{Any, CorsLayer}; +use crate::resolving::update_offchain_resolvers; + lazy_static::lazy_static! { pub static ref ROUTE_REGISTRY: Mutex>> = Mutex::new(Vec::new()); } @@ -48,6 +52,7 @@ async fn main() { .unwrap() .database(&conf.databases.sales.name), states, + dynamic_offchain_resolvers: Arc::new(Mutex::new(HashMap::new())), }); // we will know by looking at the log number which db has an issue for db in [&shared_state.starknetid_db, &shared_state.sales_db] { @@ -59,6 +64,18 @@ async fn main() { } } + // refresh offchain resolvers from indexed data + let refresh_state = shared_state.clone(); + tokio::spawn(async move { + loop { + update_offchain_resolvers(&refresh_state).await; + sleep(Duration::from_millis( + (conf.variables.refresh_delay * 1000.0) as u64, + )) + .await; + } + }); + let cors = CorsLayer::new().allow_headers(Any).allow_origin(Any); let app = ROUTE_REGISTRY .lock() diff --git a/src/models.rs b/src/models.rs index 3a5ae76..697d944 100644 --- a/src/models.rs +++ b/src/models.rs @@ -4,15 +4,22 @@ use mongodb::{ }; use starknet::core::types::FieldElement; -use crate::{config::Config, utils::to_hex}; +use crate::{ + config::{Config, OffchainResolver}, + utils::to_hex, +}; use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer}; -use std::collections::HashMap; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; pub struct AppState { pub conf: Config, pub starknetid_db: Database, pub sales_db: Database, pub states: States, + pub dynamic_offchain_resolvers: Arc>>, } fn serialize_felt(field_element: &FieldElement, serializer: S) -> Result diff --git a/src/resolving.rs b/src/resolving.rs index 6537d97..ea3444c 100644 --- a/src/resolving.rs +++ b/src/resolving.rs @@ -1,8 +1,14 @@ +use std::sync::Arc; + +use futures::StreamExt; use mongodb::{ - bson::{doc, Document}, + bson::{doc, Bson, Document}, + options::AggregateOptions, Collection, }; +use crate::{config::OffchainResolver, models::AppState, utils::clean_string}; + pub async fn get_custom_resolver(domains: &Collection, domain: &str) -> Option { // Split the domain into parts let domain_parts: Vec<&str> = domain.split('.').collect(); @@ -48,3 +54,113 @@ pub async fn get_custom_resolver(domains: &Collection, domain: &str) - // If no custom resolver found None } + +pub async fn update_offchain_resolvers(state: &Arc) { + let offchain_resolvers = state + .starknetid_db + .collection::("offchain_resolvers"); + + let pipeline = [ + doc! { + "$match": doc! { + "_cursor.to": Bson::Null + } + }, + doc! { + "$lookup": doc! { + "from": "domains", + "let": doc! { + "local_resolver_contract": "$resolver_contract" + }, + "pipeline": [ + doc! { + "$match": doc! { + "$expr": doc! { + "$eq": [ + "$resolver", + "$$local_resolver_contract" + ] + }, + "_cursor.to": Bson::Null + } + } + ], + "as": "domainData" + } + }, + doc! { + "$unwind": doc! { + "path": "$domainData", + "preserveNullAndEmptyArrays": true + } + }, + doc! { + "$project": doc! { + "_id": 0, + "resolver_contract": "$resolver_contract", + "uri": "$uri", + "domain": "$domainData.domain", + } + }, + ]; + let aggregate_options = AggregateOptions::default(); + let cursor = offchain_resolvers + .aggregate(pipeline, aggregate_options) + .await; + match cursor { + Ok(mut cursor) => { + while let Some(doc) = cursor.next().await { + if let Ok(doc) = doc { + let domain = doc.get_str("domain").unwrap_or_default(); + if domain.is_empty() { + continue; + } + // values in config file override onchain events + match (&state.conf).offchain_resolvers.get(domain) { + Some(_) => continue, + None => { + let resolver = OffchainResolver { + resolver_address: doc + .get_str("resolver_contract") + .unwrap_or_default() + .to_owned(), + uri: vec![clean_string(doc.get_str("uri").unwrap_or_default())], + }; + state + .dynamic_offchain_resolvers + .lock() + .unwrap() + .insert(domain.to_owned(), resolver); + } + } + } + } + } + Err(err) => { + println!("Error while building offchain_resolver hashmap from collection offchain_resolvers: {}", err); + } + } +} + +pub fn is_offchain_resolver( + prefix: String, + root_domain: String, + state: &Arc, +) -> Option { + if prefix.is_empty() { + return None; + } + state + .conf + .offchain_resolvers + .get(&root_domain) + .cloned() + .or_else(|| { + state + .dynamic_offchain_resolvers + .lock() + .unwrap() + .get(&root_domain) + .cloned() + }) +} diff --git a/src/utils.rs b/src/utils.rs index 01e3bfa..d6f170f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -107,6 +107,10 @@ pub async fn fetch_img_url( .and_then(|v| v.as_str().map(ToString::to_string)) } +pub fn clean_string(input: &str) -> String { + input.chars().filter(|&c| c != '\0').collect() +} + // required for axum_auto_routes pub trait WithState: Send { fn to_router(self: Box, shared_state: Arc) -> Router; From f4711ad08d3e8e1c9134d4833ed7c06378f9d321 Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 18 Apr 2024 11:02:17 +0200 Subject: [PATCH 6/6] fix: add support for multiple uri & domains --- src/endpoints/domain_to_addr.rs | 5 +- src/resolving.rs | 86 ++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/src/endpoints/domain_to_addr.rs b/src/endpoints/domain_to_addr.rs index b6c6a2f..3e31ef9 100644 --- a/src/endpoints/domain_to_addr.rs +++ b/src/endpoints/domain_to_addr.rs @@ -1,7 +1,6 @@ use crate::{ models::{AppState, OffchainResolverHint}, - resolving::is_offchain_resolver, - root, + resolving::get_offchain_resolver, utils::{extract_prefix_and_root, get_error, to_hex}, }; use axum::{ @@ -72,7 +71,7 @@ pub async fn handler( } None => { - match is_offchain_resolver(prefix, root_domain, &state) { + match get_offchain_resolver(prefix, root_domain, &state) { // offchain resolver Some(offchain_resolver) => { // query offchain_resolver uri diff --git a/src/resolving.rs b/src/resolving.rs index ea3444c..486d310 100644 --- a/src/resolving.rs +++ b/src/resolving.rs @@ -63,7 +63,8 @@ pub async fn update_offchain_resolvers(state: &Arc) { let pipeline = [ doc! { "$match": doc! { - "_cursor.to": Bson::Null + "_cursor.to": Bson::Null, + "active": true } }, doc! { @@ -81,25 +82,20 @@ pub async fn update_offchain_resolvers(state: &Arc) { "$$local_resolver_contract" ] }, - "_cursor.to": Bson::Null + "_cursor.to": Bson::Null, } } ], "as": "domainData" } }, - doc! { - "$unwind": doc! { - "path": "$domainData", - "preserveNullAndEmptyArrays": true - } - }, doc! { "$project": doc! { "_id": 0, "resolver_contract": "$resolver_contract", "uri": "$uri", - "domain": "$domainData.domain", + "active": "$active", + "domains": "$domainData.domain", } }, ]; @@ -111,26 +107,60 @@ pub async fn update_offchain_resolvers(state: &Arc) { Ok(mut cursor) => { while let Some(doc) = cursor.next().await { if let Ok(doc) = doc { - let domain = doc.get_str("domain").unwrap_or_default(); - if domain.is_empty() { + let domains = doc.get_array("domains"); + let domains = match domains { + Ok(domains) => domains, + Err(err) => { + println!("Error while getting array of domains: {}", err); + continue; + } + }; + if domains.is_empty() { continue; } - // values in config file override onchain events - match (&state.conf).offchain_resolvers.get(domain) { - Some(_) => continue, - None => { - let resolver = OffchainResolver { - resolver_address: doc - .get_str("resolver_contract") - .unwrap_or_default() - .to_owned(), - uri: vec![clean_string(doc.get_str("uri").unwrap_or_default())], - }; - state - .dynamic_offchain_resolvers - .lock() - .unwrap() - .insert(domain.to_owned(), resolver); + for domain in domains { + let domain = match domain { + Bson::String(domain) => domain, + _ => { + println!("Error while getting domain: {:?}", domain); + continue; + } + }; + // values in config file override onchain events + match (&state.conf).offchain_resolvers.get(domain) { + Some(_) => continue, + None => { + let mut resolver_map = + state.dynamic_offchain_resolvers.lock().unwrap(); + match resolver_map.get(domain) { + Some(existing_resolvers) => { + // there is already a resolver for this domain + let new_uri = + clean_string(doc.get_str("uri").unwrap_or_default()); + // we check the uri is not already in the list + if !existing_resolvers.uri.contains(&new_uri) { + if let Some(existing_resolver) = + resolver_map.get_mut(domain) + { + existing_resolver.uri.push(new_uri); + } + } + } + None => { + // there is no resolver for this domain yet + let resolver = OffchainResolver { + resolver_address: doc + .get_str("resolver_contract") + .unwrap_or_default() + .to_owned(), + uri: vec![clean_string( + doc.get_str("uri").unwrap_or_default(), + )], + }; + resolver_map.insert(domain.to_owned(), resolver); + } + } + } } } } @@ -142,7 +172,7 @@ pub async fn update_offchain_resolvers(state: &Arc) { } } -pub fn is_offchain_resolver( +pub fn get_offchain_resolver( prefix: String, root_domain: String, state: &Arc,