diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index bae035b7..14b5c639 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -80,9 +80,9 @@ function Remote(opts, trace) { var self = this; - this.trusted = opts.trusted; - this.local_sequence = opts.local_sequence; // Locally track sequence numbers - this.local_fee = opts.local_fee; // Locally set fees + this.trusted = Boolean(opts.trusted); + this.local_sequence = Boolean(opts.local_sequence); // Locally track sequence numbers + this.local_fee = (typeof opts.local_fee === 'undefined') ? true : Boolean(opts.local_fee); // Locally set fees this.local_signing = (typeof opts.local_signing === 'undefined') ? true : Boolean(opts.local_signing); this.fee_cushion = (typeof opts.fee_cushion === 'undefined') ? 1.5 : Number(opts.fee_cushion); this.max_fee = (typeof opts.max_fee === 'undefined') ? Infinity : Number(opts.max_fee); @@ -110,6 +110,7 @@ function Remote(opts, trace) { this._connection_count = 0; this._connected = false; this._connection_offset = 1000 * (Number(opts.connection_offset) || 5); + this._submission_timeout = 1000 * (Number(opts.submission_timeout) || 10); this._last_tx = null; this._cur_path_find = null; @@ -477,7 +478,13 @@ Remote.prototype._handle_message = function (message, server) { if (load_changed) { self._load_base = message.load_base; self._load_factor = message.load_factor; - self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); + var obj = { + load_base: self._load_base, + load_factor: self._load_factor, + fee_units: self.fee_tx_unit() + } + self.emit('load', obj); + self.emit('load_changed', obj); } break; diff --git a/src/js/ripple/transaction.js b/src/js/ripple/transaction.js index 7ef5d9eb..72311475 100644 --- a/src/js/ripple/transaction.js +++ b/src/js/ripple/transaction.js @@ -564,7 +564,7 @@ Transaction.prototype.submit = function (callback) { } return this; -} +}; exports.Transaction = Transaction; diff --git a/src/js/ripple/transactionmanager.js b/src/js/ripple/transactionmanager.js index 65505804..c00e21d2 100644 --- a/src/js/ripple/transactionmanager.js +++ b/src/js/ripple/transactionmanager.js @@ -2,6 +2,7 @@ var util = require('util'); var EventEmitter = require('events').EventEmitter; var RippleError = require('./rippleerror').RippleError; var Queue = require('./transactionqueue').TransactionQueue; +var Amount = require('./amount'); /** * @constructor TransactionManager @@ -11,32 +12,38 @@ var Queue = require('./transactionqueue').TransactionQueue; function TransactionManager(account) { EventEmitter.call(this); - var self = this; + var self = this; this.account = account; this.remote = account._remote; this._timeout = void(0); - this._resubmitting = false; this._pending = new Queue; this._next_sequence = void(0); this._cache = { }; //XX Fee units - this._max_fee = Number(this.remote.max_fee) || Infinity; + this._max_fee = this.remote.max_fee; + + this._submission_timeout = this.remote._submission_timeout; + + function remote_reconnected() { + self.account.get_next_sequence(function(err, sequence) { + sequence_loaded(err, sequence); + self._resubmit(3); + }); + } function remote_disconnected() { - function remote_reconnected() { - self._resubmit(); - }; self.remote.once('connect', remote_reconnected); - }; + } this.remote.on('disconnect', remote_disconnected); - function sequence_loaded(err, sequence) { - self._next_sequence = sequence; + function sequence_loaded(err, sequence, callback) { + self._next_sequence = sequence; self.emit('sequence_loaded', sequence); - }; + callback && callback(); + } this.account.get_next_sequence(sequence_loaded); @@ -54,7 +61,18 @@ function TransactionManager(account) { self._cache[message.transaction.Sequence] = transaction; } - this.account.on('transaction', cache_transaction); + this.account.on('transaction-outbound', cache_transaction); + + function adjust_fees() { + self._pending.forEach(function(pending) { + if (pending.tx_json.Fee) { + var newFee = self.remote.fee_tx(pending.fee_units()).to_json(); + pending.tx_json.Fee = newFee; + } + }); + } + + this.remote.on('load_changed', adjust_fees); }; util.inherits(TransactionManager, EventEmitter); @@ -79,14 +97,22 @@ function rewrite_transaction(tx) { hash: tx.hash } } - } catch(exception) { - } + } catch(exception) { } return result || { }; }; -TransactionManager.prototype._resubmit = function() { +TransactionManager.prototype._resubmit = function(wait_ledgers) { var self = this; + if (wait_ledgers) { + var ledgers = Number(wait_ledgers) || 3; + this._wait_ledgers(ledgers, function() { + self._pending.forEach(resubmit); + }); + } else { + self._pending.forEach(resubmit); + } + function resubmit(pending, index) { if (pending.finalized) { // Transaction has been finalized, nothing to do @@ -109,22 +135,25 @@ TransactionManager.prototype._resubmit = function() { function pending_check(err, res) { if (self._is_not_found(err)) { + //XX self._request(pending); } else { pending.emit('success', rewrite_transaction(res)); } } - self.remote.request_tx(pending.hash, pending_check); + var request = self.remote.request_tx(pending.hash, pending_check); + + request.timeout(self._submission_timeout, function() { + if (self.remote._connected) { + self._resubmit(1); + } + }); } else { self._request(pending); } } - - this._wait_ledgers(3, function() { - self._pending.forEach(resubmit); - }); -} +}; TransactionManager.prototype._wait_ledgers = function(ledgers, callback) { var self = this; @@ -144,27 +173,6 @@ TransactionManager.prototype._request = function(tx) { var self = this; var remote = this.remote; - if (!tx._secret && !tx.tx_json.TxnSignature) { - tx.emit('error', new RippleError('tejSecretUnknown', 'Missing secret')); - return; - } - - if (!remote.trusted && !remote.local_signing) { - tx.emit('error', new RippleError('tejServerUntrusted', 'Attempt to give secret to untrusted server')); - return; - } - - function finalize(message) { - if (!tx.finalized) { - tx.finalized = true; - tx.emit('final', message); - self._pending.removeSequence(tx.tx_json.Sequence); - } - } - - tx.once('error', finalize); - tx.once('success', finalize); - // Listen for 'ledger closed' events to verify // that the transaction is discovered in the // ledger before considering the transaction @@ -189,16 +197,9 @@ TransactionManager.prototype._request = function(tx) { tx.set_state('client_proposed'); tx.emit('proposed', { tx_json: message.tx_json, - - result: message.engine_result, engine_result: message.engine_result, - - result_code: message.engine_result_code, engine_result_code: message.engine_result_code, - - result_message: message.engine_result_message, engine_result_message: message.engine_result_message, - // If server is honest, don't expect a final if rejected. rejected: tx.isRejected(message.engine_result_code), }); @@ -209,33 +210,56 @@ TransactionManager.prototype._request = function(tx) { function transaction_requested(err, res) { if (self._is_not_found(err)) { - self._resubmit(); + self._resubmit(1); } else { + //XX tx.emit('error', new RippleError(message)); - self._pending.removeSequence(tx.tx_json.Sequence); } } self.remote.request_tx(tx.hash, transaction_requested); } - function submission_error(err) { + function transaction_retry(message) { + switch (message.engine_result) { + case 'terPRE_SEQ': + self._resubmit(1); + break; + default: + submission_error(new RippleError(message)); + } + } + + function submission_error(error) { + //Decrement sequence + self._next_sequence--; tx.set_state('remoteError'); - tx.emit('error', new RippleError(err)); + tx.emit('submitted', error); + tx.emit('error', new RippleError(error)); } function submission_success(message) { + tx.emit('submitted', message); + + if (tx.attempts > 5) { + tx.emit('error', new RippleError(message)); + return; + } + var engine_result = message.engine_result || ''; tx.hash = message.tx_json.hash; switch (engine_result.slice(0, 3)) { + case 'tes': + transaction_proposed(message); + break; case 'tef': //tefPAST_SEQ transaction_failed(message); break; - case 'tes': - transaction_proposed(message); + case 'ter': + transaction_retry(message); break; default: submission_error(message); @@ -246,14 +270,14 @@ TransactionManager.prototype._request = function(tx) { submit_request.once('error', submission_error); submit_request.request(); - submit_request.timeout(1000 * 10, function() { + submit_request.timeout(this._submission_timeout, function() { if (self.remote._connected) { - self._resubmit(); + self._resubmit(1); } }); tx.set_state('client_submitted'); - tx.emit('submitted'); + tx.attempts++; return submit_request; }; @@ -329,6 +353,7 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) { tx.once('proposed', transaction_proposed); tx.once('final', transaction_finalized); + tx.once('abort', transaction_finalized); tx.once('resubmit', transaction_finalized); }; @@ -349,13 +374,31 @@ TransactionManager.prototype.submit = function(tx) { } tx.tx_json.Sequence = this._next_sequence++; + tx.attempts = 0; tx.complete(); - this._pending.push(tx); + function finalize(message) { + if (!tx.finalized) { + self._pending.removeSequence(tx.tx_json.Sequence); + tx.finalized = true; + tx.emit('final', message); + } + } + + tx.once('error', finalize); + tx.once('success', finalize); var fee = tx.tx_json.Fee; + var remote = this.remote; - if (fee === void(0) || fee <= this._max_fee) { + if (!tx._secret && !tx.tx_json.TxnSignature) { + tx.emit('error', new RippleError('tejSecretUnknown', 'Missing secret')); + } else if (!remote.trusted && !remote.local_signing) { + tx.emit('error', new RippleError('tejServerUntrusted', 'Attempt to give secret to untrusted server')); + } else if (fee && fee > this._max_fee) { + tx.emit('error', new RippleError('tejMaxFeeExceeded', 'Max fee exceeded')); + } else { + this._pending.push(tx); this._request(tx); } };