Skip to content

Commit

Permalink
Tombstones (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
mistermoe authored Jun 1, 2023
1 parent 173dc1a commit d4a953d
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 15 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 14 additions & 2 deletions src/json-rpc-handlers/dwn/process-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 {
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;
Expand Down
38 changes: 33 additions & 5 deletions tests/http-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

// Intentionally delete a required property to produce an invalid RecordsWrite 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 invalid 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);
Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit d4a953d

Please sign in to comment.