diff --git a/package.json b/package.json index d414285d..bba369d7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "description": "", "main": "gatekeeper-api.js", "scripts": { - "start": "node gatekeeper-api.js", + "gatekeeper": "node src/gatekeeper-api.js", + "keymaster": "node src/keymaster-api.js", "test": "node --experimental-vm-modules node_modules/.bin/jest --runInBand --verbose --coverage", "lint": "eslint ." }, diff --git a/src/admin-cli.js b/src/admin-cli.js index 0dac060d..1fe51084 100644 --- a/src/admin-cli.js +++ b/src/admin-cli.js @@ -60,9 +60,9 @@ program }); program - .command('perf-test') + .command('perf-test [full]') .description('DID resolution performance test') - .action(async () => { + .action(async (full) => { try { console.time('getDIDs'); const dids = await gatekeeper.getDIDs(); @@ -76,17 +76,48 @@ program } console.timeEnd('resolveDID(did, { confirm: true })'); + let batch = []; + console.time('getDIDs({ dids: batch, confirm: true, resolve: true })'); + for (const did of dids) { + batch.push(did); + + if (batch.length > 99) { + await gatekeeper.getDIDs({ dids: batch, confirm: true, resolve: true }); + batch = []; + } + } + await gatekeeper.getDIDs({ dids: batch, confirm: true, resolve: true }); + console.timeEnd('getDIDs({ dids: batch, confirm: true, resolve: true })'); + console.time('resolveDID(did, { confirm: false })'); for (const did of dids) { await gatekeeper.resolveDID(did, { confirm: false }); } console.timeEnd('resolveDID(did, { confirm: false })'); - console.time('resolveDID(did, { verify: true })'); + console.time('getDIDs({ dids: batch, confirm: false, resolve: true })'); for (const did of dids) { - await gatekeeper.resolveDID(did, { verify: true }); + batch.push(did); + + if (batch.length > 99) { + await gatekeeper.getDIDs({ dids: batch, confirm: false, resolve: true }); + batch = []; + } + } + await gatekeeper.getDIDs({ dids: batch, confirm: false, resolve: true }); + console.timeEnd('getDIDs({ dids: batch, confirm: false, resolve: true })'); + + console.time('exportDIDs'); + await gatekeeper.exportDIDs(dids); + console.timeEnd('exportDIDs'); + + if (full) { + console.time('resolveDID(did, { verify: true })'); + for (const did of dids) { + await gatekeeper.resolveDID(did, { verify: true }); + } + console.timeEnd('resolveDID(did, { verify: true })'); } - console.timeEnd('resolveDID(did, { verify: true })'); } catch (error) { console.error(error); diff --git a/src/gatekeeper-lib.js b/src/gatekeeper-lib.js index 3f7b3156..a60a15f5 100644 --- a/src/gatekeeper-lib.js +++ b/src/gatekeeper-lib.js @@ -13,9 +13,7 @@ const validRegistries = ['local', 'hyperswarm', 'TESS']; let db = null; let helia = null; let ipfs = null; - -const confirmedCache = {}; -const unconfirmedCache = {}; +let eventsCache = {}; export async function listRegistries() { return validRegistries; @@ -37,7 +35,6 @@ export async function stop() { export async function verifyDID(did) { await resolveDID(did, { verify: true }); - await resolveDID(did, { confirm: true }); } export async function verifyDb(chatty = true) { @@ -64,6 +61,7 @@ export async function verifyDb(chatty = true) { } invalid += 1; await db.deleteEvents(did); + delete eventsCache[did]; } } @@ -77,6 +75,7 @@ export async function verifyDb(chatty = true) { // For testing purposes export async function resetDb() { await db.resetDb(); + eventsCache = {}; } export async function anchorSeed(seed) { @@ -187,7 +186,7 @@ export async function createDID(operation) { } } -async function generateDoc(anchor) { +export async function generateDoc(anchor) { let doc = {}; try { if (!anchor?.mdip) { @@ -288,19 +287,22 @@ async function verifyUpdate(operation, doc) { return cipher.verifySig(msgHash, signature.value, publicJwk); } -export async function resolveDID(did, { atTime, atVersion, confirm, verify } = {}) { - const confirmedCacheable = !!confirm && !atTime && !atVersion; - const unconfirmedCacheable = !confirm && !atTime && !atVersion; +async function getEvents(did) { + let events = eventsCache[did]; - if (confirmedCacheable && !verify && confirmedCache[did]) { - return JSON.parse(JSON.stringify(confirmedCache[did])); - } + if (!events) { + events = await db.getEvents(did); - if (unconfirmedCacheable && !verify && unconfirmedCache[did]) { - return JSON.parse(JSON.stringify(unconfirmedCache[did])); + if (events.length > 0) { + eventsCache[did] = events; + } } - const events = await db.getEvents(did); + return JSON.parse(JSON.stringify(events)); +} + +export async function resolveDID(did, { atTime, atVersion, confirm, verify } = {}) { + const events = await getEvents(did); if (events.length === 0) { throw new Error(exceptions.INVALID_DID); @@ -382,14 +384,6 @@ export async function resolveDID(did, { atTime, atVersion, confirm, verify } = { } } - if (confirmedCacheable) { - confirmedCache[did] = doc; - } - - if (unconfirmedCacheable) { - unconfirmedCache[did] = doc; - } - return JSON.parse(JSON.stringify(doc)); } @@ -411,8 +405,7 @@ export async function updateDID(operation) { operation: operation }); - delete confirmedCache[operation.did]; - delete unconfirmedCache[operation.did]; + delete eventsCache[operation.did]; if (registry === 'local') { return true; @@ -469,14 +462,14 @@ export async function getDIDs({ dids, updatedAfter, updatedBefore, confirm, reso } export async function exportDID(did) { - return await db.getEvents(did); + return await getEvents(did); } export async function exportDIDs(dids) { const batch = []; for (const did of dids) { - batch.push(await db.getEvents(did)); + batch.push(await getEvents(did)); } return batch; @@ -489,6 +482,7 @@ export async function removeDIDs(dids) { for (const did of dids) { await db.deleteEvents(did); + delete eventsCache[did]; } return true; @@ -582,8 +576,7 @@ export async function importEvent(event) { current[index] = event; db.setEvents(did, current); - delete confirmedCache[did]; - delete unconfirmedCache[did]; + delete eventsCache[did]; return true; } @@ -596,8 +589,8 @@ export async function importEvent(event) { throw new Error(exceptions.INVALID_OPERATION); } - delete confirmedCache[did]; - delete unconfirmedCache[did]; + delete eventsCache[did]; + return true; } diff --git a/src/gatekeeper.test.js b/src/gatekeeper.test.js index 3935e92b..4cca9fdd 100644 --- a/src/gatekeeper.test.js +++ b/src/gatekeeper.test.js @@ -128,6 +128,104 @@ async function createAssetOp(agent, keypair, registry = 'local') { }; } +describe('generateDoc', () => { + it('should generate an agent doc from a valid anchor', async () => { + const keypair = cipher.generateRandomJwk(); + const agentOp = await createAgentOp(keypair); + const doc = await gatekeeper.generateDoc(agentOp); + const expected = { + // eslint-disable-next-line + "@context": "https://w3id.org/did-resolution/v1", + didDocument: { + "@context": [ + // eslint-disable-next-line + "https://www.w3.org/ns/did/v1", + ], + authentication: [ + "#key-1", + ], + id: expect.any(String), + verificationMethod: [ + { + controller: expect.any(String), + id: "#key-1", + publicKeyJwk: agentOp.publicJwk, + type: "EcdsaSecp256k1VerificationKey2019", + }, + ], + }, + didDocumentData: {}, + didDocumentMetadata: { + created: expect.any(String), + }, + mdip: agentOp.mdip, + }; + + expect(doc).toStrictEqual(expected); + }); + + it('should generate an asset doc from a valid anchor', async () => { + const keypair = cipher.generateRandomJwk(); + const agentOp = await createAgentOp(keypair); + const agent = await gatekeeper.createDID(agentOp); + const assetOp = await createAssetOp(agent, keypair); + const doc = await gatekeeper.generateDoc(assetOp); + const expected = { + // eslint-disable-next-line + "@context": "https://w3id.org/did-resolution/v1", + didDocument: { + "@context": [ + // eslint-disable-next-line + "https://www.w3.org/ns/did/v1", + ], + id: expect.any(String), + controller: agent, + }, + didDocumentData: assetOp.data, + didDocumentMetadata: { + created: expect.any(String), + }, + mdip: assetOp.mdip, + }; + + expect(doc).toStrictEqual(expected); + }); + + it('should return an empty doc if mdip missing from anchor', async () => { + const keypair = cipher.generateRandomJwk(); + const agentOp = await createAgentOp(keypair); + delete agentOp.mdip; + const doc = await gatekeeper.generateDoc(agentOp); + + expect(doc).toStrictEqual({}); + }); + + it('should return an empty doc if mdip version invalid', async () => { + const keypair = cipher.generateRandomJwk(); + const agentOp = await createAgentOp(keypair, 0); + const doc = await gatekeeper.generateDoc(agentOp); + + expect(doc).toStrictEqual({}); + }); + + it('should return an empty doc if mdip type invalid', async () => { + const keypair = cipher.generateRandomJwk(); + const agentOp = await createAgentOp(keypair); + agentOp.mdip.type = 'mock'; + const doc = await gatekeeper.generateDoc(agentOp); + + expect(doc).toStrictEqual({}); + }); + + it('should return an empty doc if mdip registry invalid', async () => { + const keypair = cipher.generateRandomJwk(); + const agentOp = await createAgentOp(keypair, 1, 'mock'); + const doc = await gatekeeper.generateDoc(agentOp); + + expect(doc).toStrictEqual({}); + }); +}); + describe('createDID', () => { afterEach(() => { mockFs.restore(); diff --git a/src/keymaster-app/src/keymaster-lib.js b/src/keymaster-app/src/keymaster-lib.js index d5574649..4ceb16f1 100644 --- a/src/keymaster-app/src/keymaster-lib.js +++ b/src/keymaster-app/src/keymaster-lib.js @@ -376,7 +376,7 @@ function fetchId(id) { idInfo = wallet.ids[wallet.current]; if (!idInfo) { - throw new Error(exceptions.UNKNOWN_ID); + throw new Error(exceptions.NO_CURRENT_ID); } }