Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

Auto-retry with increased timeout for attachToTangle & getTransactionsToApprove APIs #1285

Merged
merged 1 commit into from Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/shared/config.js
Expand Up @@ -34,6 +34,8 @@ export const DEFAULT_BALANCES_THRESHOLD = 100;

export const BUNDLE_OUTPUTS_THRESHOLD = 50;

export const MAX_REQUEST_TIMEOUT = 60 * 1000 * 2;

export const DEFAULT_NODE_REQUEST_TIMEOUT = 6000 * 2;
export const GET_NODE_INFO_REQUEST_TIMEOUT = 2500;
export const GET_BALANCES_REQUEST_TIMEOUT = 6000;
Expand Down
1 change: 1 addition & 0 deletions src/shared/libs/errors.js
Expand Up @@ -57,4 +57,5 @@ export default {
LEDGER_CANCELLED: 'Ledger transaction cancelled',
LEDGER_DENIED: 'Ledger transaction denied by user',
LEDGER_INVALID_INDEX: 'Incorrect Ledger device or changed mnemonic',
REQUEST_TIMED_OUT: 'Request timed out',
};
67 changes: 36 additions & 31 deletions src/shared/libs/iota/extendedApi.js
Expand Up @@ -26,7 +26,7 @@ import {
isBundle,
isBundleTraversable,
} from './transfers';
import { EMPTY_HASH_TRYTES } from './utils';
import { EMPTY_HASH_TRYTES, withRequestTimeoutsHandler } from './utils';

/**
* Returns timeouts for specific quorum requests
Expand Down Expand Up @@ -498,36 +498,41 @@ const attachToTangleAsync = (provider, seedStore) => (
const shouldOffloadPow = get(seedStore, 'offloadPow') === true;

if (shouldOffloadPow) {
return new Promise((resolve, reject) => {
getIotaInstance(provider, getApiTimeout('attachToTangle')).api.attachToTangle(
trunkTransaction,
branchTransaction,
minWeightMagnitude,
// Make sure trytes are sorted properly
sortTransactionTrytesArray(trytes),
(err, attachedTrytes) => {
if (err) {
reject(err);
} else {
constructBundleFromAttachedTrytes(attachedTrytes, seedStore)
.then((transactionObjects) => {
if (
isBundle(transactionObjects) &&
isBundleTraversable(transactionObjects, trunkTransaction, branchTransaction)
) {
resolve({
transactionObjects,
trytes: attachedTrytes,
});
} else {
reject(new Error(Errors.INVALID_BUNDLE_CONSTRUCTED_WITH_REMOTE_POW));
}
})
.catch(reject);
}
},
);
});
const request = (requestTimeout) =>
new Promise((resolve, reject) => {
getIotaInstance(provider, requestTimeout).api.attachToTangle(
trunkTransaction,
branchTransaction,
minWeightMagnitude,
// Make sure trytes are sorted properly
sortTransactionTrytesArray(trytes),
(err, attachedTrytes) => {
if (err) {
reject(err);
} else {
constructBundleFromAttachedTrytes(attachedTrytes, seedStore)
.then((transactionObjects) => {
if (
isBundle(transactionObjects) &&
isBundleTraversable(transactionObjects, trunkTransaction, branchTransaction)
) {
resolve({
transactionObjects,
trytes: attachedTrytes,
});
} else {
reject(new Error(Errors.INVALID_BUNDLE_CONSTRUCTED_WITH_REMOTE_POW));
}
})
.catch(reject);
}
},
);
});

const defaultRequestTimeout = getApiTimeout('attachToTangle');

return withRequestTimeoutsHandler(defaultRequestTimeout)(request);
}

return seedStore
Expand Down
37 changes: 36 additions & 1 deletion src/shared/libs/iota/utils.js
Expand Up @@ -10,7 +10,7 @@ import URL from 'url-parse';
import { BigNumber } from 'bignumber.js';
import { iota } from './index';
import { isNodeHealthy } from './extendedApi';
import { NODELIST_URL } from '../../config';
import { NODELIST_URL, MAX_REQUEST_TIMEOUT } from '../../config';
import Errors from '../errors';

export const MAX_SEED_LENGTH = 81;
Expand Down Expand Up @@ -442,3 +442,38 @@ export const throwIfNodeNotHealthy = (provider) => {
return isSynced;
});
};

/**
* Handles timeouts for network requests made to IRI nodes
* Catches "request timeout" exceptions and retries network request with increased timeout
* See (https://github.com/iotaledger/iota.js/blob/master/lib/utils/makeRequest.js#L115)
*
* @method withRequestTimeoutsHandler
*
* @param {number} timeout
*
* @returns {function}
*/
export const withRequestTimeoutsHandler = (timeout) => {
let attempt = 1;

const getNextTimeout = () => attempt * timeout;

const handleTimeout = (promiseFunc) => {
return promiseFunc(getNextTimeout()).catch((error) => {
attempt += 1;

if (
(includes(error.message, Errors.REQUEST_TIMED_OUT) ||
includes(error.message, Errors.REQUEST_TIMED_OUT.toLowerCase())) &&
getNextTimeout() < MAX_REQUEST_TIMEOUT
) {
return handleTimeout(promiseFunc);
}

throw error;
});
};

return handleTimeout;
};