Skip to content

Commit

Permalink
agent did api resolver cache
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed Aug 27, 2024
1 parent fea0535 commit 4fabf58
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 29 deletions.
2 changes: 1 addition & 1 deletion packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"@tbd54566975/dwn-sdk-js": "0.4.5",
"@web5/common": "1.0.0",
"@web5/crypto": "workspace:*",
"@web5/dids": "1.1.0",
"@web5/dids": "workspace:*",
"abstract-level": "1.0.4",
"ed25519-keygen": "0.4.11",
"isomorphic-ws": "^5.0.0",
Expand Down
55 changes: 55 additions & 0 deletions packages/agent/src/agent-did-resolver-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { DidResolutionResult, DidResolverCache, DidResolverCacheLevel, DidResolverCacheLevelParams } from "@web5/dids";
import { Web5PlatformAgent } from "./types/agent.js";


export class AgentDidResolverCache extends DidResolverCacheLevel implements DidResolverCache {

private _agent?: Web5PlatformAgent;

private _resolving: Record<string, boolean> = {};

constructor({ agent, db, location, ttl }: DidResolverCacheLevelParams & { agent?: Web5PlatformAgent }) {
super ({ db, location, ttl });
this._agent = agent;
}

get agent() {
if (!this._agent) {
throw new Error("Agent not initialized");
}
return this._agent;
}

set agent(agent: Web5PlatformAgent) {
this._agent = agent;
}

async get(did: string): Promise<DidResolutionResult | void> {
try {
const str = await this.cache.get(did);
const cachedResult = JSON.parse(str);
if (!this._resolving[did] && Date.now() >= cachedResult.ttlMillis) {
this._resolving[did] = true;
const list = await this.agent.identity.list();
if (this.agent.agentDid.uri === did || list.find(identity => identity.did.uri === did)) {
this.agent.did.resolve(did).then(result => {
if (!result.didResolutionMetadata.error) {
this.set(did, result);
}
}).finally(() => delete this._resolving[did])
}
else {
delete this._resolving[did];
this.cache.nextTick(() => this.cache.del(did));
return;
}
}
return cachedResult.value;
} catch(error: any) {
if (error.notFound) {
return;
}
throw error;
}
}
}
45 changes: 32 additions & 13 deletions packages/agent/src/did-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import type {
DidMetadata,
PortableDid,
DidMethodApi,
DidResolverCache,
DidDhtCreateOptions,
DidJwkCreateOptions,
DidResolutionResult,
DidResolutionOptions,
DidVerificationMethod,
DidUrlDereferencer,
DidResolver,
DidDereferencingOptions,
DidDereferencingResult,
DidResolverCache,
} from '@web5/dids';

import { BearerDid, Did, UniversalResolver } from '@web5/dids';
Expand All @@ -18,7 +22,7 @@ import type { AgentKeyManager } from './types/key-manager.js';
import type { ResponseStatus, Web5PlatformAgent } from './types/agent.js';

import { InMemoryDidStore } from './store-did.js';
import { DidResolverCacheMemory } from './prototyping/dids/resolver-cache-memory.js';
import { AgentDidResolverCache } from './agent-did-resolver-cache.js';

export enum DidInterface {
Create = 'Create',
Expand Down Expand Up @@ -84,11 +88,9 @@ export interface DidApiParams {
agent?: Web5PlatformAgent;

/**
* An optional `DidResolverCache` instance used for caching resolved DID documents.
*
* Providing a cache implementation can significantly enhance resolution performance by avoiding
* redundant resolutions for previously resolved DIDs. If omitted, a no-operation cache is used,
* which effectively disables caching.
* An optional `AgentDidResolverCache` instance used for caching resolved DID documents.
*
* If omitted, the default LevelDB and parameters are used to create a new cache instance.
*/
resolverCache?: DidResolverCache;

Expand All @@ -101,7 +103,7 @@ export function isDidRequest<T extends DidInterface>(
return didRequest.messageType === messageType;
}

export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> extends UniversalResolver {
export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> implements DidResolver, DidUrlDereferencer {
/**
* Holds the instance of a `Web5PlatformAgent` that represents the current execution context for
* the `AgentDidApi`. This agent is used to interact with other Web5 agent components. It's vital
Expand All @@ -110,20 +112,24 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager>
*/
private _agent?: Web5PlatformAgent;

private _cache: DidResolverCache;

private _didMethods: Map<string, DidMethodApi> = new Map();

private _resolver: UniversalResolver;

private _store: AgentDataStore<PortableDid>;

constructor({ agent, didMethods, resolverCache, store }: DidApiParams) {
if (!didMethods) {
throw new TypeError(`AgentDidApi: Required parameter missing: 'didMethods'`);
}

// Initialize the DID resolver with the given DID methods and resolver cache, or use a default
// in-memory cache if none is provided.
super({
didResolvers : didMethods,
cache : resolverCache ?? new DidResolverCacheMemory()
this._cache = resolverCache ?? new AgentDidResolverCache({ location: 'DATA/AGENT/DID_CACHE' });

this._resolver = new UniversalResolver({
cache: this._cache,
didResolvers: didMethods
});

this._agent = agent;
Expand Down Expand Up @@ -152,6 +158,11 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager>

set agent(agent: Web5PlatformAgent) {
this._agent = agent;

// AgentDidResolverCache should set the agent if it is the type of cache being used
if ('agent' in this._cache) {
this._cache.agent = agent;
}
}

public async create({
Expand Down Expand Up @@ -342,6 +353,14 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager>
throw new Error(`AgentDidApi: Unsupported request type: ${request.messageType}`);
}

public async resolve(didUri: string, options?: DidResolutionOptions): Promise<DidResolutionResult> {
return this._resolver.resolve(didUri, options);
}

public async dereference(didUrl: string, options?: DidDereferencingOptions): Promise<DidDereferencingResult> {
return this._resolver.dereference(didUrl);
}

private getMethod(methodName: string): DidMethodApi {
const didMethodApi = this._didMethods.get(methodName);

Expand Down
16 changes: 8 additions & 8 deletions packages/agent/src/identity-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,14 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
// Retrieve the list of Identities from the Agent's Identity store.
const storedIdentities = await this._store.list({ agent: this.agent, tenant });

const identities: BearerIdentity[] = [];

for (const metadata of storedIdentities) {
const identity = await this.get({ didUri: metadata.uri, tenant: metadata.tenant });
identities.push(identity!);
}

return identities;
const identities = await Promise.all(
storedIdentities.map(async metadata => {
console.log('metadata', metadata.uri);
return this.get({ didUri: metadata.uri, tenant: metadata.tenant });
})
);

return identities.filter(identity => typeof identity !== 'undefined') as BearerIdentity[];
}

public async manage({ portableIdentity }: {
Expand Down
1 change: 1 addition & 0 deletions packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type * from './types/permissions.js';
export type * from './types/sync.js';
export type * from './types/vc.js';

export * from './agent-did-resolver-cache.js';
export * from './bearer-identity.js';
export * from './cached-permissions.js';
export * from './crypto-api.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/dids/src/resolver/resolver-cache-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ type CachedDidResolutionResult = {
*/
export class DidResolverCacheLevel implements DidResolverCache {
/** The underlying LevelDB store used for caching. */
private cache;
protected cache;

/** The time-to-live for cache entries in milliseconds. */
private ttl: number;
protected ttl: number;

constructor({
db,
Expand Down
2 changes: 1 addition & 1 deletion packages/dids/src/resolver/universal-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class UniversalResolver implements DidResolver, DidUrlDereferencer {
/**
* A cache for storing resolved DID documents.
*/
private cache: DidResolverCache;
protected cache: DidResolverCache;

/**
* A map to store method resolvers against method names.
Expand Down
12 changes: 10 additions & 2 deletions packages/user-agent/src/user-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ProcessDwnRequest,
Web5PlatformAgent,
AgentPermissionsApi,
AgentDidResolverCache,
} from '@web5/agent';

import { LevelStore } from '@web5/common';
Expand Down Expand Up @@ -150,9 +151,11 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager

cryptoApi ??= new AgentCryptoApi();

const didResolverCache = new AgentDidResolverCache({ location: `${dataPath}/DID_RESOLVERCACHE` });

didApi ??= new AgentDidApi({
didMethods : [DidDht, DidJwk],
resolverCache : new DidResolverCacheLevel({ location: `${dataPath}/DID_RESOLVERCACHE` }),
resolverCache : didResolverCache,
store : new DwnDidStore()
});

Expand All @@ -171,7 +174,7 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager
syncApi ??= new AgentSyncApi({ syncEngine: new SyncEngineLevel({ dataPath }) });

// Instantiate the Agent using the provided or default components.
return new Web5UserAgent({
const web5USerAgent = new Web5UserAgent({
agentDid,
agentVault,
cryptoApi,
Expand All @@ -183,6 +186,11 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager
rpcClient,
syncApi
});

// Set the agent on the resolver cache
didResolverCache.agent = web5USerAgent;

return web5USerAgent;
}

public async firstLaunch(): Promise<boolean> {
Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4fabf58

Please sign in to comment.