Skip to content
This repository has been archived by the owner on Jan 4, 2022. It is now read-only.

Commit

Permalink
feat: store document ID as a part of the document
Browse files Browse the repository at this point in the history
* Store Document ID as a part of the document
  • Loading branch information
Konstantin Shuplenkov authored Mar 3, 2020
1 parent b13a9bb commit 3d10a01
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 40 deletions.
14 changes: 6 additions & 8 deletions lib/document/Document.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const bs58 = require('bs58');
const lodashGet = require('lodash.get');
const lodashSet = require('lodash.set');

Expand All @@ -14,9 +13,13 @@ class Document {
constructor(rawDocument) {
const data = { ...rawDocument };

this.id = undefined;
this.action = undefined;

if (Object.prototype.hasOwnProperty.call(rawDocument, '$id')) {
this.id = rawDocument.$id;
delete data.$id;
}

if (Object.prototype.hasOwnProperty.call(rawDocument, '$type')) {
this.type = rawDocument.$type;
delete data.$type;
Expand Down Expand Up @@ -51,12 +54,6 @@ class Document {
* @return {string}
*/
getId() {
if (!this.id) {
this.id = bs58.encode(
hash(this.contractId + this.ownerId + this.type + this.entropy),
);
}

return this.id;
}

Expand Down Expand Up @@ -191,6 +188,7 @@ class Document {
*/
toJSON() {
return {
$id: this.getId(),
$type: this.getType(),
$contractId: this.getDataContractId(),
$ownerId: this.getOwnerId(),
Expand Down
18 changes: 15 additions & 3 deletions lib/document/DocumentFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const InvalidDocumentError = require('./errors/InvalidDocumentError');
const InvalidDocumentTypeError = require('../errors/InvalidDocumentTypeError');
const SerializedObjectParsingError = require('../errors/SerializedObjectParsingError');

const generateDocumentId = require('./generateDocumentId');

class DocumentFactory {
/**
* @param {validateDocument} validateDocument
Expand All @@ -33,11 +35,22 @@ class DocumentFactory {
throw new InvalidDocumentTypeError(type, dataContract);
}

const documentEntropy = entropy.generate();
const contractId = dataContract.getId();

const id = generateDocumentId(
contractId,
ownerId,
type,
documentEntropy,
);

const rawDocument = {
$id: id,
$type: type,
$contractId: dataContract.getId(),
$contractId: contractId,
$ownerId: ownerId,
$entropy: entropy.generate(),
$entropy: documentEntropy,
$rev: Document.DEFAULTS.REVISION,
...data,
};
Expand All @@ -49,7 +62,6 @@ class DocumentFactory {
return document;
}


/**
* Create Document from plain object
*
Expand Down
1 change: 1 addition & 0 deletions lib/document/RawDocumentInterface.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* @typedef {Object} RawDocument
* @property {string} $id
* @property {string} $type
* @property {string} $contractId
* @property {string} $ownerId
Expand Down
25 changes: 25 additions & 0 deletions lib/document/generateDocumentId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const bs58 = require('bs58');

const hash = require('../util/hash');

/**
* Generates document ID
*
* @param {string} contractId
* @param {string} ownerId
* @param {string} type
* @param {string} entropy
* @returns {string}
*/
function generateDocumentId(contractId, ownerId, type, entropy) {
return bs58.encode(
hash(Buffer.concat([
bs58.decode(contractId),
bs58.decode(ownerId),
Buffer.from(type),
bs58.decode(entropy),
])),
);
}

module.exports = generateDocumentId;
21 changes: 21 additions & 0 deletions lib/document/validateDocumentFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ const documentBaseSchema = require('../../schema/base/document');

const ValidationResult = require('../validation/ValidationResult');

const InvalidDocumentIdError = require('../errors/InvalidDocumentIdError');
const InvalidDocumentTypeError = require('../errors/InvalidDocumentTypeError');
const MissingDocumentTypeError = require('../errors/MissingDocumentTypeError');
const InvalidDocumentEntropyError = require('../errors/InvalidDocumentEntropyError');
const MismatchDocumentContractIdAndDataContractError = require('../errors/MismatchDocumentContractIdAndDataContractError');

const entropy = require('../util/entropy');

const generateDocumentId = require('./generateDocumentId');


/**
* @param {JsonSchemaValidator} validator
* @param {enrichDataContractWithBaseDocument} enrichDataContractWithBaseDocument
Expand Down Expand Up @@ -106,6 +110,23 @@ module.exports = function validateDocumentFactory(
);
}

if (!result.isValid()) {
return result;
}

const documentId = generateDocumentId(
rawDocument.$contractId,
rawDocument.$ownerId,
rawDocument.$type,
rawDocument.$entropy,
);

if (rawDocument.$id !== documentId) {
result.addError(
new InvalidDocumentIdError(rawDocument),
);
}

return result;
}

Expand Down
23 changes: 23 additions & 0 deletions lib/errors/InvalidDocumentIdError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const ConsensusError = require('./ConsensusError');

class InvalidDocumentIdError extends ConsensusError {
/**
* @param {RawDocument} rawDocument
*/
constructor(rawDocument) {
super('Invalid Document ID');

this.rawDocument = rawDocument;
}

/**
* Get raw Document
*
* @return {RawDocument}
*/
getRawDocument() {
return this.rawDocument;
}
}

module.exports = InvalidDocumentIdError;
9 changes: 3 additions & 6 deletions lib/test/fixtures/getDpnsDocumentFixture.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
const { Transaction, PrivateKey } = require('@dashevo/dashcore-lib');
const entropy = require('../../../lib/util/entropy');
const multihash = require('../../../lib/util/multihashDoubleSHA256');
const getDpnsContractFixture = require('./getDpnsContractFixture');
const DocumentFactory = require('../../document/DocumentFactory');
const generateRandomId = require('../utils/generateRandomId');

const transaction = new Transaction().setType(Transaction.TYPES.TRANSACTION_SUBTX_REGISTER);
transaction.extraPayload.setUserName('MyUser').setPubKeyIdFromPrivateKey(new PrivateKey());

const ownerId = transaction.hash;
const ownerId = generateRandomId();

/**
* @return {Document}
Expand All @@ -30,7 +27,7 @@ function getParentDocumentFixture(options = {}) {
normalizedParentDomainName: 'grandparent',
preorderSalt: entropy.generate(),
records: {
dashIdentity: transaction.hash,
dashIdentity: ownerId,
},
...options,
};
Expand Down
7 changes: 7 additions & 0 deletions schema/base/document.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
"$id": "https://schema.dash.org/dpp-0-4-0/base/document",
"type": "object",
"properties": {
"$id": {
"type": "string",
"minLength": 42,
"maxLength": 44,
"pattern": "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$"
},
"$type": {
"type": "string"
},
Expand Down Expand Up @@ -30,6 +36,7 @@
}
},
"required": [
"$id",
"$type",
"$rev",
"$contractId",
Expand Down
2 changes: 1 addition & 1 deletion test/integration/document/DocumentFacade.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('DocumentFacade', () => {

beforeEach(function beforeEach() {
dataContract = getDataContractFixture();
ownerId = '6b74011f5d2ad1a8d45b71b9702f54205ce75253593c3cfbba3fdadeca278288';
ownerId = '5zcXZpTLWFwZjKjq3ME5KVavtZa9YUaZESVzrndehBhq';

dataProviderMock = createDataProviderMock(this.sinonSandbox);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('executeDataTriggersFactory', () => {
dpnsDeleteDomainDataTriggerMock
.execute.resolves(new DataTriggerExecutionResult());

const ownerId = 'ownerId';
const ownerId = '5zcXZpTLWFwZjKjq3ME5KVavtZa9YUaZESVzrndehBhq';

context = new DataTriggerExecutionContext(
null, ownerId, contractMock,
Expand Down
91 changes: 91 additions & 0 deletions test/integration/document/validateDocumentFactory.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ const getDocumentsFixture = require('../../../lib/test/fixtures/getDocumentsFixt

const MissingDocumentTypeError = require('../../../lib/errors/MissingDocumentTypeError');
const InvalidDocumentTypeError = require('../../../lib/errors/InvalidDocumentTypeError');
const InvalidDocumentIdError = require('../../../lib/errors/InvalidDocumentIdError');
const InvalidDocumentEntropyError = require('../../../lib/errors/InvalidDocumentEntropyError');
const ConsensusError = require('../../../lib/errors/ConsensusError');
const JsonSchemaError = require('../../../lib/errors/JsonSchemaError');
const MismatchDocumentContractIdAndDataContractError = require('../../../lib/errors/MismatchDocumentContractIdAndDataContractError');

const originalDocumentBaseSchema = require('../../../schema/base/document');

const generateDocumentId = require('../../../lib/document/generateDocumentId');

const {
expectValidationError,
expectJsonSchemaError,
Expand Down Expand Up @@ -56,6 +59,94 @@ describe('validateDocumentFactory', () => {
});

describe('Base schema', () => {
describe('$id', () => {
it('should be present', () => {
delete rawDocument.$id;

const result = validateDocument(rawDocument, dataContract);

expectJsonSchemaError(result);

const [error] = result.getErrors();

expect(error.dataPath).to.equal('');
expect(error.keyword).to.equal('required');
expect(error.params.missingProperty).to.equal('$id');
});

it('should be a string', () => {
rawDocument.$id = 1;

const result = validateDocument(rawDocument, dataContract);

expectJsonSchemaError(result);

const [error] = result.getErrors();

expect(error.dataPath).to.equal('.$id');
expect(error.keyword).to.equal('type');
});

it('should be no less than 42 chars', () => {
rawDocument.$id = '1'.repeat(41);

const result = validateDocument(rawDocument, dataContract);

expectJsonSchemaError(result);

const [error] = result.getErrors();

expect(error.dataPath).to.equal('.$id');
expect(error.keyword).to.equal('minLength');
});

it('should be no longer than 44 chars', () => {
rawDocument.$id = '1'.repeat(45);

const result = validateDocument(rawDocument, dataContract);

expectJsonSchemaError(result);

const [error] = result.getErrors();

expect(error.dataPath).to.equal('.$id');
expect(error.keyword).to.equal('maxLength');
});

it('should be base58 encoded', () => {
rawDocument.$id = '&'.repeat(44);

const result = validateDocument(rawDocument, dataContract);

expectJsonSchemaError(result);

const [error] = result.getErrors();

expect(error.keyword).to.equal('pattern');
expect(error.dataPath).to.equal('.$id');
});

it('should be a concatenation of contractId, ownerId, type and entropy', async () => {
rawDocument.$id = generateDocumentId(
rawDocument.$contractId,
rawDocument.$ownerId,
rawDocument.$type,
'',
);

const result = validateDocument(rawDocument, dataContract);

expectValidationError(
result,
InvalidDocumentIdError,
);

const [error] = result.getErrors();

expect(error.getRawDocument()).to.equal(rawDocument);
});
});

describe('$type', () => {
it('should be present', () => {
delete rawDocument.$type;
Expand Down
Loading

0 comments on commit 3d10a01

Please sign in to comment.