From 60f2419b5cbf36e20567a18974c1bd1bf7fe40ea Mon Sep 17 00:00:00 2001 From: Alan Cohen Date: Tue, 15 Sep 2015 12:06:42 -0700 Subject: [PATCH] Add PendingLedgerVersionError MissingLedgerHistoryError - no minLedgerVersion or maxLedgerVersion There is a ledger gap, but a range should be provided to narrow down the range of the gap. MissingLedgerHistoryError When requesting a tx, if maxLedgerVersion and minLedgerVersion provided, this means there is a ledger gap in the provided range. PendingLedgerVersionError: If maxLedgerVersion provided, check if ledger is ahead of the server's last validated ledger. --- src/api/common/errors.js | 8 + src/api/ledger/transaction.js | 18 ++- src/api/ledger/utils.js | 8 + test/api-test.js | 283 +++++++++++++++++++--------------- 4 files changed, 183 insertions(+), 134 deletions(-) diff --git a/src/api/common/errors.js b/src/api/common/errors.js index 2190793b98..6a412c3fed 100644 --- a/src/api/common/errors.js +++ b/src/api/common/errors.js @@ -58,6 +58,13 @@ function MissingLedgerHistoryError(message) { MissingLedgerHistoryError.prototype = new RippleError(); MissingLedgerHistoryError.prototype.name = 'MissingLedgerHistoryError'; +function PendingLedgerVersionError(message) { + this.message = message || + 'maxLedgerVersion is greater than server\'s most recent validated ledger'; +} +PendingLedgerVersionError.prototype = new RippleError(); +PendingLedgerVersionError.prototype.name = 'PendingLedgerVersionError'; + /** * Request timed out */ @@ -82,6 +89,7 @@ module.exports = { TransactionError, RippledNetworkError, NotFoundError, + PendingLedgerVersionError, MissingLedgerHistoryError, TimeOutError, ApiError, diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index 17f4416a5e..126dd353c0 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -52,8 +52,8 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, validate.getTransactionOptions(options); const remote = this.remote; - const maxLedgerVersion = Math.min(options.maxLedgerVersion || Infinity, - remote.getLedgerSequence()); + const maxLedgerVersion = + options.maxLedgerVersion || remote.getLedgerSequence(); function callbackWrapper(error_?: Error, tx?: Object) { let error = error_; @@ -67,13 +67,19 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, error = new errors.NotFoundError('Transaction not found'); } + // Missing complete ledger range if (error instanceof errors.NotFoundError - && !utils.hasCompleteLedgerRange(remote, - options.minLedgerVersion, maxLedgerVersion)) { - callback(new errors.MissingLedgerHistoryError('Transaction not found,' - + ' but the server\'s ledger history is incomplete')); + && !utils.hasCompleteLedgerRange(remote, options.minLedgerVersion, + maxLedgerVersion)) { + if (utils.isPendingLedgerVersion(remote, maxLedgerVersion)) { + callback(new errors.PendingLedgerVersionError()); + } else { + callback(new errors.MissingLedgerHistoryError()); + } + // Transaction is found, but not in specified range } else if (!error && tx && !isTransactionInRange(tx, options)) { callback(new errors.NotFoundError('Transaction not found')); + // Transaction is not found } else if (error) { convertErrors(callback)(error); } else if (!tx) { diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index d0fc866fe1..75a966d2cb 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -99,12 +99,19 @@ function compareTransactions(first: Outcome, second: Outcome): number { function hasCompleteLedgerRange(remote: Remote, minLedgerVersion?: number, maxLedgerVersion?: number ): boolean { + const firstLedgerVersion = 32570; // earlier versions have been lost return remote.getServer().hasLedgerRange( minLedgerVersion || firstLedgerVersion, maxLedgerVersion || remote.getLedgerSequence()); } +function isPendingLedgerVersion(remote: Remote, maxLedgerVersion: ?number +): boolean { + const currentLedger = remote.getLedgerSequence(); + return currentLedger < (maxLedgerVersion || 0); +} + module.exports = { getXRPBalance, compareTransactions, @@ -112,6 +119,7 @@ module.exports = { renameCounterpartyToIssuerInOrder, getRecursive, hasCompleteLedgerRange, + isPendingLedgerVersion, promisify: common.promisify, clamp: clamp, common: common diff --git a/test/api-test.js b/test/api-test.js index 5c207fb020..d042a69fe8 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -160,157 +160,184 @@ describe('RippleAPI', function() { _.partial(checkResult, responses.getBalances, 'getBalances')); }); - it('getTransaction - payment', function() { - return this.api.getTransaction(hashes.VALID_TRANSACTION_HASH).then( - _.partial(checkResult, responses.getTransaction.payment, + describe('getTransaction', () => { + it('getTransaction - payment', function() { + return this.api.getTransaction(hashes.VALID_TRANSACTION_HASH).then( + _.partial(checkResult, responses.getTransaction.payment, + 'getTransaction')); + }); + + it('getTransaction - settings', function() { + const hash = + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.settings, + 'getTransaction')); + }); + + it('getTransaction - order', function() { + const hash = + '10A6FB4A66EE80BED46AAE4815D7DC43B97E944984CCD5B93BCF3F8538CABC51'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.order, 'getTransaction')); - }); + }); - it('getTransaction - settings', function() { - const hash = - '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.settings, - 'getTransaction')); - }); + it('getTransaction - order cancellation', function() { + const hash = + '809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.orderCancellation, + 'getTransaction')); + }); - it('getTransaction - order', function() { - const hash = - '10A6FB4A66EE80BED46AAE4815D7DC43B97E944984CCD5B93BCF3F8538CABC51'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.order, - 'getTransaction')); - }); + it('getTransaction - trustline set', function() { + const hash = + '635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.trustline, + 'getTransaction')); + }); - it('getTransaction - order cancellation', function() { - const hash = - '809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.orderCancellation, - 'getTransaction')); - }); + it('getTransaction - trustline frozen off', function() { + const hash = + 'FE72FAD0FA7CA904FB6C633A1666EDF0B9C73B2F5A4555D37EEF2739A78A531B'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.trustlineFrozenOff, + 'getTransaction')); + }); - it('getTransaction - trustline set', function() { - const hash = - '635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.trustline, - 'getTransaction')); - }); + it('getTransaction - trustline no quality', function() { + const hash = + 'BAF1C678323C37CCB7735550C379287667D8288C30F83148AD3C1CB019FC9002'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.trustlineNoQuality, + 'getTransaction')); + }); - it('getTransaction - trustline frozen off', function() { - const hash = - 'FE72FAD0FA7CA904FB6C633A1666EDF0B9C73B2F5A4555D37EEF2739A78A531B'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.trustlineFrozenOff, - 'getTransaction')); - }); + it('getTransaction - not validated', function() { + const hash = + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA10'; + return this.api.getTransaction(hash).then(() => { + assert(false, 'Should throw NotFoundError'); + }).catch(error => { + assert(error instanceof this.api.errors.NotFoundError); + }); + }); - it('getTransaction - trustline no quality', function() { - const hash = - 'BAF1C678323C37CCB7735550C379287667D8288C30F83148AD3C1CB019FC9002'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.trustlineNoQuality, - 'getTransaction')); - }); + it('getTransaction - tracking on', function() { + const hash = + '8925FC8844A1E930E2CC76AD0A15E7665AFCC5425376D548BB1413F484C31B8C'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.trackingOn, + 'getTransaction')); + }); - it('getTransaction - not validated', function() { - const hash = - '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA10'; - return this.api.getTransaction(hash).then(() => { - assert(false, 'Should throw NotFoundError'); - }).catch(error => { - assert(error instanceof this.api.errors.NotFoundError); + it('getTransaction - tracking off', function() { + const hash = + 'C8C5E20DFB1BF533D0D81A2ED23F0A3CBD1EF2EE8A902A1D760500473CC9C582'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.trackingOff, + 'getTransaction')); }); - }); - it('getTransaction - tracking on', function() { - const hash = - '8925FC8844A1E930E2CC76AD0A15E7665AFCC5425376D548BB1413F484C31B8C'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.trackingOn, - 'getTransaction')); - }); + it('getTransaction - set regular key', function() { + const hash = + '278E6687C1C60C6873996210A6523564B63F2844FB1019576C157353B1813E60'; + return this.api.getTransaction(hash).then( + _.partial(checkResult, responses.getTransaction.setRegularKey, + 'getTransaction')); + }); - it('getTransaction - tracking off', function() { - const hash = - 'C8C5E20DFB1BF533D0D81A2ED23F0A3CBD1EF2EE8A902A1D760500473CC9C582'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.trackingOff, - 'getTransaction')); - }); + it('getTransaction - not found in range', function() { + const hash = + '809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'; + const options = { + minLedgerVersion: 32570, + maxLedgerVersion: 32571 + }; + return this.api.getTransaction(hash, options).then(() => { + assert(false, 'Should throw NotFoundError'); + }).catch(error => { + assert(error instanceof this.api.errors.NotFoundError); + }); + }); - it('getTransaction - set regular key', function() { - const hash = - '278E6687C1C60C6873996210A6523564B63F2844FB1019576C157353B1813E60'; - return this.api.getTransaction(hash).then( - _.partial(checkResult, responses.getTransaction.setRegularKey, - 'getTransaction')); - }); + it('getTransaction - not found by hash', function() { + const hash = hashes.NOTFOUND_TRANSACTION_HASH; + return this.api.getTransaction(hash).then(() => { + assert(false, 'Should throw NotFoundError'); + }).catch(error => { + assert(error instanceof this.api.errors.NotFoundError); + }); + }); - it('getTransaction - not found in range', function() { - const hash = - '809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'; - const options = { - minLedgerVersion: 32570, - maxLedgerVersion: 32571 - }; - return this.api.getTransaction(hash, options).then(() => { - assert(false, 'Should throw NotFoundError'); - }).catch(error => { - assert(error instanceof this.api.errors.NotFoundError); + it('getTransaction - missing ledger history', function() { + const hash = hashes.NOTFOUND_TRANSACTION_HASH; + // make gaps in history + this.api.remote.getServer().emit('message', ledgerClosed); + return this.api.getTransaction(hash).then(() => { + assert(false, 'Should throw MissingLedgerHistoryError'); + }).catch(error => { + assert(error instanceof this.api.errors.MissingLedgerHistoryError); + }); }); - }); - it('getTransaction - not found by hash', function() { - const hash = hashes.NOTFOUND_TRANSACTION_HASH; - return this.api.getTransaction(hash).then(() => { - assert(false, 'Should throw NotFoundError'); - }).catch(error => { - assert(error instanceof this.api.errors.NotFoundError); + it('getTransaction - missing ledger history with ledger range', function() { + const hash = hashes.NOTFOUND_TRANSACTION_HASH; + const options = { + minLedgerVersion: 32569, + maxLedgerVersion: 32571 + }; + return this.api.getTransaction(hash, options).then(() => { + assert(false, 'Should throw MissingLedgerHistoryError'); + }).catch(error => { + assert(error instanceof this.api.errors.MissingLedgerHistoryError); + }); }); - }); - it('getTransaction - missing ledger history', function() { - const hash = hashes.NOTFOUND_TRANSACTION_HASH; - // make gaps in history - this.api.remote.getServer().emit('message', ledgerClosed); - return this.api.getTransaction(hash).then(() => { - assert(false, 'Should throw MissingLedgerHistoryError'); - }).catch(error => { - assert(error instanceof this.api.errors.MissingLedgerHistoryError); + it('getTransaction - not found - future maxLedgerVersion', function() { + const hash = hashes.NOTFOUND_TRANSACTION_HASH; + const options = { + maxLedgerVersion: 99999999999 + }; + return this.api.getTransaction(hash, options).then(() => { + assert(false, 'Should throw PendingLedgerVersionError'); + }).catch(error => { + assert(error instanceof this.api.errors.PendingLedgerVersionError); + }); }); - }); - it('getTransaction - ledger_index not found', function() { - const hash = - '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'; - return this.api.getTransaction(hash).then(() => { - assert(false, 'Should throw NotFoundError'); - }).catch(error => { - assert(error instanceof this.api.errors.NotFoundError); - assert(error.message.indexOf('ledger_index') !== -1); + it('getTransaction - ledger_index not found', function() { + const hash = + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'; + return this.api.getTransaction(hash).then(() => { + assert(false, 'Should throw NotFoundError'); + }).catch(error => { + assert(error instanceof this.api.errors.NotFoundError); + assert(error.message.indexOf('ledger_index') !== -1); + }); }); - }); - it('getTransaction - transaction ledger not found', function() { - const hash = - '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12'; - return this.api.getTransaction(hash).then(() => { - assert(false, 'Should throw NotFoundError'); - }).catch(error => { - assert(error instanceof this.api.errors.NotFoundError); - assert(error.message.indexOf('ledger not found') !== -1); + it('getTransaction - transaction ledger not found', function() { + const hash = + '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12'; + return this.api.getTransaction(hash).then(() => { + assert(false, 'Should throw NotFoundError'); + }).catch(error => { + assert(error instanceof this.api.errors.NotFoundError); + assert(error.message.indexOf('ledger not found') !== -1); + }); }); - }); - it('getTransaction - ledger missing close time', function() { - const hash = - '0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04'; - return this.api.getTransaction(hash).then(() => { - assert(false, 'Should throw ApiError'); - }).catch(error => { - assert(error instanceof this.api.errors.ApiError); + it('getTransaction - ledger missing close time', function() { + const hash = + '0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04'; + return this.api.getTransaction(hash).then(() => { + assert(false, 'Should throw ApiError'); + }).catch(error => { + assert(error instanceof this.api.errors.ApiError); + }); }); });