From 667ab462160c70ef8a145da1422d2a32e4b9ee11 Mon Sep 17 00:00:00 2001 From: Moe Jangda Date: Wed, 31 May 2023 19:52:18 -0500 Subject: [PATCH 1/4] bump `dwn-sdk-js` to `0.0.33` --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index f527de4..2d3d4fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "dwn-server", "version": "0.0.2", "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.0.32", + "@tbd54566975/dwn-sdk-js": "0.0.33", "bytes": "3.1.2", "cors": "2.8.5", "express": "4.18.2", @@ -326,9 +326,9 @@ "integrity": "sha512-aWItSZvJj4+GI6FWkjZR13xPNPctq2RRakzo+O6vN7bC2yjwdg5EFpgaSAUn95b7BGSgcflvzVDPoKmJv24IOg==" }, "node_modules/@tbd54566975/dwn-sdk-js": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.32.tgz", - "integrity": "sha512-SijEHpmJDa0I9hC7jn/8P7XeYeFAi9byMc+b36yuCPDiF+j/hLLYGMpZpnbylJ6+ldWqWWEunumeWjxU4zrc3g==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.33.tgz", + "integrity": "sha512-ZdQtRTd0M2VcgGli7kDzkePsuxpwiOg+PRsBJ/UPC5fc6oflzbRqV6Lg9v8bWHozjdnijP58J3RMWmf4cj7bWw==", "dependencies": { "@ipld/dag-cbor": "9.0.0", "@js-temporal/polyfill": "0.4.3", @@ -5891,9 +5891,9 @@ "integrity": "sha512-aWItSZvJj4+GI6FWkjZR13xPNPctq2RRakzo+O6vN7bC2yjwdg5EFpgaSAUn95b7BGSgcflvzVDPoKmJv24IOg==" }, "@tbd54566975/dwn-sdk-js": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.32.tgz", - "integrity": "sha512-SijEHpmJDa0I9hC7jn/8P7XeYeFAi9byMc+b36yuCPDiF+j/hLLYGMpZpnbylJ6+ldWqWWEunumeWjxU4zrc3g==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.0.33.tgz", + "integrity": "sha512-ZdQtRTd0M2VcgGli7kDzkePsuxpwiOg+PRsBJ/UPC5fc6oflzbRqV6Lg9v8bWHozjdnijP58J3RMWmf4cj7bWw==", "requires": { "@ipld/dag-cbor": "9.0.0", "@js-temporal/polyfill": "0.4.3", diff --git a/package.json b/package.json index a91a79d..4ff2a54 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "version": "0.0.2", "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.0.32", + "@tbd54566975/dwn-sdk-js": "0.0.33", "bytes": "3.1.2", "node-fetch": "3.3.1", "cors": "2.8.5", From f0de70590a960d250ae0ee33baa78a0733aa8499 Mon Sep 17 00:00:00 2001 From: Moe Jangda Date: Wed, 31 May 2023 19:52:46 -0500 Subject: [PATCH 2/4] permit `RecordsWrites` with no associated data --- src/json-rpc-handlers/dwn/process-message.ts | 16 +++++++-- tests/http-api.spec.ts | 38 +++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/json-rpc-handlers/dwn/process-message.ts b/src/json-rpc-handlers/dwn/process-message.ts index 2f5a088..887dc40 100644 --- a/src/json-rpc-handlers/dwn/process-message.ts +++ b/src/json-rpc-handlers/dwn/process-message.ts @@ -2,17 +2,29 @@ import type { Readable as IsomorphicReadable } from 'readable-stream'; import type { JsonRpcHandler, HandlerResponse } from '../../lib/json-rpc-router.js'; import { v4 as uuidv4 } from 'uuid'; +import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js'; import { JsonRpcErrorCodes, createJsonRpcErrorResponse, createJsonRpcSuccessResponse } from '../../lib/json-rpc.js'; export const handleDwnProcessMessage: JsonRpcHandler = async (dwnRequest, context) => { let { dwn, dataStream } = context; const { target, message } = dwnRequest.params; - const requestId = dwnRequest.id ?? uuidv4(); try { - const reply = await dwn.processMessage(target, message, dataStream as IsomorphicReadable); + let reply; + const messageType = message?.descriptor?.interface + message?.descriptor?.method; + + // When a record is deleted, the initial RecordsWrite is kept as a tombstone _in addition_ to the RecordsDelete message. + // the data associated to that initial RecordsWrite is deleted. If a record was written _and_ deleted before it ever got + // to dwn-server, we end up in a situation where we still need to process the tombstone so that we can process the + // RecordsDelete. This + if (messageType === DwnInterfaceName.Records + DwnMethodName.Write && !dataStream) { + reply = await dwn.synchronizePrunedInitialRecordsWrite(target, message); + } else { + reply = await dwn.processMessage(target, message, dataStream as IsomorphicReadable); + } + // RecordsRead messages return record data as a stream to for accommodate large amounts of data let recordDataStream; diff --git a/tests/http-api.spec.ts b/tests/http-api.spec.ts index 27b5336..06fc17b 100644 --- a/tests/http-api.spec.ts +++ b/tests/http-api.spec.ts @@ -57,20 +57,27 @@ describe('http api', function() { it('responds with a 2XX HTTP status if JSON RPC handler returns 4XX/5XX DWN status code', async function() { const alice = await createProfile(); + const { recordsWrite, dataStream } = await createRecordsWriteMessage(alice); + + // bork the message + const message = recordsWrite.toJSON(); + delete message['descriptor']['interface']; const requestId = uuidv4(); - const { recordsWrite } = await createRecordsWriteMessage(alice); const dwnRequest = createJsonRpcRequest(requestId, 'dwn.processMessage', { - message : recordsWrite.toJSON(), + message : message, target : alice.did, }); - // Attempt an initial RecordsWrite without any data to ensure the DWN returns an error. + const dataBytes = await DataStream.toBytes(dataStream); + + // Attempt an initial RecordsWrite with the borked message to ensure the DWN returns an error. let responseInitialWrite = await fetch('http://localhost:3000', { method : 'POST', headers : { 'dwn-request': JSON.stringify(dwnRequest) - } + }, + body: new Blob([dataBytes]) }); expect(responseInitialWrite.status).to.equal(200); @@ -79,9 +86,10 @@ describe('http api', function() { expect(body.id).to.equal(requestId); expect(body.error).to.not.exist; + const { reply } = body.result; expect(reply.status.code).to.equal(400); - expect(reply.status.detail).to.include('RecordsWriteMissingDataStream'); + expect(reply.status.detail).to.include('Both interface and method must be present'); }); it('exposes dwn-response header', async function() { @@ -216,6 +224,26 @@ describe('http api', function() { const { reply } = body.result; expect(reply.status.code).to.equal(202); }); + + it('handles a RecordsWrite tombstone', async function() { + const alice = await createProfile(); + const { recordsWrite: tombstone } = await createRecordsWriteMessage(alice); + + let requestId = uuidv4(); + let dwnRequest = createJsonRpcRequest(requestId, 'dwn.processMessage', { + message : tombstone.toJSON(), + target : alice.did + }); + + let responeTombstone = await fetch('http://localhost:3000', { + method : 'POST', + headers : { + 'dwn-request': JSON.stringify(dwnRequest) + }, + }); + + expect(responeTombstone.status).to.equal(200); + }); }); describe('health check', function() { From cad90d09262e15f6332e1627f5a5377b238343ad Mon Sep 17 00:00:00 2001 From: Moe Jangda Date: Wed, 31 May 2023 21:20:43 -0500 Subject: [PATCH 3/4] fix comment --- src/json-rpc-handlers/dwn/process-message.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/json-rpc-handlers/dwn/process-message.ts b/src/json-rpc-handlers/dwn/process-message.ts index 887dc40..d4c7f8c 100644 --- a/src/json-rpc-handlers/dwn/process-message.ts +++ b/src/json-rpc-handlers/dwn/process-message.ts @@ -15,10 +15,10 @@ export const handleDwnProcessMessage: JsonRpcHandler = async (dwnRequest, contex let reply; const messageType = message?.descriptor?.interface + message?.descriptor?.method; - // When a record is deleted, the initial RecordsWrite is kept as a tombstone _in addition_ to the RecordsDelete message. - // the data associated to that initial RecordsWrite is deleted. If a record was written _and_ deleted before it ever got - // to dwn-server, we end up in a situation where we still need to process the tombstone so that we can process the - // RecordsDelete. This + // When a record is deleted via `RecordsDelete`, the initial RecordsWrite is kept as a tombstone _in addition_ + // to the RecordsDelete message. the data associated to that initial RecordsWrite is deleted. If a record was written + // _and_ deleted before it ever got to dwn-server, we end up in a situation where we still need to process the tombstone + // so that we can process the RecordsDelete. if (messageType === DwnInterfaceName.Records + DwnMethodName.Write && !dataStream) { reply = await dwn.synchronizePrunedInitialRecordsWrite(target, message); } else { From 31a972f154c08de42d1c513d4c595767afb8f6d2 Mon Sep 17 00:00:00 2001 From: Frank Hinek Date: Thu, 1 Jun 2023 13:28:59 -0400 Subject: [PATCH 4/4] tests: minor change to make comment more descriptive Signed-off-by: Frank Hinek --- tests/http-api.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/http-api.spec.ts b/tests/http-api.spec.ts index 06fc17b..917dade 100644 --- a/tests/http-api.spec.ts +++ b/tests/http-api.spec.ts @@ -59,7 +59,7 @@ describe('http api', function() { const alice = await createProfile(); const { recordsWrite, dataStream } = await createRecordsWriteMessage(alice); - // bork the message + // Intentionally delete a required property to produce an invalid RecordsWrite message. const message = recordsWrite.toJSON(); delete message['descriptor']['interface']; @@ -71,7 +71,7 @@ describe('http api', function() { const dataBytes = await DataStream.toBytes(dataStream); - // Attempt an initial RecordsWrite with the borked message to ensure the DWN returns an error. + // Attempt an initial RecordsWrite with the invalid message to ensure the DWN returns an error. let responseInitialWrite = await fetch('http://localhost:3000', { method : 'POST', headers : {