'use strict';
/**
* @module lib/protocol/references
* @summary Whiteflag message reference validation module
* @description Module for reference verification functions
* @tutorial modules
* @tutorial protocol
*/
module.exports = {
verify: verifyReference
};
/* Common internal functions and classes */
const arr = require('../_common/arrays');
const log = require('../_common/logger');
const { ProtocolError } = require('../_common/errors');
const { type,
getSchema } = require('./_common/messages');
/* Whiteflag modules */
const wfRetrieve = require('./retrieve');
/* Module constants */
const MODULELOG = 'references';
const TESTMESSAGECODE = 'T';
const wfSpec = getSchema().specifications;
/* MAIN MODULE FUNCTIONS */
/**
* Checks if message references antother message correclty and updates metaheader accordingly
* @function verifyReference
* @alias module:lib/protocol/validation.verifyReference
* @param {wfMessage} wfMessage a Whiteflag message
* @param {wfMessageCb} callback function called on completion
*/
function verifyReference(wfMessage, callback) {
// Check message metaheader
if (!wfMessage?.MetaHeader) {
return callback(new ProtocolError('Missing metaheader', null, 'WF_METAHEADER_ERROR'), wfMessage);
}
let { MetaHeader: meta } = wfMessage;
// Check message header
if (!wfMessage.MessageHeader) {
meta.formatValid = false;
return callback(new ProtocolError('Missing message header', null, 'WF_FORMAT_ERROR'), wfMessage);
}
let { MessageHeader: header } = wfMessage;
// If rerference indcator is 0, then referenced transcation hash may be anything
if (header.ReferenceIndicator === '0') {
meta.referenceValid = true;
return callback(null, wfMessage);
}
// Message referenced; but no transaction hash
if (/^0{64}$/.test(header.ReferencedMessage)) {
const refError = 'Missing transaction hash of referenced message';
meta.referenceValid = false;
meta.validationErrors = arr.addItem(meta.validationErrors, refError);
return callback(new ProtocolError(refError, null, 'WF_REFERENCE_ERROR'), wfMessage);
}
// Retrieve referenced message and validate reference
wfRetrieve.getMessage(header.ReferencedMessage, meta.blockchain,
function verifyReferenceRetrieveCb(err, refMessages) {
// Return if referenced message cannot be retrieved
if (err) return callback(err, wfMessage);
if (refMessages.length === 0) return callback(new Error('Could not retrieve referenced message'), wfMessage);
// Call evaluation function and return result
evaluateReference(wfMessage, refMessages[0], function verifyReferenceEvalCb(err) {
if (err) {
if (err instanceof ProtocolError) meta.referenceValid = false;
return callback(err, wfMessage);
}
meta.referenceValid = true;
return callback(null, wfMessage);
}
);
});
}
/* PRIVATE MODULE FUNCTIONS */
/**
* Evaluates reference by checking referenced message
* @private
* @param {wfMessage} wfMessage a Whiteflag message
* @param {Object} wfRefMessage the refereced Whiteflag message
* @param {errorCb} callback function called on completion
*/
function evaluateReference(wfMessage, wfRefMessage, callback) {
// Get message reference data
let { MetaHeader: meta } = wfMessage;
const { MessageHeader: header } = wfMessage;
if (header.MessageCode === TESTMESSAGECODE) {
// Test message may reference any message
return callback(null);
}
// Get referenced message data
const { MetaHeader: refMeta } = wfRefMessage;
const { MessageHeader: refHeader } = wfRefMessage;
if (!refHeader) return callback(new Error('Cannot evaluate references without header of referenced message'));
// Array and metaheader for errors
let refErrors = [];
// Perform reference checks
log.trace(MODULELOG, `Evaluating ${type(wfMessage)} message ${JSON.stringify(header)} referencing ${type(wfRefMessage)} message ${JSON.stringify(refHeader)}`)
try {
// CHECK 1: may the message type reference the referenced message type?
const mesageTypeIndex = wfSpec.MessageCode.findIndex(
mesageCode => mesageCode.const === header.MessageCode
);
if (mesageTypeIndex < 0) {
return callback(new Error(`Cannot evaluate reference for non-existing message type: ${header.MessageCode}`));
}
const wfTypeSpec = wfSpec.MessageCode[mesageTypeIndex];
const refTypeIndex = wfTypeSpec.allowedToReference.findIndex(
allowed => allowed.referencedMessageCode === refHeader.MessageCode
);
if (refTypeIndex < 0) {
refErrors.push(`Message type ${header.MessageCode} may not reference message type ${refHeader.MessageCode}`);
} else {
// CHECK 2: is the reference code allowed?
const wfRefSpec = wfTypeSpec.allowedToReference[refTypeIndex].allowedReferenceIndicator;
if (!wfRefSpec.includes(header.ReferenceIndicator)) {
refErrors.push(`Message type ${header.MessageCode} may not use reference code ${header.ReferenceIndicator} to reference message type ${refHeader.MessageCode}`);
}
// CHECK 3: are the reference codes of both messages compatible (i.e. meaningful)?
const refIndex = wfSpec.ReferenceIndicator.findIndex(
referenceIndicator => referenceIndicator.const === header.ReferenceIndicator
);
if (refIndex < 0) {
refErrors.push(`Reference Indicator ${header.ReferenceIndicator} is not allowed`);
} else {
// Check if allowed from same originator
if (
!wfSpec.ReferenceIndicator[refIndex].allowedToReferenceSameOriginator
&& meta.originatorAddress?.toLowerCase() === refMeta.originatorAddress?.toLowerCase()
) {
refErrors.push(`Reference code ${header.ReferenceIndicator} may not be used by the same originator`);
}
// Check if allowed from different originator
if (
!wfSpec.ReferenceIndicator[refIndex].allowedToReferenceDifferentOriginator
&& meta.originatorAddress?.toLowerCase() !== refMeta.originatorAddress?.toLowerCase()
) {
refErrors.push(`Reference code ${header.ReferenceIndicator} may not be used by a different originator`);
}
// Check if reference code may reference reference code
const wfSpecAllowedRefIndicators = wfSpec.ReferenceIndicator[refIndex].allowedToReference;
if (!wfSpecAllowedRefIndicators.includes(refHeader.ReferenceIndicator)) {
refErrors.push(`Reference code ${header.ReferenceIndicator} cannot meaningfully reference reference code ${refHeader.ReferenceIndicator}`);
}
}
}
} catch(err) {
return callback(new Error(`Error while evaluating reference: ${err.message}`));
}
// Evaluate and return result
if (refErrors.length > 0) {
return callback(new ProtocolError('Invalid message reference', refErrors, 'WF_REFERENCE_ERROR'), wfMessage);
}
return callback(null);
}