ripplelib
Version:
A JavaScript API for interacting with Ripple in Node.js and the browser
237 lines (190 loc) • 5.98 kB
JavaScript
'use strict';
var extend = require('extend');
var utils = require('./utils');
var UInt160 = require('./uint160').UInt160;
var Amount = require('./amount').Amount;
/**
* Meta data processing facility
*
* @constructor
* @param {Object} transaction metadata
*/
function Meta(data) {
var self = this;
this.nodes = [];
if (typeof data !== 'object') {
throw new TypeError('Missing metadata');
}
if (!Array.isArray(data.AffectedNodes)) {
throw new TypeError('Metadata missing AffectedNodes');
}
data.AffectedNodes.forEach(this.addNode, this);
};
Meta.NODE_TYPES = ['CreatedNode', 'ModifiedNode', 'DeletedNode'];
Meta.AMOUNT_FIELDS_AFFECTING_ISSUER = ['LowLimit', 'HighLimit', 'TakerPays', 'TakerGets'];
Meta.ACCOUNT_FIELDS = ['Account', 'Owner', 'Destination', 'Issuer', 'Target'];
/**
* @param {Object} node
* @api private
*/
Meta.prototype.getNodeType = function (node) {
var result = null;
for (var i = 0; i < Meta.NODE_TYPES.length; i++) {
var type = Meta.NODE_TYPES[i];
if (node.hasOwnProperty(type)) {
result = type;
break;
}
}
return result;
};
/**
* @param {String} field
* @api private
*/
Meta.prototype.isAccountField = function (field) {
return Meta.ACCOUNT_FIELDS.indexOf(field) !== -1;
};
/**
* Add node to metadata
*
* @param {Object} node
* @api private
*/
Meta.prototype.addNode = function (node) {
this._affectedAccounts = void 0;
this._affectedBooks = void 0;
var result = {};
if (result.nodeType = this.getNodeType(node)) {
node = node[result.nodeType];
result.diffType = result.nodeType;
result.entryType = node.LedgerEntryType;
result.ledgerIndex = node.LedgerIndex;
result.fields = extend({}, node.PreviousFields, node.NewFields, node.FinalFields);
result.fieldsPrev = node.PreviousFields || {};
result.fieldsNew = node.NewFields || {};
result.fieldsFinal = node.FinalFields || {};
// getAffectedBooks will set this
// result.bookKey = undefined;
this.nodes.push(result);
}
};
/**
* Get affected nodes array
*
* @param {Object} filter options
* @return {Array} nodes
*/
Meta.prototype.getNodes = function (options) {
if (typeof options === 'object') {
return this.nodes.filter(function (node) {
if (options.nodeType && options.nodeType !== node.nodeType) {
return false;
}
if (options.entryType && options.entryType !== node.entryType) {
return false;
}
if (options.bookKey && options.bookKey !== node.bookKey) {
return false;
}
return true;
});
} else {
return this.nodes;
}
};
Meta.prototype.getAffectedAccounts = function (from) {
if (this._affectedAccounts) {
return this._affectedAccounts;
}
var accounts = [];
// This code should match the behavior of the C++ method:
// TransactionMetaSet::getAffectedAccounts
for (var i = 0; i < this.nodes.length; i++) {
var node = this.nodes[i];
var fields = node.nodeType === 'CreatedNode' ? node.fieldsNew : node.fieldsFinal;
for (var fieldName in fields) {
var field = fields[fieldName];
if (this.isAccountField(fieldName) && UInt160.is_valid(field)) {
accounts.push(field);
} else if (~Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName)) {
var amount = Amount.from_json(field);
var issuer = amount.issuer();
if (issuer.is_valid() && !issuer.is_zero()) {
accounts.push(issuer.to_json());
}
}
}
}
this._affectedAccounts = utils.arrayUnique(accounts);
return this._affectedAccounts;
};
Meta.prototype.getAffectedBooks = function () {
if (this._affectedBooks) {
return this._affectedBooks;
}
var books = [];
for (var i = 0; i < this.nodes.length; i++) {
var node = this.nodes[i];
if (node.entryType !== 'Offer') {
continue;
}
var gets = Amount.from_json(node.fields.TakerGets);
var pays = Amount.from_json(node.fields.TakerPays);
var getsKey = gets.currency().to_json();
var paysKey = pays.currency().to_json();
if (getsKey !== 'XRP') {
getsKey += '/' + gets.issuer().to_json();
}
if (paysKey !== 'XRP') {
paysKey += '/' + pays.issuer().to_json();
}
var key = getsKey + ':' + paysKey;
// Hell of a lot of work, so we are going to cache this. We can use this
// later to good effect in OrderBook.notify to make sure we only process
// pertinent offers.
node.bookKey = key;
books.push(key);
}
this._affectedBooks = utils.arrayUnique(books);
return this._affectedBooks;
};
/**
* Execute a function on each affected node.
*
* The callback is passed two parameters. The first is a node object which looks
* like this:
*
* {
* // Type of diff, e.g. CreatedNode, ModifiedNode
* nodeType: 'CreatedNode'
*
* // Type of node affected, e.g. RippleState, AccountRoot
* entryType: 'RippleState',
*
* // Index of the ledger this change occurred in
* ledgerIndex: '01AB01AB...',
*
* // Contains all fields with later versions taking precedence
* //
* // This is a shorthand for doing things like checking which account
* // this affected without having to check the nodeType.
* fields: {...},
*
* // Old fields (before the change)
* fieldsPrev: {...},
*
* // New fields (that have been added)
* fieldsNew: {...},
*
* // Changed fields
* fieldsFinal: {...}
* }
*/
['forEach', 'map', 'filter', 'every', 'some', 'reduce'].forEach(function (fn) {
Meta.prototype[fn] = function () {
return Array.prototype[fn].apply(this.nodes, arguments);
};
});
Meta.prototype.each = Meta.prototype.forEach;
exports.Meta = Meta;