diff --git a/src/binformat.js b/src/binformat.js index d1897b15..c93ad184 100644 --- a/src/binformat.js +++ b/src/binformat.js @@ -224,7 +224,9 @@ var base = [ [ 'Fee' , REQUIRED ], [ 'OperationLimit' , OPTIONAL ], [ 'SigningPubKey' , REQUIRED ], - [ 'TxnSignature' , OPTIONAL ] + [ 'TxnSignature' , OPTIONAL ], + [ 'AccountTxnID' , OPTIONAL ], + [ 'Memos' , OPTIONAL ] ]; exports.tx = { diff --git a/src/serializedobject.js b/src/serializedobject.js index 8766f27f..e97248c6 100644 --- a/src/serializedobject.js +++ b/src/serializedobject.js @@ -97,39 +97,60 @@ SerializedObject.from_json = function(obj) { } // ND: This from_*json* seems a reasonable place to put validation of `json` - SerializedObject.check_no_missing_fields(typedef, obj); + SerializedObject.check_fields(typedef, obj); so.serialize(typedef, obj); return so; }; -SerializedObject.check_no_missing_fields = function(typedef, obj) { - var missing_fields = []; +SerializedObject.check_fields = function(typedef, obj) { + let missingFields = []; + let unknownFields = []; + let fieldsMap = {}; - for (var i = typedef.length - 1; i >= 0; i--) { - var spec = typedef[i]; - var field = spec[0]; - var requirement = spec[1]; + // Get missing required fields + typedef.forEach(function(field) { + var fieldName = field[0]; + var isRequired = field[1] === binformat.REQUIRED; - if (binformat.REQUIRED === requirement && obj[field] === undefined) { - missing_fields.push(field); - } - } - - if (missing_fields.length > 0) { - var object_name; - - if (obj.TransactionType !== undefined) { - object_name = SerializedObject.lookup_type_tx(obj.TransactionType); - } else if (obj.LedgerEntryType !== undefined) { - object_name = SerializedObject.lookup_type_le(obj.LedgerEntryType); + if (isRequired && obj[fieldName] === undefined) { + missingFields.push(fieldName); } else { - object_name = 'TransactionMetaData'; + fieldsMap[fieldName] = true; } + }); - throw new Error(object_name + ' is missing fields: ' + - JSON.stringify(missing_fields)); + // Get fields that are not specified in format + Object.keys(obj).forEach(function(key) { + if (!fieldsMap[key] && /^[A-Z]/.test(key)) { + unknownFields.push(key); + } + }); + + if (!(missingFields.length || unknownFields.length)) { + // No missing or unknown fields + return; } + + var errorMessage; + + if (obj.TransactionType !== undefined) { + errorMessage = SerializedObject.lookup_type_tx(obj.TransactionType); + } else if (obj.LedgerEntryType !== undefined) { + errorMessage = SerializedObject.lookup_type_le(obj.LedgerEntryType); + } else { + errorMessage = 'TransactionMetaData'; + } + + if (missingFields.length > 0) { + errorMessage += ' is missing fields: ' + JSON.stringify(missingFields); + } + if (unknownFields.length > 0) { + errorMessage += (missingFields.length ? ' and' : '') + + ' has unknown fields: ' + JSON.stringify(unknownFields); + } + + throw new Error(errorMessage); }; SerializedObject.prototype.append = function(bytes) { diff --git a/test/serializedobject-test.js b/test/serializedobject-test.js index 13e8a150..b5bd84dd 100644 --- a/test/serializedobject-test.js +++ b/test/serializedobject-test.js @@ -12,6 +12,7 @@ /* eslint-disable quotes*/ var assert = require('assert'); +var lodash = require('lodash'); var SerializedObject = require('ripple-lib').SerializedObject; var Amount = require('ripple-lib').Amount; var sjcl = require('ripple-lib').sjcl; @@ -150,6 +151,52 @@ describe('Serialized object', function() { assert.equal("DirectoryNode", output_json.LedgerEntryType); }); + it('checks for missing required fields', function() { + var input_json = { + TransactionType: 'Payment', + // no non required fields + Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', + Amount: '274579388', + Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', + Fee: '15', + Sequence: 351, + SigningPubKey: '02' + }; + + Object.keys(input_json).slice(1).forEach(function(k) { + var bad_json = lodash.merge({}, input_json); + delete bad_json[k]; + + assert.strictEqual(bad_json[k], undefined); + assert.throws(function() { + SerializedObject.from_json(bad_json); + }, new RegExp('Payment is missing fields: \\["' + k + '"\\]')); + + }); + }); + it('checks for unknown fields', function() { + var input_json = { + TransactionType: 'Payment', + // no non required fields + Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', + Amount: '274579388', + Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', + Fee: '15', + Sequence: 351, + SigningPubKey: '02' + }; + + Object.keys(input_json).slice(1).forEach(function(k) { + var bad_json = lodash.merge({}, input_json); + bad_json[k + 'z'] = bad_json[k]; + + assert.throws(function() { + SerializedObject.from_json(bad_json); + }, new RegExp('Payment has unknown fields: \\["' + k + 'z"\\]')); + + }); + }); + describe('Format validation', function() { // Peercover actually had a problem submitting transactions without a `Fee` // and rippled was only informing of "transaction is invalid"