diff --git a/src/components/admin-lte/configure/ipfs-tab/index.js b/src/components/admin-lte/configure/ipfs-tab/index.js index 5d977d2..bc3e898 100644 --- a/src/components/admin-lte/configure/ipfs-tab/index.js +++ b/src/components/admin-lte/configure/ipfs-tab/index.js @@ -7,8 +7,8 @@ import CommandRouter from '../lib/commands' import './ipfs.css' -// import RetryQueue from '../../../../lib/retry-queue.mjs' -// const queue = new RetryQueue() +import RetryQueue from '../../../../lib/retry-queue.mjs' +const queue = new RetryQueue() const WalletService = require('../lib/wallet-service') const BchWallet = typeof window !== 'undefined' ? window.SlpWallet : null @@ -301,6 +301,8 @@ class IPFS extends React.Component { } // pollForServices callback + // This method is called from lib/ipfs-control.js. It executes when a BCH + // wallet service is found. async onAppStatus (msg) { try { let output @@ -339,83 +341,48 @@ class IPFS extends React.Component { jsonRpcWalletService: walletService } - _this.bchWalletLib = new _this.BchWallet(mnemonic, advancedConfig) - - let utxosInitialized = false - let cnt = 0 - do { - cnt++ - - try { - // Wait for wallet to be created. - await _this.bchWalletLib.walletInfoPromise - - // await _this.bchWalletLib.bchjs.Util.sleep(5000) - await _this.sleep(5000) - - const utxos = await _this.bchWalletLib.getUtxos() - if (_this.bchWalletLib.utxos.utxoStore) { - _this.onStatusLog( - `utxo initialization succeeded: ${JSON.stringify(utxos, null, 2)}` - ) - utxosInitialized = true - } else { - _this.onStatusLog( - `Attempt ${cnt} to re-initialize timed out. Will try again.` - ) - } - } catch (err) { - _this.onStatusLog( - `Attempt ${cnt} to re-initialize wallet failed. Will try again.` - ) - console.log(err) - - await _this.sleep(5000) - } - // } while (!utxosInitialized) - } while (!utxosInitialized && cnt < 10) - - // console.log('_this.bchWalletLib: ', _this.bchWalletLib) - // - // setInterval(async function () { - // const now = new Date() - // console.log(`Getting UTXOs at ${now.toLocaleString()}`) - // await _this.bchWalletLib.getUtxos() - // console.log( - // `_this.bchWalletLib.utxos.utxoStore: ${JSON.stringify( - // _this.bchWalletLib.utxos.utxoStore, - // null, - // 2 - // )}` - // ) - // }, 20000) - - // let now = new Date() - // console.log(`Starting at ${now.toLocaleString()}`) - // await _this.bchWalletLib.bchjs.Util.sleep(5000) - // now = new Date() - // console.log(`Finished at ${now.toLocaleString()}`) - - // // If UTXOs fail to update, try one more time. - // if (!_this.bchWalletLib.utxos.utxoStore) { - // await _this.bchWalletLib.getUtxos() - // - // // Throw an error if UTXOs are still not updated. - // if (!_this.bchWalletLib.utxos.utxoStore) { - // throw new Error('UTXOs failed to update. Try again.') - // } - // } + // Initialize the wallet, using auth-rety on failure. + const walletIn = { mnemonic, advancedConfig } + await queue.retryWrapper(_this.initWallet, walletIn) // Update redux state _this.props.setBchWallet(_this.bchWalletLib) _this.onStatusLog('re-initialize success!') } catch (error) { - _this.onStatusLog('Error on re-initialize') + _this.onStatusLog('Error in reInitialize()') _this.onStatusLog(error.message) // Don't throw an error as this is a top-level handler. } } + // Initialize the wallet and retrieve the UTXOs for the wallet. + // This function is called by the queue library, to do automatic retry on + // network failure. + async initWallet (walletIn) { + try { + const { mnemonic, advancedConfig } = walletIn + + _this.bchWalletLib = new _this.BchWallet(mnemonic, advancedConfig) + + // Wait for wallet to be created. + await _this.bchWalletLib.walletInfoPromise + + // If auto UTXO initialization fails, do it manually. + let utxos = _this.bchWalletLib.utxos.utxoStore + if (!utxos) { + utxos = await _this.bchWalletLib.getUtxos() + } + + _this.onStatusLog( + `utxo initialization succeeded: ${JSON.stringify(utxos, null, 2)}` + ) + + return _this.bchWalletLib + } catch (err) { + console.error('Error in initWallet(): ', err) + } + } + // Fill the queue with the incoming data (private messages) from the petitions // So it will be sweeped by the lib/wallet-service/waitForRPCResponse() function handleRpcDataQueue (data) { diff --git a/src/lib/retry-queue.mjs b/src/lib/retry-queue.mjs index f553481..6fd32d9 100644 --- a/src/lib/retry-queue.mjs +++ b/src/lib/retry-queue.mjs @@ -9,9 +9,6 @@ pay-to-write-access-controller.js depends on this library. */ -// const PQueue = require('p-queue').default -'use strict' - import PQueue from 'p-queue' import pRetry from 'p-retry' @@ -55,7 +52,7 @@ class RetryQueue { ) return returnVal } catch (err) { - console.error('Error in addToQueue(): ', err) + console.error('Error in addToQueue()') throw err } } @@ -99,11 +96,12 @@ class RetryQueue { try { const errorMsg = `Attempt ${error.attemptNumber} to validate entry. There are ${error.retriesLeft} retries left. Waiting before trying again.` console.log(errorMsg) - const SLEEP_TIME = 30000 + + const SLEEP_TIME = _this.retryPeriod console.log(`Waiting ${SLEEP_TIME} milliseconds before trying again.\n`) - await _this.bchjs.Util.sleep(SLEEP_TIME) // 30 sec + await _this.sleep(SLEEP_TIME) // 30 sec } catch (err) { - console.error('Error in handleValidationError(): ', err) + console.error('Error in handleValidationError()') throw err } } diff --git a/test/unit/retry-queue.adapter.unit.mjs b/test/unit/retry-queue.adapter.unit.mjs index 78bd3c7..e8d5139 100644 --- a/test/unit/retry-queue.adapter.unit.mjs +++ b/test/unit/retry-queue.adapter.unit.mjs @@ -32,127 +32,138 @@ describe('#retry-queue.js', () => { assert.include(err.message, 'function handler is required') } }) - // - // it('should throw an error if input object is not provided', async () => { - // try { - // const funcHandler = () => {} - // await uut.retryWrapper(funcHandler) - // assert.fail('unexpected code path') - // } catch (err) { - // assert.include(err.message, 'input object is required') - // } - // }) - // - // it('should execute the given function.', async () => { - // const inputTest = 'test' - // // func mock to execute into the retry wrapper - // const funcHandle = sinon.spy() - // - // await uut.retryWrapper(funcHandle, inputTest) - // - // assert.equal(inputTest, funcHandle.getCall(0).args[0]) - // assert.equal(funcHandle.callCount, 1) - // }) - // - // it('should call handleValidationError() when p-retry error is thrown', async () => { - // try { - // // Mock for ignore sleep time - // sandbox.stub(uut.bchjs.Util, 'sleep').resolves({}) - // - // const inputTest = 'test' - // const funcHandle = () => { - // throw new Error('test error') - // } - // uut.attempts = 1 - // await uut.retryWrapper(funcHandle, inputTest) - // assert.fail('unexpected code path') - // } catch (err) { - // assert.include(err.message, 'test error') - // } - // }) - // - // it('should retry the specific number of times before giving up', async () => { - // // Mock for ignore sleep time - // sandbox.stub(uut.bchjs.Util, 'sleep').resolves({}) - // - // const inputTest = 'test' - // const funcHandle = () => { - // throw new Error('test error') - // } - // // func handler - // const spy = sinon.spy(funcHandle) - // - // // p-retry attempts - // const attempts = 1 - // - // try { - // uut.attempts = attempts - // await uut.retryWrapper(spy, inputTest) - // assert.fail('unexpected code path') - // } catch (error) { - // assert.equal(spy.callCount, attempts + 1) - // } - // }) + + it('should throw an error if input object is not provided', async () => { + try { + const funcHandler = () => {} + await uut.retryWrapper(funcHandler) + assert.fail('unexpected code path') + } catch (err) { + assert.include(err.message, 'input object is required') + } + }) + + it('should execute the given function.', async () => { + const inputTest = 'test' + // func mock to execute into the retry wrapper + const funcHandle = sinon.spy() + + await uut.retryWrapper(funcHandle, inputTest) + + assert.equal(inputTest, funcHandle.getCall(0).args[0]) + assert.equal(funcHandle.callCount, 1) + }) + + it('should call handleValidationError() when p-retry error is thrown', async () => { + try { + // Mock for ignore sleep time + sandbox.stub(uut, 'sleep').resolves({}) + + const inputTest = 'test' + const funcHandle = () => { + throw new Error('test error') + } + uut.attempts = 1 + + await uut.retryWrapper(funcHandle, inputTest) + + assert.fail('unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + + it('should retry the specific number of times before giving up', async () => { + // Mock for ignore sleep time + sandbox.stub(uut, 'sleep').resolves({}) + + const inputTest = 'test' + const funcHandle = () => { + throw new Error('test error') + } + // func handler + const spy = sinon.spy(funcHandle) + + // p-retry attempts + const attempts = 1 + + try { + uut.attempts = attempts + + await uut.retryWrapper(spy, inputTest) + + assert.fail('unexpected code path') + } catch (error) { + assert.equal(spy.callCount, attempts + 1) + } + }) }) - // describe('#addToQueue', () => { - // it('should throw an error if function handler is not provided', async () => { - // try { - // await uut.addToQueue() - // assert.fail('unexpected code path') - // } catch (err) { - // assert.include(err.message, 'function handler is required') - // } - // }) - // - // it('should throw an error if input object is not provided', async () => { - // try { - // const funcHandler = () => {} - // await uut.addToQueue(funcHandler) - // assert.fail('unexpected code path') - // } catch (err) { - // assert.include(err.message, 'input object is required') - // } - // }) - // - // it('should add a function and input object to the queue and execute them', async () => { - // const inputTest = 'test' - // // func mock to execute into the retry wrapper - // const funcHandle = sinon.spy() - // - // await uut.addToQueue(funcHandle, inputTest) - // assert.equal(inputTest, funcHandle.getCall(0).args[0]) - // assert.equal(funcHandle.callCount, 1) - // }) - // - // it('should catch and throw an error', async () => { - // try { - // // Mock for ignore sleep time - // sandbox.stub(uut.bchjs.Util, 'sleep').resolves({}) - // - // const inputTest = 'test' - // - // const funcHandle = () => { - // throw new Error('test error') - // } - // uut.attempts = 1 - // await uut.retryWrapper(funcHandle, inputTest) - // assert.fail('unexpected code path') - // } catch (err) { - // assert.include(err.message, 'test error') - // } - // }) - // }) + describe('#addToQueue', () => { + it('should throw an error if function handler is not provided', async () => { + try { + await uut.addToQueue() - // describe('#handleValidationError', () => { - // it('should catch and throw an error', async () => { - // try { - // await uut.handleValidationError() - // assert.fail('unexpected code path') - // } catch (err) { - // console.log(err) - // assert.include(err.message, 'Cannot read property') - // } - // }) - // }) + assert.fail('unexpected code path') + } catch (err) { + assert.include(err.message, 'function handler is required') + } + }) + + it('should throw an error if input object is not provided', async () => { + try { + const funcHandler = () => {} + + await uut.addToQueue(funcHandler) + + assert.fail('unexpected code path') + } catch (err) { + assert.include(err.message, 'input object is required') + } + }) + + it('should add a function and input object to the queue and execute them', async () => { + const inputTest = 'test' + // func mock to execute into the retry wrapper + const funcHandle = sinon.spy() + + await uut.addToQueue(funcHandle, inputTest) + + assert.equal(inputTest, funcHandle.getCall(0).args[0]) + assert.equal(funcHandle.callCount, 1) + }) + + it('should catch and throw an error', async () => { + try { + // Mock for ignore sleep time + sandbox.stub(uut, 'sleep').resolves({}) + + const inputTest = 'test' + + const funcHandle = () => { + throw new Error('test error') + } + + uut.attempts = 1 + await uut.retryWrapper(funcHandle, inputTest) + + assert.fail('unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + }) + + describe('#handleValidationError', () => { + it('should catch and throw an error', async () => { + try { + await uut.handleValidationError() + + assert.fail('unexpected code path') + } catch (err) { + // console.log(err) + assert.include(err.message, 'Cannot read property') + } + }) + }) })