Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1a892d5

Browse files
wltsmrzgeertweening
authored andcommittedDec 10, 2014
Rework tef and tel-class transaction error handling
Do not finalize tef or tel-class errors until LastLedgerSequence is exceeded. Transactions that fail in this way will now be aborted with a tej-class error. Errors like tefALREADY and tefMAX_LEDGER should now be opaque to the user and have no consequence in determining a final state.
1 parent 73a3cce commit 1a892d5

File tree

1 file changed

+59
-46
lines changed

1 file changed

+59
-46
lines changed
 

‎src/js/ripple/transactionmanager.js

+59-46
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,17 @@ function TransactionManager(account) {
4141

4242
this._remote.on('ledger_closed', updatePendingStatus);
4343

44-
this._remote.on('disconnect', function() {
45-
self._remote.removeListener('ledger_closed', updatePendingStatus);
46-
self._remote.once('connect', function() {
44+
function handleReconnect() {
45+
self._handleReconnect(function() {
46+
// Handle reconnect, account_tx procedure first, before
47+
// hooking back into ledger_closed
4748
self._remote.on('ledger_closed', updatePendingStatus);
48-
self._handleReconnect();
4949
});
50+
};
51+
52+
this._remote.on('disconnect', function() {
53+
self._remote.removeListener('ledger_closed', updatePendingStatus);
54+
self._remote.once('connect', handleReconnect);
5055
});
5156

5257
// Query server for next account transaction sequence
@@ -55,6 +60,29 @@ function TransactionManager(account) {
5560

5661
util.inherits(TransactionManager, EventEmitter);
5762

63+
TransactionManager._isNoOp = function(transaction) {
64+
return (typeof transaction === 'object')
65+
&& (typeof transaction.tx_json === 'object')
66+
&& (transaction.tx_json.TransactionType === 'AccountSet')
67+
&& (transaction.tx_json.Flags === 0);
68+
};
69+
70+
TransactionManager._isRemoteError = function(error) {
71+
return (typeof error === 'object')
72+
&& (error.error === 'remoteError')
73+
&& (typeof error.remote === 'object');
74+
};
75+
76+
TransactionManager._isNotFound = function(error) {
77+
return TransactionManager._isRemoteError(error)
78+
&& /^(txnNotFound|transactionNotFound)$/.test(error.remote.error);
79+
};
80+
81+
TransactionManager._isTooBusy = function(error) {
82+
return TransactionManager._isRemoteError(error)
83+
&& (error.remote.error === 'tooBusy');
84+
};
85+
5886
/**
5987
* Normalize transactions received from account transaction stream and
6088
* account_tx
@@ -220,6 +248,16 @@ TransactionManager.prototype._updatePendingStatus = function(ledger) {
220248
transaction.emit('lost', ledger);
221249
break;
222250
}
251+
252+
if (transaction.finalized) {
253+
return;
254+
}
255+
256+
if (ledger.ledger_index > transaction.tx_json.LastLedgerSequence) {
257+
// Transaction must fail
258+
transaction.emit('error', new RippleError(
259+
'tejMaxLedger', 'Transaction LastLedgerSequence exceeded'));
260+
}
223261
});
224262
};
225263

@@ -297,11 +335,13 @@ TransactionManager.prototype._loadSequence = function(callback) {
297335
* On reconnect, load account_tx in case a pending transaction succeeded while
298336
* disconnected
299337
*
338+
* @param [Function] callback
300339
* @api private
301340
*/
302341

303-
TransactionManager.prototype._handleReconnect = function() {
342+
TransactionManager.prototype._handleReconnect = function(callback) {
304343
var self = this;
344+
var callback = (typeof callback === 'function') ? callback : function(){};
305345

306346
if (!this._pending.length) {
307347
return callback();
@@ -312,6 +352,7 @@ TransactionManager.prototype._handleReconnect = function() {
312352
if (self._remote.trace) {
313353
log.info('error requesting account_tx', err);
314354
}
355+
callback();
315356
return;
316357
}
317358

@@ -320,6 +361,8 @@ TransactionManager.prototype._handleReconnect = function() {
320361
transactions.transactions.forEach(self._transactionReceived, self);
321362
}
322363

364+
callback();
365+
323366
self._loadSequence(function() {
324367
// Resubmit pending transactions after sequence is loaded
325368
self._resubmit();
@@ -480,37 +523,30 @@ TransactionManager.prototype._request = function(tx) {
480523
}
481524

482525
function transactionFailed(message) {
483-
switch (message.engine_result) {
484-
case 'tefPAST_SEQ':
485-
self._resubmit(1, tx);
486-
break;
487-
case 'tefALREADY':
488-
if (tx.responses === tx.submissions) {
489-
tx.emit('error', message);
490-
} else {
491-
submitRequest.once('success', submitted);
492-
}
493-
break;
494-
default:
495-
tx.emit('error', message);
526+
if (message.engine_result === 'tefPAST_SEQ') {
527+
// Transaction may succeed after Sequence is updated
528+
self._resubmit(1, tx);
496529
}
497530
};
498531

499532
function transactionRetry(message) {
533+
// XXX This may no longer be necessary. Instead, update sequence numbers
534+
// after a transaction fails definitively
500535
self._fillSequence(tx, function() {
501536
self._resubmit(1, tx);
502537
});
503538
};
504539

505540
function transactionFailedLocal(message) {
506-
if (!self._remote.local_fee) {
507-
submissionError(message);
508-
} else if (message.engine_result === 'telINSUF_FEE_P') {
509-
self._resubmit(2, tx);
541+
if (message.engine_result === 'telINSUF_FEE_P') {
542+
// Transaction may succeed after Fee is updated
543+
self._resubmit(1, tx);
510544
}
511545
};
512546

513547
function submissionError(error) {
548+
// Either a tem-class error or generic server error such as tooBusy. This
549+
// should be a definitive failure
514550
if (TransactionManager._isTooBusy(error)) {
515551
self._resubmit(1, tx);
516552
} else {
@@ -577,7 +613,7 @@ TransactionManager.prototype._request = function(tx) {
577613
if (remote.trace) {
578614
log.info('timeout:', tx.tx_json);
579615
}
580-
self._resubmit(3, tx);
616+
self._resubmit(1, tx);
581617
}
582618
};
583619

@@ -616,29 +652,6 @@ TransactionManager.prototype._request = function(tx) {
616652
return submitRequest;
617653
};
618654

619-
TransactionManager._isNoOp = function(transaction) {
620-
return (typeof transaction === 'object')
621-
&& (typeof transaction.tx_json === 'object')
622-
&& (transaction.tx_json.TransactionType === 'AccountSet')
623-
&& (transaction.tx_json.Flags === 0);
624-
};
625-
626-
TransactionManager._isRemoteError = function(error) {
627-
return (typeof error === 'object')
628-
&& (error.error === 'remoteError')
629-
&& (typeof error.remote === 'object');
630-
};
631-
632-
TransactionManager._isNotFound = function(error) {
633-
return TransactionManager._isRemoteError(error)
634-
&& /^(txnNotFound|transactionNotFound)$/.test(error.remote.error);
635-
};
636-
637-
TransactionManager._isTooBusy = function(error) {
638-
return TransactionManager._isRemoteError(error)
639-
&& (error.remote.error === 'tooBusy');
640-
};
641-
642655
/**
643656
* Entry point for TransactionManager submission
644657
*

0 commit comments

Comments
 (0)
Please sign in to comment.