Add TransactionManager test

This commit is contained in:
wltsmrz
2015-02-25 18:49:59 -08:00
parent 2b531d2a1f
commit c3b274b18f
6 changed files with 1057 additions and 106 deletions

52
npm-shrinkwrap.json generated
View File

@@ -4,27 +4,27 @@
"dependencies": { "dependencies": {
"async": { "async": {
"version": "0.9.0", "version": "0.9.0",
"from": "async@>=0.9.0 <0.10.0", "from": "async@~0.9.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
}, },
"bignumber.js": { "bignumber.js": {
"version": "2.0.3", "version": "2.0.3",
"from": "bignumber.js@>=2.0.3 <3.0.0", "from": "bignumber.js@^2.0.3",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.3.tgz" "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.3.tgz"
}, },
"extend": { "extend": {
"version": "1.2.1", "version": "1.2.1",
"from": "extend@>=1.2.1 <1.3.0", "from": "extend@~1.2.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz" "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
}, },
"lodash": { "lodash": {
"version": "3.1.0", "version": "3.3.1",
"from": "lodash@>=3.1.0 <4.0.0", "from": "lodash@^3.1.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.1.0.tgz" "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.3.1.tgz"
}, },
"lru-cache": { "lru-cache": {
"version": "2.5.0", "version": "2.5.0",
"from": "lru-cache@>=2.5.0 <2.6.0", "from": "lru-cache@~2.5.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz" "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz"
}, },
"ripple-wallet-generator": { "ripple-wallet-generator": {
@@ -34,7 +34,7 @@
}, },
"superagent": { "superagent": {
"version": "0.18.2", "version": "0.18.2",
"from": "superagent@>=0.18.0 <0.19.0", "from": "superagent@^0.18.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-0.18.2.tgz", "resolved": "https://registry.npmjs.org/superagent/-/superagent-0.18.2.tgz",
"dependencies": { "dependencies": {
"qs": { "qs": {
@@ -69,7 +69,7 @@
}, },
"debug": { "debug": {
"version": "1.0.4", "version": "1.0.4",
"from": "debug@>=1.0.1 <1.1.0", "from": "debug@~1.0.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
"dependencies": { "dependencies": {
"ms": { "ms": {
@@ -91,7 +91,7 @@
"dependencies": { "dependencies": {
"combined-stream": { "combined-stream": {
"version": "0.0.7", "version": "0.0.7",
"from": "combined-stream@>=0.0.4 <0.1.0", "from": "combined-stream@~0.0.4",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"dependencies": { "dependencies": {
"delayed-stream": { "delayed-stream": {
@@ -110,7 +110,7 @@
"dependencies": { "dependencies": {
"core-util-is": { "core-util-is": {
"version": "1.0.1", "version": "1.0.1",
"from": "core-util-is@>=1.0.0 <1.1.0", "from": "core-util-is@~1.0.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
}, },
"isarray": { "isarray": {
@@ -120,12 +120,12 @@
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
"from": "string_decoder@>=0.10.0 <0.11.0", "from": "string_decoder@~0.10.x",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0", "from": "inherits@~2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
} }
} }
@@ -134,7 +134,7 @@
}, },
"ws": { "ws": {
"version": "0.7.1", "version": "0.7.1",
"from": "ws@>=0.7.1 <0.8.0", "from": "ws@~0.7.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-0.7.1.tgz",
"dependencies": { "dependencies": {
"options": { "options": {
@@ -144,38 +144,40 @@
}, },
"ultron": { "ultron": {
"version": "1.0.1", "version": "1.0.1",
"from": "ultron@>=1.0.0 <1.1.0", "from": "ultron@1.0.x",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.1.tgz" "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.1.tgz"
}, },
"bufferutil": { "bufferutil": {
"version": "1.0.1", "version": "1.0.1",
"from": "bufferutil@>=1.0.0 <1.1.0", "from": "bufferutil@1.0.x",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.0.1.tgz", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.0.1.tgz",
"dependencies": { "dependencies": {
"bindings": { "bindings": {
"version": "1.2.1", "version": "1.2.1",
"from": "bindings@>=1.2.0 <1.3.0" "from": "bindings@1.2.x",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
}, },
"nan": { "nan": {
"version": "1.6.1", "version": "1.6.2",
"from": "nan@>=1.6.0 <1.7.0", "from": "nan@1.6.x",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.6.1.tgz" "resolved": "https://registry.npmjs.org/nan/-/nan-1.6.2.tgz"
} }
} }
}, },
"utf-8-validate": { "utf-8-validate": {
"version": "1.0.1", "version": "1.0.1",
"from": "utf-8-validate@>=1.0.0 <1.1.0", "from": "utf-8-validate@1.0.x",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.0.1.tgz",
"dependencies": { "dependencies": {
"bindings": { "bindings": {
"version": "1.2.1", "version": "1.2.1",
"from": "bindings@>=1.2.0 <1.3.0" "from": "bindings@1.2.x",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
}, },
"nan": { "nan": {
"version": "1.6.1", "version": "1.6.2",
"from": "nan@>=1.6.0 <1.7.0", "from": "nan@1.6.x",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.6.1.tgz" "resolved": "https://registry.npmjs.org/nan/-/nan-1.6.2.tgz"
} }
} }
} }

View File

@@ -25,7 +25,7 @@
"superagent": "^0.18.0" "superagent": "^0.18.0"
}, },
"devDependencies": { "devDependencies": {
"assert-diff": "0.0.4", "assert-diff": "^1.0.1",
"coveralls": "~2.10.0", "coveralls": "~2.10.0",
"eslint": "^0.13.0", "eslint": "^0.13.0",
"gulp": "~3.8.10", "gulp": "~3.8.10",

View File

@@ -1,11 +1,13 @@
var util = require('util'); 'use strict';
var assert = require('assert');
var async = require('async'); var util = require('util');
var assert = require('assert');
var async = require('async');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var Transaction = require('./transaction').Transaction; var Transaction = require('./transaction').Transaction;
var RippleError = require('./rippleerror').RippleError; var RippleError = require('./rippleerror').RippleError;
var PendingQueue = require('./transactionqueue').TransactionQueue; var PendingQueue = require('./transactionqueue').TransactionQueue;
var log = require('./log').internal.sub('transactionmanager'); var log = require('./log').internal.sub('transactionmanager');
/** /**
* @constructor TransactionManager * @constructor TransactionManager
@@ -20,7 +22,7 @@ function TransactionManager(account) {
this._account = account; this._account = account;
this._accountID = account._account_id; this._accountID = account._account_id;
this._remote = account._remote; this._remote = account._remote;
this._nextSequence = void(0); this._nextSequence = undefined;
this._maxFee = this._remote.max_fee; this._maxFee = this._remote.max_fee;
this._maxAttempts = this._remote.max_attempts; this._maxAttempts = this._remote.max_attempts;
this._submissionTimeout = this._remote.submission_timeout; this._submissionTimeout = this._remote.submission_timeout;
@@ -37,7 +39,7 @@ function TransactionManager(account) {
function updatePendingStatus(ledger) { function updatePendingStatus(ledger) {
self._updatePendingStatus(ledger); self._updatePendingStatus(ledger);
}; }
this._remote.on('ledger_closed', updatePendingStatus); this._remote.on('ledger_closed', updatePendingStatus);
@@ -47,7 +49,7 @@ function TransactionManager(account) {
// hooking back into ledger_closed // hooking back into ledger_closed
self._remote.on('ledger_closed', updatePendingStatus); self._remote.on('ledger_closed', updatePendingStatus);
}); });
}; }
this._remote.on('disconnect', function() { this._remote.on('disconnect', function() {
self._remote.removeListener('ledger_closed', updatePendingStatus); self._remote.removeListener('ledger_closed', updatePendingStatus);
@@ -56,7 +58,7 @@ function TransactionManager(account) {
// Query server for next account transaction sequence // Query server for next account transaction sequence
this._loadSequence(); this._loadSequence();
}; }
util.inherits(TransactionManager, EventEmitter); util.inherits(TransactionManager, EventEmitter);
@@ -96,7 +98,7 @@ TransactionManager.normalizeTransaction = function(tx) {
var transaction = { }; var transaction = { };
var keys = Object.keys(tx); var keys = Object.keys(tx);
for (var i=0; i<keys.length; i++) { for (var i = 0; i < keys.length; i++) {
var k = keys[i]; var k = keys[i];
switch (k) { switch (k) {
case 'transaction': case 'transaction':
@@ -162,7 +164,8 @@ TransactionManager.prototype._transactionReceived = function(tx) {
if (!(submission instanceof Transaction)) { if (!(submission instanceof Transaction)) {
// The received transaction does not correlate to one submitted // The received transaction does not correlate to one submitted
return this._pending.addReceivedId(hash, transaction); this._pending.addReceivedId(hash, transaction);
return;
} }
// ND: A `success` handler will `finalize` this later // ND: A `success` handler will `finalize` this later
@@ -197,7 +200,7 @@ TransactionManager.prototype._adjustFees = function() {
transaction.once('presubmit', function() { transaction.once('presubmit', function() {
transaction.emit('error', 'tejMaxFeeExceeded'); transaction.emit('error', 'tejMaxFeeExceeded');
}); });
}; }
this._pending.forEach(function(transaction) { this._pending.forEach(function(transaction) {
if (transaction._setFixedFee) { if (transaction._setFixedFee) {
@@ -209,7 +212,8 @@ TransactionManager.prototype._adjustFees = function() {
if (Number(newFee) > self._maxFee) { if (Number(newFee) > self._maxFee) {
// Max transaction fee exceeded, abort submission // Max transaction fee exceeded, abort submission
return maxFeeExceeded(transaction); maxFeeExceeded(transaction);
return;
} }
transaction.tx_json.Fee = newFee; transaction.tx_json.Fee = newFee;
@@ -244,6 +248,10 @@ TransactionManager.prototype._updatePendingStatus = function(ledger) {
assert.strictEqual(typeof ledger.ledger_index, 'number'); assert.strictEqual(typeof ledger.ledger_index, 'number');
this._pending.forEach(function(transaction) { this._pending.forEach(function(transaction) {
if (transaction.finalized) {
return;
}
switch (ledger.ledger_index - transaction.submitIndex) { switch (ledger.ledger_index - transaction.submitIndex) {
case 4: case 4:
transaction.emit('missing', ledger); transaction.emit('missing', ledger);
@@ -253,10 +261,6 @@ TransactionManager.prototype._updatePendingStatus = function(ledger) {
break; break;
} }
if (transaction.finalized) {
return;
}
if (ledger.ledger_index > transaction.tx_json.LastLedgerSequence) { if (ledger.ledger_index > transaction.tx_json.LastLedgerSequence) {
// Transaction must fail // Transaction must fail
transaction.emit('error', new RippleError( transaction.emit('error', new RippleError(
@@ -265,46 +269,54 @@ TransactionManager.prototype._updatePendingStatus = function(ledger) {
}); });
}; };
//Fill an account transaction sequence // Fill an account transaction sequence
TransactionManager.prototype._fillSequence = function(tx, callback) { TransactionManager.prototype._fillSequence = function(tx, callback) {
var self = this; var self = this;
function submitFill(sequence, callback) { function submitFill(sequence, fCallback) {
var fill = self._remote.transaction(); var fillTransaction = self._remote.createTransaction('AccountSet', {
fill.account_set(self._accountID); account: self._accountID
fill.tx_json.Sequence = sequence; });
fill.once('submitted', callback); fillTransaction.tx_json.Sequence = sequence;
// Secrets may be set on a per-transaction basis // Secrets may be set on a per-transaction basis
if (tx._secret) { if (tx._secret) {
fill.secret(tx._secret); fillTransaction.secret(tx._secret);
} }
fill.submit(); fillTransaction.once('submitted', fCallback);
}; fillTransaction.submit();
}
function sequenceLoaded(err, sequence) { function sequenceLoaded(err, sequence) {
if (typeof sequence !== 'number') { if (typeof sequence !== 'number') {
return callback(new Error('Failed to fetch account transaction sequence')); log.info('fill sequence: failed to fetch account transaction sequence');
return callback();
} }
var sequenceDif = tx.tx_json.Sequence - sequence; var sequenceDiff = tx.tx_json.Sequence - sequence;
var submitted = 0; var submitted = 0;
;(function nextFill(sequence) { async.whilst(
if (sequence >= tx.tx_json.Sequence) { function() {
return; return submitted < sequenceDiff;
} },
function(asyncCallback) {
submitFill(sequence, function() { submitFill(sequence, function(res) {
if (++submitted === sequenceDif) { ++submitted;
if (res.engine_result === 'tesSUCCESS') {
self.emit('sequence_filled', err);
}
asyncCallback();
});
},
function() {
if (callback) {
callback(); callback();
} else {
nextFill(sequence + 1);
} }
}); }
})(sequence); );
}; }
this._loadSequence(sequenceLoaded); this._loadSequence(sequenceLoaded);
}; };
@@ -318,7 +330,7 @@ TransactionManager.prototype._fillSequence = function(tx, callback) {
TransactionManager.prototype._loadSequence = function(callback) { TransactionManager.prototype._loadSequence = function(callback) {
var self = this; var self = this;
var callback = (typeof callback === 'function') ? callback : function(){}; callback = (typeof callback === 'function') ? callback : function() {};
function sequenceLoaded(err, sequence) { function sequenceLoaded(err, sequence) {
if (err || typeof sequence !== 'number') { if (err || typeof sequence !== 'number') {
@@ -331,7 +343,7 @@ TransactionManager.prototype._loadSequence = function(callback) {
self._nextSequence = sequence; self._nextSequence = sequence;
self.emit('sequence_loaded', sequence); self.emit('sequence_loaded', sequence);
callback(err, sequence); callback(err, sequence);
}; }
this._account.getNextSequence(sequenceLoaded); this._account.getNextSequence(sequenceLoaded);
}; };
@@ -346,10 +358,11 @@ TransactionManager.prototype._loadSequence = function(callback) {
TransactionManager.prototype._handleReconnect = function(callback) { TransactionManager.prototype._handleReconnect = function(callback) {
var self = this; var self = this;
var callback = (typeof callback === 'function') ? callback : function(){}; callback = (typeof callback === 'function') ? callback : function() {};
if (!this._pending.length()) { if (!this._pending.length()) {
return callback(); callback();
return;
} }
function handleTransactions(err, transactions) { function handleTransactions(err, transactions) {
@@ -372,7 +385,7 @@ TransactionManager.prototype._handleReconnect = function(callback) {
// Resubmit pending transactions after sequence is loaded // Resubmit pending transactions after sequence is loaded
self._resubmit(); self._resubmit();
}); });
}; }
var options = { var options = {
account: this._accountID, account: this._accountID,
@@ -410,7 +423,7 @@ TransactionManager.prototype._waitLedgers = function(ledgers, callback) {
self._remote.removeListener('ledger_closed', ledgerClosed); self._remote.removeListener('ledger_closed', ledgerClosed);
callback(); callback();
} }
}; }
this._remote.on('ledger_closed', ledgerClosed); this._remote.on('ledger_closed', ledgerClosed);
}; };
@@ -426,8 +439,16 @@ TransactionManager.prototype._waitLedgers = function(ledgers, callback) {
TransactionManager.prototype._resubmit = function(ledgers, pending) { TransactionManager.prototype._resubmit = function(ledgers, pending) {
var self = this; var self = this;
var ledgers = ledgers || 0;
var pending = pending ? [ pending ] : this._pending; if (ledgers && typeof ledgers !== 'number') {
pending = ledgers;
ledgers = 0;
}
ledgers = ledgers || 0;
pending = pending instanceof Transaction
? [pending]
: this.getPending().getQueue();
function resubmitTransaction(transaction, next) { function resubmitTransaction(transaction, next) {
if (!transaction || transaction.finalized) { if (!transaction || transaction.finalized) {
@@ -449,7 +470,7 @@ TransactionManager.prototype._resubmit = function(ledgers, pending) {
} }
while (self._pending.hasSequence(transaction.tx_json.Sequence)) { while (self._pending.hasSequence(transaction.tx_json.Sequence)) {
//Sequence number has been consumed by another transaction // Sequence number has been consumed by another transaction
transaction.tx_json.Sequence += 1; transaction.tx_json.Sequence += 1;
if (self._remote.trace) { if (self._remote.trace) {
@@ -467,7 +488,7 @@ TransactionManager.prototype._resubmit = function(ledgers, pending) {
}); });
self._request(transaction); self._request(transaction);
}; }
this._waitLedgers(ledgers, function() { this._waitLedgers(ledgers, function() {
async.eachSeries(pending, resubmitTransaction); async.eachSeries(pending, resubmitTransaction);
@@ -528,8 +549,8 @@ TransactionManager.prototype._request = function(tx) {
} }
if (tx.attempts > 0 && !remote.local_signing) { if (tx.attempts > 0 && !remote.local_signing) {
var message = 'Automatic resubmission requires local signing'; var errMessage = 'Automatic resubmission requires local signing';
tx.emit('error', new RippleError('tejLocalSigningRequired', message)); tx.emit('error', new RippleError('tejLocalSigningRequired', errMessage));
return; return;
} }
@@ -542,22 +563,22 @@ TransactionManager.prototype._request = function(tx) {
// Transaction may succeed after Sequence is updated // Transaction may succeed after Sequence is updated
self._resubmit(1, tx); self._resubmit(1, tx);
} }
}; }
function transactionRetry(message) { function transactionRetry() {
// XXX This may no longer be necessary. Instead, update sequence numbers // XXX This may no longer be necessary. Instead, update sequence numbers
// after a transaction fails definitively // after a transaction fails definitively
self._fillSequence(tx, function() { self._fillSequence(tx, function() {
self._resubmit(1, tx); self._resubmit(1, tx);
}); });
}; }
function transactionFailedLocal(message) { function transactionFailedLocal(message) {
if (message.engine_result === 'telINSUF_FEE_P') { if (message.engine_result === 'telINSUF_FEE_P') {
// Transaction may succeed after Fee is updated // Transaction may succeed after Fee is updated
self._resubmit(1, tx); self._resubmit(1, tx);
} }
}; }
function submissionError(error) { function submissionError(error) {
// Either a tem-class error or generic server error such as tooBusy. This // Either a tem-class error or generic server error such as tooBusy. This
@@ -568,7 +589,7 @@ TransactionManager.prototype._request = function(tx) {
self._nextSequence--; self._nextSequence--;
tx.emit('error', error); tx.emit('error', error);
} }
}; }
function submitted(message) { function submitted(message) {
if (tx.finalized) { if (tx.finalized) {
@@ -611,7 +632,7 @@ TransactionManager.prototype._request = function(tx) {
// tem // tem
submissionError(message); submissionError(message);
} }
}; }
function requestTimeout() { function requestTimeout() {
// ND: What if the response is just slow and we get a response that // ND: What if the response is just slow and we get a response that
@@ -630,7 +651,7 @@ TransactionManager.prototype._request = function(tx) {
} }
self._resubmit(1, tx); self._resubmit(1, tx);
} }
}; }
tx.submitIndex = this._remote._ledger_current_index; tx.submitIndex = this._remote._ledger_current_index;
@@ -661,10 +682,7 @@ TransactionManager.prototype._request = function(tx) {
tx.emit('postsubmit'); tx.emit('postsubmit');
//XXX
submitRequest.timeout(self._submissionTimeout, requestTimeout); submitRequest.timeout(self._submissionTimeout, requestTimeout);
return submitRequest;
}; };
/** /**
@@ -675,7 +693,6 @@ TransactionManager.prototype._request = function(tx) {
TransactionManager.prototype.submit = function(tx) { TransactionManager.prototype.submit = function(tx) {
var self = this; var self = this;
var remote = this._remote;
if (typeof this._nextSequence !== 'number') { if (typeof this._nextSequence !== 'number') {
// If sequence number is not yet known, defer until it is. // If sequence number is not yet known, defer until it is.

View File

@@ -1,3 +1,6 @@
'use strict';
var lodash = require('lodash');
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var Transaction = require('./transaction').Transaction; var Transaction = require('./transaction').Transaction;
@@ -7,9 +10,9 @@ var Transaction = require('./transaction').Transaction;
function TransactionQueue() { function TransactionQueue() {
this._queue = [ ]; this._queue = [ ];
this._idCache = LRU({ max: 200 }); this._idCache = new LRU({max: 200});
this._sequenceCache = LRU({ max: 200 }); this._sequenceCache = new LRU({max: 200});
}; }
/** /**
* Store received (validated) sequence * Store received (validated) sequence
@@ -64,16 +67,9 @@ TransactionQueue.prototype.getReceived = function(id) {
*/ */
TransactionQueue.prototype.getSubmission = function(id) { TransactionQueue.prototype.getSubmission = function(id) {
var result = void(0); return lodash.find(this._queue, function(tx) {
return lodash.contains(tx.submittedIDs, id);
for (var i=0, tx; (tx=this._queue[i]); i++) { });
if (~tx.submittedIDs.indexOf(id)) {
result = tx;
break;
}
}
return result;
}; };
/** /**
@@ -83,11 +79,15 @@ TransactionQueue.prototype.getSubmission = function(id) {
*/ */
TransactionQueue.prototype.getMinLedger = function() { TransactionQueue.prototype.getMinLedger = function() {
if (this.length() < 1) {
return -1;
}
var result = Infinity; var result = Infinity;
for (var i=0, tx; (tx=this._queue[i]); i++) { for (var i = 0; i < this.length(); i++) {
if (tx.initialSubmitIndex < result) { if (this._queue[i].initialSubmitIndex < result) {
result = tx.initialSubmitIndex; result = this._queue[i].initialSubmitIndex;
} }
} }
@@ -153,4 +153,12 @@ TransactionQueue.prototype.getLength = function() {
return this._queue.length; return this._queue.length;
}; };
/**
* @return {Array} pending queue
*/
TransactionQueue.prototype.getQueue = function() {
return this._queue;
};
exports.TransactionQueue = TransactionQueue; exports.TransactionQueue = TransactionQueue;

294
test/fixtures/transactionmanager.json vendored Normal file
View File

@@ -0,0 +1,294 @@
{
"ACCOUNT": {
"address": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"secret": "snoBcf899BdZP6nyaRHnSvE7qAJFP"
},
"ACCOUNT2": {
"address": "rn7GZeJpUafLAyKQ7wrU5SUCu2hpQq7S2W",
"secret": "ssThngbFXQWyJpK1mTeaNBrhHxHhp"
},
"SUBSCRIBE_RESPONSE": {
"id": 1,
"type": "response",
"status": "success",
"result": {
"fee_base": 10,
"fee_ref": 10,
"hostid": "NBZ",
"ledger_hash": "F1AF7D977B01D99D013EEE75136263A0937575882CC9A741662C3C111B08B112",
"ledger_index": 1,
"ledger_time": 463782770,
"load_base": 256,
"load_factor": 256,
"pubkey_node": "n3Lp7DfQmxjHF5mYJsV2U9anALHmPem8PWQHWGpw4XMz79HA5aJH",
"random": "EECFEE93BBB608914F190EC177B11DE52FC1D75D2C97DACBD26D2DFC6050E874",
"reserve_base": 20000000,
"reserve_inc": 5000000,
"server_status": "full",
"validated_ledgers": "32570-11692908"
}
},
"ACCOUNT_INFO_RESPONSE": {
"id": 1,
"type": "response",
"status": "success",
"result": {
"account_data": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Balance": "10000",
"Flags": 4849664,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 1,
"Sequence": 3,
"index": "01F3A2D58A5B986BF31CD92B513EB539CE48F705BB0E18FA39EF042DBE07A5DE"
},
"ledger_current_index": 11699032,
"validated": false
}
},
"TX_STREAM_TRANSACTION": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"ledger_hash": "8093A78DEFD1F02037ABD349BD452081554A1DB1FE5E20DCB82D8DF16DD23B6D",
"ledger_index": 1,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Balance": "1000",
"Flags": 4849664,
"OwnerCount": 1,
"Sequence": 1
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "A4B28FB972EF890DC39A8557DF8960D41DADA00D39B0F1EFCD4BBB85FCA13A30",
"PreviousFields": {
"Balance": "1000",
"Sequence": 3864
},
"PreviousTxnID": "F4910E55A39C42AB82071212D84119631DDE0B0F4F8F9040F252B0066898DBDF",
"PreviousTxnLgrSeq": 11693103
}
}
],
"TransactionIndex": 9,
"TransactionResult": "tesSUCCESS"
},
"status": "closed",
"transaction": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Fee": "10",
"Flags": 2147483648,
"LastLedgerSequence": 11693114,
"Sequence": 1,
"SigningPubKey": "2D7F6A1F5F1AE2E0105A88F47D33A0B68031DE7FC43FF42388DA7D4B4C93865F",
"TransactionType": "AccountSet",
"TxnSignature": "39A0EC2307723546901BC8017CED16DF3DD79F50CA68A32377E0CF1BE097DCA004C21D99120220321921230C53A87629D79E6A5E74C08ECB59F8CBB9D695256DE5D0F8CB22FF0A",
"date": 4771619,
"hash": "01D66ACBD00B2A8F5D66FC8F67AC879CAECF49BC94FB97CF24F66B8406F4C040"
},
"type": "transaction",
"validated": true
},
"ACCOUNT_TX_TRANSACTION": {
"validated": true,
"meta": {
"TransactionIndex": 3,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 11693103,
"PreviousTxnID": "F4910E55A39C42AB82071212D84119631DDE0B0F4F8F9040F252B0066898DBDF",
"LedgerIndex": "A4B28FB972EF890DC39A8557DF8960D41DADA00D39B0F1EFCD4BBB85FCA13A30",
"PreviousFields": {
"Sequence": 3864,
"Balance": "1000"
},
"FinalFields": {
"Flags": 4849664,
"Sequence": 3865,
"OwnerCount": 1,
"Balance": "1000",
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d"
}
}
}
],
"TransactionResult": "tesSUCCESS"
},
"tx": {
"TransactionType": "AccountSet",
"Flags": 2147483648,
"Sequence": 3864,
"LastLedgerSequence": 2,
"Fee": "10",
"SigningPubKey": "2D7F6A1F5F1AE2E0105A88F47D33A0B68031DE7FC43FF42388DA7D4B4C93865F",
"TxnSignature": "39A0EC2307723546901BC8017CED16DF3DD79F50CA68A32377E0CF1BE097DCA004C21D99120220321921230C53A87629D79E6A5E74C08ECB59F8CBB9D695256DE5D0F8CB22FF",
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"hash": "01D66ACBD00B2A8F5D66FC8F67AC879CAECF49BC94FB97CF24F66B8406F4C040",
"ledger_index": 1,
"inLedger": 1
}
},
"ACCOUNT_TX_RESPONSE": {
"id": 1,
"status": "success",
"type": "response",
"result": {
"account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"ledger_index_max": 100,
"ledger_index_min": 1,
"limit": 10,
"transactions": [ ]
}
},
"LEDGER": {
"type": "ledgerClosed",
"fee_base": 10,
"fee_ref": 10,
"ledger_hash": "FF757ECCF710C27DBCEC569840C38C5583594B56C49693079D95D8A99C30A928",
"ledger_index": 1,
"ledger_time": 478088,
"reserve_base": 20000000,
"reserve_inc": 5000000,
"txn_count": 1,
"validated_ledgers": "1-2"
},
"ACCOUNT_TX_ERROR": {
"id": 1,
"status": "error",
"type": "response",
"error": "actMalformed",
"error_code": 33,
"error_message": "Account malformed.",
"request": {
"account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3dZ",
"binary": true,
"command": "account_tx",
"id": 1,
"ledger_index_max": -1,
"ledger_index_min": -1,
"limit": 10
}
},
"SUBMIT_RESPONSE": {
"id": 1,
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "12000322800000002400000001201B0000000568400000000000000C732102999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC744730450221008DFE8B50D66F2094652AA6BD1B354CC97E885BC1AEDF586C7ED61C7D786AA66202200FEE38460CF25C726A7F31626BD7A45542AFB2E468EBE9A175F9A9EC3FD19DDB811492DECA2DC92352BE97C1F6347F7E6CCB9A8241C8",
"tx_json": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Fee": "12000",
"Flags": 2147483648,
"LastLedgerSequence": 1,
"Sequence": 1,
"SigningPubKey": "02999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC",
"TransactionType": "AccountSet",
"TxnSignature": "30440220332316AC7B96B1385F4BD0159706439889A9E05AD62D168E61AC5CBD7BD417C00220361D9FBC31A5919F68FA70E9FB78C6E6C25CC7F89D0A2F9CC42660594EE0D0A2",
"hash": "0D15A847D605DB5F1B76A4EE88EDCD5D279D47F009CFCE27F1D3DFE763225975"
}
},
"status": "success",
"type": "response"
},
"SUBMIT_TEC_RESPONSE": {
"id": 1,
"result": {
"engine_result": "tecNO_REGULAR_KEY",
"engine_result_code": 131,
"engine_result_message": "Regular key is not set.",
"tx_blob": "12000322800000002400000001201B0000000520210000000468400000000000000C732102999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC74473045022100EEB2D1EFDC4B63A3FA9BD934307D3377197E86133A156022A01FFAD7CE3A78FA022024EB39D525FE42853F8E6B62BB74124FD0A1F763C56621AA8EC6A0FC0D0A6FC0811492DECA2DC92352BE97C1F6347F7E6CCB9A8241C8",
"tx_json": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Fee": "12000",
"Flags": 2147483648,
"LastLedgerSequence": 2,
"Sequence": 1,
"SetFlag": 4,
"SigningPubKey": "02999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC",
"TransactionType": "AccountSet",
"TxnSignature": "304402202DF7ED29DCA16F7A6191A74127437ACA04ADFCE6DB570101737C6426DD664FF5022069E9C6965DED37207708420B3B77A8CE9D0D009EE1D2434580ED4871398574D0",
"hash": "56DCAE121213AA2E3A74921F934E78635DC0576E4DA1184AEE7D0F8E49046790"
}
},
"status": "success",
"type": "response"
},
"SUBMIT_TER_RESPONSE": {
"id": 1,
"result": {
"engine_result": "terNO_ACCOUNT",
"engine_result_code": -96,
"engine_result_message": "The source account does not exist.",
"tx_blob": "12000022800000002400000004201B0000000561400000000000000168400000000000000C732102999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC74473045022100BDEF89CF25B541FFDBBE0B652BF29D8C30715C8226346C72DBA69B275BC471C50220582C5F3DE6DE489E10594BD8911B2917EE9A452E6ED3A1BEED25E402CD2E3EFF811492DECA2DC92352BE97C1F6347F7E6CCB9A8241C883143108B9AC27BF036EFE5CBE787921F54D622B7A5B",
"tx_json": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Amount": "1",
"Destination": "rn7GZeJpUafLAyKQ7wrU5SUCu2hpQq7S2W",
"Fee": "12000",
"Flags": 2147483648,
"LastLedgerSequence": 2,
"Sequence": 1,
"SigningPubKey": "02999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC",
"TransactionType": "Payment",
"TxnSignature": "3044022041F099FD8F35F67E73B5F41FFD3CBCF5781748BFEBA6FD1AB56586EE71B7EE9902205F1C4DF58B40859A404939B44A169DA995DA03C6ED713C5DF76E57A843CCE7D1",
"hash": "31090EA7996DFA6C2E0B43F704C30EC9AE635C18D7BEF90F42D4E0E2943A2680"
}
},
"status": "success",
"type": "response"
},
"SUBMIT_TEF_RESPONSE": {
"id": 1,
"result": {
"engine_result": "tefPAST_SEQ",
"engine_result_code": -189,
"engine_result_message": "This sequence number has already past.",
"tx_blob": "12000322800000002400000002201B0000000568400000000000000C732102999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC744730450221008B213EA1B4AACA9545E3DBFE43C69711DF295170B6F510CCD24B383684852C0702200E2A8FA3B205F1AFECF6D6DE298889D71E06336942ADF538847C8EFB88D3AA74811492DECA2DC92352BE97C1F6347F7E6CCB9A8241C8",
"tx_json": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Fee": "12000",
"Flags": 2147483648,
"LastLedgerSequence": 2,
"Sequence": 1,
"SigningPubKey": "02999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC",
"TransactionType": "AccountSet",
"TxnSignature": "304402203F6B9445B3B2176957C1008EBC0F18668464080A2CB10E78D83FB67DE2ABC3F50220769673DE79E6C1AF0E076A716ABE53018158385615E7EC2866DCFD6268806BF8",
"hash": "731E94632E219ECAF2043C09C0075F1DEA2EC08E66A381CD536F0B1F3B30844D"
}
},
"status": "success",
"type": "response"
},
"SUBMIT_TEL_RESPONSE": {
"id": 1,
"result": {
"engine_result": "telINSUF_FEE_P",
"engine_result_code": -394,
"engine_result_message": "Fee insufficient.",
"tx_blob": "12000322800000002400000003201B00000005684000000000000001732102999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC7446304402206480AC2410BE4370E74E27B4427EF05F8A156D313C9D768E57B6EEC3BB89CDD402201503759955F367F88E5442F08D713B5369C5528475F046500721E676C24D0DFD811492DECA2DC92352BE97C1F6347F7E6CCB9A8241C8",
"tx_json": {
"Account": "rNP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d",
"Fee": "12000",
"Flags": 2147483648,
"LastLedgerSequence": 2,
"Sequence": 1,
"SigningPubKey": "02999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC",
"TransactionType": "AccountSet",
"TxnSignature": "3044022002C0C13B21F9B690C6A3FF4B5A3F966AC47ACEE80453DAD553F5051FB3CED0E902202851BCC9EF8E2160BD1F3F6CF91B7C679149BD5FAD502C2BA544F7E61755AD1B",
"hash": "ABF970193B8C9BE9BBD2777750924B28E7542DE1491EB55F4795539045AEA8B9"
}
},
"status": "success",
"type": "response"
}
}

View File

@@ -0,0 +1,630 @@
'use strict';
var ws = require('ws');
var lodash = require('lodash');
var assert = require('assert-diff');
var Remote = require('ripple-lib').Remote;
var SerializedObject = require('ripple-lib').SerializedObject;
var Transaction = require('ripple-lib').Transaction;
var TransactionManager = require('../src/js/ripple/transactionmanager')
.TransactionManager;
var LEDGER = require('./fixtures/transactionmanager').LEDGER;
var ACCOUNT = require('./fixtures/transactionmanager').ACCOUNT;
var ACCOUNT2 = require('./fixtures/transactionmanager').ACCOUNT2;
var SUBSCRIBE_RESPONSE = require('./fixtures/transactionmanager')
.SUBSCRIBE_RESPONSE;
var ACCOUNT_INFO_RESPONSE = require('./fixtures/transactionmanager')
.ACCOUNT_INFO_RESPONSE;
var TX_STREAM_TRANSACTION = require('./fixtures/transactionmanager')
.TX_STREAM_TRANSACTION;
var ACCOUNT_TX_TRANSACTION = require('./fixtures/transactionmanager')
.ACCOUNT_TX_TRANSACTION;
var ACCOUNT_TX_RESPONSE = require('./fixtures/transactionmanager')
.ACCOUNT_TX_RESPONSE;
var ACCOUNT_TX_ERROR = require('./fixtures/transactionmanager')
.ACCOUNT_TX_ERROR;
var SUBMIT_RESPONSE = require('./fixtures/transactionmanager')
.SUBMIT_RESPONSE;
var SUBMIT_TEC_RESPONSE = require('./fixtures/transactionmanager')
.SUBMIT_TEC_RESPONSE;
var SUBMIT_TER_RESPONSE = require('./fixtures/transactionmanager')
.SUBMIT_TER_RESPONSE;
var SUBMIT_TEF_RESPONSE = require('./fixtures/transactionmanager')
.SUBMIT_TEF_RESPONSE;
var SUBMIT_TEL_RESPONSE = require('./fixtures/transactionmanager')
.SUBMIT_TEL_RESPONSE;
describe('TransactionManager', function() {
var rippled;
var rippledConnection;
var remote;
var account;
var transactionManager;
beforeEach(function(done) {
rippled = new ws.Server({port: 5763});
rippled.on('connection', function(c) {
var ledger = lodash.extend({}, LEDGER);
c.sendJSON = function(v) {
try {
c.send(JSON.stringify(v));
} catch (e) {
}
};
c.sendResponse = function(baseResponse, ext) {
assert.strictEqual(typeof baseResponse, 'object');
assert.strictEqual(baseResponse.type, 'response');
c.sendJSON(lodash.extend(baseResponse, ext));
};
c.closeLedger = function() {
c.sendJSON(lodash.extend(ledger, {
ledger_index: ++ledger.ledger_index
}));
};
c.on('message', function(m) {
m = JSON.parse(m);
rippled.emit('request_' + m.command, m, c);
});
rippledConnection = c;
});
rippled.on('request_subscribe', function(message, c) {
if (lodash.isEqual(message.streams, ['ledger', 'server'])) {
c.sendResponse(SUBSCRIBE_RESPONSE, {id: message.id});
}
});
rippled.on('request_account_info', function(message, c) {
if (message.account === ACCOUNT.address) {
c.sendResponse(ACCOUNT_INFO_RESPONSE, {id: message.id});
}
});
remote = new Remote({servers: ['ws://localhost:5763']});
remote.setSecret(ACCOUNT.address, ACCOUNT.secret);
account = remote.account(ACCOUNT.address);
transactionManager = account._transactionManager;
remote.connect(function() {
setTimeout(done, 10);
});
});
afterEach(function(done) {
remote.disconnect(function() {
rippled.close();
setImmediate(done);
});
});
it('Normalize transaction', function() {
var t1 = TransactionManager.normalizeTransaction(TX_STREAM_TRANSACTION);
var t2 = TransactionManager.normalizeTransaction(ACCOUNT_TX_TRANSACTION);
[t1, t2].forEach(function(t) {
assert(t.hasOwnProperty('metadata'));
assert(t.hasOwnProperty('tx_json'));
assert.strictEqual(t.validated, true);
assert.strictEqual(t.ledger_index, 1);
assert.strictEqual(t.engine_result, 'tesSUCCESS');
assert.strictEqual(t.type, 'transaction');
assert.strictEqual(t.tx_json.hash,
'01D66ACBD00B2A8F5D66FC8F67AC879CAECF49BC94FB97CF24F66B8406F4C040');
});
});
it('Handle received transaction', function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
transaction.once('success', function() {
done();
});
transaction.addId(TX_STREAM_TRANSACTION.transaction.hash);
transactionManager.getPending().push(transaction);
rippledConnection.sendJSON(TX_STREAM_TRANSACTION);
});
it('Handle received transaction -- failed', function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
transaction.once('error', function(err) {
assert.strictEqual(err.engine_result, 'tecINSUFF_FEE_P');
done();
});
transaction.addId(TX_STREAM_TRANSACTION.transaction.hash);
transactionManager.getPending().push(transaction);
rippledConnection.sendJSON(lodash.extend({ }, TX_STREAM_TRANSACTION, {
engine_result: 'tecINSUFF_FEE_P'
}));
});
it('Handle received transaction -- not submitted', function(done) {
rippledConnection.sendJSON(TX_STREAM_TRANSACTION);
remote.once('transaction', function() {
assert(transactionManager.getPending().getReceived(
TX_STREAM_TRANSACTION.transaction.hash));
done();
});
});
it('Handle received transaction -- Account mismatch', function(done) {
var tx = lodash.extend({ }, TX_STREAM_TRANSACTION);
lodash.extend(tx.transaction, {
Account: 'rMP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d'
});
rippledConnection.sendJSON(tx);
setImmediate(function() {
assert(!transactionManager.getPending().getReceived(
TX_STREAM_TRANSACTION.transaction.hash));
done();
});
});
it('Handle received transaction -- not validated', function(done) {
var tx = lodash.extend({ }, TX_STREAM_TRANSACTION, {
validated: false
});
rippledConnection.sendJSON(tx);
setImmediate(function() {
assert(!transactionManager.getPending().getReceived(
TX_STREAM_TRANSACTION.transaction.hash));
done();
});
});
it('Handle received transaction -- from account_tx', function(done) {
var transaction = Transaction.from_json(ACCOUNT_TX_TRANSACTION.tx);
transaction.once('success', function() {
done();
});
transaction.addId(ACCOUNT_TX_TRANSACTION.tx.hash);
transactionManager.getPending().push(transaction);
transactionManager._transactionReceived(ACCOUNT_TX_TRANSACTION);
});
it('Adjust pending transaction fee', function(done) {
var transaction = new Transaction(remote);
transaction.tx_json = ACCOUNT_TX_TRANSACTION.tx;
transaction.once('fee_adjusted', function(a, b) {
assert.strictEqual(a, '10');
assert.strictEqual(b, '24');
assert.strictEqual(transaction.tx_json.Fee, '24');
done();
});
transactionManager.getPending().push(transaction);
rippledConnection.sendJSON({
type: 'serverStatus',
load_base: 256,
load_factor: 256 * 2,
server_status: 'full'
});
});
it('Adjust pending transaction fee -- max fee exceeded', function(done) {
transactionManager._maxFee = 10;
var transaction = new Transaction(remote);
transaction.tx_json = ACCOUNT_TX_TRANSACTION.tx;
transaction.once('fee_adjusted', function() {
assert(false, 'Fee should not be adjusted');
});
transactionManager.getPending().push(transaction);
rippledConnection.sendJSON({
type: 'serverStatus',
load_base: 256,
load_factor: 256 * 2,
server_status: 'full'
});
setImmediate(done);
});
it('Adjust pending transaction fee -- no local fee', function(done) {
remote.local_fee = false;
var transaction = new Transaction(remote);
transaction.tx_json = ACCOUNT_TX_TRANSACTION.tx;
transaction.once('fee_adjusted', function() {
assert(false, 'Fee should not be adjusted');
});
transactionManager.getPending().push(transaction);
rippledConnection.sendJSON({
type: 'serverStatus',
load_base: 256,
load_factor: 256 * 2,
server_status: 'full'
});
setImmediate(done);
});
it('Wait ledgers', function(done) {
transactionManager._waitLedgers(3, done);
for (var i = 1; i <= 3; i++) {
rippledConnection.closeLedger();
}
});
it('Wait ledgers -- no ledgers', function(done) {
transactionManager._waitLedgers(0, done);
});
it('Update pending status', function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
transaction.submitIndex = 1;
transaction.tx_json.LastLedgerSequence = 10;
var receivedMissing = false;
var receivedLost = false;
transaction.once('missing', function() {
receivedMissing = true;
});
transaction.once('lost', function() {
receivedLost = true;
});
transaction.once('error', function(err) {
assert.strictEqual(err.engine_result, 'tejMaxLedger');
assert(receivedMissing);
assert(receivedLost);
done();
});
transaction.addId(TX_STREAM_TRANSACTION.transaction.hash);
transactionManager.getPending().push(transaction);
for (var i = 1; i <= 10; i++) {
rippledConnection.closeLedger();
}
});
it('Update pending status -- finalized before max ledger exceeded',
function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
transaction.submitIndex = 1;
transaction.tx_json.LastLedgerSequence = 10;
transaction.finalized = true;
var receivedMissing = false;
var receivedLost = false;
transaction.once('missing', function() {
receivedMissing = true;
});
transaction.once('lost', function() {
receivedLost = true;
});
transaction.once('error', function() {
assert(false, 'Should not err');
});
transaction.addId(TX_STREAM_TRANSACTION.transaction.hash);
transactionManager.getPending().push(transaction);
for (var i = 1; i <= 10; i++) {
rippledConnection.closeLedger();
}
setImmediate(function() {
assert(!receivedMissing);
assert(!receivedLost);
done();
});
});
it('Handle reconnect', function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
var binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, {
ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index,
tx_blob: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.tx).to_hex(),
meta: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.meta).to_hex()
});
var hash = new SerializedObject(binaryTx.tx_blob).hash(0x54584E00).to_hex();
transaction.addId(hash);
transaction.once('success', function(res) {
assert.strictEqual(res.engine_result, 'tesSUCCESS');
done();
});
transactionManager.getPending().push(transaction);
rippled.once('request_account_tx', function(m, req) {
var response = lodash.extend({}, ACCOUNT_TX_RESPONSE);
response.result.transactions = [binaryTx];
req.sendResponse(response, {id: m.id});
});
remote.disconnect(remote.connect);
});
it('Handle reconnect -- no matching transaction found', function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
var binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, {
ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index,
tx_blob: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.tx).to_hex(),
meta: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.meta).to_hex()
});
transactionManager._request = function() {
// Resubmitting
done();
};
transactionManager.getPending().push(transaction);
rippled.once('request_account_tx', function(m, req) {
var response = lodash.extend({}, ACCOUNT_TX_RESPONSE);
response.result.transactions = [binaryTx];
req.sendResponse(response, {id: m.id});
});
remote.disconnect(remote.connect);
});
it('Handle reconnect -- account_tx error', function(done) {
var transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction);
transactionManager.getPending().push(transaction);
transactionManager._resubmit = function() {
assert(false, 'Should not resubmit');
};
rippled.once('request_account_tx', function(m, req) {
req.sendResponse(ACCOUNT_TX_ERROR, {id: m.id});
setImmediate(done);
});
remote.disconnect(remote.connect);
});
it('Submit transaction', function(done) {
var transaction = remote.createTransaction('AccountSet', {
account: ACCOUNT.address
});
var receivedInitialSuccess = false;
var receivedProposed = false;
transaction.once('proposed', function(m) {
assert.strictEqual(m.engine_result, 'tesSUCCESS');
receivedProposed = true;
});
transaction.once('submitted', function(m) {
assert.strictEqual(m.engine_result, 'tesSUCCESS');
receivedInitialSuccess = true;
});
rippled.once('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_RESPONSE, {id: m.id});
setImmediate(function() {
var txEvent = lodash.extend({}, TX_STREAM_TRANSACTION);
txEvent.transaction = transaction.tx_json;
txEvent.transaction.hash = transaction.hash();
rippledConnection.sendJSON(txEvent);
});
});
transaction.submit(function(err, res) {
assert(!err, 'Transaction submission should succeed');
assert(receivedInitialSuccess);
assert(receivedProposed);
assert.strictEqual(res.engine_result, 'tesSUCCESS');
assert.strictEqual(transactionManager.getPending().length(), 0);
done();
});
});
it('Submit transaction -- tec error', function(done) {
var transaction = remote.createTransaction('AccountSet', {
account: ACCOUNT.address,
set_flag: 'asfDisableMaster'
});
var receivedSubmitted = false;
transaction.once('proposed', function() {
assert(false, 'Should not receive proposed event');
});
transaction.once('submitted', function(m) {
assert.strictEqual(m.engine_result, 'tecNO_REGULAR_KEY');
receivedSubmitted = true;
});
rippled.once('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_TEC_RESPONSE, {id: m.id});
setImmediate(function() {
var txEvent = lodash.extend({}, TX_STREAM_TRANSACTION,
SUBMIT_TEC_RESPONSE.result);
txEvent.transaction = transaction.tx_json;
txEvent.transaction.hash = transaction.hash();
rippledConnection.sendJSON(txEvent);
});
});
transaction.submit(function(err) {
assert(err, 'Transaction submission should not succeed');
assert(receivedSubmitted);
assert.strictEqual(err.engine_result, 'tecNO_REGULAR_KEY');
assert.strictEqual(transactionManager.getPending().length(), 0);
done();
});
});
it('Submit transaction -- ter error', function(done) {
var transaction = remote.createTransaction('Payment', {
account: ACCOUNT.address,
destination: ACCOUNT2.address,
amount: '1'
});
transaction.tx_json.Sequence = ACCOUNT_INFO_RESPONSE.result
.account_data.Sequence + 1;
var receivedSubmitted = false;
transaction.once('proposed', function() {
assert(false, 'Should not receive proposed event');
});
transaction.once('submitted', function(m) {
assert.strictEqual(m.engine_result, 'terNO_ACCOUNT');
receivedSubmitted = true;
});
rippled.on('request_submit', function(m, req) {
var deserialized = new SerializedObject(m.tx_blob).to_json();
switch (deserialized.TransactionType) {
case 'Payment':
assert.strictEqual(transactionManager.getPending().length(), 1);
assert.deepEqual(deserialized, transaction.tx_json);
req.sendResponse(SUBMIT_TER_RESPONSE, {id: m.id});
break;
case 'AccountSet':
assert.strictEqual(deserialized.Account, ACCOUNT.address);
assert.strictEqual(deserialized.Flags, 2147483648);
req.sendResponse(SUBMIT_RESPONSE, {id: m.id});
req.closeLedger();
break;
}
});
rippled.once('request_submit', function(m, req) {
req.sendJSON(lodash.extend({}, LEDGER, {
ledger_index: transaction.tx_json.LastLedgerSequence + 1
}));
});
transaction.submit(function(err) {
assert(err, 'Transaction submission should not succeed');
assert.strictEqual(err.engine_result, 'tejMaxLedger');
assert(receivedSubmitted);
assert.strictEqual(transactionManager.getPending().length(), 0);
transactionManager.once('sequence_filled', done);
});
});
it('Submit transaction -- tef error', function(done) {
var transaction = remote.createTransaction('AccountSet', {
account: ACCOUNT.address
});
transaction.tx_json.Sequence = ACCOUNT_INFO_RESPONSE.result
.account_data.Sequence - 1;
var receivedSubmitted = false;
var receivedResubmitted = false;
transaction.once('proposed', function() {
assert(false, 'Should not receive proposed event');
});
transaction.once('submitted', function(m) {
assert.strictEqual(m.engine_result, 'tefPAST_SEQ');
receivedSubmitted = true;
});
rippled.on('request_submit', function(m, req) {
assert.strictEqual(transactionManager.getPending().length(), 1);
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
req.sendResponse(SUBMIT_TEF_RESPONSE, {id: m.id});
});
rippled.once('request_submit', function(m, req) {
transaction.once('resubmitted', function() {
receivedResubmitted = true;
req.sendJSON(lodash.extend({}, LEDGER, {
ledger_index: transaction.tx_json.LastLedgerSequence + 1
}));
});
req.closeLedger();
});
transaction.submit(function(err) {
assert(err, 'Transaction submission should not succeed');
assert(receivedSubmitted);
assert(receivedResubmitted);
assert.strictEqual(err.engine_result, 'tejMaxLedger');
assert.strictEqual(transactionManager.getPending().length(), 0);
done();
});
});
it('Submit transaction -- tel error', function(done) {
var transaction = remote.createTransaction('AccountSet', {
account: ACCOUNT.address
});
var receivedSubmitted = false;
var receivedResubmitted = false;
transaction.once('proposed', function() {
assert(false, 'Should not receive proposed event');
});
transaction.once('submitted', function(m) {
assert.strictEqual(m.engine_result, 'telINSUF_FEE_P');
receivedSubmitted = true;
});
rippled.on('request_submit', function(m, req) {
assert.strictEqual(transactionManager.getPending().length(), 1);
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
});
rippled.once('request_submit', function(m, req) {
transaction.once('resubmitted', function() {
receivedResubmitted = true;
req.sendJSON(lodash.extend({}, LEDGER, {
ledger_index: transaction.tx_json.LastLedgerSequence + 1
}));
});
req.closeLedger();
});
transaction.submit(function(err) {
assert(err, 'Transaction submission should not succeed');
assert(receivedSubmitted);
assert(receivedResubmitted);
assert.strictEqual(err.engine_result, 'tejMaxLedger');
assert.strictEqual(transactionManager.getPending().length(), 0);
done();
});
});
it('Submit transaction -- invalid secret', function(done) {
remote.setSecret(ACCOUNT.address, ACCOUNT.secret + 'z');
var transaction = remote.createTransaction('AccountSet', {
account: ACCOUNT.address
});
rippled.once('request_submit', function() {
assert(false, 'Should not request submit');
});
transaction.submit(function(err) {
assert.strictEqual(err.engine_result, 'tejSecretInvalid');
assert.strictEqual(transactionManager.getPending().length(), 0);
done();
});
});
});