Skip to content

Commit

Permalink
exported some CID utility functions
Browse files Browse the repository at this point in the history
* exported some CID utility functions
* fixed a bug caught by TypeScript linter
* brought code coverage for `cid.ts` to 100%
  • Loading branch information
thehenrytsai authored Feb 28, 2023
1 parent 576fda4 commit 9ed4302
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 35 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK

Code Coverage
![Statements](https://img.shields.io/badge/statements-94.36%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.95%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.3%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.36%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-94.58%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.7%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.34%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.58%25-brightgreen.svg?style=flat)

## Introduction

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tbd54566975/dwn-sdk-js",
"version": "0.0.22",
"version": "0.0.23",
"description": "A reference implementation of https://identity.foundation/decentralized-web-node/spec/",
"type": "module",
"types": "./dist/esm/src/index.d.ts",
Expand Down Expand Up @@ -132,4 +132,4 @@
"url": "https://github.com/TBD54566975/dwn-sdk-js/issues"
},
"homepage": "https://github.com/TBD54566975/dwn-sdk-js#readme"
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type { HooksWriteMessage } from './interfaces/hooks/types.js';
export type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage, ProtocolsQueryMessage } from './interfaces/protocols/types.js';
export type { RecordsDeleteMessage, RecordsQueryMessage, RecordsWriteMessage } from './interfaces/records/types.js';
export { AllowAllTenantGate, TenantGate } from './core/tenant-gate.js';
export { Cid } from './utils/cid.js';
export { DataStore } from './store/data-store.js';
export { DateSort } from './interfaces/records/messages/records-query.js';
export { DataStream } from './utils/data-stream.js';
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/records/handlers/records-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class RecordsWriteHandler implements MethodHandler {
const newMessageIsInitialWrite = await recordsWrite.isInitialWrite();
if (!newMessageIsInitialWrite) {
try {
const initialWrite = RecordsWrite.getInitialWrite(existingMessages);
const initialWrite = await RecordsWrite.getInitialWrite(existingMessages);
RecordsWrite.verifyEqualityOfImmutableProperties(initialWrite, incomingMessage);
} catch (e) {
return new MessageReply({
Expand Down
14 changes: 7 additions & 7 deletions src/interfaces/records/messages/records-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ProtocolAuthorization } from '../../../core/protocol-authorization.js';
import { removeUndefinedProperties } from '../../../utils/object.js';

import { authorize, validateAuthorizationIntegrity } from '../../../core/auth.js';
import { computeCid, computeDagPbCid } from '../../../utils/cid.js';
import { Cid, computeCid } from '../../../utils/cid.js';
import { DwnInterfaceName, DwnMethodName } from '../../../core/message.js';
import { GeneralJws, SignatureInput } from '../../../jose/jws/general/types.js';

Expand Down Expand Up @@ -73,7 +73,7 @@ export class RecordsWrite extends Message {
/**
* Creates a RecordsWrite message.
* @param options.recordId If `undefined`, will be auto-filled as a originating message as convenience for developer.
* @param options.data Readable stream of the data to be stored. Must specify `option.dataCid` if `undefined`.
* @param options.data Data used to compute the `dataCid`. Must specify `option.dataCid` if `undefined`.
* @param options.dataCid CID of the data that is already stored in the DWN. Must specify `option.data` if `undefined`.
* @param options.dateCreated If `undefined`, it will be auto-filled with current time.
* @param options.dateModified If `undefined`, it will be auto-filled with current time.
Expand All @@ -85,7 +85,7 @@ export class RecordsWrite extends Message {
options.data !== undefined && options.dataCid !== undefined) {
throw new Error('one and only one parameter between `data` and `dataCid` is allowed');
}
const dataCid = options.dataCid ?? await computeDagPbCid(options.data);
const dataCid = options.dataCid ?? await Cid.computeDagPbCidFromBytes(options.data!);

const descriptor: RecordsWriteDescriptor = {
interface : DwnInterfaceName.Records,
Expand Down Expand Up @@ -177,7 +177,7 @@ export class RecordsWrite extends Message {
// inherit published value from parent if neither published nor datePublished is specified
const published = options.published ?? (options.datePublished ? true : unsignedMessage.descriptor.published);
// use current time if published but no explicit time given
let datePublished = undefined;
let datePublished: string | undefined = undefined;
// if given explicitly published dated
if (options.datePublished) {
datePublished = options.datePublished;
Expand Down Expand Up @@ -396,14 +396,14 @@ export class RecordsWrite extends Message {
/**
* Gets the initial write from the given list or record write.
*/
public static getInitialWrite(messages: BaseMessage[]): RecordsWriteMessage{
public static async getInitialWrite(messages: BaseMessage[]): Promise<RecordsWriteMessage>{
for (const message of messages) {
if (RecordsWrite.isInitialWrite(message)) {
if (await RecordsWrite.isInitialWrite(message)) {
return message as RecordsWriteMessage;
}
}

throw new Error(`initial write is not found `);
throw new Error(`initial write is not found`);
}

/**
Expand Down
47 changes: 33 additions & 14 deletions src/utils/cid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as cbor from '@ipld/dag-cbor';

import { CID } from 'multiformats/cid';
import { importer } from 'ipfs-unixfs-importer';
import { Readable } from 'stream';
import { sha256 } from 'multiformats/hashes/sha2';

// a map of all supported CID hashing algorithms. This map is used to select the appropriate hasher
Expand All @@ -16,20 +17,6 @@ const codecs = {
[cbor.code]: cbor
};


/**
* @returns V1 CID of the DAG comprised by chunking data into unixfs dag-pb encoded blocks
*/
export async function computeDagPbCid(content: Uint8Array): Promise<string> {
const asyncDataBlocks = importer([{ content }], undefined, { onlyHash: true, cidVersion: 1 });

// NOTE: the last block contains the root CID
let block;
for await (block of asyncDataBlocks) { ; }

return block.cid.toString();
}

/**
* Computes a V1 CID for the provided payload
* @param payload
Expand Down Expand Up @@ -71,3 +58,35 @@ export function parseCid(str: string): CID {

return cid;
}


/**
* Utility class for creating CIDs. Exported for the convenience of developers.
*/
export class Cid {
/**
* @returns V1 CID of the DAG comprised by chunking data into unixfs DAG-PB encoded blocks
*/
public static async computeDagPbCidFromBytes(content: Uint8Array): Promise<string> {
const asyncDataBlocks = importer([{ content }], undefined, { onlyHash: true, cidVersion: 1 });

// NOTE: the last block contains the root CID
let block;
for await (block of asyncDataBlocks) { ; }

return block.cid.toString();
}

/**
* @returns V1 CID of the DAG comprised by chunking data into unixfs DAG-PB encoded blocks
*/
public static async computeDagPbCidFromStream(dataStream: Readable): Promise<string> {
const asyncDataBlocks = importer([{ content: dataStream }], undefined, { onlyHash: true, cidVersion: 1 });

// NOTE: the last block contains the root CID
let block;
for await (block of asyncDataBlocks) { ; }

return block.cid.toString();
}
}
42 changes: 34 additions & 8 deletions tests/utils/cid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,42 @@ import * as cbor from '@ipld/dag-cbor';
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';

import { computeCid } from '../../src/utils/cid.js';
import { DataStream } from '../../src/index.js';
import { sha256 } from 'multiformats/hashes/sha2';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Cid, computeCid, parseCid } from '../../src/utils/cid.js';

// extend chai to test promises
chai.use(chaiAsPromised);

describe('CID', () => {
it('should yield the same CID using either computeDagPbCidFromBytes() & computeDagPbCidFromStream()', async () => {
const randomBytes = TestDataGenerator.randomBytes(500_000);
const randomByteStream = await DataStream.fromBytes(randomBytes);

const cid1 = await Cid.computeDagPbCidFromBytes(randomBytes);
const cid2 = await Cid.computeDagPbCidFromStream(randomByteStream);
expect(cid1).to.equal(cid2);
});

describe('computeCid', () => {
xit('throws an error if codec is not supported');
xit('throws an error if multihasher is not supported');
xit('generates a cbor/sha256 v1 cid by default');
it('throws an error if codec is not supported', async () => {
const anyTestData = {
a: TestDataGenerator.randomString(32),
};
const computeCidPromise = computeCid(anyTestData, 'unknownCodec');
await expect(computeCidPromise).to.be.rejectedWith('codec [unknownCodec] not supported');
});

it(' should generate a CBOR SHA256 CID identical to IPFS block encoding algorithm', async () => {
it('throws an error if multihasher is not supported', async () => {
const anyTestData = {
a: TestDataGenerator.randomString(32),
};
const computeCidPromise = computeCid(anyTestData, '113', 'unknownHashingAlgorithm'); // 113 = CBOR
await expect(computeCidPromise).to.be.rejectedWith('multihash code [unknownHashingAlgorithm] not supported');
});

it('should by default generate a CBOR SHA256 CID identical to IPFS block encoding algorithm', async () => {
const anyTestData = {
a : TestDataGenerator.randomString(32),
b : TestDataGenerator.randomString(32),
Expand Down Expand Up @@ -49,8 +71,12 @@ describe('CID', () => {
});

describe('parseCid', () => {
xit('throws an error if codec is not supported');
xit('throws an error if multihasher is not supported');
xit('parses provided str into a V1 cid');
it('throws an error if codec is not supported', async () => {
expect(() => parseCid('bafybeihzdcfjv55kxiz7sxwxaxbnjgj7rm2amvrxpi67jpwkgygjzoh72y')).to.throw('codec [112] not supported'); // a DAG-PB CID
});

it('throws an error if multihasher is not supported', async () => {
expect(() => parseCid('bafy2bzacec2qlo3cohxyaoulipd3hurlq6pspvmpvmnmqsxfg4vbumpq3ufag')).to.throw('multihash code [45600] not supported'); // 45600 = BLAKE2b-256 CID
});
});
});

0 comments on commit 9ed4302

Please sign in to comment.