From 022d4a03cbca98bf88e7285c19bdec9a4b18a67f Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Fri, 5 Jul 2024 14:13:07 -0700 Subject: [PATCH] Removed temporary hack --- src/dwn-server.ts | 47 ++++++++++------ src/storage.ts | 6 +- tests/scenarios/registration.spec.ts | 83 ++++++++++++---------------- tests/scenarios/web5-connect.spec.ts | 2 +- 4 files changed, 72 insertions(+), 66 deletions(-) diff --git a/src/dwn-server.ts b/src/dwn-server.ts index d7b480d..642dfb2 100644 --- a/src/dwn-server.ts +++ b/src/dwn-server.ts @@ -1,22 +1,32 @@ +import type { DidResolver } from '@web5/dids'; import type { EventStream } from '@tbd54566975/dwn-sdk-js'; -import { Dwn, EventEmitterStream } from '@tbd54566975/dwn-sdk-js'; - import type { ProcessHandlers } from './process-handlers.js'; import type { Server } from 'http'; +import type { WebSocketServer } from 'ws'; +import type { DwnServerConfig } from './config.js'; + import log from 'loglevel'; import prefix from 'loglevel-plugin-prefix'; -import { type WebSocketServer } from 'ws'; - +import { config as defaultConfig } from './config.js'; +import { getDWNConfig } from './storage.js'; import { HttpServerShutdownHandler } from './lib/http-server-shutdown-handler.js'; - -import { type DwnServerConfig, config as defaultConfig } from './config.js'; import { HttpApi } from './http-api.js'; -import { setProcessHandlers, unsetProcessHandlers } from './process-handlers.js'; -import { getDWNConfig } from './storage.js'; -import { WsApi } from './ws-api.js'; import { RegistrationManager } from './registration/registration-manager.js'; +import { WsApi } from './ws-api.js'; +import { Dwn, EventEmitterStream } from '@tbd54566975/dwn-sdk-js'; +import { setProcessHandlers, unsetProcessHandlers } from './process-handlers.js'; +/** + * Options for the DwnServer constructor. + * This is different to DwnServerConfig in that the DwnServerConfig defines configuration that come from environment variables so (more) user facing. + * Where as DwnServerOptions wraps DwnServerConfig with additional overrides that can be used for testing. + */ export type DwnServerOptions = { + /** + * A custom DID resolver to use in the DWN. + * Mainly for testing purposes. Ignored if `dwn` is provided. + */ + didResolver?: DidResolver; dwn?: Dwn; config?: DwnServerConfig; }; @@ -29,6 +39,12 @@ export enum DwnServerState { export class DwnServer { serverState = DwnServerState.Stopped; processHandlers: ProcessHandlers; + + /** + * A custom DID resolver to use in the DWN. + * Mainly for testing purposes. Ignored if `dwn` is provided. + */ + didResolver?: DidResolver; dwn?: Dwn; config: DwnServerConfig; #httpServerShutdownHandler: HttpServerShutdownHandler; @@ -40,6 +56,8 @@ export class DwnServer { */ constructor(options: DwnServerOptions = {}) { this.config = options.config ?? defaultConfig; + + this.didResolver = options.didResolver; this.dwn = options.dwn; log.setLevel(this.config.logLevel as log.LogLevelDesc); @@ -84,10 +102,12 @@ export class DwnServer { eventStream = new EventEmitterStream(); } - this.dwn = await Dwn.create(getDWNConfig(this.config, { + const dwnConfig = getDWNConfig(this.config, { + didResolver: this.didResolver, tenantGate: registrationManager, eventStream, - })); + }) + this.dwn = await Dwn.create(dwnConfig); } this.#httpApi = await HttpApi.create(this.config, this.dwn, registrationManager); @@ -114,11 +134,6 @@ export class DwnServer { return; } - // F YEAH! - if (this.dwn['didResolver']['cache']['cache']) { - await this.dwn['didResolver']['cache']['cache']['close'](); - } - await this.dwn.close(); await this.#httpApi.stop(); diff --git a/src/storage.ts b/src/storage.ts index 189f00f..e74b3d4 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -6,6 +6,7 @@ import { MessageStoreLevel, ResumableTaskStoreLevel, } from '@tbd54566975/dwn-sdk-js'; +import type { DidResolver } from '@web5/dids'; import type { DataStore, DwnConfig, @@ -52,17 +53,18 @@ export type StoreType = DataStore | EventLog | MessageStore | ResumableTaskStore export function getDWNConfig( config : DwnServerConfig, options : { + didResolver? : DidResolver, tenantGate? : TenantGate, eventStream? : EventStream, } ): DwnConfig { - const { tenantGate, eventStream } = options; + const { tenantGate, eventStream, didResolver } = options; const dataStore: DataStore = getStore(config.dataStore, EStoreType.DataStore); const eventLog: EventLog = getStore(config.eventLog, EStoreType.EventLog); const messageStore: MessageStore = getStore(config.messageStore, EStoreType.MessageStore); const resumableTaskStore: ResumableTaskStore = getStore(config.messageStore, EStoreType.ResumableTaskStore); - return { eventStream, eventLog, dataStore, messageStore, resumableTaskStore, tenantGate }; + return { didResolver, eventStream, eventLog, dataStore, messageStore, resumableTaskStore, tenantGate }; } function getLevelStore( diff --git a/tests/scenarios/registration.spec.ts b/tests/scenarios/registration.spec.ts index a826bc0..987a3c9 100644 --- a/tests/scenarios/registration.spec.ts +++ b/tests/scenarios/registration.spec.ts @@ -1,33 +1,26 @@ +import type { DwnServerConfig } from '../../src/config.js'; import type { Persona } from '@tbd54566975/dwn-sdk-js'; -import fetch from 'node-fetch'; +import type { ProofOfWorkChallengeModel } from '../../src/registration/proof-of-work-types.js'; +import type { RegistrationManager } from '../../src/registration/registration-manager.js'; +import type { JsonRpcRequest, JsonRpcResponse } from '../../src/lib/json-rpc.js'; +import type { RegistrationData, RegistrationRequest } from '../../src/registration/registration-types.js'; +import fetch from 'node-fetch'; +import { config } from '../../src/config.js'; +import { createJsonRpcRequest} from '../../src/lib/json-rpc.js'; +import { createRecordsWriteMessage } from '../utils.js'; +import { DwnServer } from '../../src/dwn-server.js'; +import { DwnServerErrorCode } from '../../src/dwn-error.js'; import { expect } from 'chai'; +import { ProofOfWork } from '../../src/registration/proof-of-work.js'; +import { ProofOfWorkManager } from '../../src/registration/proof-of-work-manager.js'; +import { randomBytes } from 'crypto'; import { readFileSync } from 'fs'; -import { webcrypto } from 'node:crypto'; import { useFakeTimers } from 'sinon'; import { v4 as uuidv4 } from 'uuid'; +import { webcrypto } from 'node:crypto'; import { DataStream, TestDataGenerator } from '@tbd54566975/dwn-sdk-js'; - -import type { DwnServerConfig } from '../../src/config.js'; -import { config } from '../../src/config.js'; -import type { - JsonRpcRequest, - JsonRpcResponse, -} from '../../src/lib/json-rpc.js'; -import { - createJsonRpcRequest, -} from '../../src/lib/json-rpc.js'; -import { ProofOfWork } from '../../src/registration/proof-of-work.js'; -import { - createRecordsWriteMessage, -} from '../utils.js'; -import type { ProofOfWorkChallengeModel } from '../../src/registration/proof-of-work-types.js'; -import type { RegistrationData, RegistrationRequest } from '../../src/registration/registration-types.js'; -import type { RegistrationManager } from '../../src/registration/registration-manager.js'; -import { DwnServerErrorCode } from '../../src/dwn-error.js'; -import { ProofOfWorkManager } from '../../src/registration/proof-of-work-manager.js'; -import { DwnServer } from '../../src/dwn-server.js'; -import { randomBytes } from 'crypto'; +import { DidDht, DidKey, UniversalResolver } from '@web5/dids'; // node.js 18 and earlier, needs globalThis.crypto polyfill if (!globalThis.crypto) { @@ -65,7 +58,16 @@ describe('Registration scenarios', function () { dwnServerConfig.termsOfServiceFilePath = './tests/fixtures/terms-of-service.txt'; dwnServerConfig.registrationProofOfWorkInitialMaxHash = '0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; // 1 in 16 chance of solving - dwnServer = new DwnServer({ config: dwnServerConfig }); + // CRITICAL: We need to create a custom DID resolver that does not use a LevelDB based cache (which is the default cache used in `DWN`) + // otherwise we will receive a `Database is not open` coming from LevelDB. + // This is likely due to the fact that LevelDB is the default cache used in `DWN`, and we have tests creating default DWN instances, + // so here we have to create a DWN that does not use the same LevelDB cache to avoid hitting LevelDB locked issues. + // Long term we should investigate and unify approach of DWN instantiation taken by tests to avoid this "workaround" entirely. + const didResolver = new UniversalResolver({ + didResolvers : [DidDht, DidKey], + }); + + dwnServer = new DwnServer({ config: dwnServerConfig, didResolver }); await dwnServer.start(); registrationManager = dwnServer.registrationManager; }); @@ -74,22 +76,6 @@ describe('Registration scenarios', function () { clock.restore(); }); - beforeEach(async () => { - // sinon.restore(); // wipe all previous stubs/spies/mocks/fakes/clock - - // // IMPORTANT: MUST be called AFTER `sinon.restore()` because `sinon.restore()` resets fake timers - // clock = useFakeTimers({ shouldAdvanceTime: true }); - - // dwnServer = new DwnServer({ config: dwnServerConfig }); - // await dwnServer.start(); - // registrationManager = dwnServer.registrationManager; - }); - - afterEach(async () =>{ - // await dwnServer.registrationManager['registrationStore']['db'].destroy(); - // await dwnServer.stop(); - }); - it('should facilitate tenant registration with terms-of-service and proof-or-work turned on', async () => { // Scenario: // 1. Alice fetches the terms-of-service. @@ -540,11 +526,6 @@ describe('Registration scenarios', function () { }); - /** - * NOTE: The tests below instantiate their own server configs and should should take care to stop the `dwnServer` - * This is done to avoid LevelDB locking for the default `DidResolver` cache. - */ - it('should initialize ProofOfWorkManager with challenge nonce seed if given.', async function () { await dwnServer.stop(); @@ -563,7 +544,15 @@ describe('Registration scenarios', function () { it('should allow tenant registration to be turned off to allow all DWN messages through.', async () => { await dwnServer.stop(); - // await dwnServer.registrationManager['registrationStore']['db'].destroy(); + + // CRITICAL: We need to create a custom DID resolver that does not use a LevelDB based cache (which is the default cache used in `DWN`) + // otherwise we will receive a `Database is not open` coming from LevelDB. + // This is likely due to the fact that LevelDB is the default cache used in `DWN`, and we have tests creating default DWN instances, + // so here we have to create a DWN that does not use the same LevelDB cache to avoid hitting LevelDB locked issues. + // Long term we should investigate and unify approach of DWN instantiation taken by tests to avoid this "workaround" entirely. + const didResolver = new UniversalResolver({ + didResolvers : [DidDht, DidKey], + }); // Scenario: // 1. There is a DWN that does not require tenant registration. @@ -575,7 +564,7 @@ describe('Registration scenarios', function () { registrationProofOfWorkEnabled: false, termsOfServiceFilePath: undefined, }; - dwnServer = new DwnServer({ config: configClone }); + dwnServer = new DwnServer({ config: configClone, didResolver }); await dwnServer.start(); const { jsonRpcRequest, dataBytes } = await generateRecordsWriteJsonRpcRequest(alice); diff --git a/tests/scenarios/web5-connect.spec.ts b/tests/scenarios/web5-connect.spec.ts index 0301127..f3243c6 100644 --- a/tests/scenarios/web5-connect.spec.ts +++ b/tests/scenarios/web5-connect.spec.ts @@ -23,7 +23,7 @@ describe('Web5 Connect scenarios', function () { before(async function () { - // NOTE: using SQL to workaround an issue where multiple instances of DwnServer can be started using LevelDB in the same test run, + // NOTE: using SQL to workaround an issue where multiple instances of DwnServer cannot be started using LevelDB in the same test run, // and dwn-server.spec.ts already uses LevelDB. dwnServerConfig.messageStore = 'sqlite://', dwnServerConfig.dataStore = 'sqlite://',