Merge pull request #165 from Permissionless-Software-Foundation/ct-unstable

fix(IPFS RPC): Reinitializing wallet with auto-retry
This commit is contained in:
Chris Troutner
2021-11-11 14:13:38 -08:00
committed by GitHub
3 changed files with 172 additions and 196 deletions
@@ -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) {
+5 -7
View File
@@ -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
}
}
+131 -120
View File
@@ -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')
}
})
})
})