···1_A [gravitational slingshot](https://en.wikipedia.org/wiki/Gravity_assist) makes use of the gravity and relative movements of celestial bodies to accelerate a spacecraft and change its trajectory._
234-# Slingshot: edge record cache
56Applications in [ATProtocol](https://atproto.com/) store data in users' own [PDS](https://atproto.com/guides/self-hosting) (Personal Data Server), which are distributed across thousands of independently-run servers all over the world. Trying to access this data poses challenges for client applications:
7
···1_A [gravitational slingshot](https://en.wikipedia.org/wiki/Gravity_assist) makes use of the gravity and relative movements of celestial bodies to accelerate a spacecraft and change its trajectory._
234+# Slingshot: edge record and identity cache
56Applications in [ATProtocol](https://atproto.com/) store data in users' own [PDS](https://atproto.com/guides/self-hosting) (Personal Data Server), which are distributed across thousands of independently-run servers all over the world. Trying to access this data poses challenges for client applications:
7
+42-23
slingshot/src/identity.rs
···11/// 1. handle -> DID resolution: getRecord must accept a handle for `repo` param
12/// 2. DID -> PDS resolution: so we know where to getRecord
13/// 3. DID -> handle resolution: for bidirectional handle validation and in case we want to offer this
14-use std::time::Duration;
15use tokio::sync::Mutex;
16use tokio_util::sync::CancellationToken;
17···264 handle: &Handle,
265 ) -> Result<Option<Did>, IdentityError> {
266 let key = IdentityKey::Handle(handle.clone());
0267 let entry = self
268 .cache
269 .get_or_fetch(&key, {
270 let handle = handle.clone();
271 let resolver = self.handle_resolver.clone();
272 || async move {
273- match resolver.resolve(&handle).await {
274- Ok(did) => Ok(IdentityVal(UtcDateTime::now(), IdentityData::Did(did))),
275- Err(atrium_identity::Error::NotFound) => {
276- Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound))
277- }
00000278 Err(other) => {
279 log::debug!("other error resolving handle: {other:?}");
280- Err(IdentityError::ResolutionFailed(other))
281 }
282- }
000283 }
284 })
285 .await?;
···314 did: &Did,
315 ) -> Result<Option<PartialMiniDoc>, IdentityError> {
316 let key = IdentityKey::Did(did.clone());
0317 let entry = self
318 .cache
319 .get_or_fetch(&key, {
320 let did = did.clone();
321 let resolver = self.did_resolver.clone();
322 || async move {
323- match resolver.resolve(&did).await {
324- Ok(did_doc) => {
0325 // TODO: fix in atrium: should verify id is did
326- if did_doc.id != did.to_string() {
327- return Err(IdentityError::BadDidDoc(
328- "did doc's id did not match did".to_string(),
329- ));
330- }
331- let mini_doc = did_doc.try_into().map_err(IdentityError::BadDidDoc)?;
332- Ok(IdentityVal(UtcDateTime::now(), IdentityData::Doc(mini_doc)))
333- }
334- Err(atrium_identity::Error::NotFound) => {
335- Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound))
336- }
337- Err(other) => Err(IdentityError::ResolutionFailed(other)),
338- }
00000000339 }
340 })
341 .await?;
···11/// 1. handle -> DID resolution: getRecord must accept a handle for `repo` param
12/// 2. DID -> PDS resolution: so we know where to getRecord
13/// 3. DID -> handle resolution: for bidirectional handle validation and in case we want to offer this
14+use std::time::{Duration, Instant};
15use tokio::sync::Mutex;
16use tokio_util::sync::CancellationToken;
17···264 handle: &Handle,
265 ) -> Result<Option<Did>, IdentityError> {
266 let key = IdentityKey::Handle(handle.clone());
267+ metrics::counter!("slingshot_get_handle").increment(1);
268 let entry = self
269 .cache
270 .get_or_fetch(&key, {
271 let handle = handle.clone();
272 let resolver = self.handle_resolver.clone();
273 || async move {
274+ let t0 = Instant::now();
275+ let (res, success) = match resolver.resolve(&handle).await {
276+ Ok(did) => (
277+ Ok(IdentityVal(UtcDateTime::now(), IdentityData::Did(did))),
278+ "true",
279+ ),
280+ Err(atrium_identity::Error::NotFound) => (
281+ Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)),
282+ "false",
283+ ),
284 Err(other) => {
285 log::debug!("other error resolving handle: {other:?}");
286+ (Err(IdentityError::ResolutionFailed(other)), "false")
287 }
288+ };
289+ metrics::histogram!("slingshot_fetch_handle", "success" => success)
290+ .record(t0.elapsed());
291+ res
292 }
293 })
294 .await?;
···323 did: &Did,
324 ) -> Result<Option<PartialMiniDoc>, IdentityError> {
325 let key = IdentityKey::Did(did.clone());
326+ metrics::counter!("slingshot_get_did_doc").increment(1);
327 let entry = self
328 .cache
329 .get_or_fetch(&key, {
330 let did = did.clone();
331 let resolver = self.did_resolver.clone();
332 || async move {
333+ let t0 = Instant::now();
334+ let (res, success) = match resolver.resolve(&did).await {
335+ Ok(did_doc) if did_doc.id != did.to_string() => (
336 // TODO: fix in atrium: should verify id is did
337+ Err(IdentityError::BadDidDoc(
338+ "did doc's id did not match did".to_string(),
339+ )),
340+ "false",
341+ ),
342+ Ok(did_doc) => match did_doc.try_into() {
343+ Ok(mini_doc) => (
344+ Ok(IdentityVal(UtcDateTime::now(), IdentityData::Doc(mini_doc))),
345+ "true",
346+ ),
347+ Err(e) => (Err(IdentityError::BadDidDoc(e)), "false"),
348+ },
349+ Err(atrium_identity::Error::NotFound) => (
350+ Ok(IdentityVal(UtcDateTime::now(), IdentityData::NotFound)),
351+ "false",
352+ ),
353+ Err(other) => (Err(IdentityError::ResolutionFailed(other)), "false"),
354+ };
355+ metrics::histogram!("slingshot_fetch_did_doc", "success" => success)
356+ .record(t0.elapsed());
357+ res
358 }
359 })
360 .await?;
+13-4
slingshot/src/server.rs
···9use std::path::PathBuf;
10use std::str::FromStr;
11use std::sync::Arc;
012use tokio_util::sync::CancellationToken;
1314use poem::{
···609610 let at_uri = format!("at://{}/{}/{}", &*did, &*collection, &*rkey);
6110612 let fr = self
613 .cache
614 .get_or_fetch(&at_uri, {
615 let cid = cid.clone();
616 let repo_api = self.repo.clone();
617- || async move { repo_api.get_record(&did, &collection, &rkey, &cid).await }
0000000618 })
619 .await;
620···690 }
691692 // TODO
693- // #[oai(path = "/com.atproto.identity.resolveHandle", method = "get")]
694 // #[oai(path = "/com.atproto.identity.resolveDid", method = "get")]
695 // but these are both not specified to do bidirectional validation, which is what we want to offer
696 // com.atproto.identity.resolveIdentity seems right, but requires returning the full did-doc
···699 // handle -> verified did + pds url
700 //
701 // we could do horrible things and implement resolveIdentity with only a stripped-down fake did doc
702- // but this will *definitely* cause problems because eg. we're not currently storing pubkeys and
703- // those are a little bit important
0704}
705706#[derive(Debug, Clone, Serialize)]
···9use std::path::PathBuf;
10use std::str::FromStr;
11use std::sync::Arc;
12+use std::time::Instant;
13use tokio_util::sync::CancellationToken;
1415use poem::{
···610611 let at_uri = format!("at://{}/{}/{}", &*did, &*collection, &*rkey);
612613+ metrics::counter!("slingshot_get_record").increment(1);
614 let fr = self
615 .cache
616 .get_or_fetch(&at_uri, {
617 let cid = cid.clone();
618 let repo_api = self.repo.clone();
619+ || async move {
620+ let t0 = Instant::now();
621+ let res = repo_api.get_record(&did, &collection, &rkey, &cid).await;
622+ let success = if res.is_ok() { "true" } else { "false" };
623+ metrics::histogram!("slingshot_fetch_record", "success" => success)
624+ .record(t0.elapsed());
625+ res
626+ }
627 })
628 .await;
629···699 }
700701 // TODO
0702 // #[oai(path = "/com.atproto.identity.resolveDid", method = "get")]
703 // but these are both not specified to do bidirectional validation, which is what we want to offer
704 // com.atproto.identity.resolveIdentity seems right, but requires returning the full did-doc
···707 // handle -> verified did + pds url
708 //
709 // we could do horrible things and implement resolveIdentity with only a stripped-down fake did doc
710+ // but this will *definitely* cause problems probably
711+ //
712+ // resolveMiniDoc gets most of this well enough.
713}
714715#[derive(Debug, Clone, Serialize)]