From 15bb3c50cfd3f37353e9fe80efd6f94c2896bc24 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 8 Nov 2021 07:10:39 -0800 Subject: [PATCH 1/3] Retrying wallet init if it fails --- .../admin-lte/configure/ipfs-tab/index.js | 79 +++++++++++++++---- .../admin-lte/configure/lib/wallet-service.js | 2 +- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/components/admin-lte/configure/ipfs-tab/index.js b/src/components/admin-lte/configure/ipfs-tab/index.js index 4d3f8f7..c73fcc8 100644 --- a/src/components/admin-lte/configure/ipfs-tab/index.js +++ b/src/components/admin-lte/configure/ipfs-tab/index.js @@ -44,15 +44,15 @@ class IPFS extends React.Component { - + @@ -338,17 +338,68 @@ class IPFS extends React.Component { _this.bchWalletLib = new _this.BchWallet(mnemonic, advancedConfig) - await _this.bchWalletLib.walletInfoPromise // Wait for wallet to be created. + let utxosInitialized = false + let cnt = 0 + do { + cnt++ - // If UTXOs fail to update, try one more time. - if (!_this.bchWalletLib.utxos.utxoStore) { - await _this.bchWalletLib.getUtxos() + try { + // Wait for wallet to be created. + await _this.bchWalletLib.walletInfoPromise - // Throw an error if UTXOs are still not updated. - if (!_this.bchWalletLib.utxos.utxoStore) { - throw new Error('UTXOs failed to update. Try again.') + // 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) } - } + // } 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.') + // } + // } // Update redux state _this.props.setBchWallet(_this.bchWalletLib) diff --git a/src/components/admin-lte/configure/lib/wallet-service.js b/src/components/admin-lte/configure/lib/wallet-service.js index a36602e..3a01188 100644 --- a/src/components/admin-lte/configure/lib/wallet-service.js +++ b/src/components/admin-lte/configure/lib/wallet-service.js @@ -206,7 +206,7 @@ class WalletService { // Wait between loops. // await this.sleep(1000) - await this.ipfsControl.wallet.bchjs.Util.sleep(5000) + await this.ipfsControl.wallet.bchjs.Util.sleep(4500) cnt++ From e9e86f939845bf4b54bd56eeb3c458881dfe69fb Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 8 Nov 2021 07:40:01 -0800 Subject: [PATCH 2/3] fix(ipfs): Got rough auto-retry working --- src/components/admin-lte/configure/ipfs-tab/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/admin-lte/configure/ipfs-tab/index.js b/src/components/admin-lte/configure/ipfs-tab/index.js index c73fcc8..4bfd704 100644 --- a/src/components/admin-lte/configure/ipfs-tab/index.js +++ b/src/components/admin-lte/configure/ipfs-tab/index.js @@ -366,6 +366,8 @@ class IPFS extends React.Component { `Attempt ${cnt} to re-initialize wallet failed. Will try again.` ) console.log(err) + + await _this.sleep(5000) } // } while (!utxosInitialized) } while (!utxosInitialized && cnt < 10) From aa331474235974dc7a4644e46e06dff5c25d1171 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 8 Nov 2021 08:25:38 -0800 Subject: [PATCH 3/3] Adding in initial tests --- package-lock.json | 466 ++++++++++++++++-- package.json | 5 +- .../admin-lte/configure/ipfs-tab/index.js | 12 +- src/lib/retry-queue.js | 112 +++++ test/unit/retry-queue.adapter.unit.js | 156 ++++++ 5 files changed, 711 insertions(+), 40 deletions(-) create mode 100644 src/lib/retry-queue.js create mode 100644 test/unit/retry-queue.adapter.unit.js diff --git a/package-lock.json b/package-lock.json index 6231af5..22c7f74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,8 @@ "https-browserify": "^1.0.0", "ipfs-coord": "^6.7.3", "jsonrpc-lite": "^2.2.0", + "p-queue": "^7.1.0", + "p-retry": "^5.0.0", "path-browserify": "^1.0.1", "process": "^0.11.10", "prop-types": "^15.7.2", @@ -2131,6 +2133,32 @@ "multiformats": "^9.4.2" } }, + "node_modules/@chris.troutner/ipfs-core/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@chris.troutner/ipfs-core/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@chris.troutner/ipfs-daemon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/@chris.troutner/ipfs-daemon/-/ipfs-daemon-0.8.2.tgz", @@ -4940,6 +4968,19 @@ "node": ">=4.0.0" } }, + "node_modules/@semantic-release/github/node_modules/p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "dev": true, + "dependencies": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@semantic-release/npm": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.1.3.tgz", @@ -18522,6 +18563,32 @@ "stream-to-it": "^0.2.2" } }, + "node_modules/ipfs-repo/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ipfs-repo/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ipfs-unixfs": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-4.0.3.tgz", @@ -20744,6 +20811,32 @@ "p-queue": "^6.2.1" } }, + "node_modules/libp2p-delegated-content-routing/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/libp2p-delegated-content-routing/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/libp2p-delegated-peer-routing": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/libp2p-delegated-peer-routing/-/libp2p-delegated-peer-routing-0.9.0.tgz", @@ -20756,6 +20849,32 @@ "peer-id": "^0.14.0" } }, + "node_modules/libp2p-delegated-peer-routing/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/libp2p-delegated-peer-routing/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/libp2p-floodsub": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/libp2p-floodsub/-/libp2p-floodsub-0.25.3.tgz", @@ -20876,6 +20995,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/libp2p-kad-dht/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/libp2p-kad-dht/node_modules/p-queue/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/libp2p-mdns": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/libp2p-mdns/-/libp2p-mdns-0.16.0.tgz", @@ -21406,6 +21551,44 @@ "varint": "^6.0.0" } }, + "node_modules/libp2p/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/libp2p/node_modules/p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "dependencies": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/libp2p/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/libp2p/node_modules/peer-id": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.15.3.tgz", @@ -23264,6 +23447,32 @@ "promise-timeout": "^1.3.0" } }, + "node_modules/mortice/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mortice/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -28443,6 +28652,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/orbit-db-store/node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/orbit-db-store/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/orbit-db-store/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -28670,29 +28905,29 @@ } }, "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.1.0.tgz", + "integrity": "sha512-V+0vPJbhYkBqknPp0qnaz+dWcj8cNepfXZcsVIVEHPbFQXMPwrzCNIiM4FoxGtwHXtPzVCPHDvqCr1YrOJX2Gw==", "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" + "eventemitter3": "^4.0.7", + "p-timeout": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue/node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dependencies": { - "p-finally": "^1.0.0" - }, + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.0.2.tgz", + "integrity": "sha512-sEmji9Yaq+Tw+STwsGAE56hf7gMy9p0tQfJojIAamB7WHJYJKf1qlsg9jqBWG8q9VCxKPhZaP/AcXwEoBcYQhQ==", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-reduce": { @@ -28712,15 +28947,18 @@ } }, "node_modules/p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.0.0.tgz", + "integrity": "sha512-swGFiU6Y1Q3rBikAGHpaT0FHSbiO9H04fSsJRKVtWyEQMAe2Sb1uXeBcqE/RlZqt2prlq4W2HA/+MZAt3V2NkQ==", "dependencies": { - "@types/retry": "^0.12.0", + "@types/retry": "^0.12.1", "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-series": { @@ -40068,6 +40306,25 @@ "peer-id": "^0.14.1", "streaming-iterables": "^5.0.2", "uint8arrays": "^2.1.3" + }, + "dependencies": { + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + } } }, "@chris.troutner/ipfs-core-types": { @@ -42306,6 +42563,16 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true + }, + "p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "dev": true, + "requires": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + } } } }, @@ -53110,6 +53377,23 @@ "node-fetch": "^2.6.1", "stream-to-it": "^0.2.2" } + }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } } } }, @@ -54799,6 +55083,32 @@ "varint": "^6.0.0" } }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "requires": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, "peer-id": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.15.3.tgz", @@ -54959,6 +55269,25 @@ "multiaddr": "^9.0.1", "p-defer": "^3.0.0", "p-queue": "^6.2.1" + }, + "dependencies": { + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + } } }, "libp2p-delegated-peer-routing": { @@ -54971,6 +55300,25 @@ "p-defer": "^3.0.0", "p-queue": "^6.3.0", "peer-id": "^0.14.0" + }, + "dependencies": { + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + } } }, "libp2p-floodsub": { @@ -55085,6 +55433,25 @@ "requires": { "aggregate-error": "^3.0.0" } + }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "dependencies": { + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + } + } } } }, @@ -56798,6 +57165,25 @@ "observable-webworkers": "^1.0.0", "p-queue": "^6.0.0", "promise-timeout": "^1.3.0" + }, + "dependencies": { + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + } } }, "mri": { @@ -60719,6 +61105,23 @@ "aggregate-error": "^3.0.0" } }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -60848,21 +61251,18 @@ } }, "p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.1.0.tgz", + "integrity": "sha512-V+0vPJbhYkBqknPp0qnaz+dWcj8cNepfXZcsVIVEHPbFQXMPwrzCNIiM4FoxGtwHXtPzVCPHDvqCr1YrOJX2Gw==", "requires": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" + "eventemitter3": "^4.0.7", + "p-timeout": "^5.0.0" }, "dependencies": { "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "requires": { - "p-finally": "^1.0.0" - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.0.2.tgz", + "integrity": "sha512-sEmji9Yaq+Tw+STwsGAE56hf7gMy9p0tQfJojIAamB7WHJYJKf1qlsg9jqBWG8q9VCxKPhZaP/AcXwEoBcYQhQ==" } } }, @@ -60877,11 +61277,11 @@ "integrity": "sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg==" }, "p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.0.0.tgz", + "integrity": "sha512-swGFiU6Y1Q3rBikAGHpaT0FHSbiO9H04fSsJRKVtWyEQMAe2Sb1uXeBcqE/RlZqt2prlq4W2HA/+MZAt3V2NkQ==", "requires": { - "@types/retry": "^0.12.0", + "@types/retry": "^0.12.1", "retry": "^0.13.1" } }, diff --git a/package.json b/package.json index e2329d1..4ccfba2 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "start": "npm run develop", "serve": "gatsby serve", "clean": "gatsby clean", - "test": "npm run lint" + "test": "npm run lint", + "unit": "mocha" }, "dependencies": { "@chris.troutner/gatsby-plugin-ipfs": "^2.0.3", @@ -38,6 +39,8 @@ "https-browserify": "^1.0.0", "ipfs-coord": "^6.7.3", "jsonrpc-lite": "^2.2.0", + "p-queue": "^7.1.0", + "p-retry": "^5.0.0", "path-browserify": "^1.0.1", "process": "^0.11.10", "prop-types": "^15.7.2", diff --git a/src/components/admin-lte/configure/ipfs-tab/index.js b/src/components/admin-lte/configure/ipfs-tab/index.js index 4bfd704..77d7eeb 100644 --- a/src/components/admin-lte/configure/ipfs-tab/index.js +++ b/src/components/admin-lte/configure/ipfs-tab/index.js @@ -44,15 +44,15 @@ class IPFS extends React.Component { - + diff --git a/src/lib/retry-queue.js b/src/lib/retry-queue.js new file mode 100644 index 0000000..c198cba --- /dev/null +++ b/src/lib/retry-queue.js @@ -0,0 +1,112 @@ +/* + This library leverages the p-retry and p-queue libraries, to create a + validation queue with automatic retry. + + New nodes syncing will attempt to rapidly validate a lot of entries. + A promise-based queue allows this to happen while respecting rate-limits + of the blockchain service provider. + + pay-to-write-access-controller.js depends on this library. +*/ + +// const PQueue = require('p-queue').default +import PQueue from 'p-queue' +const pRetry = require('p-retry') + +let _this + +class RetryQueue { + constructor (localConfig = {}) { + // if (!localConfig.bchjs) { + // throw new Error( + // 'Must pass instance of bch-js when instantiating RetryQueue Class.' + // ) + // } + // this.bchjs = localConfig.bchjs + + // Encapsulate dependencies + this.validationQueue = new PQueue({ concurrency: 1 }) + this.pRetry = pRetry + + this.attempts = 5 + this.retryPeriod = 5000 + + _this = this + } + + // Add an async function to the queue, and execute it with the input object. + async addToQueue (funcHandle, inputObj) { + try { + console.log('addToQueue inputObj: ', inputObj) + + if (!funcHandle) { + throw new Error('function handler is required') + } + if (!inputObj) { + throw new Error('input object is required') + } + + const returnVal = await _this.validationQueue.add(() => + _this.retryWrapper(funcHandle, inputObj) + ) + return returnVal + } catch (err) { + console.error('Error in addToQueue(): ', err) + throw err + } + } + + // Wrap the p-retry library. + // This function returns a promise that will resolve to the output of the + // function 'funcHandle'. + async retryWrapper (funcHandle, inputObj) { + try { + console.log('retryWrapper inputObj: ', inputObj) + + if (!funcHandle) { + throw new Error('function handler is required') + } + if (!inputObj) { + throw new Error('input object is required') + } + console.log('Entering retryWrapper()') + + // Add artificial delay to prevent 429 errors. + // await this.sleep(this.retryPeriod) + + return this.pRetry( + async () => { + return await funcHandle(inputObj) + }, + { + onFailedAttempt: _this.handleValidationError, + retries: this.attempts // Retry 5 times + } + ) + } catch (err) { + console.error('Error in retryWrapper: ', err) + throw err + } + } + + // Notifies the user that an error occured and that a retry will be attempted. + // It tracks the number of retries until it fails. + async handleValidationError (error) { + 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 + console.log(`Waiting ${SLEEP_TIME} milliseconds before trying again.\n`) + await _this.bchjs.Util.sleep(SLEEP_TIME) // 30 sec + } catch (err) { + console.error('Error in handleValidationError(): ', err) + throw err + } + } + + sleep (ms) { + return new Promise(resolve => setTimeout(resolve, ms)) + } +} + +module.exports = RetryQueue diff --git a/test/unit/retry-queue.adapter.unit.js b/test/unit/retry-queue.adapter.unit.js new file mode 100644 index 0000000..2f6ef16 --- /dev/null +++ b/test/unit/retry-queue.adapter.unit.js @@ -0,0 +1,156 @@ +/* + Unit tests for the retry-queue library. +*/ + +const sinon = require('sinon') +const assert = require('chai').assert +// const BCHJS = require('@psf/bch-js') + +const RetryQueue = require('../../src/lib/retry-queue') + +// const bchjs = new BCHJS() +let uut +let sandbox + +describe('#retry-queue.js', () => { + beforeEach(() => { + uut = new RetryQueue() + + sandbox = sinon.createSandbox() + }) + + afterEach(() => sandbox.restore()) + + describe('#_retryWrapper', () => { + it('should throw an error if function handler is not provided', async () => { + try { + await uut.retryWrapper() + 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.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) + // } + // }) + }) + + // 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('#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') + // } + // }) + // }) +})