From f770f7229b47fb819aff3cadf558a3fb438f52e5 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 27 Jun 2021 23:41:35 +0200 Subject: [PATCH 01/16] feat(Keychain): getHardenedDIP15AccountKey --- src/types/KeyChain/KeyChain.d.ts | 2 +- src/types/KeyChain/KeyChain.js | 1 + src/types/KeyChain/KeyChain.spec.js | 12 +++++++++++- .../methods/getHardenedDIP15AccountKey.js | 15 +++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/types/KeyChain/methods/getHardenedDIP15AccountKey.js diff --git a/src/types/KeyChain/KeyChain.d.ts b/src/types/KeyChain/KeyChain.d.ts index 17eb7ae6c..2d2b82bd9 100644 --- a/src/types/KeyChain/KeyChain.d.ts +++ b/src/types/KeyChain/KeyChain.d.ts @@ -23,7 +23,7 @@ export declare class KeyChain { getHardenedBIP44Path(type?: HDKeyTypesParam): HDKeyTypes; getHardenedDIP9FeaturePath(type?: HDKeyTypesParam): HDKeyTypes; - + getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; getPrivateKey(): PrivateKey; diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 486194e80..980f2f45d 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -36,6 +36,7 @@ KeyChain.prototype.generateKeyForChild = require('./methods/generateKeyForChild' KeyChain.prototype.generateKeyForPath = require('./methods/generateKeyForPath'); KeyChain.prototype.getHardenedBIP44Path = require('./methods/getHardenedBIP44Path'); KeyChain.prototype.getHardenedDIP9FeaturePath = require('./methods/getHardenedDIP9FeaturePath'); +KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); KeyChain.prototype.getKeyForChild = require('./methods/getKeyForChild'); KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); KeyChain.prototype.getPrivateKey = require('./methods/getPrivateKey'); diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 90eb76f0b..75d85bd0d 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -6,6 +6,10 @@ const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); let keychain; const mnemonic = 'during develop before curtain hazard rare job language become verb message travel'; const pk = '4226d5e2fe8cbfe6f5beb7adf5a5b08b310f6c4a67fc27826779073be6f5699e'; + +const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; +const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; +const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; describe('Keychain', function suite() { this.timeout(10000); it('should create a keychain', () => { @@ -32,6 +36,12 @@ describe('Keychain', function suite() { const pk2 = keychain.getKeyForPath('m/44\'/1\''); expect(pk2.toString()).to.equal(hardenedPk.toString()); }); + it('should get DIP15 account path', function () { + const rootDIP15AccountKey_0 = keychain.getHardenedDIP15AccountKey(0); + expect(rootDIP15AccountKey_0.toString()).to.deep.equal(expectedRootDIP15AccountKey_0); + const rootDIP15AccountKey_1 = keychain.getHardenedDIP15AccountKey(1); + expect(rootDIP15AccountKey_1.toString()).to.deep.equal(expectedRootDIP15AccountKey_1); + }); it('should derive from hardened feature path', () => { const hardenedPk = keychain.getHardenedBIP44Path(); const derivedPk = hardenedPk.deriveChild(0, true).deriveChild(0).deriveChild(0); @@ -41,7 +51,7 @@ describe('Keychain', function suite() { it('should generate key for child', () => { const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); const keyForChild = keychain2.generateKeyForChild(0); - expect(keyForChild.toString()).to.equal('tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'); + expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); }); it('should sign', () => { diff --git a/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js b/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js new file mode 100644 index 000000000..6540e3d19 --- /dev/null +++ b/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js @@ -0,0 +1,15 @@ +/** + * Return a safier root path to derivate from + * @param {number} [accountIndex=0] - set the account index + * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys + * @return {HDPrivateKey|HDPublicKey} + */ +function getHardenedDIP15AccountKey(accountIndex = 0, type = 'HDPrivateKey') { + const hardenedFeatureRootKey = this.getHardenedDIP9FeaturePath(type); + + // Feature is set to 15' for all DashPay Incoming Funds derivation paths (see DIP15). + const featureKey = hardenedFeatureRootKey.deriveChild(15, true); + + return featureKey.deriveChild(accountIndex, true); +} +module.exports = getHardenedDIP15AccountKey; From 693dbdde4902c804a883a51f5f5e311ffc65acfd Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 27 Jun 2021 23:44:30 +0200 Subject: [PATCH 02/16] feat(Plugin): added ability to handle pluginDependency --- src/plugins/StandardPlugin.js | 2 +- src/types/Account/_initializeAccount.js | 7 +- src/types/Account/_preparePlugins.js | 36 +++++ src/types/Account/_preparePlugins.spec.js | 36 +++++ src/types/Account/_sortPlugins.js | 82 +++++++++++ src/types/Account/_sortPlugins.spec.js | 162 ++++++++++++++++++++++ 6 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 src/types/Account/_preparePlugins.js create mode 100644 src/types/Account/_preparePlugins.spec.js create mode 100644 src/types/Account/_sortPlugins.js create mode 100644 src/types/Account/_sortPlugins.spec.js diff --git a/src/plugins/StandardPlugin.js b/src/plugins/StandardPlugin.js index 753a64c23..186ed8c3a 100644 --- a/src/plugins/StandardPlugin.js +++ b/src/plugins/StandardPlugin.js @@ -13,7 +13,7 @@ class StandardPlugin extends EventEmitter { this.pluginType = _.has(opts, 'type') ? opts.type : 'Standard'; this.name = _.has(opts, 'name') ? opts.name : 'UnnamedPlugin'; this.dependencies = _.has(opts, 'dependencies') ? opts.dependencies : []; - + this.pluginDependencies = _.has(opts, 'pluginDependencies') ? opts.pluginDependencies : []; this.executeOnStart = _.has(opts, 'executeOnStart') ? opts.executeOnStart : defaultOpts.executeOnStart; diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index e1fa07766..68d7d5af3 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -31,11 +31,12 @@ async function _initializeAccount(account, userUnsafePlugins) { account.index, account.getAddress.bind(account), ); - } - - if (account.walletType === WALLET_TYPES.SINGLE_ADDRESS) { + } else if (account.walletType === WALLET_TYPES.SINGLE_ADDRESS) { await account.getAddress('0'); // We force what is usually done by the BIP44Worker. + } else { + throw new Error(`InitializateAccount failed - Unexpected walletType: ${account.walletType}`); } + if (!account.offlineMode) { await account.injectPlugin(ChainPlugin, true); diff --git a/src/types/Account/_preparePlugins.js b/src/types/Account/_preparePlugins.js new file mode 100644 index 000000000..50ffd9a90 --- /dev/null +++ b/src/types/Account/_preparePlugins.js @@ -0,0 +1,36 @@ +const { each } = require('lodash'); +const TransactionSyncStreamWorker = require('../../plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker'); +const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); +const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); +const { WALLET_TYPES } = require('../../CONSTANTS'); + +const preparePlugins = (account, userUnsafePlugins) => { + const plugins = []; + + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + try { + if (account.injectDefaultPlugins) { + if (!account.offlineMode) { + plugins.push([ChainPlugin, true]); + plugins.push([TransactionSyncStreamWorker, true]); + + if (account.walletType === WALLET_TYPES.HDWALLET) { + plugins.push([IdentitySyncWorker, true]); + } + } + } + each(userUnsafePlugins, (UnsafePlugin) => { + if(UnsafePlugin.dependencies){ + console.log({dependencies: UnsafePlugin.dependencies}) + } + account.injectPlugin(UnsafePlugin, account.allowSensitiveOperations); + }); + + resolve(plugins); + } catch (e) { + reject(e); + } + }); +}; +module.exports = preparePlugins; diff --git a/src/types/Account/_preparePlugins.spec.js b/src/types/Account/_preparePlugins.spec.js new file mode 100644 index 000000000..77c8e4da2 --- /dev/null +++ b/src/types/Account/_preparePlugins.spec.js @@ -0,0 +1,36 @@ +const { expect } = require('chai'); +const { WALLET_TYPES } = require('../../CONSTANTS'); + +const preparePlugins = require('./_preparePlugins'); + +const baseAccount = { + walletType: WALLET_TYPES.HDWALLET +} +const accountOnlineWithDefaultPlugins = { + ...baseAccount, + injectDefaultPlugins: true, +}; +const accountOnlineWithoutDefaultPlugins = { + ...baseAccount, + injectDefaultPlugins: false, +}; +const accountOfflineWithDefaultPlugins = { + ...baseAccount, + offlineMode: true, + injectDefaultPlugins: true, +}; +const accountOfflineWithoutDefaultPlugins = { + ...baseAccount, + offlineMode: true, + injectDefaultPlugins: false, +}; +const userPlugins = {}; +describe('Account - prepareDependencies', () => { + it('should be able to load default plugins', async function () { + const preparedPlugins = await preparePlugins(accountOnlineWithDefaultPlugins, userPlugins); + console.log({preparedPlugins}); + }); + // it('should not load unneccessary plugins', function () { + // preparePlugins(accountWithoutDefaultPlugins, userPlugins); + // }); +}); diff --git a/src/types/Account/_sortPlugins.js b/src/types/Account/_sortPlugins.js new file mode 100644 index 000000000..6c9c104fd --- /dev/null +++ b/src/types/Account/_sortPlugins.js @@ -0,0 +1,82 @@ +const { each, findIndex } = require('lodash'); +const TransactionSyncStreamWorker = require('../../plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker'); +const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); +const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); +const { WALLET_TYPES } = require('../../CONSTANTS'); + +const initPlugin = (UnsafePlugin) => { + const isInit = !(typeof UnsafePlugin === 'function'); + return (isInit) ? UnsafePlugin : new UnsafePlugin(); +}; + +const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins) => { + const sortedPlugins = []; + const initializedSortedPlugins = []; + + each(defaultSortedPlugins, (defaultPluginParams) => { + sortedPlugins.push(defaultPluginParams); + + // We also need to initialize them so we actually as we gonna need to read some properties. + const plugin = initPlugin(defaultPluginParams[0]); + initializedSortedPlugins.push(plugin); + }); + + each(userUnsafePlugins, (UnsafePlugin) => { + const plugin = initPlugin(UnsafePlugin); + + const { pluginDependencies } = plugin; + const hasPluginDependencies = !!(pluginDependencies && pluginDependencies.length); + + let lastDependencyIndex = -1; + if (hasPluginDependencies) { + each(pluginDependencies, (pluginDependencyName) => { + const pluginDependencyIndex = findIndex(initializedSortedPlugins, ['name', pluginDependencyName]); + if (lastDependencyIndex < pluginDependencyIndex) { + lastDependencyIndex = pluginDependencyIndex; + } + if (pluginDependencyIndex === -1) { + // This will throw if user has defined the plugin but we didn't processed it yet. + // We could process that by delaying and retry after that, + // complexity of such would exist in case of cross-dependency + // For now we just implement the naive way to handle dependency, but adding a TODO here. + // Let's note : We will still want to throw after retrying if we still fail + // to sort the concerned plugin (to avoid looping forever and ever) + throw new Error(`Plugin ${plugin.name} has missing dependency ${pluginDependencyName}`); + } + }); + } + + // We insert both initialized and uninitialized plugins as we gonna need to read property. + initializedSortedPlugins.splice( + (lastDependencyIndex >= 0) ? lastDependencyIndex + 1 : initializedSortedPlugins.length, + 0, + plugin, + ); + sortedPlugins.splice( + (lastDependencyIndex >= 0) ? lastDependencyIndex + 1 : initializedSortedPlugins.length, + 0, + [UnsafePlugin, true], + ); + }); + each(initializedSortedPlugins, (initializedSortedPlugin, i) => { + delete initializedSortedPlugins[i]; + }); + return sortedPlugins; +}; +const sortPlugins = (account, userUnsafePlugins) => { + const plugins = []; + + // eslint-disable-next-line no-async-promise-executor + if (account.injectDefaultPlugins) { + if (!account.offlineMode) { + plugins.push([ChainPlugin, true]); + plugins.push([TransactionSyncStreamWorker, true]); + + if (account.walletType === WALLET_TYPES.HDWALLET) { + plugins.push([IdentitySyncWorker, true]); + } + } + } + return sortUserPlugins(plugins, userUnsafePlugins); +}; +module.exports = sortPlugins; diff --git a/src/types/Account/_sortPlugins.spec.js b/src/types/Account/_sortPlugins.spec.js new file mode 100644 index 000000000..622d795b1 --- /dev/null +++ b/src/types/Account/_sortPlugins.spec.js @@ -0,0 +1,162 @@ +const { expect } = require('chai'); +const { WALLET_TYPES } = require('../../CONSTANTS'); +const sortPlugins = require('./_sortPlugins'); + +const TransactionSyncStreamWorker = require('../../plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker'); +const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); +const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); + +const Worker = require('./../../plugins/Worker'); +class withoutPluginDependenciesWorker extends Worker { + constructor() { + super({ + name: 'withoutPluginDependenciesWorker', + }); + } +} +class withSinglePluginDependenciesWorker extends Worker { + constructor() { + super({ + name: 'withSinglePluginDependenciesWorker', + pluginDependencies: [ + 'IdentitySyncWorker' + ] + }); + } +} +class withSinglePluginDependenciesWorker2 extends Worker { + constructor() { + super({ + name: 'withSinglePluginDependenciesWorker2', + pluginDependencies: [ + 'TransactionSyncStreamWorker' + ] + }); + } +} +class pluginWithMultiplePluginDependencies extends Worker { + constructor() { + super({ + name: 'pluginWithMultiplePluginDependencies', + pluginDependencies: [ + 'TransactionSyncStreamWorker', + 'withSinglePluginDependenciesWorker' + ] + }); + } +} + +const baseAccount = { + walletType: WALLET_TYPES.HDWALLET +} +const accountOnlineWithDefaultPlugins = { + ...baseAccount, + injectDefaultPlugins: true, +}; +const accountOnlineWithoutDefaultPlugins = { + ...baseAccount, + injectDefaultPlugins: false, +}; +const accountOfflineWithDefaultPlugins = { + ...baseAccount, + offlineMode: true, + injectDefaultPlugins: true, +}; +const accountOfflineWithoutDefaultPlugins = { + ...baseAccount, + offlineMode: true, + injectDefaultPlugins: false, +}; +const userDefinedWithoutPluginDependenciesPlugins = { + "withoutPluginDependenciesWorker": withoutPluginDependenciesWorker +}; +const userDefinedWithSinglePluginDependenciesPlugins1 = { + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker +}; +const userDefinedWithSinglePluginDependenciesPlugins2 = { + "withSinglePluginDependenciesWorker2": withSinglePluginDependenciesWorker2 +}; +const userDefinedWithMultiplePluginDependenciesPlugins = { + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, + "withSinglePluginDependenciesWorker2": withSinglePluginDependenciesWorker2 +}; + +// Subtility here is in the fact that the first plugin expect second as a dependency. +// It also expect TransactionSyncStreamWorker as a dependency. +// Second item expect IdentitySyncWorker as a dependency which is loaded after TXSyncStreamWorker +const userDefinedComplexPluginDependenciesPlugins = { + "pluginWithMultiplePluginDependencies": pluginWithMultiplePluginDependencies, + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, + +} + +describe('Account - _sortPlugins', () => { + describe('system plugins sorting', async function () { + it('should be able to correctly sort default plugins', async function () { + const sortedPluginsOnlineWithDefault = await sortPlugins(accountOnlineWithDefaultPlugins); + expect(sortedPluginsOnlineWithDefault).to.deep.equal([ + [ChainPlugin, true], + [TransactionSyncStreamWorker, true], + [IdentitySyncWorker, true], + ]) + const sortedPluginsOnlineWithoutDefault = await sortPlugins(accountOnlineWithoutDefaultPlugins); + expect(sortedPluginsOnlineWithoutDefault).to.deep.equal([]); + + const sortedPluginsOfflineWithDefault = await sortPlugins(accountOfflineWithDefaultPlugins); + expect(sortedPluginsOfflineWithDefault).to.deep.equal([]) + + const sortedPluginsOfflineWithoutDefault = await sortPlugins(accountOfflineWithoutDefaultPlugins); + expect(sortedPluginsOfflineWithoutDefault).to.deep.equal([]) + }); + }); + describe('user plugins sorting', async function () { + it('should handle userDefinedWithoutPluginDependenciesPlugins', async function () { + const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithoutPluginDependenciesPlugins); + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true], + [TransactionSyncStreamWorker, true], + [IdentitySyncWorker, true], + [withoutPluginDependenciesWorker, true], + ]); + }); + it('should handle userDefinedWithoutPluginDependenciesPlugins', async function () { + const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins1); + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true], + [TransactionSyncStreamWorker, true], + [IdentitySyncWorker, true], + [withSinglePluginDependenciesWorker, true] + ]) + }); + it('should handle userDefinedWithSinglePluginDependenciesPlugins2', async function () { + const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins2); + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true], + [TransactionSyncStreamWorker, true], + [withSinglePluginDependenciesWorker2 , true], + [IdentitySyncWorker, true], + ]) + }); + it('should handle userDefinedWithMultiplePluginDependenciesPlugins', async function () { + const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithMultiplePluginDependenciesPlugins); + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true], + [TransactionSyncStreamWorker, true], + [withSinglePluginDependenciesWorker2 , true], + [IdentitySyncWorker, true], + [withSinglePluginDependenciesWorker , true], + ]); + }); + it('should handle userDefinedComplexPluginDependenciesPlugins', async function () { + expect( + ()=>sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins) + ).to.throw(`Plugin pluginWithMultiplePluginDependencies has missing dependency withSinglePluginDependenciesWorker`); + // const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins); + // expect(sortedPlugins).to.deep.equal([ + // [ChainPlugin, true], + // [TransactionSyncStreamWorker, true], + // [IdentitySyncWorker, true], + // ]) + }); + }); +}); From 1f15ec7712e1d1b3a607571d52dcae4856b794cd Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Wed, 7 Jul 2021 19:38:42 +0200 Subject: [PATCH 03/16] feat(sortPlugin): manage sorting based on before/after props --- src/plugins/Plugins/ChainPlugin.js | 1 + src/plugins/StandardPlugin.js | 3 +- src/plugins/Workers/IdentitySyncWorker.js | 1 + .../TransactionSyncStreamWorker.js | 1 + src/types/Account/_initializeAccount.js | 18 +- src/types/Account/_preparePlugins.js | 31 +-- src/types/Account/_preparePlugins.spec.js | 36 ---- src/types/Account/_sortPlugins.js | 108 +++++++--- src/types/Account/_sortPlugins.spec.js | 204 ++++++++++++++---- 9 files changed, 261 insertions(+), 142 deletions(-) delete mode 100644 src/types/Account/_preparePlugins.spec.js diff --git a/src/plugins/Plugins/ChainPlugin.js b/src/plugins/Plugins/ChainPlugin.js index dad91493a..1f4d250d5 100644 --- a/src/plugins/Plugins/ChainPlugin.js +++ b/src/plugins/Plugins/ChainPlugin.js @@ -13,6 +13,7 @@ class ChainPlugin extends StandardPlugin { name: 'ChainPlugin', executeOnStart: defaultOpts.executeOnStart, firstExecutionRequired: defaultOpts.firstExecutionRequired, + awaitOnInjection: true, dependencies: [ 'storage', 'transport', diff --git a/src/plugins/StandardPlugin.js b/src/plugins/StandardPlugin.js index 186ed8c3a..4b2ec8fdf 100644 --- a/src/plugins/StandardPlugin.js +++ b/src/plugins/StandardPlugin.js @@ -13,7 +13,8 @@ class StandardPlugin extends EventEmitter { this.pluginType = _.has(opts, 'type') ? opts.type : 'Standard'; this.name = _.has(opts, 'name') ? opts.name : 'UnnamedPlugin'; this.dependencies = _.has(opts, 'dependencies') ? opts.dependencies : []; - this.pluginDependencies = _.has(opts, 'pluginDependencies') ? opts.pluginDependencies : []; + this.injectionOrder = _.has(opts, 'injectionOrder') ? opts.injectionOrder : { before: [], after: [] }; + this.awaitOnInjection = _.has(opts, 'awaitOnInjection') ? opts.awaitOnInjection : false; this.executeOnStart = _.has(opts, 'executeOnStart') ? opts.executeOnStart : defaultOpts.executeOnStart; diff --git a/src/plugins/Workers/IdentitySyncWorker.js b/src/plugins/Workers/IdentitySyncWorker.js index 88fa96015..92a175530 100644 --- a/src/plugins/Workers/IdentitySyncWorker.js +++ b/src/plugins/Workers/IdentitySyncWorker.js @@ -13,6 +13,7 @@ class IdentitySyncWorker extends Worker { executeOnStart: true, firstExecutionRequired: true, workerIntervalTime: 60 * 1000, + awaitOnInjection: true, gapLimit: 10, dependencies: [ 'storage', diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index 9a38c96af..96f4b4953 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -11,6 +11,7 @@ class TransactionSyncStreamWorker extends Worker { name: 'TransactionSyncStreamWorker', executeOnStart: true, firstExecutionRequired: true, + awaitOnInjection: true, workerIntervalTime: 0, gapLimit: 10, dependencies: [ diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index 68d7d5af3..ecdd89645 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -5,7 +5,7 @@ const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); const EVENTS = require('../../EVENTS'); const { WALLET_TYPES } = require('../../CONSTANTS'); - +const preparePlugins = require('./_preparePlugins'); const ensureAddressesToGapLimit = require('../../utils/bip44/ensureAddressesToGapLimit'); // eslint-disable-next-line no-underscore-dangle @@ -36,22 +36,10 @@ async function _initializeAccount(account, userUnsafePlugins) { } else { throw new Error(`InitializateAccount failed - Unexpected walletType: ${account.walletType}`); } - - if (!account.offlineMode) { - await account.injectPlugin(ChainPlugin, true); - - // Transaction sync worker - await account.injectPlugin(TransactionSyncStreamWorker, true); - - if (account.walletType === WALLET_TYPES.HDWALLET) { - await account.injectPlugin(IdentitySyncWorker, true); - } - } } - _.each(userUnsafePlugins, (UnsafePlugin) => { - account.injectPlugin(UnsafePlugin, account.allowSensitiveOperations); - }); + // Will sort and inject plugins. + await preparePlugins(account, userUnsafePlugins); self.emit(EVENTS.STARTED, { type: EVENTS.STARTED, payload: null }); diff --git a/src/types/Account/_preparePlugins.js b/src/types/Account/_preparePlugins.js index 50ffd9a90..cb152333c 100644 --- a/src/types/Account/_preparePlugins.js +++ b/src/types/Account/_preparePlugins.js @@ -1,36 +1,19 @@ const { each } = require('lodash'); -const TransactionSyncStreamWorker = require('../../plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker'); -const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); -const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); -const { WALLET_TYPES } = require('../../CONSTANTS'); - -const preparePlugins = (account, userUnsafePlugins) => { - const plugins = []; +const sortPlugins = require('./_sortPlugins'); +const preparePlugins = function (account, userUnsafePlugins) { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { try { - if (account.injectDefaultPlugins) { - if (!account.offlineMode) { - plugins.push([ChainPlugin, true]); - plugins.push([TransactionSyncStreamWorker, true]); - - if (account.walletType === WALLET_TYPES.HDWALLET) { - plugins.push([IdentitySyncWorker, true]); - } - } - } - each(userUnsafePlugins, (UnsafePlugin) => { - if(UnsafePlugin.dependencies){ - console.log({dependencies: UnsafePlugin.dependencies}) - } - account.injectPlugin(UnsafePlugin, account.allowSensitiveOperations); + const sortedPlugins = sortPlugins(account, userUnsafePlugins); + each(sortedPlugins, async (pluginArgs) => { + const [plugin, allowSensitiveOperation, awaitOnInjection] = pluginArgs; + await account.injectPlugin(plugin, allowSensitiveOperation, awaitOnInjection); }); - - resolve(plugins); } catch (e) { reject(e); } }); }; + module.exports = preparePlugins; diff --git a/src/types/Account/_preparePlugins.spec.js b/src/types/Account/_preparePlugins.spec.js deleted file mode 100644 index 77c8e4da2..000000000 --- a/src/types/Account/_preparePlugins.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -const { expect } = require('chai'); -const { WALLET_TYPES } = require('../../CONSTANTS'); - -const preparePlugins = require('./_preparePlugins'); - -const baseAccount = { - walletType: WALLET_TYPES.HDWALLET -} -const accountOnlineWithDefaultPlugins = { - ...baseAccount, - injectDefaultPlugins: true, -}; -const accountOnlineWithoutDefaultPlugins = { - ...baseAccount, - injectDefaultPlugins: false, -}; -const accountOfflineWithDefaultPlugins = { - ...baseAccount, - offlineMode: true, - injectDefaultPlugins: true, -}; -const accountOfflineWithoutDefaultPlugins = { - ...baseAccount, - offlineMode: true, - injectDefaultPlugins: false, -}; -const userPlugins = {}; -describe('Account - prepareDependencies', () => { - it('should be able to load default plugins', async function () { - const preparedPlugins = await preparePlugins(accountOnlineWithDefaultPlugins, userPlugins); - console.log({preparedPlugins}); - }); - // it('should not load unneccessary plugins', function () { - // preparePlugins(accountWithoutDefaultPlugins, userPlugins); - // }); -}); diff --git a/src/types/Account/_sortPlugins.js b/src/types/Account/_sortPlugins.js index 6c9c104fd..9859beeff 100644 --- a/src/types/Account/_sortPlugins.js +++ b/src/types/Account/_sortPlugins.js @@ -9,10 +9,21 @@ const initPlugin = (UnsafePlugin) => { return (isInit) ? UnsafePlugin : new UnsafePlugin(); }; -const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins) => { +/** + * Sort user defined plugins using the injectionOrder properties before or after when specified. + * + * Except if specified using before property, all system plugins (TxSyncStream, IdentitySync...) + * will be sorted on top. + * + * @param defaultSortedPlugins + * @param userUnsafePlugins + * @returns {*[]} + */ +const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitiveOperations) => { const sortedPlugins = []; const initializedSortedPlugins = []; + // We start by ensuring all default plugins get loaded and initialized on top each(defaultSortedPlugins, (defaultPluginParams) => { sortedPlugins.push(defaultPluginParams); @@ -21,41 +32,79 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins) => { initializedSortedPlugins.push(plugin); }); + // Iterate accross all user defined plugins each(userUnsafePlugins, (UnsafePlugin) => { const plugin = initPlugin(UnsafePlugin); - const { pluginDependencies } = plugin; - const hasPluginDependencies = !!(pluginDependencies && pluginDependencies.length); + const { + awaitOnInjection, + injectionOrder: { + before: injectBefore, + after: injectAfter, + }, + } = plugin; + + const hasAfterDependencies = !!(injectAfter && injectAfter.length); + const hasBeforeDependencies = !!(injectBefore && injectBefore.length); + const hasPluginDependencies = (hasAfterDependencies || hasBeforeDependencies); + + let injectionIndex = initializedSortedPlugins.length; - let lastDependencyIndex = -1; if (hasPluginDependencies) { - each(pluginDependencies, (pluginDependencyName) => { - const pluginDependencyIndex = findIndex(initializedSortedPlugins, ['name', pluginDependencyName]); - if (lastDependencyIndex < pluginDependencyIndex) { - lastDependencyIndex = pluginDependencyIndex; - } - if (pluginDependencyIndex === -1) { - // This will throw if user has defined the plugin but we didn't processed it yet. - // We could process that by delaying and retry after that, - // complexity of such would exist in case of cross-dependency - // For now we just implement the naive way to handle dependency, but adding a TODO here. - // Let's note : We will still want to throw after retrying if we still fail - // to sort the concerned plugin (to avoid looping forever and ever) - throw new Error(`Plugin ${plugin.name} has missing dependency ${pluginDependencyName}`); - } - }); + let injectionBeforeIndex = -1; + let injectionAfterIndex = -1; + + if (hasBeforeDependencies) { + each(injectBefore, (pluginDependencyName) => { + const beforePluginIndex = findIndex(initializedSortedPlugins, ['name', pluginDependencyName]); + // TODO: we could have an handling that would postpone trying to insert the dependencies + // ensuring the case where we try to rely and sort based on user specified dependencies + // For now, require user to sort them when specifying the plugins. + if (beforePluginIndex === -1) throw new Error(`Dependency ${pluginDependencyName} not found`); + if (injectionBeforeIndex === -1 || injectionIndex > beforePluginIndex) { + injectionBeforeIndex = beforePluginIndex; + } + }); + } + + if (hasAfterDependencies) { + each(injectAfter, (pluginDependencyName) => { + const afterPluginIndex = findIndex(initializedSortedPlugins, ['name', pluginDependencyName]); + if (afterPluginIndex === -1) throw new Error(`Dependency ${pluginDependencyName} not found`); + if (injectionAfterIndex === -1 || injectionAfterIndex < afterPluginIndex) { + injectionAfterIndex = afterPluginIndex + 1; + } + }); + } + + if ( + injectionBeforeIndex !== -1 + && injectionAfterIndex !== -1 + && injectionAfterIndex > injectionBeforeIndex + ) { + throw new Error(`Conflicting dependency order for ${plugin.name}`); + } + + if ( + injectionBeforeIndex !== -1 + || injectionAfterIndex !== -1 + ) { + injectionIndex = (injectionBeforeIndex !== -1) + ? injectionBeforeIndex + : injectionAfterIndex; + } } // We insert both initialized and uninitialized plugins as we gonna need to read property. initializedSortedPlugins.splice( - (lastDependencyIndex >= 0) ? lastDependencyIndex + 1 : initializedSortedPlugins.length, + injectionIndex, 0, plugin, ); sortedPlugins.splice( - (lastDependencyIndex >= 0) ? lastDependencyIndex + 1 : initializedSortedPlugins.length, + injectionIndex, 0, - [UnsafePlugin, true], + [UnsafePlugin, allowSensitiveOperations, awaitOnInjection], ); }); each(initializedSortedPlugins, (initializedSortedPlugin, i) => { @@ -63,20 +112,27 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins) => { }); return sortedPlugins; }; + +/** + * Sort plugins defined by users based on the before and after properties + * @param account + * @param userUnsafePlugins + * @returns {*[]} + */ const sortPlugins = (account, userUnsafePlugins) => { const plugins = []; // eslint-disable-next-line no-async-promise-executor if (account.injectDefaultPlugins) { if (!account.offlineMode) { - plugins.push([ChainPlugin, true]); - plugins.push([TransactionSyncStreamWorker, true]); + plugins.push([ChainPlugin, true, true]); + plugins.push([TransactionSyncStreamWorker, true, true]); if (account.walletType === WALLET_TYPES.HDWALLET) { - plugins.push([IdentitySyncWorker, true]); + plugins.push([IdentitySyncWorker, true, true]); } } } - return sortUserPlugins(plugins, userUnsafePlugins); + return sortUserPlugins(plugins, userUnsafePlugins, account.allowSensitiveOperations); }; module.exports = sortPlugins; diff --git a/src/types/Account/_sortPlugins.spec.js b/src/types/Account/_sortPlugins.spec.js index 622d795b1..0e7e3f774 100644 --- a/src/types/Account/_sortPlugins.spec.js +++ b/src/types/Account/_sortPlugins.spec.js @@ -7,6 +7,14 @@ const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); const Worker = require('./../../plugins/Worker'); + +class dummyWorker extends Worker { + constructor() { + super({ + name: 'dummyWorker', + }); + } +} class withoutPluginDependenciesWorker extends Worker { constructor() { super({ @@ -14,38 +22,135 @@ class withoutPluginDependenciesWorker extends Worker { }); } } +const userDefinedWithoutPluginDependenciesPlugins = { + "dummyWorker": dummyWorker, + "withoutPluginDependenciesWorker": withoutPluginDependenciesWorker +}; + class withSinglePluginDependenciesWorker extends Worker { constructor() { super({ name: 'withSinglePluginDependenciesWorker', - pluginDependencies: [ - 'IdentitySyncWorker' - ] + injectionOrder: { + after: [ + 'IdentitySyncWorker' + ] + } + }); + } +} +const userDefinedWithSinglePluginDependenciesPlugins1 = { + "dummyWorker": dummyWorker, + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker +}; + +class withSingleInjectBeforePluginDependenciesWorker extends Worker { + constructor() { + super({ + name: 'withSingleInjectBeforePluginDependenciesWorker', + injectionOrder: { + before: [ + 'IdentitySyncWorker' + ] + } + }); + } +} +const userDefinedWithSingleInjectBeforePluginDependenciesPlugins1 = { + "dummyWorker": dummyWorker, + "withSingleInjectBeforePluginDependenciesWorker": withSingleInjectBeforePluginDependenciesWorker +}; +class withSinglePluginAndSingleInjectBeforeDependenciesWorker extends Worker { + constructor() { + super({ + name: 'withSinglePluginAndSingleInjectBeforeDependenciesWorker', + injectionOrder: { + after: [ + 'ChainPlugin' + ], + before: [ + 'TransactionSyncStreamWorker' + ] + } }); } } +const userDefinedWithSinglePluginAndSingleInjectBeforeDependenciesWorker = { + "dummyWorker": dummyWorker, + "withSinglePluginAndSingleInjectBeforeDependenciesWorker": withSinglePluginAndSingleInjectBeforeDependenciesWorker +}; + class withSinglePluginDependenciesWorker2 extends Worker { constructor() { super({ name: 'withSinglePluginDependenciesWorker2', - pluginDependencies: [ - 'TransactionSyncStreamWorker' - ] + injectionOrder: { + before: [ + 'TransactionSyncStreamWorker' + ] + } + }); + } +} + +const userDefinedWithSinglePluginDependenciesPlugins2 = { + "dummyWorker": dummyWorker, + "withSinglePluginDependenciesWorker2": withSinglePluginDependenciesWorker2 +}; + +const userDefinedWithMultiplePluginDependenciesPlugins = { + "dummyWorker": dummyWorker, + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, + "withSinglePluginDependenciesWorker2": withSinglePluginDependenciesWorker2 +}; + + +class userDefinedConflictingDependenciesWorker extends Worker { + constructor() { + super({ + name: 'userDefinedConflictingDependenciesWorker', + injectionOrder: { + before: [ + 'ChainPlugin' + ], + after: [ + 'TransactionSyncStreamWorker', + ] + } }); } } + + +const userDefinedConflictingDependencies = { + "dummyWorker": dummyWorker, + "userDefinedConflictingDependenciesWorker": userDefinedConflictingDependenciesWorker, + +} + class pluginWithMultiplePluginDependencies extends Worker { constructor() { super({ name: 'pluginWithMultiplePluginDependencies', - pluginDependencies: [ - 'TransactionSyncStreamWorker', - 'withSinglePluginDependenciesWorker' - ] + injectionOrder: { + before: [ + 'TransactionSyncStreamWorker', + 'withSinglePluginDependenciesWorker' + ] + } }); } } +const userDefinedComplexPluginDependenciesPlugins = { + "dummyWorker": dummyWorker, + "pluginWithMultiplePluginDependencies": pluginWithMultiplePluginDependencies, + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, + +} + + + const baseAccount = { walletType: WALLET_TYPES.HDWALLET } @@ -67,28 +172,9 @@ const accountOfflineWithoutDefaultPlugins = { offlineMode: true, injectDefaultPlugins: false, }; -const userDefinedWithoutPluginDependenciesPlugins = { - "withoutPluginDependenciesWorker": withoutPluginDependenciesWorker -}; -const userDefinedWithSinglePluginDependenciesPlugins1 = { - "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker -}; -const userDefinedWithSinglePluginDependenciesPlugins2 = { - "withSinglePluginDependenciesWorker2": withSinglePluginDependenciesWorker2 -}; -const userDefinedWithMultiplePluginDependenciesPlugins = { - "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, - "withSinglePluginDependenciesWorker2": withSinglePluginDependenciesWorker2 -}; -// Subtility here is in the fact that the first plugin expect second as a dependency. -// It also expect TransactionSyncStreamWorker as a dependency. -// Second item expect IdentitySyncWorker as a dependency which is loaded after TXSyncStreamWorker -const userDefinedComplexPluginDependenciesPlugins = { - "pluginWithMultiplePluginDependencies": pluginWithMultiplePluginDependencies, - "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, -} + describe('Account - _sortPlugins', () => { describe('system plugins sorting', async function () { @@ -116,46 +202,84 @@ describe('Account - _sortPlugins', () => { [ChainPlugin, true], [TransactionSyncStreamWorker, true], [IdentitySyncWorker, true], + [dummyWorker, true], [withoutPluginDependenciesWorker, true], ]); }); - it('should handle userDefinedWithoutPluginDependenciesPlugins', async function () { + + it('should handle userDefinedWithSinglePluginDependenciesPlugins1', async function () { const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins1); expect(sortedPlugins).to.deep.equal([ [ChainPlugin, true], [TransactionSyncStreamWorker, true], [IdentitySyncWorker, true], - [withSinglePluginDependenciesWorker, true] + [withSinglePluginDependenciesWorker, true], + [dummyWorker, true], + ]) + }); + it('should handle userDefinedWithSinglePluginDependenciesPlugins1', async function () { + const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSingleInjectBeforePluginDependenciesPlugins1); + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true], + [TransactionSyncStreamWorker, true], + [withSingleInjectBeforePluginDependenciesWorker, true], + [IdentitySyncWorker, true], + [dummyWorker, true], ]) }); + it('should handle userDefinedWithSinglePluginDependenciesPlugins2', async function () { const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins2); expect(sortedPlugins).to.deep.equal([ [ChainPlugin, true], - [TransactionSyncStreamWorker, true], [withSinglePluginDependenciesWorker2 , true], + [TransactionSyncStreamWorker, true], + [IdentitySyncWorker, true], + [dummyWorker, true], + ]) + }); + + it('should handle withSinglePluginAndSingleInjectBeforeDependenciesWorker', function () { + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginAndSingleInjectBeforeDependenciesWorker); + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true], + [withSinglePluginAndSingleInjectBeforeDependenciesWorker , true], + [TransactionSyncStreamWorker, true], [IdentitySyncWorker, true], + [dummyWorker, true], ]) }); + it('should handle userDefinedWithMultiplePluginDependenciesPlugins', async function () { const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithMultiplePluginDependenciesPlugins); expect(sortedPlugins).to.deep.equal([ [ChainPlugin, true], - [TransactionSyncStreamWorker, true], [withSinglePluginDependenciesWorker2 , true], + [TransactionSyncStreamWorker, true], [IdentitySyncWorker, true], [withSinglePluginDependenciesWorker , true], + [dummyWorker, true], ]); }); + it('should handle userDefinedConflictingDependencies', function () { + expect(()=> sortPlugins(accountOnlineWithDefaultPlugins, userDefinedConflictingDependencies)) + .to + .throw('Conflicting dependency order for userDefinedConflictingDependenciesWorker'); + }); it('should handle userDefinedComplexPluginDependenciesPlugins', async function () { - expect( - ()=>sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins) - ).to.throw(`Plugin pluginWithMultiplePluginDependencies has missing dependency withSinglePluginDependenciesWorker`); + // TODO: User specified wrongly sorted plugins with deps is not yet handled. + // rejecting with error for now. + expect(()=> sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins)) + .to + .throw('Dependency withSinglePluginDependenciesWorker not found'); // const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins); // expect(sortedPlugins).to.deep.equal([ - // [ChainPlugin, true], - // [TransactionSyncStreamWorker, true], - // [IdentitySyncWorker, true], + // [ChainPlugin, true], + // [TransactionSyncStreamWorker, true], + // [IdentitySyncWorker, true], + // [dummyWorker, true], + // [withSinglePluginDependenciesWorker, true], + // [pluginWithMultiplePluginDependencies, true], // ]) }); }); From 1c3d6e1c13e6efd828a462464bfe0598651b4fb1 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 12 Jul 2021 15:55:45 +0200 Subject: [PATCH 04/16] fix: resolve plugins --- src/types/Account/_preparePlugins.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/types/Account/_preparePlugins.js b/src/types/Account/_preparePlugins.js index cb152333c..558cea7c6 100644 --- a/src/types/Account/_preparePlugins.js +++ b/src/types/Account/_preparePlugins.js @@ -1,4 +1,3 @@ -const { each } = require('lodash'); const sortPlugins = require('./_sortPlugins'); const preparePlugins = function (account, userUnsafePlugins) { @@ -6,10 +5,12 @@ const preparePlugins = function (account, userUnsafePlugins) { return new Promise(async (resolve, reject) => { try { const sortedPlugins = sortPlugins(account, userUnsafePlugins); - each(sortedPlugins, async (pluginArgs) => { - const [plugin, allowSensitiveOperation, awaitOnInjection] = pluginArgs; + // eslint-disable-next-line no-restricted-syntax + for (const [plugin, allowSensitiveOperation, awaitOnInjection] of sortedPlugins) { + // eslint-disable-next-line no-await-in-loop await account.injectPlugin(plugin, allowSensitiveOperation, awaitOnInjection); - }); + } + resolve(sortedPlugins); } catch (e) { reject(e); } From 672ceaab18fe60eb0f94b712ac6892223a701b58 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 12 Jul 2021 15:56:55 +0200 Subject: [PATCH 05/16] feat: add awaitOnInjection property --- src/plugins/Worker.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/Worker.js b/src/plugins/Worker.js index 88a309d6d..6bfe8ed89 100644 --- a/src/plugins/Worker.js +++ b/src/plugins/Worker.js @@ -18,6 +18,10 @@ class Worker extends StandardPlugin { this.workerPass = 0; this.isWorkerRunning = false; + this.awaitOnInjection = _.has(opts, 'awaitOnInjection') + ? opts.awaitOnInjection + : false; + this.firstExecutionRequired = _.has(opts, 'firstExecutionRequired') ? opts.firstExecutionRequired : defaultOpts.firstExecutionRequired; From f7807794ccbd6c15bf917fa58b9dc5819754021d Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 27 Jun 2021 23:41:35 +0200 Subject: [PATCH 06/16] Revert "feat(Keychain): getHardenedDIP15AccountKey" This reverts commit f770f7229b47fb819aff3cadf558a3fb438f52e5. --- src/types/KeyChain/KeyChain.d.ts | 2 +- src/types/KeyChain/KeyChain.js | 1 - src/types/KeyChain/KeyChain.spec.js | 12 +----------- .../methods/getHardenedDIP15AccountKey.js | 15 --------------- 4 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 src/types/KeyChain/methods/getHardenedDIP15AccountKey.js diff --git a/src/types/KeyChain/KeyChain.d.ts b/src/types/KeyChain/KeyChain.d.ts index 2d2b82bd9..17eb7ae6c 100644 --- a/src/types/KeyChain/KeyChain.d.ts +++ b/src/types/KeyChain/KeyChain.d.ts @@ -23,7 +23,7 @@ export declare class KeyChain { getHardenedBIP44Path(type?: HDKeyTypesParam): HDKeyTypes; getHardenedDIP9FeaturePath(type?: HDKeyTypesParam): HDKeyTypes; - getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; + getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; getPrivateKey(): PrivateKey; diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 980f2f45d..486194e80 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -36,7 +36,6 @@ KeyChain.prototype.generateKeyForChild = require('./methods/generateKeyForChild' KeyChain.prototype.generateKeyForPath = require('./methods/generateKeyForPath'); KeyChain.prototype.getHardenedBIP44Path = require('./methods/getHardenedBIP44Path'); KeyChain.prototype.getHardenedDIP9FeaturePath = require('./methods/getHardenedDIP9FeaturePath'); -KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); KeyChain.prototype.getKeyForChild = require('./methods/getKeyForChild'); KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); KeyChain.prototype.getPrivateKey = require('./methods/getPrivateKey'); diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 75d85bd0d..90eb76f0b 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -6,10 +6,6 @@ const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); let keychain; const mnemonic = 'during develop before curtain hazard rare job language become verb message travel'; const pk = '4226d5e2fe8cbfe6f5beb7adf5a5b08b310f6c4a67fc27826779073be6f5699e'; - -const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; -const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; -const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; describe('Keychain', function suite() { this.timeout(10000); it('should create a keychain', () => { @@ -36,12 +32,6 @@ describe('Keychain', function suite() { const pk2 = keychain.getKeyForPath('m/44\'/1\''); expect(pk2.toString()).to.equal(hardenedPk.toString()); }); - it('should get DIP15 account path', function () { - const rootDIP15AccountKey_0 = keychain.getHardenedDIP15AccountKey(0); - expect(rootDIP15AccountKey_0.toString()).to.deep.equal(expectedRootDIP15AccountKey_0); - const rootDIP15AccountKey_1 = keychain.getHardenedDIP15AccountKey(1); - expect(rootDIP15AccountKey_1.toString()).to.deep.equal(expectedRootDIP15AccountKey_1); - }); it('should derive from hardened feature path', () => { const hardenedPk = keychain.getHardenedBIP44Path(); const derivedPk = hardenedPk.deriveChild(0, true).deriveChild(0).deriveChild(0); @@ -51,7 +41,7 @@ describe('Keychain', function suite() { it('should generate key for child', () => { const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); const keyForChild = keychain2.generateKeyForChild(0); - expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); + expect(keyForChild.toString()).to.equal('tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'); }); it('should sign', () => { diff --git a/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js b/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js deleted file mode 100644 index 6540e3d19..000000000 --- a/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Return a safier root path to derivate from - * @param {number} [accountIndex=0] - set the account index - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function getHardenedDIP15AccountKey(accountIndex = 0, type = 'HDPrivateKey') { - const hardenedFeatureRootKey = this.getHardenedDIP9FeaturePath(type); - - // Feature is set to 15' for all DashPay Incoming Funds derivation paths (see DIP15). - const featureKey = hardenedFeatureRootKey.deriveChild(15, true); - - return featureKey.deriveChild(accountIndex, true); -} -module.exports = getHardenedDIP15AccountKey; From 4479f7674064a272d3db7234d50f55d0ea232bd3 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 12 Jul 2021 16:14:08 +0200 Subject: [PATCH 07/16] fix: removed unused required modules --- src/types/Account/_initializeAccount.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index ecdd89645..1a47b646b 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -1,8 +1,5 @@ const _ = require('lodash'); const logger = require('../../logger'); -const TransactionSyncStreamWorker = require('../../plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker'); -const ChainPlugin = require('../../plugins/Plugins/ChainPlugin'); -const IdentitySyncWorker = require('../../plugins/Workers/IdentitySyncWorker'); const EVENTS = require('../../EVENTS'); const { WALLET_TYPES } = require('../../CONSTANTS'); const preparePlugins = require('./_preparePlugins'); From 3eeaf1c3524cdf9775278d84806cbd0da3e50854 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 12 Jul 2021 16:15:12 +0200 Subject: [PATCH 08/16] fix: removed unused required dependency --- src/types/Account/_initializeAccount.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index 1a47b646b..260eaf573 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -1,4 +1,3 @@ -const _ = require('lodash'); const logger = require('../../logger'); const EVENTS = require('../../EVENTS'); const { WALLET_TYPES } = require('../../CONSTANTS'); From 4c27887f116337174f319aaacdebddd0d25f4825 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 11:53:06 +0200 Subject: [PATCH 09/16] fix: merge conflict --- src/types/Account/_initializeAccount.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index a90d33a24..7bbed5f20 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -14,12 +14,6 @@ async function _initializeAccount(account, userUnsafePlugins) { return new Promise(async (resolve, reject) => { try { if (account.injectDefaultPlugins) { - // TODO: Should check in other accounts if a similar is setup already - // TODO: We want to sort them by dependencies and deal with the await this way - // await parent if child has it in dependency - // if not, then is it marked as requiring a first exec - // if yes add to watcher list. - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { ensureAddressesToGapLimit( account.store.wallets[account.walletId], @@ -30,17 +24,6 @@ async function _initializeAccount(account, userUnsafePlugins) { } else { await account.getAddress('0'); // We force what is usually done by the BIP44Worker. } - - if (!account.offlineMode) { - await account.injectPlugin(ChainPlugin, true); - - // Transaction sync worker - await account.injectPlugin(TransactionSyncStreamWorker, true); - - if (account.walletType === WALLET_TYPES.HDWALLET) { - await account.injectPlugin(IdentitySyncWorker, true); - } - } } // Will sort and inject plugins. From 9de103062ecb7f4533cab78019fe8d906f739cd9 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 11:54:23 +0200 Subject: [PATCH 10/16] doc: lint fix --- src/types/Account/_preparePlugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/Account/_preparePlugins.js b/src/types/Account/_preparePlugins.js index 558cea7c6..1c965a860 100644 --- a/src/types/Account/_preparePlugins.js +++ b/src/types/Account/_preparePlugins.js @@ -1,6 +1,6 @@ const sortPlugins = require('./_sortPlugins'); -const preparePlugins = function (account, userUnsafePlugins) { +const preparePlugins = function preparePlugins(account, userUnsafePlugins) { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { try { From 1a8853bfb1806d9b1d71e0594f4588986d6fada8 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 12:17:20 +0200 Subject: [PATCH 11/16] feat: optimisation of sequential insert --- src/types/Account/_preparePlugins.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/types/Account/_preparePlugins.js b/src/types/Account/_preparePlugins.js index 1c965a860..5814071b7 100644 --- a/src/types/Account/_preparePlugins.js +++ b/src/types/Account/_preparePlugins.js @@ -1,15 +1,17 @@ const sortPlugins = require('./_sortPlugins'); const preparePlugins = function preparePlugins(account, userUnsafePlugins) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { + function reducer(accumulatorPromise, [plugin, allowSensitiveOperation, awaitOnInjection]) { + return accumulatorPromise + .then(() => account.injectPlugin(plugin, allowSensitiveOperation, awaitOnInjection)); + } + + return new Promise((resolve, reject) => { try { const sortedPlugins = sortPlugins(account, userUnsafePlugins); - // eslint-disable-next-line no-restricted-syntax - for (const [plugin, allowSensitiveOperation, awaitOnInjection] of sortedPlugins) { - // eslint-disable-next-line no-await-in-loop - await account.injectPlugin(plugin, allowSensitiveOperation, awaitOnInjection); - } + // It is important that all plugin got successfully injected in a sequential maneer + sortedPlugins.reduce(reducer, Promise.resolve()).then(() => resolve(sortedPlugins)); + resolve(sortedPlugins); } catch (e) { reject(e); From 3c6048b39c58143c0bfc7f9cfda70029ed583eb4 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 12:40:43 +0200 Subject: [PATCH 12/16] test: fix undefined allowSensitiveOperations --- src/types/Account/_sortPlugins.spec.js | 111 +++++++++++++------------ 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/src/types/Account/_sortPlugins.spec.js b/src/types/Account/_sortPlugins.spec.js index 0e7e3f774..8db8f4d6f 100644 --- a/src/types/Account/_sortPlugins.spec.js +++ b/src/types/Account/_sortPlugins.spec.js @@ -1,5 +1,5 @@ -const { expect } = require('chai'); -const { WALLET_TYPES } = require('../../CONSTANTS'); +const {expect} = require('chai'); +const {WALLET_TYPES} = require('../../CONSTANTS'); const sortPlugins = require('./_sortPlugins'); const TransactionSyncStreamWorker = require('../../plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker'); @@ -15,6 +15,7 @@ class dummyWorker extends Worker { }); } } + class withoutPluginDependenciesWorker extends Worker { constructor() { super({ @@ -22,6 +23,7 @@ class withoutPluginDependenciesWorker extends Worker { }); } } + const userDefinedWithoutPluginDependenciesPlugins = { "dummyWorker": dummyWorker, "withoutPluginDependenciesWorker": withoutPluginDependenciesWorker @@ -39,6 +41,7 @@ class withSinglePluginDependenciesWorker extends Worker { }); } } + const userDefinedWithSinglePluginDependenciesPlugins1 = { "dummyWorker": dummyWorker, "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker @@ -56,10 +59,12 @@ class withSingleInjectBeforePluginDependenciesWorker extends Worker { }); } } + const userDefinedWithSingleInjectBeforePluginDependenciesPlugins1 = { "dummyWorker": dummyWorker, "withSingleInjectBeforePluginDependenciesWorker": withSingleInjectBeforePluginDependenciesWorker }; + class withSinglePluginAndSingleInjectBeforeDependenciesWorker extends Worker { constructor() { super({ @@ -75,6 +80,7 @@ class withSinglePluginAndSingleInjectBeforeDependenciesWorker extends Worker { }); } } + const userDefinedWithSinglePluginAndSingleInjectBeforeDependenciesWorker = { "dummyWorker": dummyWorker, "withSinglePluginAndSingleInjectBeforeDependenciesWorker": withSinglePluginAndSingleInjectBeforeDependenciesWorker @@ -111,7 +117,7 @@ class userDefinedConflictingDependenciesWorker extends Worker { name: 'userDefinedConflictingDependenciesWorker', injectionOrder: { before: [ - 'ChainPlugin' + 'ChainPlugin' ], after: [ 'TransactionSyncStreamWorker', @@ -150,9 +156,9 @@ const userDefinedComplexPluginDependenciesPlugins = { } - const baseAccount = { - walletType: WALLET_TYPES.HDWALLET + walletType: WALLET_TYPES.HDWALLET, + allowSensitiveOperations: false } const accountOnlineWithDefaultPlugins = { ...baseAccount, @@ -174,102 +180,101 @@ const accountOfflineWithoutDefaultPlugins = { }; - - describe('Account - _sortPlugins', () => { describe('system plugins sorting', async function () { it('should be able to correctly sort default plugins', async function () { - const sortedPluginsOnlineWithDefault = await sortPlugins(accountOnlineWithDefaultPlugins); + const sortedPluginsOnlineWithDefault = sortPlugins(accountOnlineWithDefaultPlugins); + expect(sortedPluginsOnlineWithDefault).to.deep.equal([ - [ChainPlugin, true], - [TransactionSyncStreamWorker, true], - [IdentitySyncWorker, true], + [ChainPlugin, true, true], + [TransactionSyncStreamWorker, true, true], + [IdentitySyncWorker, true, true], ]) - const sortedPluginsOnlineWithoutDefault = await sortPlugins(accountOnlineWithoutDefaultPlugins); + + const sortedPluginsOnlineWithoutDefault = sortPlugins(accountOnlineWithoutDefaultPlugins); expect(sortedPluginsOnlineWithoutDefault).to.deep.equal([]); - const sortedPluginsOfflineWithDefault = await sortPlugins(accountOfflineWithDefaultPlugins); + const sortedPluginsOfflineWithDefault = sortPlugins(accountOfflineWithDefaultPlugins); expect(sortedPluginsOfflineWithDefault).to.deep.equal([]) - const sortedPluginsOfflineWithoutDefault = await sortPlugins(accountOfflineWithoutDefaultPlugins); + const sortedPluginsOfflineWithoutDefault = sortPlugins(accountOfflineWithoutDefaultPlugins); expect(sortedPluginsOfflineWithoutDefault).to.deep.equal([]) }); }); describe('user plugins sorting', async function () { it('should handle userDefinedWithoutPluginDependenciesPlugins', async function () { - const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithoutPluginDependenciesPlugins); + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithoutPluginDependenciesPlugins); expect(sortedPlugins).to.deep.equal([ - [ChainPlugin, true], - [TransactionSyncStreamWorker, true], - [IdentitySyncWorker, true], - [dummyWorker, true], - [withoutPluginDependenciesWorker, true], + [ChainPlugin, true, true], + [TransactionSyncStreamWorker, true,true], + [IdentitySyncWorker, true,true], + [dummyWorker, false, false], + [withoutPluginDependenciesWorker, false,false], ]); }); - it('should handle userDefinedWithSinglePluginDependenciesPlugins1', async function () { - const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins1); + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins1); expect(sortedPlugins).to.deep.equal([ - [ChainPlugin, true], - [TransactionSyncStreamWorker, true], - [IdentitySyncWorker, true], - [withSinglePluginDependenciesWorker, true], - [dummyWorker, true], + [ChainPlugin, true, true], + [TransactionSyncStreamWorker, true, true], + [IdentitySyncWorker, true, true], + [withSinglePluginDependenciesWorker, false, false], + [dummyWorker, false, false], ]) }); it('should handle userDefinedWithSinglePluginDependenciesPlugins1', async function () { - const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSingleInjectBeforePluginDependenciesPlugins1); + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSingleInjectBeforePluginDependenciesPlugins1); expect(sortedPlugins).to.deep.equal([ - [ChainPlugin, true], - [TransactionSyncStreamWorker, true], - [withSingleInjectBeforePluginDependenciesWorker, true], - [IdentitySyncWorker, true], - [dummyWorker, true], + [ChainPlugin, true, true], + [TransactionSyncStreamWorker, true, true], + [withSingleInjectBeforePluginDependenciesWorker, false, false], + [IdentitySyncWorker, true, true], + [dummyWorker, false, false], ]) }); it('should handle userDefinedWithSinglePluginDependenciesPlugins2', async function () { - const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins2); + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginDependenciesPlugins2); expect(sortedPlugins).to.deep.equal([ - [ChainPlugin, true], - [withSinglePluginDependenciesWorker2 , true], - [TransactionSyncStreamWorker, true], - [IdentitySyncWorker, true], - [dummyWorker, true], + [ChainPlugin, true, true], + [withSinglePluginDependenciesWorker2, false, false], + [TransactionSyncStreamWorker, true, true], + [IdentitySyncWorker, true, true], + [dummyWorker, false, false], ]) }); it('should handle withSinglePluginAndSingleInjectBeforeDependenciesWorker', function () { const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithSinglePluginAndSingleInjectBeforeDependenciesWorker); expect(sortedPlugins).to.deep.equal([ - [ChainPlugin, true], - [withSinglePluginAndSingleInjectBeforeDependenciesWorker , true], - [TransactionSyncStreamWorker, true], - [IdentitySyncWorker, true], - [dummyWorker, true], + [ChainPlugin, true, true], + [withSinglePluginAndSingleInjectBeforeDependenciesWorker, false, false], + [TransactionSyncStreamWorker, true, true], + [IdentitySyncWorker, true, true], + [dummyWorker, false, false], ]) }); it('should handle userDefinedWithMultiplePluginDependenciesPlugins', async function () { - const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithMultiplePluginDependenciesPlugins); + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedWithMultiplePluginDependenciesPlugins); expect(sortedPlugins).to.deep.equal([ - [ChainPlugin, true], - [withSinglePluginDependenciesWorker2 , true], - [TransactionSyncStreamWorker, true], - [IdentitySyncWorker, true], - [withSinglePluginDependenciesWorker , true], - [dummyWorker, true], + [ChainPlugin, true, true], + [withSinglePluginDependenciesWorker2, false, false], + [TransactionSyncStreamWorker, true, true], + [IdentitySyncWorker, true, true], + [withSinglePluginDependenciesWorker, false, false], + [dummyWorker, false, false], ]); }); it('should handle userDefinedConflictingDependencies', function () { - expect(()=> sortPlugins(accountOnlineWithDefaultPlugins, userDefinedConflictingDependencies)) + expect(() => sortPlugins(accountOnlineWithDefaultPlugins, userDefinedConflictingDependencies)) .to .throw('Conflicting dependency order for userDefinedConflictingDependenciesWorker'); }); it('should handle userDefinedComplexPluginDependenciesPlugins', async function () { // TODO: User specified wrongly sorted plugins with deps is not yet handled. // rejecting with error for now. - expect(()=> sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins)) + expect(() => sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins)) .to .throw('Dependency withSinglePluginDependenciesWorker not found'); // const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins); From ff785cbe81aec7eede2d3d211834489d8907222d Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 14:47:26 +0200 Subject: [PATCH 13/16] fix: multiple before cases --- src/types/Account/_sortPlugins.js | 9 ++++++--- src/types/Account/_sortPlugins.spec.js | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/types/Account/_sortPlugins.js b/src/types/Account/_sortPlugins.js index 9859beeff..6b531921a 100644 --- a/src/types/Account/_sortPlugins.js +++ b/src/types/Account/_sortPlugins.js @@ -24,7 +24,7 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitive const initializedSortedPlugins = []; // We start by ensuring all default plugins get loaded and initialized on top - each(defaultSortedPlugins, (defaultPluginParams) => { + defaultSortedPlugins.forEach((defaultPluginParams) => { sortedPlugins.push(defaultPluginParams); // We also need to initialize them so we actually as we gonna need to read some properties. @@ -62,7 +62,9 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitive // For now, require user to sort them when specifying the plugins. if (beforePluginIndex === -1) throw new Error(`Dependency ${pluginDependencyName} not found`); if (injectionBeforeIndex === -1 || injectionIndex > beforePluginIndex) { - injectionBeforeIndex = beforePluginIndex; + injectionBeforeIndex = (injectionBeforeIndex === -1 || injectBefore > beforePluginIndex) + ? beforePluginIndex + : injectionBeforeIndex; } }); } @@ -77,6 +79,7 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitive }); } + if ( injectionBeforeIndex !== -1 && injectionAfterIndex !== -1 @@ -107,7 +110,7 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitive [UnsafePlugin, allowSensitiveOperations, awaitOnInjection], ); }); - each(initializedSortedPlugins, (initializedSortedPlugin, i) => { + initializedSortedPlugins.forEach((initializedSortedPlugin, i) => { delete initializedSortedPlugins[i]; }); return sortedPlugins; diff --git a/src/types/Account/_sortPlugins.spec.js b/src/types/Account/_sortPlugins.spec.js index 8db8f4d6f..06f22b082 100644 --- a/src/types/Account/_sortPlugins.spec.js +++ b/src/types/Account/_sortPlugins.spec.js @@ -148,6 +148,12 @@ class pluginWithMultiplePluginDependencies extends Worker { } } +const userDefinedSimpleDependencyPluginDependenciesPlugins = { + "dummyWorker": dummyWorker, + "withSinglePluginDependenciesWorker": withSinglePluginDependenciesWorker, + "pluginWithMultiplePluginDependencies": pluginWithMultiplePluginDependencies, +} +// Order is wrong here, which we also need to test const userDefinedComplexPluginDependenciesPlugins = { "dummyWorker": dummyWorker, "pluginWithMultiplePluginDependencies": pluginWithMultiplePluginDependencies, @@ -271,6 +277,19 @@ describe('Account - _sortPlugins', () => { .to .throw('Conflicting dependency order for userDefinedConflictingDependenciesWorker'); }); + it('should handle userDefinedSimpleDependencyPluginDependenciesPlugins', async function () { + const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedSimpleDependencyPluginDependenciesPlugins); + + expect(sortedPlugins).to.deep.equal([ + [ChainPlugin, true, true], + [pluginWithMultiplePluginDependencies, false, false], + [TransactionSyncStreamWorker, true, true], + [IdentitySyncWorker, true, true], + [withSinglePluginDependenciesWorker, false, false], + [dummyWorker, false, false], + ]) + }); + it('should handle userDefinedComplexPluginDependenciesPlugins', async function () { // TODO: User specified wrongly sorted plugins with deps is not yet handled. // rejecting with error for now. From 69b498b2214eb6753e2e158858cc3599da4c15b0 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 14:48:09 +0200 Subject: [PATCH 14/16] doc: documents plugin injection order usage --- docs/plugins/writing-a-new-plugin.md | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/plugins/writing-a-new-plugin.md b/docs/plugins/writing-a-new-plugin.md index a6dd876dc..74532c79a 100644 --- a/docs/plugins/writing-a-new-plugin.md +++ b/docs/plugins/writing-a-new-plugin.md @@ -76,3 +76,33 @@ Due to the risk from running a plugin that have access to your keychain, these a One would need to initialize a Wallet with the option `allowSensitiveOperations` set to `true`. You can see the list of thoses [sensitive functions and properties](https://github.com/dashevo/wallet-lib/blob/master/src/CONSTANTS.js#L67), anything under `UNSAFE_*` will require this option to be set to true in order to be use from within a plugin. + +## Injection order + +While system plugins will by default be first injected in the system, in the case of a need for specific injection order. +Plugin can be sorted in such a way that in got injected before or after another set of plugins. +For this, use injectionOrder properties before and/or after. + + +In below example, this worker will be dependent on the methods getUTXOS to be internally available, and will be expected to be injected before TransactionSyncStreamWorker and after ChainPlugin. + +```js + class WithInjectBeforeDependenciesWorker extends Worker { + constructor() { + super({ + name: 'withInjectBeforeDependenciesWorker', + dependencies: [ + 'getUTXOS', + ], + injectionOrder: { + after: [ + 'ChainPlugin' + ], + before: [ + 'TransactionSyncStreamWorker' + ] + } + }); + } +} +``` From 556dbf60053ffb152c1505cd720b41f158b60853 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 10 Sep 2021 14:50:40 +0200 Subject: [PATCH 15/16] fix: lint extra spaces --- src/types/Account/_sortPlugins.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/Account/_sortPlugins.js b/src/types/Account/_sortPlugins.js index 6b531921a..1be1a89d0 100644 --- a/src/types/Account/_sortPlugins.js +++ b/src/types/Account/_sortPlugins.js @@ -79,7 +79,6 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitive }); } - if ( injectionBeforeIndex !== -1 && injectionAfterIndex !== -1 From e8fa511107ab1f6d3b6aa64918d8be8f1fa405ff Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 13 Sep 2021 23:23:17 +0200 Subject: [PATCH 16/16] fix: correctly compare with future position on after case --- src/types/Account/_sortPlugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/Account/_sortPlugins.js b/src/types/Account/_sortPlugins.js index 1be1a89d0..ab7115a40 100644 --- a/src/types/Account/_sortPlugins.js +++ b/src/types/Account/_sortPlugins.js @@ -73,7 +73,7 @@ const sortUserPlugins = (defaultSortedPlugins, userUnsafePlugins, allowSensitive each(injectAfter, (pluginDependencyName) => { const afterPluginIndex = findIndex(initializedSortedPlugins, ['name', pluginDependencyName]); if (afterPluginIndex === -1) throw new Error(`Dependency ${pluginDependencyName} not found`); - if (injectionAfterIndex === -1 || injectionAfterIndex < afterPluginIndex) { + if (injectionAfterIndex === -1 || injectionAfterIndex < afterPluginIndex + 1) { injectionAfterIndex = afterPluginIndex + 1; } });