From d8f05d26b91534faff3b285a11e571e8a20e865f Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 2 Nov 2021 02:29:40 +0100 Subject: [PATCH 01/40] feat: introduce KeyChainStore --- src/types/KeyChainStore/KeyChainStore.js | 13 ++++++++++ src/types/KeyChainStore/KeyChainStore.spec.js | 24 +++++++++++++++++++ .../KeyChainStore/methods/addKeyChain.js | 12 ++++++++++ .../KeyChainStore/methods/getKeyChain.js | 5 ++++ .../KeyChainStore/methods/getKeyChains.js | 5 ++++ .../methods/getWalletKeyChain.js | 6 +++++ 6 files changed, 65 insertions(+) create mode 100644 src/types/KeyChainStore/KeyChainStore.js create mode 100644 src/types/KeyChainStore/KeyChainStore.spec.js create mode 100644 src/types/KeyChainStore/methods/addKeyChain.js create mode 100644 src/types/KeyChainStore/methods/getKeyChain.js create mode 100644 src/types/KeyChainStore/methods/getKeyChains.js create mode 100644 src/types/KeyChainStore/methods/getWalletKeyChain.js diff --git a/src/types/KeyChainStore/KeyChainStore.js b/src/types/KeyChainStore/KeyChainStore.js new file mode 100644 index 00000000..fc0d14b7 --- /dev/null +++ b/src/types/KeyChainStore/KeyChainStore.js @@ -0,0 +1,13 @@ +class KeyChainStore { + constructor() { + this.chains = new Map(); + this.walletKeyChainId = null; + } +} + +KeyChainStore.prototype.addKeyChain = require('./methods/addKeyChain'); +KeyChainStore.prototype.getKeyChain = require('./methods/getKeyChain'); +KeyChainStore.prototype.getKeyChains = require('./methods/getKeyChains'); +KeyChainStore.prototype.getWalletKeyChain = require('./methods/getWalletKeyChain'); + +module.exports = KeyChainStore; diff --git a/src/types/KeyChainStore/KeyChainStore.spec.js b/src/types/KeyChainStore/KeyChainStore.spec.js new file mode 100644 index 00000000..6975bb26 --- /dev/null +++ b/src/types/KeyChainStore/KeyChainStore.spec.js @@ -0,0 +1,24 @@ +const {HDPrivateKey} = require("@dashevo/dashcore-lib"); +const KeyChainsStore = require('./KeyChainStore'); +const KeyChain = require("../KeyChain/KeyChain"); +const { expect } = require('chai'); + +describe('KeyChainStore', function suite() { + let keyChainsStore; + let hdPrivateKey = new HDPrivateKey() + let keyChain = new KeyChain(hdPrivateKey) + it('should create a KeyChainStore', () => { + keyChainsStore = new KeyChainsStore(); + expect(keyChainsStore).to.exist; + expect(keyChainsStore.chains).to.be.a('Map') + }); + it('should be able to add a keyChain', function () { + keyChainsStore.addKeyChain(keyChain) + expect(keyChainsStore.chains.has(keyChain.keyChainId)).to.equal(true); + }); + it('should get all keyChains', function () { + const keyChains = keyChainsStore.getKeyChains() + expect(keyChains).to.deep.equal([keyChain]); + }); +}); + diff --git a/src/types/KeyChainStore/methods/addKeyChain.js b/src/types/KeyChainStore/methods/addKeyChain.js new file mode 100644 index 00000000..45a8fa19 --- /dev/null +++ b/src/types/KeyChainStore/methods/addKeyChain.js @@ -0,0 +1,12 @@ +function addKeyChain(keychain, opts = {}) { + if (this.chains.has(keychain.keyChainId)) { + throw new Error('Trying to add already existing keyChain'); + } + this.chains.set(keychain.keyChainId, keychain); + + if (opts && opts.isWalletKeyChain && !this.walletKeyChainId) { + this.walletKeyChainId = keychain.keyChainId; + } +} + +module.exports = addKeyChain; diff --git a/src/types/KeyChainStore/methods/getKeyChain.js b/src/types/KeyChainStore/methods/getKeyChain.js new file mode 100644 index 00000000..f6794da2 --- /dev/null +++ b/src/types/KeyChainStore/methods/getKeyChain.js @@ -0,0 +1,5 @@ +function getKeyChain(keyChainId) { + return this.chains.get(keyChainId); +} + +module.exports = getKeyChain; diff --git a/src/types/KeyChainStore/methods/getKeyChains.js b/src/types/KeyChainStore/methods/getKeyChains.js new file mode 100644 index 00000000..c83673a6 --- /dev/null +++ b/src/types/KeyChainStore/methods/getKeyChains.js @@ -0,0 +1,5 @@ +function getKeyChains() { + return Array.from(this.chains.values()); +} + +module.exports = getKeyChains; diff --git a/src/types/KeyChainStore/methods/getWalletKeyChain.js b/src/types/KeyChainStore/methods/getWalletKeyChain.js new file mode 100644 index 00000000..3c8c2140 --- /dev/null +++ b/src/types/KeyChainStore/methods/getWalletKeyChain.js @@ -0,0 +1,6 @@ +function getWalletKeyChain() { + const keyChainId = this.walletKeyChainId; + return this.chains.get(keyChainId); +} + +module.exports = getWalletKeyChain; From e694a03460ecdaf0ce3cf33e85604681426110e4 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 2 Nov 2021 02:31:45 +0100 Subject: [PATCH 02/40] feat(KeyChain)!: add watched keys BREAKING-CHANGES : removed generateKetForPath, normalize base key to single rootKey, rename type to rootKeyType, remove unnecessary getKeyForChild duplicate of getKeyForType --- src/types/KeyChain/KeyChain.js | 85 +++++++++++-------- src/types/KeyChain/KeyChain.spec.js | 30 ++----- .../KeyChain/methods/addKeysToWatchedKeys.js | 27 ++++++ .../KeyChain/methods/generateKeyForChild.js | 19 ----- .../KeyChain/methods/generateKeyForPath.js | 19 ----- .../KeyChain/methods/getDIP15ExtendedKey.js | 2 +- .../KeyChain/methods/getHardenedBIP44HDKey.js | 2 +- .../methods/getHardenedDIP9FeatureHDKey.js | 2 +- src/types/KeyChain/methods/getKeyForChild.js | 10 --- src/types/KeyChain/methods/getKeyForPath.js | 31 ++----- src/types/KeyChain/methods/getPrivateKey.js | 18 ---- .../KeyChain/methods/getWatchedAddresses.js | 6 ++ src/types/KeyChain/methods/getWatchedKeys.js | 5 ++ .../KeyChain/methods/getWatchedPublicKeys.js | 6 ++ 14 files changed, 108 insertions(+), 154 deletions(-) create mode 100644 src/types/KeyChain/methods/addKeysToWatchedKeys.js delete mode 100644 src/types/KeyChain/methods/generateKeyForChild.js delete mode 100644 src/types/KeyChain/methods/generateKeyForPath.js delete mode 100644 src/types/KeyChain/methods/getKeyForChild.js delete mode 100644 src/types/KeyChain/methods/getPrivateKey.js create mode 100644 src/types/KeyChain/methods/getWatchedAddresses.js create mode 100644 src/types/KeyChain/methods/getWatchedKeys.js create mode 100644 src/types/KeyChain/methods/getWatchedPublicKeys.js diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 643dcc62..f646dc00 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -1,52 +1,63 @@ const { Networks, HDPrivateKey, HDPublicKey } = require('@dashevo/dashcore-lib'); -const { has } = require('lodash'); +const { doubleSha256 } = require('../../utils/crypto'); -// eslint-disable-next-line no-underscore-dangle -const _defaultOpts = { - network: Networks.testnet.toString(), - keys: {}, -}; +function generateKeyChainId(key) { + const keyChainIdSuffix = doubleSha256(key.toString()).toString('hex').slice(0, 10); + return `kc${keyChainIdSuffix}`; +} -class KeyChain { - constructor(opts = JSON.parse(JSON.stringify(_defaultOpts))) { - const defaultOpts = JSON.parse(JSON.stringify(_defaultOpts)); - this.network = defaultOpts.network; - this.keys = { ...defaultOpts.keys }; +function fromOptions(opts) { + let rootKey; + let rootKeyType; - if (has(opts, 'HDPrivateKey')) { - this.type = 'HDPrivateKey'; - this.HDPrivateKey = (typeof opts.HDPrivateKey === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; - this.network = this.HDPrivateKey.network; - } else if (has(opts, 'HDPublicKey')) { - this.type = 'HDPublicKey'; - this.HDPublicKey = (typeof opts.HDPublicKey === 'string') ? HDPublicKey(opts.HDPublicKey) : opts.HDPublicKey; - this.network = this.HDPublicKey.network; - } else if (has(opts, 'privateKey')) { - this.type = 'privateKey'; - this.privateKey = opts.privateKey; - } else if (has(opts, 'publicKey')) { - this.type = 'publicKey'; - this.publicKey = opts.publicKey; - } else if (has(opts, 'address')) { - this.type = 'address'; - this.address = opts.address.toString(); - } else { - throw new Error('Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'); + if (opts) { + if (opts.mnemonic) { + rootKeyType = 'HDPrivateKey'; + rootKey = (typeof opts.mnemonic === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; + } + if (opts.HDPrivateKey) { + rootKeyType = 'HDPrivateKey'; + rootKey = (typeof opts.HDPrivateKey === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; + } else if (opts.HDPublicKey) { + rootKeyType = 'HDPublicKey'; + rootKey = (typeof opts.HDPublicKey === 'string') ? HDPublicKey(opts.HDPublicKey) : opts.HDPublicKey; + } else if (opts.privateKey) { + rootKeyType = 'privateKey'; + rootKey = opts.privateKey; + } else if (opts.publicKey) { + rootKeyType = 'publicKey'; + rootKey = opts.publicKey; + } else if (opts.address) { + rootKeyType = 'address'; + rootKey = opts.address.toString(); } - if (opts.network) this.network = opts.network; - if (opts.keys) this.keys = { ...opts.keys }; } + return { rootKeyType, rootKey }; } -KeyChain.prototype.generateKeyForChild = require('./methods/generateKeyForChild'); -KeyChain.prototype.generateKeyForPath = require('./methods/generateKeyForPath'); +class KeyChain { + constructor(opts = {}) { + const { rootKey, rootKeyType } = fromOptions(opts); + if (!rootKeyType || !rootKey) throw new Error('Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'); + this.keyChainId = generateKeyChainId(rootKey); + + this.rootKey = rootKey; + this.network = rootKey.network || opts.network || Networks.testnet.toString(); + this.rootKeyType = rootKeyType; + // this.chainCode = chainCode; + + this.watchedKeys = new Map(); + } +} +KeyChain.prototype.addKeysToWatchedKeys = require('./methods/addKeysToWatchedKeys'); +KeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); +KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); +KeyChain.prototype.getWatchedKeys = require('./methods/getWatchedKeys'); +KeyChain.prototype.getWatchedPublicKeys = require('./methods/getWatchedPublicKeys'); KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); KeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); -KeyChain.prototype.getKeyForChild = require('./methods/getKeyForChild'); -KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); -KeyChain.prototype.getPrivateKey = require('./methods/getPrivateKey'); KeyChain.prototype.sign = require('./methods/sign'); module.exports = KeyChain; diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 3cf9bb0b..d60a8ae0 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -20,15 +20,12 @@ describe('Keychain', function suite() { expect(() => new KeyChain(mnemonic)).to.throw(expectedException1); keychain = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); - expect(keychain.type).to.equal('HDPrivateKey'); + expect(keychain.rootKeyType).to.equal('HDPrivateKey'); expect(keychain.network.toString()).to.equal('testnet'); - expect(keychain.keys).to.deep.equal({}); keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic2, 'mainnet') }); }); - it('should get private key', () => { - expect(keychain.getPrivateKey().toString()).to.equal(pk); - }); + it('should generate key for full path', () => { const path = 'm/44\'/1\'/0\'/0/0'; const pk2 = keychain.getKeyForPath(path); @@ -82,9 +79,9 @@ describe('Keychain', function suite() { expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); }); - it('should generate key for child', () => { + it('should get key for child', () => { const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); - const keyForChild = keychain2.generateKeyForChild(0); + const keyForChild = keychain2.getKeyForPath(0); expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); }); @@ -93,14 +90,6 @@ describe('Keychain', function suite() { }); }); -describe('Keychain - clone', function suite() { - this.timeout(10000); - it('should clone', () => { - const keychain2 = new KeyChain(keychain); - expect(keychain2).to.deep.equal(keychain); - expect(keychain2.keys).to.deep.equal(keychain.keys); - }); -}); describe('Keychain - single privateKey', function suite() { this.timeout(10000); it('should correctly errors out when not a HDPublicKey (privateKey)', () => { @@ -108,18 +97,15 @@ describe('Keychain - single privateKey', function suite() { const network = 'livenet'; const pkKeyChain = new KeyChain({ privateKey, network }); expect(pkKeyChain.network).to.equal(network); - expect(pkKeyChain.keys).to.deep.equal({}); - expect(pkKeyChain.type).to.equal('privateKey'); - expect(pkKeyChain.privateKey).to.equal(privateKey); + expect(pkKeyChain.rootKeyType).to.equal('privateKey'); + expect(pkKeyChain.rootKey).to.equal(privateKey); const expectedException1 = 'Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'; - const expectedException2 = 'Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate child'; - expect(() => pkKeyChain.generateKeyForPath()).to.throw(expectedException1); - expect(() => pkKeyChain.generateKeyForChild()).to.throw(expectedException2); + expect(() => pkKeyChain.getKeyForPath()).to.throw(expectedException1); }); it('should get private key', () => { const privateKey = Dashcore.PrivateKey().toString(); const pkKeyChain = new KeyChain({ privateKey, network: 'livenet' }); - expect(pkKeyChain.getPrivateKey().toString()).to.equal(privateKey); + expect(pkKeyChain.rootKey.toString()).to.equal(privateKey); }); }); diff --git a/src/types/KeyChain/methods/addKeysToWatchedKeys.js b/src/types/KeyChain/methods/addKeysToWatchedKeys.js new file mode 100644 index 00000000..79a6eb6f --- /dev/null +++ b/src/types/KeyChain/methods/addKeysToWatchedKeys.js @@ -0,0 +1,27 @@ +const { + util: { buffer: BufferUtil }, +} = require('@dashevo/dashcore-lib'); + +function addKeysToWatchedKeys(keys) { + if (!Array.isArray(keys)) { + return this.addKeysToWatchedKeys([keys]); + } + let added = 0; + + keys.forEach((key) => { + const keyDepth = Number(key.depth); + // eslint-disable-next-line no-underscore-dangle + const keyIndex = BufferUtil.integerFromBuffer(key._buffers.childIndex); + // eslint-disable-next-line no-underscore-dangle + const keyParentFingerPrint = BufferUtil.integerFromBuffer(key._buffers.parentFingerPrint); + const keyId = `${keyDepth}-${keyParentFingerPrint}/${keyIndex}`; + if (this.watchedKeys.has(keyId)) { + throw new Error('Key is already watched'); + } + this.watchedKeys.set(keyId, key); + added += 1; + }); + + return added; +} +module.exports = addKeysToWatchedKeys; diff --git a/src/types/KeyChain/methods/generateKeyForChild.js b/src/types/KeyChain/methods/generateKeyForChild.js deleted file mode 100644 index 5d6dcaeb..00000000 --- a/src/types/KeyChain/methods/generateKeyForChild.js +++ /dev/null @@ -1,19 +0,0 @@ -const { - HDPublicKey, -} = require('@dashevo/dashcore-lib'); -/** - * Derive from HDPrivateKey to a child - * @param {number} index - Child index to derivee to - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function generateKeyForChild(index, type = 'HDPrivateKey') { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.type)) { - throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate child'); - } - const HDKey = this[this.type]; - const hdPublicKey = HDKey.deriveChild(index); - if (type === 'HDPublicKey') return HDPublicKey(hdPublicKey); - return hdPublicKey; -} -module.exports = generateKeyForChild; diff --git a/src/types/KeyChain/methods/generateKeyForPath.js b/src/types/KeyChain/methods/generateKeyForPath.js deleted file mode 100644 index e266c96d..00000000 --- a/src/types/KeyChain/methods/generateKeyForPath.js +++ /dev/null @@ -1,19 +0,0 @@ -const { - HDPublicKey, -} = require('@dashevo/dashcore-lib'); -/** - * Derive from HDPrivateKey to a specific path - * @param {string} path - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function generateKeyForPath(path, type = 'HDPrivateKey') { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.type)) { - throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); - } - const HDKey = this[this.type]; - const hdPrivateKey = HDKey.derive(path); - if (type === 'HDPublicKey') return HDPublicKey(hdPrivateKey); - return hdPrivateKey; -} -module.exports = generateKeyForPath; diff --git a/src/types/KeyChain/methods/getDIP15ExtendedKey.js b/src/types/KeyChain/methods/getDIP15ExtendedKey.js index a7eed154..b65dc10d 100644 --- a/src/types/KeyChain/methods/getDIP15ExtendedKey.js +++ b/src/types/KeyChain/methods/getDIP15ExtendedKey.js @@ -8,7 +8,7 @@ * @return {HDPrivateKey|HDPublicKey} */ function getDIP15ExtendedKey(userUniqueId, contactUniqueId, index = 0, accountIndex = 0, type = 'HDPrivateKey') { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.type)) { + if (!['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType)) { throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); } if (!userUniqueId || !contactUniqueId) throw new Error('Required userUniqueId and contactUniqueId to be defined'); diff --git a/src/types/KeyChain/methods/getHardenedBIP44HDKey.js b/src/types/KeyChain/methods/getHardenedBIP44HDKey.js index 75a66284..4c45a08f 100644 --- a/src/types/KeyChain/methods/getHardenedBIP44HDKey.js +++ b/src/types/KeyChain/methods/getHardenedBIP44HDKey.js @@ -7,6 +7,6 @@ const { BIP44_TESTNET_ROOT_PATH, BIP44_LIVENET_ROOT_PATH } = require('../../../C */ function getHardenedBIP44HDKey(type = 'HDPrivateKey') { const pathRoot = (this.network.toString() === 'testnet') ? BIP44_TESTNET_ROOT_PATH : BIP44_LIVENET_ROOT_PATH; - return this.generateKeyForPath(pathRoot, type); + return this.getKeyForPath(pathRoot, type); } module.exports = getHardenedBIP44HDKey; diff --git a/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js b/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js index 06a4733a..7a77803a 100644 --- a/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js +++ b/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js @@ -7,6 +7,6 @@ const { DIP9_LIVENET_ROOT_PATH, DIP9_TESTNET_ROOT_PATH } = require('../../../CON */ function getHardenedDIP9FeatureHDKey(type = 'HDPrivateKey') { const pathRoot = (this.network.toString() === 'testnet') ? DIP9_TESTNET_ROOT_PATH : DIP9_LIVENET_ROOT_PATH; - return this.generateKeyForPath(pathRoot, type); + return this.getKeyForPath(pathRoot, type); } module.exports = getHardenedDIP9FeatureHDKey; diff --git a/src/types/KeyChain/methods/getKeyForChild.js b/src/types/KeyChain/methods/getKeyForChild.js deleted file mode 100644 index 768f1740..00000000 --- a/src/types/KeyChain/methods/getKeyForChild.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generate a key by deriving it's direct child - * @param index - {Number} - * @return {HDPrivateKey | HDPublicKey} - */ -function getKeyForChild(index = 0, type = 'HDPrivateKey') { - return this.generateKeyForChild(index, type); -} - -module.exports = getKeyForChild; diff --git a/src/types/KeyChain/methods/getKeyForPath.js b/src/types/KeyChain/methods/getKeyForPath.js index 4f7bb180..ae79ca28 100644 --- a/src/types/KeyChain/methods/getKeyForPath.js +++ b/src/types/KeyChain/methods/getKeyForPath.js @@ -1,29 +1,8 @@ -const { HDPrivateKey, PrivateKey } = require('@dashevo/dashcore-lib'); -/** - * Get a key from the cache or generate if none - * @param path - * @param type - def : HDPrivateKey - Expected return datatype of the keys - * @return {HDPrivateKey | HDPublicKey} - */ -function getKeyForPath(path, type = 'HDPrivateKey') { - if (type === 'HDPublicKey') { - // In this case, we do not generate or keep in cache. - return this.generateKeyForPath(path, type); +function getKeyForPath(path) { + if (!['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType)) { + throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); } - - if (this.type === 'HDPrivateKey') { - if (!this.keys[path]) { - this.keys[path] = this.generateKeyForPath(path, type).toString(); - } - return new HDPrivateKey(this.keys[path]); - } - if (this.type === 'privateKey') { - if (!this.keys[path]) { - this.keys[path] = this.getPrivateKey(path).toString(); - return new PrivateKey(this.keys[path]); - } - return new PrivateKey(this.keys[path]); - } - return new HDPrivateKey(this.keys[path]); + return this.rootKey.derive(path); } + module.exports = getKeyForPath; diff --git a/src/types/KeyChain/methods/getPrivateKey.js b/src/types/KeyChain/methods/getPrivateKey.js deleted file mode 100644 index 602c287d..00000000 --- a/src/types/KeyChain/methods/getPrivateKey.js +++ /dev/null @@ -1,18 +0,0 @@ -const { - PrivateKey, -} = require('@dashevo/dashcore-lib'); - -/** - * @return {PrivateKey} - */ -function getPrivateKey() { - let pk; - if (this.type === 'HDPrivateKey') { - pk = PrivateKey(this.HDPrivateKey.privateKey); - } - if (this.type === 'privateKey') { - pk = PrivateKey(this.privateKey); - } - return pk; -} -module.exports = getPrivateKey; diff --git a/src/types/KeyChain/methods/getWatchedAddresses.js b/src/types/KeyChain/methods/getWatchedAddresses.js new file mode 100644 index 00000000..f964c37b --- /dev/null +++ b/src/types/KeyChain/methods/getWatchedAddresses.js @@ -0,0 +1,6 @@ +function getWatchedAddresses() { + const keys = this.getWatchedKeys(); + return keys.map((key) => key.publicKey.toAddress().toString()); +} + +module.exports = getWatchedAddresses; diff --git a/src/types/KeyChain/methods/getWatchedKeys.js b/src/types/KeyChain/methods/getWatchedKeys.js new file mode 100644 index 00000000..ad55b420 --- /dev/null +++ b/src/types/KeyChain/methods/getWatchedKeys.js @@ -0,0 +1,5 @@ +function getWatchedKeys() { + return Array.from(this.watchedKeys.values()); +} + +module.exports = getWatchedKeys; diff --git a/src/types/KeyChain/methods/getWatchedPublicKeys.js b/src/types/KeyChain/methods/getWatchedPublicKeys.js new file mode 100644 index 00000000..1f6e52cd --- /dev/null +++ b/src/types/KeyChain/methods/getWatchedPublicKeys.js @@ -0,0 +1,6 @@ +function getWatchedPublicKeys() { + const keys = this.getWatchedKeys(); + return keys.map((key) => key.publicKey.toString()); +} + +module.exports = getWatchedPublicKeys; From 730cb30863504c4e7f79db50f8ee58d820b6ea4c Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 2 Nov 2021 02:33:14 +0100 Subject: [PATCH 03/40] feat!: update keychain usage BREAKING-CHANGES: keyChain in wallet and account is now accessed via keyChainStore.getWalletKeyChain --- src/types/Account/Account.js | 2 +- .../Account/methods/createTransaction.js | 2 +- .../Account/methods/createTransaction.spec.js | 8 +++- src/types/Account/methods/encrypt.spec.js | 2 +- src/types/Account/methods/generateAddress.js | 16 +++++--- src/types/Account/methods/getPrivateKeys.js | 2 +- src/types/Account/methods/sign.js | 2 +- src/types/Identities/Identities.js | 2 +- src/types/Wallet/Wallet.spec.js | 15 ++++---- src/types/Wallet/methods/exportWallet.js | 4 +- src/types/Wallet/methods/exportWallet.spec.js | 6 +-- src/types/Wallet/methods/fromAddress.js | 6 ++- src/types/Wallet/methods/fromAddress.spec.js | 14 ++++--- src/types/Wallet/methods/fromHDPrivateKey.js | 6 ++- .../Wallet/methods/fromHDPrivateKey.spec.js | 7 ++-- src/types/Wallet/methods/fromHDPublicKey.js | 6 ++- .../Wallet/methods/fromHDPublicKey.spec.js | 11 ++++-- src/types/Wallet/methods/fromMnemonic.js | 6 ++- src/types/Wallet/methods/fromMnemonic.spec.js | 38 ++++++++++--------- src/types/Wallet/methods/fromPrivateKey.js | 6 ++- .../Wallet/methods/fromPrivateKey.spec.js | 12 +++--- src/types/Wallet/methods/fromPublicKey.js | 6 ++- .../Wallet/methods/fromPublicKey.spec.js | 12 +++--- src/types/Wallet/methods/fromSeed.spec.js | 12 +++--- 24 files changed, 121 insertions(+), 82 deletions(-) diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 914f372a..574d28e5 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -136,7 +136,7 @@ class Account extends EventEmitter { throw new Error(`Invalid wallet type ${this.walletType}`); } - this.keyChain = wallet.keyChain; + this.keyChainStore = wallet.keyChainStore; this.cacheTx = (opts.cacheTx) ? opts.cacheTx : defaultOptions.cacheTx; this.cacheBlockHeaders = (opts.cacheBlockHeaders) diff --git a/src/types/Account/methods/createTransaction.js b/src/types/Account/methods/createTransaction.js index 5aca76c0..078de7fd 100644 --- a/src/types/Account/methods/createTransaction.js +++ b/src/types/Account/methods/createTransaction.js @@ -117,7 +117,7 @@ function createTransaction(opts = {}) { } }); try { - const signedTx = this.keyChain.sign( + const signedTx = this.keyChainStore.getWalletKeyChain().sign( tx, transformedPrivateKeys, crypto.Signature.SIGHASH_ALL, diff --git a/src/types/Account/methods/createTransaction.spec.js b/src/types/Account/methods/createTransaction.spec.js index 7dedef8c..88198a7e 100644 --- a/src/types/Account/methods/createTransaction.spec.js +++ b/src/types/Account/methods/createTransaction.spec.js @@ -62,8 +62,12 @@ describe('Account - createTransaction', function suite() { return [new HDPrivateKey('tprv8jG3ctd1DEVADnLP3hwS1Gfzjxf5E4WL2UutfJkhAQs7rVu2b3Ryv4WQ46mddZyMbGaSUYnY9wFeuFRAejapjoB1LGzTfM55mxMhZ1X4eGX')] } }, - keyChain: { - sign: (tx, privateKeys) => tx.sign(privateKeys), + keyChainStore: { + getWalletKeyChain:() => { + return { + sign: (tx, privateKeys) => tx.sign(privateKeys), + } + } }, storage: { searchTransaction: (txId) => { diff --git a/src/types/Account/methods/encrypt.spec.js b/src/types/Account/methods/encrypt.spec.js index 89653f84..11d5ed8d 100644 --- a/src/types/Account/methods/encrypt.spec.js +++ b/src/types/Account/methods/encrypt.spec.js @@ -29,7 +29,7 @@ describe('Account - encrypt', function suite() { const secret = 'secret'; it('should encrypt extPubKey with aes', () => { - const extPubKey = account.keyChain.getKeyForPath(derivationPath, 'HDPublicKey').toString(); + const extPubKey = account.keyChainStore.getWalletKeyChain().getKeyForPath(derivationPath, 'HDPublicKey').toString(); const encryptedExtPubKey = account.encrypt('aes', extPubKey, secret).toString(); const bytes = CryptoJS.AES.decrypt(encryptedExtPubKey, secret); const decrypted = bytes.toString(CryptoJS.enc.Utf8); diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index 450f582f..7281bc7b 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -1,4 +1,3 @@ -const { PublicKey } = require('@dashevo/dashcore-lib'); const EVENTS = require('../../../EVENTS'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const { is } = require('../../../utils'); @@ -16,25 +15,30 @@ function generateAddress(path) { switch (this.walletType) { case WALLET_TYPES.ADDRESS: - address = this.keyChain.address; + address = this.keyChainStore.getWalletKeyChain().rootKey; break; case WALLET_TYPES.PUBLICKEY: - address = new PublicKey(this.keyChain.publicKey.toString()).toAddress(network).toString(); + address = this.keyChainStore.getWalletKeyChain().rootKey.toAddress(network).toString(); break; case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring index = parseInt(path.toString().split('/')[5], 10); - privateKey = this.keyChain.getKeyForPath(path); + privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(path); address = privateKey.publicKey.toAddress(network).toString(); break; case WALLET_TYPES.HDPUBLIC: index = parseInt(path.toString().split('/')[5], 10); - privateKey = this.keyChain.getKeyForChild(index); + privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(index); address = privateKey.publicKey.toAddress(network).toString(); break; + // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY + case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: + privateKey = this.keyChainStore.getWalletKeyChain().rootKey; + address = privateKey.publicKey.toAddress(network).toString(); + break; default: - privateKey = this.keyChain.getKeyForPath(path.toString()); + privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(path.toString()); address = privateKey.publicKey.toAddress(network).toString(); } diff --git a/src/types/Account/methods/getPrivateKeys.js b/src/types/Account/methods/getPrivateKeys.js index 9c8392af..c455bd78 100644 --- a/src/types/Account/methods/getPrivateKeys.js +++ b/src/types/Account/methods/getPrivateKeys.js @@ -18,7 +18,7 @@ function getPrivateKeys(addressList) { paths.forEach((path) => { const address = self.store.wallets[walletId].addresses[subwallet][path]; if (addresses.includes(address.address)) { - const privateKey = self.keyChain.getKeyForPath(path); + const privateKey = self.keyChainStore.getWalletKeyChain().getKeyForPath(path); privKeys = privKeys.concat([privateKey]); } }); diff --git a/src/types/Account/methods/sign.js b/src/types/Account/methods/sign.js index 33509703..b1c32a76 100644 --- a/src/types/Account/methods/sign.js +++ b/src/types/Account/methods/sign.js @@ -30,5 +30,5 @@ module.exports = function sign(object, privateKeys = [], sigType) { }); } - return this.keyChain.sign(object, privateKeys, sigType); + return this.keyChainStore.getWalletKeyChain().sign(object, privateKeys, sigType); }; diff --git a/src/types/Identities/Identities.js b/src/types/Identities/Identities.js index f721356d..0f2215e3 100644 --- a/src/types/Identities/Identities.js +++ b/src/types/Identities/Identities.js @@ -10,7 +10,7 @@ class Identities { this.storage = wallet.storage; - this.keyChain = wallet.keyChain; + this.keyChain = wallet.keyChainStore.getWalletKeyChain(); } } diff --git a/src/types/Wallet/Wallet.spec.js b/src/types/Wallet/Wallet.spec.js index 180f5fc1..e187a140 100644 --- a/src/types/Wallet/Wallet.spec.js +++ b/src/types/Wallet/Wallet.spec.js @@ -23,7 +23,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -50,7 +50,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -77,7 +77,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -95,7 +95,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPublicKey'); + expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPublicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -112,7 +112,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('privateKey'); + expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('privateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -132,7 +132,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('publicKey'); + expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('publicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -149,7 +149,7 @@ describe('Wallet - class', function suite() { expect(wallet2.plugins).to.be.deep.equal({}); expect(wallet2.accounts).to.be.deep.equal([]); expect(wallet2.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet2.keyChain.type).to.be.deep.equal('publicKey'); + expect(wallet2.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('publicKey'); expect(wallet2.passphrase).to.be.deep.equal(null); expect(wallet2.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet2.injectDefaultPlugins).to.be.deep.equal(true); @@ -246,6 +246,5 @@ describe('Wallet - Get/Create Account', function suite() { it('should not leak', () => { const mockOpts1 = { }; fromHDPublicKey.call(mockOpts1, gatherSail.testnet.external.hdpubkey); - expect(mockOpts1.keyChain.keys).to.deep.equal({}); }); }); diff --git a/src/types/Wallet/methods/exportWallet.js b/src/types/Wallet/methods/exportWallet.js index f64c4afe..39cc12bc 100644 --- a/src/types/Wallet/methods/exportWallet.js +++ b/src/types/Wallet/methods/exportWallet.js @@ -25,7 +25,7 @@ function exportAddressWallet(outputType = 'address') { } } -function exportSingleAddressWallet(outputType = 'privateKey') { +function exportPrivateKeyWallet(outputType = 'privateKey') { switch (outputType) { case 'privateKey': if (!this.privateKey) throw new Error('No PrivateKey to export'); @@ -81,7 +81,7 @@ module.exports = function exportWallet(outputType) { switch (this.walletType) { case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: - return exportSingleAddressWallet.call(this, outputType); + return exportPrivateKeyWallet.call(this, outputType); case WALLET_TYPES.ADDRESS: return exportAddressWallet.call(this, outputType); case WALLET_TYPES.PUBLICKEY: diff --git a/src/types/Wallet/methods/exportWallet.spec.js b/src/types/Wallet/methods/exportWallet.spec.js index d7396d6f..b0af3f6c 100644 --- a/src/types/Wallet/methods/exportWallet.spec.js +++ b/src/types/Wallet/methods/exportWallet.spec.js @@ -11,9 +11,9 @@ const cR4t6ePublicKey = new PrivateKey(cR4t6eFixture.privateKey).toPublicKey(); describe('Wallet - export Wallet', function suite() { this.timeout(10000); it('should indicate on missing data', () => { - const mockOpts1 = {}; - const mockOpts2 = { walletType: WALLET_TYPES.SINGLE_ADDRESS }; - const mockOpts3 = { walletType: WALLET_TYPES.HDWALLET }; + const mockOpts1 = { }; + const mockOpts2 = { walletType: WALLET_TYPES.PRIVATEKEY }; + const mockOpts3 = { walletType: WALLET_TYPES.HDWALLET }; const exceptedException1 = 'Trying to export from an unknown wallet type'; const exceptedException2 = 'No PrivateKey to export'; diff --git a/src/types/Wallet/methods/fromAddress.js b/src/types/Wallet/methods/fromAddress.js index c69c7749..e24064f2 100644 --- a/src/types/Wallet/methods/fromAddress.js +++ b/src/types/Wallet/methods/fromAddress.js @@ -1,6 +1,7 @@ const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * @param address @@ -10,5 +11,8 @@ module.exports = function fromAddress(address) { this.walletType = WALLET_TYPES.ADDRESS; this.mnemonic = null; this.address = address.toString(); - this.keyChain = new KeyChain({ address }); + + const keyChain = new KeyChain({ address }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromAddress.spec.js b/src/types/Wallet/methods/fromAddress.spec.js index 44bd996a..62cb1bd8 100644 --- a/src/types/Wallet/methods/fromAddress.spec.js +++ b/src/types/Wallet/methods/fromAddress.spec.js @@ -18,18 +18,20 @@ describe('Wallet - fromAddress', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.ADDRESS); expect(self1.mnemonic).to.equal(null); expect(self1.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self1.keyChain.type).to.equal('address'); - expect(self1.keyChain.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self1.keyChain.keys).to.deep.equal({}); + + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('address'); + expect(keyChain.rootKey).to.equal(cR4t6ePublicKey.toAddress().toString()); const self2 = {}; fromAddress.call(self2, cR4t6ePublicKey.toAddress().toString()); expect(self2.walletType).to.equal(WALLET_TYPES.ADDRESS); expect(self2.mnemonic).to.equal(null); expect(self2.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self2.keyChain.type).to.equal('address'); - expect(self2.keyChain.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self2.keyChain.keys).to.deep.equal({}); + + const keyChain2 = self2.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('address'); + expect(keyChain.rootKey).to.equal(cR4t6ePublicKey.toAddress().toString()); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromHDPrivateKey.js b/src/types/Wallet/methods/fromHDPrivateKey.js index da2aba6e..3976f708 100644 --- a/src/types/Wallet/methods/fromHDPrivateKey.js +++ b/src/types/Wallet/methods/fromHDPrivateKey.js @@ -3,6 +3,7 @@ const { is, } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); const { WALLET_TYPES } = require('../../../CONSTANTS'); /** @@ -14,5 +15,8 @@ module.exports = function fromHDPrivateKey(hdPrivateKey) { this.walletType = WALLET_TYPES.HDWALLET; this.mnemonic = null; this.HDPrivateKey = HDPrivateKey(hdPrivateKey); - this.keyChain = new KeyChain({ HDPrivateKey: hdPrivateKey }); + + const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromHDPrivateKey.spec.js b/src/types/Wallet/methods/fromHDPrivateKey.spec.js index 5c277483..1b3c5f2f 100644 --- a/src/types/Wallet/methods/fromHDPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromHDPrivateKey.spec.js @@ -16,9 +16,10 @@ describe('Wallet - fromHDPrivateKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(null); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self1.keyChain.keys).to.deep.equal({}); + + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromHDPublicKey.js b/src/types/Wallet/methods/fromHDPublicKey.js index 0631c051..97c2bc89 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.js +++ b/src/types/Wallet/methods/fromHDPublicKey.js @@ -2,6 +2,7 @@ const Dashcore = require('@dashevo/dashcore-lib'); const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); const normalizeHDPubKey = (key) => (is.string(key) ? Dashcore.HDPublicKey(key) : key); /** @@ -13,5 +14,8 @@ module.exports = function fromHDPublicKey(_hdPublicKey) { this.walletType = WALLET_TYPES.HDPUBLIC; this.mnemonic = null; this.HDPublicKey = normalizeHDPubKey(_hdPublicKey); - this.keyChain = new KeyChain({ HDPublicKey: this.HDPublicKey }); + + const keyChain = new KeyChain({ HDPublicKey: this.HDPublicKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromHDPublicKey.spec.js b/src/types/Wallet/methods/fromHDPublicKey.spec.js index 96a1c9cf..8d497553 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.spec.js +++ b/src/types/Wallet/methods/fromHDPublicKey.spec.js @@ -30,9 +30,10 @@ describe('Wallet - HDPublicKey', function suite() { expect(mockOpts1.mnemonic).to.equal(null); expect(mockOpts1.HDPublicKey.toString()).to.equal(gatherTestnet.external.hdpubkey); expect(new Dashcore.HDPublicKey(mockOpts1.HDPublicKey)).to.equal(mockOpts1.HDPublicKey); - expect(mockOpts1.keyChain.type).to.equal('HDPublicKey'); - expect(mockOpts1.keyChain.HDPublicKey).to.deep.equal(Dashcore.HDPublicKey(gatherTestnet.external.hdpubkey)); - expect(mockOpts1.keyChain.keys).to.deep.equal({}); + + const keyChain = mockOpts1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPublicKey'); + expect(keyChain.rootKey).to.deep.equal(Dashcore.HDPublicKey(gatherTestnet.external.hdpubkey)); }); it('should work from a HDPubKey', () => { const wallet1 = new Wallet( @@ -45,7 +46,9 @@ describe('Wallet - HDPublicKey', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPublicKey'); + + const keyChain = wallet1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.be.deep.equal('HDPublicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); diff --git a/src/types/Wallet/methods/fromMnemonic.js b/src/types/Wallet/methods/fromMnemonic.js index 4bdeaefe..ede7a240 100644 --- a/src/types/Wallet/methods/fromMnemonic.js +++ b/src/types/Wallet/methods/fromMnemonic.js @@ -3,6 +3,7 @@ const { is, } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); const { WALLET_TYPES } = require('../../../CONSTANTS'); /** @@ -17,5 +18,8 @@ module.exports = function fromMnemonic(mnemonic) { this.walletType = WALLET_TYPES.HDWALLET; this.mnemonic = trimmedMnemonic; // todo : What about without this ? this.HDPrivateKey = mnemonicToHDPrivateKey(trimmedMnemonic, this.network, this.passphrase); - this.keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); + + this.keyChainStore = new KeyChainStore(); + const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); + this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromMnemonic.spec.js b/src/types/Wallet/methods/fromMnemonic.spec.js index 14e81028..fa9f374b 100644 --- a/src/types/Wallet/methods/fromMnemonic.spec.js +++ b/src/types/Wallet/methods/fromMnemonic.spec.js @@ -20,22 +20,24 @@ describe('Wallet - fromMnemonic', function suite() { expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.network.name).to.equal('livenet'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self1.keyChain.keys).to.deep.equal({}); + + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.network.name).to.equal('livenet'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); const self2 = {}; fromMnemonic.call(self2, knifeFixture.mnemonic); expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); - expect(self2.keyChain.network.name).to.equal('testnet'); + + const keyChain2 = self2.keyChainStore.getWalletKeyChain() + expect(keyChain2.network.name).to.equal('testnet'); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); - expect(self2.keyChain.type).to.equal('HDPrivateKey'); - expect(self2.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - expect(self2.keyChain.keys).to.deep.equal({}); + expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ @@ -63,13 +65,13 @@ describe('Wallet - fromMnemonic - with passphrase', function suite() { expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.network.name).to.equal('livenet'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.network.name).to.equal('livenet'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); const path1 = 'm/44\'/5\'/0\'/0/0'; - const pubKey1 = self1.keyChain.getKeyForPath(path1).publicKey.toAddress(); + const pubKey1 = keyChain.getKeyForPath(path1).publicKey.toAddress(); expect(new Dashcore.Address(pubKey1).toString()).to.equal('Xq3zjky18WjwAHpLgGLasvX5g8TeLRKaxt'); const self2 = { @@ -80,13 +82,13 @@ describe('Wallet - fromMnemonic - with passphrase', function suite() { expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); - expect(self2.keyChain.type).to.equal('HDPrivateKey'); - expect(self2.keyChain.network.name).to.equal('testnet'); - expect(self2.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getWalletKeyChain() + expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain2.network.name).to.equal('testnet'); + expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); const path2 = 'm/44\'/1\'/0\'/0/0'; - const pubKey2 = self2.keyChain.getKeyForPath(path2).publicKey.toAddress(); + const pubKey2 = keyChain2.getKeyForPath(path2).publicKey.toAddress(); expect(new Dashcore.Address(pubKey2, 'testnet').toString()).to.equal('yWYCH9XDRnpdNxh67jQJFkovToBVwWr8Ck'); }); }); diff --git a/src/types/Wallet/methods/fromPrivateKey.js b/src/types/Wallet/methods/fromPrivateKey.js index 2f8f919b..d6abb9a5 100644 --- a/src/types/Wallet/methods/fromPrivateKey.js +++ b/src/types/Wallet/methods/fromPrivateKey.js @@ -1,6 +1,7 @@ const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) @@ -11,5 +12,8 @@ module.exports = function fromPrivateKey(privateKey) { this.walletType = WALLET_TYPES.PRIVATEKEY; this.mnemonic = null; this.privateKey = privateKey; - this.keyChain = new KeyChain({ privateKey }); + + const keyChain = new KeyChain({ privateKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPrivateKey.spec.js b/src/types/Wallet/methods/fromPrivateKey.spec.js index c707c2fa..cf7fa70c 100644 --- a/src/types/Wallet/methods/fromPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromPrivateKey.spec.js @@ -16,18 +16,18 @@ describe('Wallet - fromPrivateKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self1.mnemonic).to.equal(null); expect(self1.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self1.keyChain.type).to.equal('privateKey'); - expect(self1.keyChain.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('privateKey'); + expect(keyChain.rootKey).to.equal(cR4t6eFixture.privateKey); const self2 = {}; fromPrivateKey.call(self2, cR4t6eFixture.privateKey); expect(self2.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self2.mnemonic).to.equal(null); expect(self2.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self2.keyChain.type).to.equal('privateKey'); - expect(self2.keyChain.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getWalletKeyChain() + expect(keyChain2.rootKeyType).to.equal('privateKey'); + expect(keyChain2.rootKey).to.equal(cR4t6eFixture.privateKey); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromPublicKey.js b/src/types/Wallet/methods/fromPublicKey.js index 9e6867ac..907a0600 100644 --- a/src/types/Wallet/methods/fromPublicKey.js +++ b/src/types/Wallet/methods/fromPublicKey.js @@ -1,6 +1,7 @@ const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) @@ -11,5 +12,8 @@ module.exports = function fromPublicKey(publicKey) { this.walletType = WALLET_TYPES.PUBLICKEY; this.mnemonic = null; this.publicKey = publicKey; - this.keyChain = new KeyChain({ publicKey }); + + const keyChain = new KeyChain({ publicKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPublicKey.spec.js b/src/types/Wallet/methods/fromPublicKey.spec.js index 8852ee6c..eed8797c 100644 --- a/src/types/Wallet/methods/fromPublicKey.spec.js +++ b/src/types/Wallet/methods/fromPublicKey.spec.js @@ -18,18 +18,18 @@ describe('Wallet - fromPublicKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.PUBLICKEY); expect(self1.mnemonic).to.equal(null); expect(self1.publicKey).to.equal(cR4t6ePublicKey); - expect(self1.keyChain.type).to.equal('publicKey'); - expect(self1.keyChain.publicKey).to.equal(cR4t6ePublicKey); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('publicKey'); + expect(keyChain.rootKey).to.equal(cR4t6ePublicKey); const self2 = {}; fromPublicKey.call(self2, cR4t6ePublicKey.toString()); expect(self2.walletType).to.equal(WALLET_TYPES.PUBLICKEY); expect(self2.mnemonic).to.equal(null); expect(self2.publicKey).to.equal(cR4t6ePublicKey.toString()); - expect(self2.keyChain.type).to.equal('publicKey'); - expect(self2.keyChain.publicKey).to.equal(cR4t6ePublicKey.toString()); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getWalletKeyChain() + expect(keyChain2.rootKeyType).to.equal('publicKey'); + expect(keyChain2.rootKey).to.equal(cR4t6ePublicKey.toString()); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromSeed.spec.js b/src/types/Wallet/methods/fromSeed.spec.js index 75e9d32c..5c3efc7a 100644 --- a/src/types/Wallet/methods/fromSeed.spec.js +++ b/src/types/Wallet/methods/fromSeed.spec.js @@ -19,9 +19,9 @@ describe('Wallet - fromSeed', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(null); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getWalletKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); const self2 = { fromHDPrivateKey, @@ -32,9 +32,9 @@ describe('Wallet - fromSeed', function suite() { expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(null); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self2.keyChain.type).to.equal('HDPrivateKey'); - expect(self2.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getWalletKeyChain() + expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ From 2c55741cea0621966501cb4c6aa980b5d08b18bf Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 2 Nov 2021 02:47:00 +0100 Subject: [PATCH 04/40] feat: add generated address to watched address --- src/types/Account/methods/generateAddress.js | 23 ++++++++++++++++++-- src/types/Account/methods/getAddress.js | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index 7281bc7b..851d0ff1 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -1,12 +1,14 @@ const EVENTS = require('../../../EVENTS'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const { is } = require('../../../utils'); + /** * Generate an address from a path and import it to the store * @param {string} path + * @param {boolean} [isWatchedAddress=true] - if the address will be watched * @return {AddressObj} Address information * */ -function generateAddress(path) { +function generateAddress(path, isWatchedAddress = true) { if (is.undefOrNull(path)) throw new Error('Expected path to generate an address'); let index = 0; let privateKey; @@ -19,26 +21,42 @@ function generateAddress(path) { break; case WALLET_TYPES.PUBLICKEY: address = this.keyChainStore.getWalletKeyChain().rootKey.toAddress(network).toString(); + if (isWatchedAddress) { + this.keyChainStore.getWalletKeyChain() + .addKeysToWatchedKeys(this.keyChainStore.getWalletKeyChain().rootKey); + } break; case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring index = parseInt(path.toString().split('/')[5], 10); privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(path); + if (isWatchedAddress) { + this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); + } address = privateKey.publicKey.toAddress(network).toString(); break; case WALLET_TYPES.HDPUBLIC: index = parseInt(path.toString().split('/')[5], 10); privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(index); + if (isWatchedAddress) { + this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); + } address = privateKey.publicKey.toAddress(network).toString(); break; - // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY + // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: privateKey = this.keyChainStore.getWalletKeyChain().rootKey; + if (isWatchedAddress) { + this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); + } address = privateKey.publicKey.toAddress(network).toString(); break; default: privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(path.toString()); + if (isWatchedAddress) { + this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); + } address = privateKey.publicKey.toAddress(network).toString(); } @@ -59,4 +77,5 @@ function generateAddress(path) { this.emit(EVENTS.GENERATED_ADDRESS, { type: EVENTS.GENERATED_ADDRESS, payload: addressData }); return addressData; } + module.exports = generateAddress; diff --git a/src/types/Account/methods/getAddress.js b/src/types/Account/methods/getAddress.js index d4771dc7..d6798537 100644 --- a/src/types/Account/methods/getAddress.js +++ b/src/types/Account/methods/getAddress.js @@ -30,7 +30,7 @@ const getTypePathFromWalletType = (walletType, addressType = 'external', index, * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) * @return */ -function getAddress(index = 0, _type = 'external') { +function getAddress(index = 0, _type = 'external', addToWatchedAddress) { const { type, path } = getTypePathFromWalletType(this.walletType, _type, index, this.BIP44PATH); const { wallets } = this.storage.getStore(); From da0465a83997f1e71d95eba7e90c843c5f9fd86d Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 2 Nov 2021 02:47:20 +0100 Subject: [PATCH 05/40] feat: use keychain watched address for address to sync --- .../TransactionSyncStreamWorker.js | 1 + .../methods/getAddressesToSync.js | 14 +------------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index 73d4bfda..0f17d76a 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -25,6 +25,7 @@ class TransactionSyncStreamWorker extends Worker { 'importBlockHeader', 'importInstantLock', 'storage', + 'keyChainStore', 'transport', 'walletId', 'getAddress', diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js index 7e3f57e5..94641559 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js @@ -1,15 +1,3 @@ -const { WALLET_TYPES } = require('../../../../CONSTANTS'); - module.exports = function getAddressesToSync() { - const { BIP44PATH, walletId, walletType } = this; - const { addresses } = this.storage.getStore().wallets[walletId]; - - const isHDWallet = [WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(walletType); - // We have two cases, for privateKey based wallet, we return all address in store. - // But for HDWallet, addresses in store can be of another account, that we filter out - return Object.keys(addresses) - .map((addressType) => Object.values(addresses[addressType])) - .flatMap((addressList) => addressList) - .filter((accountAddress) => !isHDWallet || accountAddress.path.startsWith(BIP44PATH)) - .map((filteredAccountAddress) => filteredAccountAddress.address); + return this.keyChainStore.getKeyChains().map((keychain) => keychain.getWatchedAddresses()); }; From 12a94f6b8d87d9f945f605a410bde9c339bb30d2 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 5 Nov 2021 10:29:33 +0100 Subject: [PATCH 06/40] tests(KeyChainStore): update tests --- src/types/KeyChainStore/KeyChainStore.spec.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/types/KeyChainStore/KeyChainStore.spec.js b/src/types/KeyChainStore/KeyChainStore.spec.js index 6975bb26..d2eba2a4 100644 --- a/src/types/KeyChainStore/KeyChainStore.spec.js +++ b/src/types/KeyChainStore/KeyChainStore.spec.js @@ -6,7 +6,10 @@ const { expect } = require('chai'); describe('KeyChainStore', function suite() { let keyChainsStore; let hdPrivateKey = new HDPrivateKey() + let hdPublicKey = new HDPrivateKey().hdPublicKey let keyChain = new KeyChain(hdPrivateKey) + let keyChainPublic = new KeyChain(hdPublicKey) + let walletKeyChain = new KeyChain(new HDPrivateKey()); it('should create a KeyChainStore', () => { keyChainsStore = new KeyChainsStore(); expect(keyChainsStore).to.exist; @@ -15,10 +18,24 @@ describe('KeyChainStore', function suite() { it('should be able to add a keyChain', function () { keyChainsStore.addKeyChain(keyChain) expect(keyChainsStore.chains.has(keyChain.keyChainId)).to.equal(true); + keyChainsStore.addKeyChain(keyChainPublic) + expect(keyChainsStore.chains.has(keyChainPublic.keyChainId)).to.equal(true); + }); + it('should allow to specify a specific wallet keychain', function () { + keyChainsStore.addKeyChain(walletKeyChain, { isWalletKeyChain: true }); + expect(keyChainsStore.chains.has(walletKeyChain.keyChainId)).to.equal(true); }); it('should get all keyChains', function () { const keyChains = keyChainsStore.getKeyChains() - expect(keyChains).to.deep.equal([keyChain]); + expect(keyChains).to.deep.equal([keyChain, keyChainPublic, walletKeyChain]); + }); + it('should get a keychain by its ID', () => { + const requestedKeychain = keyChainsStore.getKeyChain(keyChainPublic.keyChainId); + expect(requestedKeychain).to.equal(keyChainPublic); + }) + it('should get a wallet keychain', function () { + const requestedWalletKeyChain = keyChainsStore.getWalletKeyChain(); + expect(requestedWalletKeyChain).to.equal(walletKeyChain); }); }); From afa5c3579619eeb9d3d49cb3fd245ebb9d745154 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 5 Nov 2021 10:44:52 +0100 Subject: [PATCH 07/40] feat: added ability to remove keys from watched keys --- src/types/KeyChain/KeyChain.js | 1 + .../methods/removeKeysToWatchedKeys.js | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/types/KeyChain/methods/removeKeysToWatchedKeys.js diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index f646dc00..84471a0e 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -58,6 +58,7 @@ KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey' KeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); +KeyChain.prototype.removeKeysToWatchedKeys = require('./methods/removeKeysToWatchedKeys'); KeyChain.prototype.sign = require('./methods/sign'); module.exports = KeyChain; diff --git a/src/types/KeyChain/methods/removeKeysToWatchedKeys.js b/src/types/KeyChain/methods/removeKeysToWatchedKeys.js new file mode 100644 index 00000000..4db729fa --- /dev/null +++ b/src/types/KeyChain/methods/removeKeysToWatchedKeys.js @@ -0,0 +1,27 @@ +const { + util: { buffer: BufferUtil }, +} = require('@dashevo/dashcore-lib'); + +function removeKeysToWatchedKeys(keys) { + if (!Array.isArray(keys)) { + return this.removeKeysToWatchedKeys([keys]); + } + let removed = 0; + + keys.forEach((key) => { + const keyDepth = Number(key.depth); + // eslint-disable-next-line no-underscore-dangle + const keyIndex = BufferUtil.integerFromBuffer(key._buffers.childIndex); + // eslint-disable-next-line no-underscore-dangle + const keyParentFingerPrint = BufferUtil.integerFromBuffer(key._buffers.parentFingerPrint); + const keyId = `${keyDepth}-${keyParentFingerPrint}/${keyIndex}`; + if (!this.watchedKeys.has(keyId)) { + throw new Error('Key is not watched'); + } + this.watchedKeys.delete(keyId); + removed += 1; + }); + + return removed; +} +module.exports = removeKeysToWatchedKeys; From e37e34a8134eb421808a995d53ec1de1c914c2ee Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 5 Nov 2021 10:44:56 +0100 Subject: [PATCH 08/40] tests(KeyChain): added watched keys tests --- src/types/KeyChain/KeyChain.spec.js | 42 ++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index d60a8ae0..7ebb817e 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -13,7 +13,7 @@ const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWu const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; describe('Keychain', function suite() { - this.timeout(10000); + this.timeout(1000); it('should create a keychain', () => { const expectedException1 = 'Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'; expect(() => new KeyChain()).to.throw(expectedException1); @@ -79,15 +79,45 @@ describe('Keychain', function suite() { expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); }); - it('should get key for child', () => { + it('should get key for path', () => { const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); const keyForChild = keychain2.getKeyForPath(0); expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); }); - - - it('should sign', () => { - + it('should add keys to watched keys', ()=>{ + const keys = [keychain.getKeyForPath(0), keychain.getKeyForPath(1), keychain.getKeyForPath(2)]; + expect(keychain.addKeysToWatchedKeys(keys)).to.equal(3); + }); + it('should get watched keys', function () { + const watchedKeys = keychain.getWatchedKeys(); + let expectedWatchedKeys = [keychain.getKeyForPath(0), keychain.getKeyForPath(1), keychain.getKeyForPath(2)]; + expect(watchedKeys).to.deep.equal(expectedWatchedKeys); + }); + it('should get watched addresses', function () { + const watchedAddresses = keychain.getWatchedAddresses(); + const expectedWatchedAddresses = [ + 'ybQDfNwiDjk8ZH5UUmHQzAMEmjbrbK5dAj', + 'yhFX5rseJPitV45HUCaa9haeGHtLuooBaq', + 'yhqxsmYk6jfoGWf1hJKq7d4U2cGHCgzpFU' + ] + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); + }); + it('should get watched public keys', function () { + const watchedPubKeys = keychain.getWatchedPublicKeys(); + const expectedWatchedPubKeys = [ + '03e6ab8177a7ca2699da4f83ca3c27768fb88b70ae9d6bde1cba8de88355ccf199', + '0246a870b65153b98e453ff08f7198d06bce2a790286f44d90929aceafafa0673f', + '025ad16f78f67801e52abe5b512d66e3896d4c2fa3ca3150349437a5dd13519967' + ] + expect(watchedPubKeys).to.deep.equal(expectedWatchedPubKeys) + }); + it('should remove a key from watched keys', function () { + let watchedKeys = [keychain.getKeyForPath(0), keychain.getKeyForPath(1), keychain.getKeyForPath(2)]; + expect(keychain.removeKeysToWatchedKeys(watchedKeys[1])).to.equal(1); + expect(keychain.getWatchedKeys().length).to.equal(2); + const updatedWatchedKeys = keychain.getWatchedKeys(); + const expectedUpdatedWatchedKeys = [watchedKeys[0], watchedKeys[2]] + expect(updatedWatchedKeys).to.deep.equal(expectedUpdatedWatchedKeys); }); }); describe('Keychain - single privateKey', function suite() { From 02f06f6e16dfaceb95abe779c77419ed717070f6 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 5 Nov 2021 10:54:26 +0100 Subject: [PATCH 09/40] style: fix lint issue --- src/types/Account/methods/getAddress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/Account/methods/getAddress.js b/src/types/Account/methods/getAddress.js index d6798537..d4771dc7 100644 --- a/src/types/Account/methods/getAddress.js +++ b/src/types/Account/methods/getAddress.js @@ -30,7 +30,7 @@ const getTypePathFromWalletType = (walletType, addressType = 'external', index, * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) * @return */ -function getAddress(index = 0, _type = 'external', addToWatchedAddress) { +function getAddress(index = 0, _type = 'external') { const { type, path } = getTypePathFromWalletType(this.walletType, _type, index, this.BIP44PATH); const { wallets } = this.storage.getStore(); From 5983f71a563f2c146b3f9309cdf1edd48580af37 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 5 Nov 2021 11:14:36 +0100 Subject: [PATCH 10/40] tests: fix slow tests by comparing stringified keys instead of object --- src/types/KeyChain/KeyChain.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 7ebb817e..b2233606 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -117,7 +117,7 @@ describe('Keychain', function suite() { expect(keychain.getWatchedKeys().length).to.equal(2); const updatedWatchedKeys = keychain.getWatchedKeys(); const expectedUpdatedWatchedKeys = [watchedKeys[0], watchedKeys[2]] - expect(updatedWatchedKeys).to.deep.equal(expectedUpdatedWatchedKeys); + expect(updatedWatchedKeys.map((key)=>key.toString())).to.deep.equal(expectedUpdatedWatchedKeys.map((key)=>key.toString())); }); }); describe('Keychain - single privateKey', function suite() { From adb19b1a45cf0fa05562313f3db3023a4224c232 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Fri, 5 Nov 2021 16:13:55 +0100 Subject: [PATCH 11/40] feat: allow to pass network to get watched addresses --- .../methods/getAddressesToSync.js | 6 ++- .../methods/getAddressesToSync.spec.js | 41 ++++++++++++++----- src/types/KeyChain/KeyChain.js | 7 ++-- src/types/KeyChain/KeyChain.spec.js | 1 + .../KeyChain/methods/addKeysToWatchedKeys.js | 19 ++++++--- src/types/KeyChain/methods/getRootKey.js | 5 +++ .../KeyChain/methods/getWatchedAddresses.js | 12 +++++- 7 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 src/types/KeyChain/methods/getRootKey.js diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js index 94641559..83c58c4c 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js @@ -1,3 +1,5 @@ -module.exports = function getAddressesToSync() { - return this.keyChainStore.getKeyChains().map((keychain) => keychain.getWatchedAddresses()); +module.exports = function getAddressesToSync(opts = {}) { + return this.keyChainStore.getKeyChains() + .map((keychain) => keychain.getWatchedAddresses(opts)) + .reduce((pre, cur) => pre.concat(cur)); }; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js index 16597f84..07338a6c 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js @@ -1,5 +1,27 @@ const { expect } = require('chai'); const getAddressesToSync = require('./getAddressesToSync'); +const KeyChainStore = require("../../../../types/KeyChainStore/KeyChainStore"); +const KeyChain = require("../../../../types/KeyChain/KeyChain"); +const { HDPrivateKey, HDPublicKey, PrivateKey } = require("@dashevo/dashcore-lib"); + + +const privateKey = new PrivateKey('ee56be968a42e58fda23b83da17f90e002cafbe35a702c2f5598b13fdaa238db', 'testnet') +const hdprivateKey1 = new HDPrivateKey("xprv9s21ZrQH143K39R9Ux28kCBUHcQFdBeVE2CXFVz6GnA2a6pqTsPhHR5QHtMP5ZTRpYkKqc9ifjkJ2V1h318qWsYgyxCBUurRdTNthjgwKMw", 'testnet'); +const hdpublicKey1 = new HDPublicKey("xpub661MyMwAqRbcFhaucFQun3ivEyA5gy5NKnjr1xMUVkyqdF3VNNy3TLinwnYMSUye5FF5pDSrn2SPX3zvKRQGrpZ44VVUBeuxuzov7enWpkf",'testnet'); + +const keychainPrivate1 = new KeyChain({privateKey}); +const keychainHDPrivate1 = new KeyChain({HDPrivateKey: hdprivateKey1}); +const keychainHDPublic1 = new KeyChain({HDPublicKey: hdpublicKey1}); + +const keychainStorePrivateKeyWallet = new KeyChainStore(); +keychainStorePrivateKeyWallet.addKeyChain(keychainPrivate1, { isWalletKeyChain: true}); +keychainPrivate1.addKeysToWatchedKeys(keychainPrivate1.getRootKey()) + +const keychainStoreHDPrivateKeyWallet = new KeyChainStore(); +keychainStoreHDPrivateKeyWallet.addKeyChain(keychainHDPrivate1, { isWalletKeyChain: true}); +keychainHDPrivate1.addKeysToWatchedKeys(keychainHDPrivate1.getKeyForPath(`m/44'/1'/0/0`)) +keychainHDPrivate1.addKeysToWatchedKeys(keychainHDPrivate1.getKeyForPath(`m/44'/1'/0/1`)) + const mockedStore1 = { wallets: { @@ -107,31 +129,30 @@ const mockedStore2 = { const mockSelfPrivateKeyType = { storage: { getStore:()=>mockedStore1 }, + keyChainStore: keychainStorePrivateKeyWallet, walletId: '123456789', walletType: 'privateKey', } + const mockSelfIndex0 = { storage: { getStore:()=>mockedStore2 }, + keyChainStore: keychainStoreHDPrivateKeyWallet, walletId: '123456789', walletType: 'hdwallet', BIP44PATH: `m/44'/1'/0'` } -const mockSelfIndex1 = { - ...mockSelfIndex0, - BIP44PATH: `m/44'/1'/1'` -} describe('TransactionSyncStreamWorker#getAddressesToSync', function suite() { it('should correctly fetch addresses to sync', async () => { - const addressesIndex0 = getAddressesToSync.call(mockSelfIndex0 ); - expect(addressesIndex0).to.deep.equal(['yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt9']) - - const addressesIndex1 = getAddressesToSync.call(mockSelfIndex1 ); - expect(addressesIndex1).to.deep.equal(['yQ5TfKcj3NHM4V4K5VBgoFJj9Q4LKX13gn']) + const addressesIndex0 = getAddressesToSync.call(mockSelfIndex0, { network: 'testnet'} ); + expect(addressesIndex0).to.deep.equal([ + 'yWRLbb7nke6NWGBCZf4GAPn1UcuSSdTr4g', + 'yP7ZfDzfFR4FJATHcfP8XDac1mLdqbscs5' + ]) const addressesIndex2 = getAddressesToSync.call(mockSelfPrivateKeyType ); - expect(addressesIndex2).to.deep.equal(['yizmJb63ygipuJaRgYtpWCV2erQodmaZt1']) + expect(addressesIndex2).to.deep.equal(['yZprpQkn7FYUHjqm3dY4sCs9SorMCi4oyR']) }); }); diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 84471a0e..962a4733 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -50,14 +50,15 @@ class KeyChain { } } KeyChain.prototype.addKeysToWatchedKeys = require('./methods/addKeysToWatchedKeys'); -KeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); -KeyChain.prototype.getWatchedKeys = require('./methods/getWatchedKeys'); -KeyChain.prototype.getWatchedPublicKeys = require('./methods/getWatchedPublicKeys'); KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); KeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); +KeyChain.prototype.getRootKey = require('./methods/getRootKey'); +KeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); +KeyChain.prototype.getWatchedKeys = require('./methods/getWatchedKeys'); +KeyChain.prototype.getWatchedPublicKeys = require('./methods/getWatchedPublicKeys'); KeyChain.prototype.removeKeysToWatchedKeys = require('./methods/removeKeysToWatchedKeys'); KeyChain.prototype.sign = require('./methods/sign'); diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index b2233606..063a1e7d 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -136,6 +136,7 @@ describe('Keychain - single privateKey', function suite() { it('should get private key', () => { const privateKey = Dashcore.PrivateKey().toString(); const pkKeyChain = new KeyChain({ privateKey, network: 'livenet' }); + expect(pkKeyChain.getRootKey().toString()).to.equal(privateKey); expect(pkKeyChain.rootKey.toString()).to.equal(privateKey); }); }); diff --git a/src/types/KeyChain/methods/addKeysToWatchedKeys.js b/src/types/KeyChain/methods/addKeysToWatchedKeys.js index 79a6eb6f..75c12bbb 100644 --- a/src/types/KeyChain/methods/addKeysToWatchedKeys.js +++ b/src/types/KeyChain/methods/addKeysToWatchedKeys.js @@ -1,6 +1,7 @@ const { util: { buffer: BufferUtil }, } = require('@dashevo/dashcore-lib'); +const { sha256 } = require('../../../utils/crypto'); function addKeysToWatchedKeys(keys) { if (!Array.isArray(keys)) { @@ -9,12 +10,18 @@ function addKeysToWatchedKeys(keys) { let added = 0; keys.forEach((key) => { - const keyDepth = Number(key.depth); - // eslint-disable-next-line no-underscore-dangle - const keyIndex = BufferUtil.integerFromBuffer(key._buffers.childIndex); - // eslint-disable-next-line no-underscore-dangle - const keyParentFingerPrint = BufferUtil.integerFromBuffer(key._buffers.parentFingerPrint); - const keyId = `${keyDepth}-${keyParentFingerPrint}/${keyIndex}`; + let keyId; + if (key.depth) { + const keyDepth = Number(key.depth); + // eslint-disable-next-line no-underscore-dangle + const keyIndex = BufferUtil.integerFromBuffer(key._buffers.childIndex); + // eslint-disable-next-line no-underscore-dangle + const keyParentFingerPrint = BufferUtil.integerFromBuffer(key._buffers.parentFingerPrint); + keyId = `${keyDepth}-${keyParentFingerPrint}/${keyIndex}`; + } else { + keyId = sha256(key.toString()).slice(0, 5).toString('hex'); + } + if (this.watchedKeys.has(keyId)) { throw new Error('Key is already watched'); } diff --git a/src/types/KeyChain/methods/getRootKey.js b/src/types/KeyChain/methods/getRootKey.js new file mode 100644 index 00000000..f2b54723 --- /dev/null +++ b/src/types/KeyChain/methods/getRootKey.js @@ -0,0 +1,5 @@ +function getRootKey() { + return this.rootKey; +} + +module.exports = getRootKey; diff --git a/src/types/KeyChain/methods/getWatchedAddresses.js b/src/types/KeyChain/methods/getWatchedAddresses.js index f964c37b..9f43bce8 100644 --- a/src/types/KeyChain/methods/getWatchedAddresses.js +++ b/src/types/KeyChain/methods/getWatchedAddresses.js @@ -1,6 +1,14 @@ -function getWatchedAddresses() { +function getWatchedAddresses(opts = {}) { const keys = this.getWatchedKeys(); - return keys.map((key) => key.publicKey.toAddress().toString()); + + const mapKeyToAddress = (key) => { + const network = opts.network || key.network || 'testnet'; + return key.toAddress + ? key.toAddress(network).toString() + : key.publicKey.toAddress(network).toString(); + }; + + return keys.map(mapKeyToAddress); } module.exports = getWatchedAddresses; From 890924b832345514e5e34b53871cce2c84c9f3a6 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 7 Nov 2021 19:47:09 +0100 Subject: [PATCH 12/40] feat: ensure gap internal are only generated on HDWallet --- src/types/KeyChain/KeyChain.js | 2 +- src/types/KeyChain/KeyChain.spec.js | 18 ++++++++- .../Wallet/methods/fromHDPublicKey.spec.js | 38 +++++++++++-------- src/utils/bip44/ensureAddressesToGapLimit.js | 9 +++-- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 962a4733..5900bbb4 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -42,7 +42,7 @@ class KeyChain { this.keyChainId = generateKeyChainId(rootKey); this.rootKey = rootKey; - this.network = rootKey.network || opts.network || Networks.testnet.toString(); + this.network = opts.network || rootKey.network || Networks.testnet.toString(); this.rootKeyType = rootKeyType; // this.chainCode = chainCode; diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 063a1e7d..9b7f19e9 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -8,7 +8,7 @@ let keychain2; const mnemonic = 'during develop before curtain hazard rare job language become verb message travel'; const mnemonic2 = 'birth kingdom trash renew flavor utility donkey gasp regular alert pave layer'; const pk = '4226d5e2fe8cbfe6f5beb7adf5a5b08b310f6c4a67fc27826779073be6f5699e'; - +const hdPublicKey = 'xpub661MyMwAqRbcFGB6XSWBsD725rJDUbFUpy4zWe2u22nJ2BxpoHFxtVDfKnTnvVQHohnY7AsVpRTHDv6PyPQTYu1KxFPKw29MAVXPEpz1G7V'; const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; @@ -120,6 +120,22 @@ describe('Keychain', function suite() { expect(updatedWatchedKeys.map((key)=>key.toString())).to.deep.equal(expectedUpdatedWatchedKeys.map((key)=>key.toString())); }); }); +describe('Keychain - HDPublicKey', function suite(){ + let hdpubKeyChain; + it('should initiate from a HDPublicKey', function () { + hdpubKeyChain = new KeyChain({ + HDPublicKey: new Dashcore.HDPublicKey(hdPublicKey), + network: 'testnet' + }); + expect(hdpubKeyChain.network.toString()).to.equal('testnet'); + expect(hdpubKeyChain.keyChainId).to.equal('kc5059442d66'); + expect(hdpubKeyChain.getRootKey().toString()).to.equal(hdPublicKey); + }); + it('should derivate', function () { + const key0_1 = hdpubKeyChain.getKeyForPath(1); + expect(key0_1.publicKey.toAddress(hdpubKeyChain.network).toString()).to.equal('yYxgMZG9f4GoS53CpoJHuHDsMnUUf1ezZv') + }); +}) describe('Keychain - single privateKey', function suite() { this.timeout(10000); it('should correctly errors out when not a HDPublicKey (privateKey)', () => { diff --git a/src/types/Wallet/methods/fromHDPublicKey.spec.js b/src/types/Wallet/methods/fromHDPublicKey.spec.js index 8d497553..d58923ef 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.spec.js +++ b/src/types/Wallet/methods/fromHDPublicKey.spec.js @@ -57,22 +57,28 @@ describe('Wallet - HDPublicKey', function suite() { expect(wallet1.exportWallet()).to.be.equal(gatherTestnet.external.hdpubkey); - wallet1.getAccount().then((account)=>{ - const unusedAddress = account.getUnusedAddress(); - const expectedUnused = { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yNJ3xxTXXBBf39VfMBbBuLH2k57uAwxBxj', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false, - }; - expect(unusedAddress).to.deep.equal(expectedUnused); + // FIXME: it appears we had introduced a bug here, + // as it is not possible to have a HDPublicKey derivation with hardened + // Either our path is m/44/1/0/0/0 or it is m/0/0. + // We should clarify this before merging TODO + wallet1 + .getAccount() + .then((account)=>{ + const unusedAddress = account.getUnusedAddress(); + const expectedUnused = { + path: "m/44'/1'/0'/0/0", + index: 0, + address: 'yNJ3xxTXXBBf39VfMBbBuLH2k57uAwxBxj', + transactions: [], + balanceSat: 0, + unconfirmedBalanceSat: 0, + utxos: {}, + fetchedLast: 0, + used: false, + }; + expect(unusedAddress).to.deep.equal(expectedUnused); - wallet1.disconnect(); - }) + wallet1.disconnect(); + }) }); }); diff --git a/src/utils/bip44/ensureAddressesToGapLimit.js b/src/utils/bip44/ensureAddressesToGapLimit.js index 318d7959..020fab42 100644 --- a/src/utils/bip44/ensureAddressesToGapLimit.js +++ b/src/utils/bip44/ensureAddressesToGapLimit.js @@ -1,5 +1,5 @@ const logger = require('../../logger'); -const { BIP44_ADDRESS_GAP } = require('../../CONSTANTS'); +const { BIP44_ADDRESS_GAP, WALLET_TYPES } = require('../../CONSTANTS'); const is = require('../is'); const getMissingIndexes = require('./getMissingIndexes'); @@ -77,12 +77,15 @@ function ensureAccountAddressesToGapLimit(walletStore, walletType, accountIndex, const gapBetweenLastUsedAndLastGenerated = { external: lastGeneratedIndexes.external - lastUsedIndexes.external, - internal: lastGeneratedIndexes.internal - lastUsedIndexes.internal, }; const addressesToGenerate = { external: BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.external, - internal: BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.internal, }; + if (walletType.includes(WALLET_TYPES.HDWALLET)) { + // eslint-disable-next-line max-len + gapBetweenLastUsedAndLastGenerated.internal = lastGeneratedIndexes.internal - lastUsedIndexes.internal; + addressesToGenerate.internal = BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.internal; + } Object.entries(addressesToGenerate) .forEach(([typeToGenerate, numberToGenerate]) => { From 07690fbf4aeca3bb5daf4d45c557339450f296aa Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 15 Nov 2021 05:30:38 +0100 Subject: [PATCH 13/40] feat: introduce KeyChainStore --- src/index.d.ts | 2 + src/index.js | 2 + src/types/KeyChain/KeyChain.js | 65 +++- src/types/KeyChain/KeyChain.spec.js | 48 ++- .../KeyChain/methods/addKeysToWatchedKeys.js | 34 -- src/types/KeyChain/methods/getForAddress.js | 12 + src/types/KeyChain/methods/getForPath.js | 27 ++ src/types/KeyChain/methods/getKeyForPath.js | 8 - .../KeyChain/methods/getWatchedAddresses.js | 15 +- src/types/KeyChain/methods/getWatchedKeys.js | 5 - .../KeyChain/methods/getWatchedPublicKeys.js | 6 - .../KeyChain/methods/markAddressAsUsed.js | 8 + src/types/KeyChain/methods/maybeLookAhead.js | 83 ++++ .../methods/removeKeysToWatchedKeys.js | 27 -- src/types/KeyChainStore/KeyChainStore.js | 7 +- src/types/KeyChainStore/KeyChainStore.spec.js | 28 +- .../KeyChainStore/methods/addKeyChain.js | 11 +- .../KeyChainStore/methods/getKeyChain.js | 2 +- .../KeyChainStore/methods/getKeyChains.js | 2 +- .../methods/getMasterKeyChain.js | 6 + .../methods/getWalletKeyChain.js | 6 - .../methods/makeChildKeyChainStore.js | 19 + src/utils/bip44/getBIP44AccountPath.js | 11 + tests/functional/keys.spec.js | 359 ++++++++++++++++++ 24 files changed, 648 insertions(+), 145 deletions(-) delete mode 100644 src/types/KeyChain/methods/addKeysToWatchedKeys.js create mode 100644 src/types/KeyChain/methods/getForAddress.js create mode 100644 src/types/KeyChain/methods/getForPath.js delete mode 100644 src/types/KeyChain/methods/getKeyForPath.js delete mode 100644 src/types/KeyChain/methods/getWatchedKeys.js delete mode 100644 src/types/KeyChain/methods/getWatchedPublicKeys.js create mode 100644 src/types/KeyChain/methods/markAddressAsUsed.js create mode 100644 src/types/KeyChain/methods/maybeLookAhead.js delete mode 100644 src/types/KeyChain/methods/removeKeysToWatchedKeys.js create mode 100644 src/types/KeyChainStore/methods/getMasterKeyChain.js delete mode 100644 src/types/KeyChainStore/methods/getWalletKeyChain.js create mode 100644 src/types/KeyChainStore/methods/makeChildKeyChainStore.js create mode 100644 src/utils/bip44/getBIP44AccountPath.js create mode 100644 tests/functional/keys.spec.js diff --git a/src/index.d.ts b/src/index.d.ts index 9ea8f7c7..7ac3af55 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -5,6 +5,7 @@ import { Account } from "./types/Account/Account"; import { Wallet } from "./types/Wallet/Wallet"; import { Identities } from "./types/Identities/Identities"; import { KeyChain } from "./types/KeyChain/KeyChain"; +import { KeyChainStore } from "./types/KeyChainStore/KeyChainStore"; import CONSTANTS from "./CONSTANTS"; import EVENTS from "./EVENTS"; import utils from "./utils"; @@ -14,6 +15,7 @@ export { Account, Wallet, KeyChain, + KeyChainStore, Identities, EVENTS, CONSTANTS, diff --git a/src/index.js b/src/index.js index 2fa9d8a9..a2858bf4 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ const Wallet = require('./types/Wallet/Wallet'); const Account = require('./types/Account/Account'); const Identities = require('./types/Identities/Identities'); const KeyChain = require('./types/KeyChain/KeyChain'); +const KeyChainStore = require('./types/KeyChainStore/KeyChainStore'); const EVENTS = require('./EVENTS'); const CONSTANTS = require('./CONSTANTS'); const utils = require('./utils'); @@ -16,6 +17,7 @@ module.exports = { Account, Identities, KeyChain, + KeyChainStore, EVENTS, CONSTANTS, utils, diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 5900bbb4..46da8eb6 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -1,5 +1,7 @@ const { Networks, HDPrivateKey, HDPublicKey } = require('@dashevo/dashcore-lib'); +const { PrivateKey, PublicKey } = require('@dashevo/dashcore-lib'); const { doubleSha256 } = require('../../utils/crypto'); +const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); function generateKeyChainId(key) { const keyChainIdSuffix = doubleSha256(key.toString()).toString('hex').slice(0, 10); @@ -9,57 +11,96 @@ function generateKeyChainId(key) { function fromOptions(opts) { let rootKey; let rootKeyType; + let network = Networks.testnet.toString(); + let passphrase = ''; if (opts) { + if (opts.passphrase) { + passphrase = opts.passphrase; + } if (opts.mnemonic) { rootKeyType = 'HDPrivateKey'; rootKey = (typeof opts.mnemonic === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; } + if (opts.network) { + network = opts.network; + } if (opts.HDPrivateKey) { rootKeyType = 'HDPrivateKey'; rootKey = (typeof opts.HDPrivateKey === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; + network = rootKey.network.toString(); } else if (opts.HDPublicKey) { rootKeyType = 'HDPublicKey'; rootKey = (typeof opts.HDPublicKey === 'string') ? HDPublicKey(opts.HDPublicKey) : opts.HDPublicKey; + network = rootKey.network.toString(); } else if (opts.privateKey) { rootKeyType = 'privateKey'; - rootKey = opts.privateKey; + rootKey = (typeof opts.privateKey === 'string') ? new PrivateKey(opts.privateKey, opts.network) : opts.privateKey; + network = rootKey.network.toString(); } else if (opts.publicKey) { rootKeyType = 'publicKey'; - rootKey = opts.publicKey; + rootKey = (typeof opts.publicKey === 'string') ? new PublicKey(opts.publicKey, opts.network) : opts.publicKey; + network = rootKey.network.toString(); } else if (opts.address) { rootKeyType = 'address'; rootKey = opts.address.toString(); + } else if (opts.mnemonic) { + return fromOptions({ + ...opts, + HDPrivateKey: mnemonicToHDPrivateKey(opts.mnemonic, network, passphrase), + }); } } - return { rootKeyType, rootKey }; + + const lookAheadOpts = { + isWatched: true, + paths: {}, + ...opts.lookAheadOpts, + }; + + return { + rootKeyType, + rootKey, + network, + passphrase, + lookAheadOpts, + }; } class KeyChain { constructor(opts = {}) { - const { rootKey, rootKeyType } = fromOptions(opts); - if (!rootKeyType || !rootKey) throw new Error('Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'); + const { + rootKey, + rootKeyType, + network, + lookAheadOpts, + } = fromOptions(opts); + if (!rootKeyType || !rootKey) { + throw new Error('Expect one of [mnemonic, HDPrivateKey, HDPublicKey, privateKey, publicKey, address] to be provided.'); + } this.keyChainId = generateKeyChainId(rootKey); this.rootKey = rootKey; - this.network = opts.network || rootKey.network || Networks.testnet.toString(); + this.network = network; this.rootKeyType = rootKeyType; - // this.chainCode = chainCode; + this.lookAheadOpts = { isWatched: true, ...lookAheadOpts }; + + this.issuedPaths = new Map(); this.watchedKeys = new Map(); + this.maybeLookAhead(); } } -KeyChain.prototype.addKeysToWatchedKeys = require('./methods/addKeysToWatchedKeys'); -KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); +KeyChain.prototype.getForPath = require('./methods/getForPath'); +KeyChain.prototype.getForAddress = require('./methods/getForAddress'); KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); KeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); KeyChain.prototype.getRootKey = require('./methods/getRootKey'); KeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); -KeyChain.prototype.getWatchedKeys = require('./methods/getWatchedKeys'); -KeyChain.prototype.getWatchedPublicKeys = require('./methods/getWatchedPublicKeys'); -KeyChain.prototype.removeKeysToWatchedKeys = require('./methods/removeKeysToWatchedKeys'); +KeyChain.prototype.maybeLookAhead = require('./methods/maybeLookAhead'); +KeyChain.prototype.markAddressAsUsed = require('./methods/markAddressAsUsed'); KeyChain.prototype.sign = require('./methods/sign'); module.exports = KeyChain; diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 9b7f19e9..3adf98ed 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -15,26 +15,26 @@ const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzH describe('Keychain', function suite() { this.timeout(1000); it('should create a keychain', () => { - const expectedException1 = 'Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'; + const expectedException1 = 'Expect one of [mnemonic, HDPrivateKey, HDPublicKey, privateKey, publicKey, address] to be provided.'; expect(() => new KeyChain()).to.throw(expectedException1); - expect(() => new KeyChain(mnemonic)).to.throw(expectedException1); - keychain = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); + keychain = new KeyChain({ mnemonic: mnemonic, network: 'testnet' }); expect(keychain.rootKeyType).to.equal('HDPrivateKey'); expect(keychain.network.toString()).to.equal('testnet'); + expect(keychain.rootKey.network.toString()).to.equal('testnet'); - keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic2, 'mainnet') }); + keychain2 = new KeyChain({ mnemonic: mnemonic2, network: 'livenet' }); }); it('should generate key for full path', () => { const path = 'm/44\'/1\'/0\'/0/0'; - const pk2 = keychain.getKeyForPath(path); + const pk2 = keychain.getForPath(path).key; const address = new Dashcore.Address(pk2.publicKey.toAddress()).toString(); expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); }); it('should get hardened feature path', () => { const hardenedPk = keychain.getHardenedBIP44HDKey(); - const pk2 = keychain.getKeyForPath('m/44\'/1\''); + const pk2 = keychain.getForPath('m/44\'/1\'').key; expect(pk2.toString()).to.equal(hardenedPk.toString()); }); it('should get DIP15 account key', function () { @@ -66,7 +66,7 @@ describe('Keychain', function suite() { }); it('should derive from hardened feature path', () => { const hardenedHDKey = keychain.getHardenedBIP44HDKey(); - const pk2 = keychain.getKeyForPath(`m/44'/1'`); + const pk2 = keychain.getForPath(`m/44'/1'`).key; expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8dtrJNytYHRiZY585hmHGbguS6VjGpK49puSB7oXZjLHcQfrAzQkF4ZCxM2DkEbyY85J4EYcZ8EjT5ZCU8ozB727TDdodbfXet5GkGau2RQ'); const derivedPk = hardenedHDKey.deriveChild(0, true).deriveChild(0).deriveChild(0); @@ -75,22 +75,22 @@ describe('Keychain', function suite() { }); it('should get hardened DIP9FeatureHDKey', function () { const hardenedHDKey = keychain.getHardenedDIP9FeatureHDKey(); - const pk2 = keychain.getKeyForPath(`m/9'/1'`); + const pk2 = keychain.getForPath(`m/9'/1'`).key; expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); }); it('should get key for path', () => { const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); - const keyForChild = keychain2.getKeyForPath(0); + const keyForChild = keychain2.getForPath(0).key; expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); }); it('should add keys to watched keys', ()=>{ - const keys = [keychain.getKeyForPath(0), keychain.getKeyForPath(1), keychain.getKeyForPath(2)]; + const keys = [keychain.getForPath(0).key, keychain.getForPath(1).key, keychain.getForPath(2).key]; expect(keychain.addKeysToWatchedKeys(keys)).to.equal(3); }); it('should get watched keys', function () { const watchedKeys = keychain.getWatchedKeys(); - let expectedWatchedKeys = [keychain.getKeyForPath(0), keychain.getKeyForPath(1), keychain.getKeyForPath(2)]; + let expectedWatchedKeys = [keychain.getForPath(0), keychain.getForPath(1), keychain.getForPath(2)]; expect(watchedKeys).to.deep.equal(expectedWatchedKeys); }); it('should get watched addresses', function () { @@ -112,13 +112,22 @@ describe('Keychain', function suite() { expect(watchedPubKeys).to.deep.equal(expectedWatchedPubKeys) }); it('should remove a key from watched keys', function () { - let watchedKeys = [keychain.getKeyForPath(0), keychain.getKeyForPath(1), keychain.getKeyForPath(2)]; + let watchedKeys = [keychain.getForPath(0), keychain.getForPath(1), keychain.getForPath(2)]; expect(keychain.removeKeysToWatchedKeys(watchedKeys[1])).to.equal(1); expect(keychain.getWatchedKeys().length).to.equal(2); const updatedWatchedKeys = keychain.getWatchedKeys(); const expectedUpdatedWatchedKeys = [watchedKeys[0], watchedKeys[2]] expect(updatedWatchedKeys.map((key)=>key.toString())).to.deep.equal(expectedUpdatedWatchedKeys.map((key)=>key.toString())); }); + it('should get address for path', function (){ + const address0_1 = keychain.getAddressForPath(1); + expect(address0_1.toString()).to.equal('yhFX5rseJPitV45HUCaa9haeGHtLuooBaq') + }) + it('should mark address as used', function () { + const address0_0 = keychain.getAddressForPath(0); + keychain.markAddressAsUsed(address0_0); + expect(keychain.issuedAddress.get(0).isUsed).to.equal(true) + }); }); describe('Keychain - HDPublicKey', function suite(){ let hdpubKeyChain; @@ -127,14 +136,19 @@ describe('Keychain - HDPublicKey', function suite(){ HDPublicKey: new Dashcore.HDPublicKey(hdPublicKey), network: 'testnet' }); - expect(hdpubKeyChain.network.toString()).to.equal('testnet'); + // As the HDPublicKey starts with xpub, it's livenet and should take priority over our network being set. + expect(hdpubKeyChain.network.toString()).to.equal('livenet'); expect(hdpubKeyChain.keyChainId).to.equal('kc5059442d66'); expect(hdpubKeyChain.getRootKey().toString()).to.equal(hdPublicKey); }); it('should derivate', function () { - const key0_1 = hdpubKeyChain.getKeyForPath(1); - expect(key0_1.publicKey.toAddress(hdpubKeyChain.network).toString()).to.equal('yYxgMZG9f4GoS53CpoJHuHDsMnUUf1ezZv') + const key0_1 = hdpubKeyChain.getForPath(1); + expect(key0_1.publicKey.toAddress(hdpubKeyChain.network).toString()).to.equal('XoL5LcBiDWcj6L7fFwytsFoX5Vz7BVXw9w') }); + it('should get address for path', function (){ + const address0_1 = hdpubKeyChain.getAddressForPath(2); + expect(address0_1.toString()).to.equal('XwAzpxQKbgebaLiadq1c6rDeFJ4FKPUufy') + }) }) describe('Keychain - single privateKey', function suite() { this.timeout(10000); @@ -144,10 +158,10 @@ describe('Keychain - single privateKey', function suite() { const pkKeyChain = new KeyChain({ privateKey, network }); expect(pkKeyChain.network).to.equal(network); expect(pkKeyChain.rootKeyType).to.equal('privateKey'); - expect(pkKeyChain.rootKey).to.equal(privateKey); + expect(pkKeyChain.rootKey.toString()).to.equal(privateKey); const expectedException1 = 'Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'; - expect(() => pkKeyChain.getKeyForPath()).to.throw(expectedException1); + expect(() => pkKeyChain.getForPath()).to.throw(expectedException1); }); it('should get private key', () => { const privateKey = Dashcore.PrivateKey().toString(); diff --git a/src/types/KeyChain/methods/addKeysToWatchedKeys.js b/src/types/KeyChain/methods/addKeysToWatchedKeys.js deleted file mode 100644 index 75c12bbb..00000000 --- a/src/types/KeyChain/methods/addKeysToWatchedKeys.js +++ /dev/null @@ -1,34 +0,0 @@ -const { - util: { buffer: BufferUtil }, -} = require('@dashevo/dashcore-lib'); -const { sha256 } = require('../../../utils/crypto'); - -function addKeysToWatchedKeys(keys) { - if (!Array.isArray(keys)) { - return this.addKeysToWatchedKeys([keys]); - } - let added = 0; - - keys.forEach((key) => { - let keyId; - if (key.depth) { - const keyDepth = Number(key.depth); - // eslint-disable-next-line no-underscore-dangle - const keyIndex = BufferUtil.integerFromBuffer(key._buffers.childIndex); - // eslint-disable-next-line no-underscore-dangle - const keyParentFingerPrint = BufferUtil.integerFromBuffer(key._buffers.parentFingerPrint); - keyId = `${keyDepth}-${keyParentFingerPrint}/${keyIndex}`; - } else { - keyId = sha256(key.toString()).slice(0, 5).toString('hex'); - } - - if (this.watchedKeys.has(keyId)) { - throw new Error('Key is already watched'); - } - this.watchedKeys.set(keyId, key); - added += 1; - }); - - return added; -} -module.exports = addKeysToWatchedKeys; diff --git a/src/types/KeyChain/methods/getForAddress.js b/src/types/KeyChain/methods/getForAddress.js new file mode 100644 index 00000000..4b8a01dd --- /dev/null +++ b/src/types/KeyChain/methods/getForAddress.js @@ -0,0 +1,12 @@ +function getForAddress(address) { + const searchResult = [...this.issuedPaths.entries()] + .find(([, el]) => el.address.toString() === address.toString()); + + if (!searchResult) { + return null; + } + const [path] = searchResult; + return this.getForPath(path); +} + +module.exports = getForAddress; diff --git a/src/types/KeyChain/methods/getForPath.js b/src/types/KeyChain/methods/getForPath.js new file mode 100644 index 00000000..ccb20fea --- /dev/null +++ b/src/types/KeyChain/methods/getForPath.js @@ -0,0 +1,27 @@ +function getForPath(path, opts = {}) { + if (!['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType)) { + if (path === 0 || path === '0') return this.getRootKey(); + throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); + } + + if (this.issuedPaths.has(path)) { + return this.issuedPaths.get(path); + } + + const key = this.rootKey.derive(path); + const isUsed = (opts && opts.isUsed !== undefined) ? opts.isUsed : false; + const isWatched = (opts && opts.isWatched !== undefined) ? opts.isWatched : false; + + const data = { + path, + key, + isUsed, + isWatched, + address: key.publicKey.toAddress(this.network), + }; + + this.issuedPaths.set(path, data); + return data; +} + +module.exports = getForPath; diff --git a/src/types/KeyChain/methods/getKeyForPath.js b/src/types/KeyChain/methods/getKeyForPath.js deleted file mode 100644 index ae79ca28..00000000 --- a/src/types/KeyChain/methods/getKeyForPath.js +++ /dev/null @@ -1,8 +0,0 @@ -function getKeyForPath(path) { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType)) { - throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); - } - return this.rootKey.derive(path); -} - -module.exports = getKeyForPath; diff --git a/src/types/KeyChain/methods/getWatchedAddresses.js b/src/types/KeyChain/methods/getWatchedAddresses.js index 9f43bce8..81543291 100644 --- a/src/types/KeyChain/methods/getWatchedAddresses.js +++ b/src/types/KeyChain/methods/getWatchedAddresses.js @@ -1,14 +1,7 @@ -function getWatchedAddresses(opts = {}) { - const keys = this.getWatchedKeys(); - - const mapKeyToAddress = (key) => { - const network = opts.network || key.network || 'testnet'; - return key.toAddress - ? key.toAddress(network).toString() - : key.publicKey.toAddress(network).toString(); - }; - - return keys.map(mapKeyToAddress); +function getWatchedAddresses() { + return [...this.issuedPaths.entries()] + .filter(([, el]) => el.isWatched === true) + .map(([, el]) => el.address.toString()); } module.exports = getWatchedAddresses; diff --git a/src/types/KeyChain/methods/getWatchedKeys.js b/src/types/KeyChain/methods/getWatchedKeys.js deleted file mode 100644 index ad55b420..00000000 --- a/src/types/KeyChain/methods/getWatchedKeys.js +++ /dev/null @@ -1,5 +0,0 @@ -function getWatchedKeys() { - return Array.from(this.watchedKeys.values()); -} - -module.exports = getWatchedKeys; diff --git a/src/types/KeyChain/methods/getWatchedPublicKeys.js b/src/types/KeyChain/methods/getWatchedPublicKeys.js deleted file mode 100644 index 1f6e52cd..00000000 --- a/src/types/KeyChain/methods/getWatchedPublicKeys.js +++ /dev/null @@ -1,6 +0,0 @@ -function getWatchedPublicKeys() { - const keys = this.getWatchedKeys(); - return keys.map((key) => key.publicKey.toString()); -} - -module.exports = getWatchedPublicKeys; diff --git a/src/types/KeyChain/methods/markAddressAsUsed.js b/src/types/KeyChain/methods/markAddressAsUsed.js new file mode 100644 index 00000000..034ec5ff --- /dev/null +++ b/src/types/KeyChain/methods/markAddressAsUsed.js @@ -0,0 +1,8 @@ +function markAddressAsUsed(address) { + const searchResult = [...this.issuedPaths.entries()] + .find(([, el]) => el.address.toString() === address.toString()); + const [, addressData] = searchResult; + addressData.isUsed = true; + this.maybeLookAhead(); +} +module.exports = markAddressAsUsed; diff --git a/src/types/KeyChain/methods/maybeLookAhead.js b/src/types/KeyChain/methods/maybeLookAhead.js new file mode 100644 index 00000000..47c5b828 --- /dev/null +++ b/src/types/KeyChain/methods/maybeLookAhead.js @@ -0,0 +1,83 @@ +function maybeLookAhead() { + const { lookAheadOpts } = this; + const generatedPaths = {}; + + const usedPaths = [...this.issuedPaths.entries()] + .filter(([, el]) => el.isUsed === true) + .map(([path]) => path); + + const sortedUsedPathByBase = {}; + + usedPaths + .forEach((usedPath) => { + const splitted = usedPath.split('/'); + // Removes the index to sort which and how many base path has been generated + const basePath = splitted.splice(0, splitted.length - 1).join('/'); + if (!sortedUsedPathByBase[basePath]) sortedUsedPathByBase[basePath] = []; + sortedUsedPathByBase[basePath].push(usedPath); + }); + + const lastUsedIndexes = {}; + const lastGeneratedIndexes = {}; + + Object + .entries(lookAheadOpts.paths) + .forEach(([basePath]) => { + lastUsedIndexes[basePath] = -1; + lastGeneratedIndexes[basePath] = -1; + generatedPaths[basePath] = 0; + }); + + Object + .entries(sortedUsedPathByBase) + .forEach(([basePath, basePaths]) => { + // Sorting by index is also needed as the user might have manually issue a key + // and set it up to watched or used outside of lookAhead bounds + const sortedBasePaths = basePaths.sort((a, b) => a.split('/').splice(-1) - b.split('/').splice(-1)); + + let prevIndex; + sortedBasePaths.forEach((path) => { + const addressData = this.issuedPaths.get(path); + + const currentIndex = parseInt(path.split('/').splice(-1), 10); + if (prevIndex !== undefined && prevIndex !== currentIndex - 1) { + // FIXME: May be we should generate ? + throw new Error('Addresses are expected to be contiguous'); + } + + if (addressData.isUsed) { + lastUsedIndexes[basePath] = currentIndex; + } + + lastGeneratedIndexes[basePath] = currentIndex; + prevIndex = currentIndex; + }); + }); + + const isWatched = lookAheadOpts.isWatched || false; + + Object + .entries(lastGeneratedIndexes) + .forEach(([basePath]) => { + const lastUsedAndLastGenGap = lastGeneratedIndexes[basePath] - lastUsedIndexes[basePath]; + const pathAmountToGenerate = lookAheadOpts.paths[basePath] - lastUsedAndLastGenGap; + + if (pathAmountToGenerate > 0) { + const lastIndex = lastGeneratedIndexes[basePath]; + const lastIndexToGenerate = lastIndex + pathAmountToGenerate; + + if (lastIndexToGenerate > lastIndex) { + for ( + let index = lastIndex + 1; + index <= lastIndexToGenerate; + index += 1) { + generatedPaths[basePath] += 1; + this.getForPath(`${basePath}/${index}`, { isWatched }); + } + } + } + }); + + return generatedPaths; +} +module.exports = maybeLookAhead; diff --git a/src/types/KeyChain/methods/removeKeysToWatchedKeys.js b/src/types/KeyChain/methods/removeKeysToWatchedKeys.js deleted file mode 100644 index 4db729fa..00000000 --- a/src/types/KeyChain/methods/removeKeysToWatchedKeys.js +++ /dev/null @@ -1,27 +0,0 @@ -const { - util: { buffer: BufferUtil }, -} = require('@dashevo/dashcore-lib'); - -function removeKeysToWatchedKeys(keys) { - if (!Array.isArray(keys)) { - return this.removeKeysToWatchedKeys([keys]); - } - let removed = 0; - - keys.forEach((key) => { - const keyDepth = Number(key.depth); - // eslint-disable-next-line no-underscore-dangle - const keyIndex = BufferUtil.integerFromBuffer(key._buffers.childIndex); - // eslint-disable-next-line no-underscore-dangle - const keyParentFingerPrint = BufferUtil.integerFromBuffer(key._buffers.parentFingerPrint); - const keyId = `${keyDepth}-${keyParentFingerPrint}/${keyIndex}`; - if (!this.watchedKeys.has(keyId)) { - throw new Error('Key is not watched'); - } - this.watchedKeys.delete(keyId); - removed += 1; - }); - - return removed; -} -module.exports = removeKeysToWatchedKeys; diff --git a/src/types/KeyChainStore/KeyChainStore.js b/src/types/KeyChainStore/KeyChainStore.js index fc0d14b7..fd5c24df 100644 --- a/src/types/KeyChainStore/KeyChainStore.js +++ b/src/types/KeyChainStore/KeyChainStore.js @@ -1,13 +1,16 @@ class KeyChainStore { constructor() { - this.chains = new Map(); + this.keyChains = new Map(); this.walletKeyChainId = null; + this.masterKeyChainId = null; + this.accountKeyChains = new Map(); } } KeyChainStore.prototype.addKeyChain = require('./methods/addKeyChain'); KeyChainStore.prototype.getKeyChain = require('./methods/getKeyChain'); KeyChainStore.prototype.getKeyChains = require('./methods/getKeyChains'); -KeyChainStore.prototype.getWalletKeyChain = require('./methods/getWalletKeyChain'); +KeyChainStore.prototype.makeChildKeyChainStore = require('./methods/makeChildKeyChainStore'); +KeyChainStore.prototype.getMasterKeyChain = require('./methods/getMasterKeyChain'); module.exports = KeyChainStore; diff --git a/src/types/KeyChainStore/KeyChainStore.spec.js b/src/types/KeyChainStore/KeyChainStore.spec.js index d2eba2a4..b32737bb 100644 --- a/src/types/KeyChainStore/KeyChainStore.spec.js +++ b/src/types/KeyChainStore/KeyChainStore.spec.js @@ -7,23 +7,23 @@ describe('KeyChainStore', function suite() { let keyChainsStore; let hdPrivateKey = new HDPrivateKey() let hdPublicKey = new HDPrivateKey().hdPublicKey - let keyChain = new KeyChain(hdPrivateKey) - let keyChainPublic = new KeyChain(hdPublicKey) - let walletKeyChain = new KeyChain(new HDPrivateKey()); + let keyChain = new KeyChain({HDPrivateKey: hdPrivateKey}) + let keyChainPublic = new KeyChain({HDPublicKey: hdPublicKey}) + let walletKeyChain = new KeyChain({HDPrivateKey:new HDPrivateKey()}); it('should create a KeyChainStore', () => { keyChainsStore = new KeyChainsStore(); expect(keyChainsStore).to.exist; - expect(keyChainsStore.chains).to.be.a('Map') + expect(keyChainsStore.keyChains).to.be.a('Map') }); it('should be able to add a keyChain', function () { keyChainsStore.addKeyChain(keyChain) - expect(keyChainsStore.chains.has(keyChain.keyChainId)).to.equal(true); + expect(keyChainsStore.keyChains.has(keyChain.keyChainId)).to.equal(true); keyChainsStore.addKeyChain(keyChainPublic) - expect(keyChainsStore.chains.has(keyChainPublic.keyChainId)).to.equal(true); + expect(keyChainsStore.keyChains.has(keyChainPublic.keyChainId)).to.equal(true); }); - it('should allow to specify a specific wallet keychain', function () { - keyChainsStore.addKeyChain(walletKeyChain, { isWalletKeyChain: true }); - expect(keyChainsStore.chains.has(walletKeyChain.keyChainId)).to.equal(true); + it('should allow to specify a specific master keychain', function () { + keyChainsStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }); + expect(keyChainsStore.keyChains.has(walletKeyChain.keyChainId)).to.equal(true); }); it('should get all keyChains', function () { const keyChains = keyChainsStore.getKeyChains() @@ -33,9 +33,15 @@ describe('KeyChainStore', function suite() { const requestedKeychain = keyChainsStore.getKeyChain(keyChainPublic.keyChainId); expect(requestedKeychain).to.equal(keyChainPublic); }) - it('should get a wallet keychain', function () { - const requestedWalletKeyChain = keyChainsStore.getWalletKeyChain(); + it('should get a master keychain', function () { + const requestedWalletKeyChain = keyChainsStore.getMasterKeyChain(); expect(requestedWalletKeyChain).to.equal(walletKeyChain); }); + it('should make a child key chain store', function () { + const childKeyChainStore = keyChainsStore.makeChildKeyChainStore('m/0') + expect(childKeyChainStore).to.exist; + expect(childKeyChainStore.keyChains).to.be.a('Map') + expect(childKeyChainStore.getMasterKeyChain().rootKeyType).to.be.equal(HDPrivateKey.name) + }); }); diff --git a/src/types/KeyChainStore/methods/addKeyChain.js b/src/types/KeyChainStore/methods/addKeyChain.js index 45a8fa19..62689343 100644 --- a/src/types/KeyChainStore/methods/addKeyChain.js +++ b/src/types/KeyChainStore/methods/addKeyChain.js @@ -1,11 +1,14 @@ function addKeyChain(keychain, opts = {}) { - if (this.chains.has(keychain.keyChainId)) { + if (this.keyChains.has(keychain.keyChainId)) { throw new Error('Trying to add already existing keyChain'); } - this.chains.set(keychain.keyChainId, keychain); - if (opts && opts.isWalletKeyChain && !this.walletKeyChainId) { - this.walletKeyChainId = keychain.keyChainId; + this.keyChains.set(keychain.keyChainId, keychain); + + if (opts) { + if (opts.isMasterKeyChain && !this.masterKeyChainId) { + this.masterKeyChainId = keychain.keyChainId; + } } } diff --git a/src/types/KeyChainStore/methods/getKeyChain.js b/src/types/KeyChainStore/methods/getKeyChain.js index f6794da2..d964ba39 100644 --- a/src/types/KeyChainStore/methods/getKeyChain.js +++ b/src/types/KeyChainStore/methods/getKeyChain.js @@ -1,5 +1,5 @@ function getKeyChain(keyChainId) { - return this.chains.get(keyChainId); + return this.keyChains.get(keyChainId); } module.exports = getKeyChain; diff --git a/src/types/KeyChainStore/methods/getKeyChains.js b/src/types/KeyChainStore/methods/getKeyChains.js index c83673a6..4e0e1616 100644 --- a/src/types/KeyChainStore/methods/getKeyChains.js +++ b/src/types/KeyChainStore/methods/getKeyChains.js @@ -1,5 +1,5 @@ function getKeyChains() { - return Array.from(this.chains.values()); + return Array.from(this.keyChains.values()); } module.exports = getKeyChains; diff --git a/src/types/KeyChainStore/methods/getMasterKeyChain.js b/src/types/KeyChainStore/methods/getMasterKeyChain.js new file mode 100644 index 00000000..512d6a28 --- /dev/null +++ b/src/types/KeyChainStore/methods/getMasterKeyChain.js @@ -0,0 +1,6 @@ +function getMasterKeyChain() { + const keyChainId = this.masterKeyChainId; + return this.keyChains.get(keyChainId); +} + +module.exports = getMasterKeyChain; diff --git a/src/types/KeyChainStore/methods/getWalletKeyChain.js b/src/types/KeyChainStore/methods/getWalletKeyChain.js deleted file mode 100644 index 3c8c2140..00000000 --- a/src/types/KeyChainStore/methods/getWalletKeyChain.js +++ /dev/null @@ -1,6 +0,0 @@ -function getWalletKeyChain() { - const keyChainId = this.walletKeyChainId; - return this.chains.get(keyChainId); -} - -module.exports = getWalletKeyChain; diff --git a/src/types/KeyChainStore/methods/makeChildKeyChainStore.js b/src/types/KeyChainStore/methods/makeChildKeyChainStore.js new file mode 100644 index 00000000..ac44faf7 --- /dev/null +++ b/src/types/KeyChainStore/methods/makeChildKeyChainStore.js @@ -0,0 +1,19 @@ +const { Networks } = require('@dashevo/dashcore-lib'); +const { BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../../../CONSTANTS'); +const KeyChain = require('../../KeyChain/KeyChain'); + +function makeChildKeyChainStore(path, opts) { + const masterKeyChain = this.getMasterKeyChain(); + if (!masterKeyChain) throw new Error('Requires a master keychain to be added first.'); + + const childKeyChainStore = new this.constructor(); + const keyChainOpts = { network: masterKeyChain.network, ...opts }; + + // Accessing the type from getKeyForPath would behave on browser differently due to mangling. + keyChainOpts[masterKeyChain.rootKeyType] = this.getMasterKeyChain().getForPath(path).key; + const childKeyChain = new KeyChain(keyChainOpts); + childKeyChainStore.addKeyChain(childKeyChain, { isMasterKeyChain: true }); + return childKeyChainStore; +} + +module.exports = makeChildKeyChainStore; diff --git a/src/utils/bip44/getBIP44AccountPath.js b/src/utils/bip44/getBIP44AccountPath.js new file mode 100644 index 00000000..55890e02 --- /dev/null +++ b/src/utils/bip44/getBIP44AccountPath.js @@ -0,0 +1,11 @@ +const { Networks } = require('@dashevo/dashcore-lib'); +const { BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../../CONSTANTS'); + +function getBIP44AccountPath(accountIndex, network = 'testnet') { + const path = (network === Networks.livenet.toString()) + ? `${BIP44_LIVENET_ROOT_PATH}/${accountIndex}'` + : `${BIP44_TESTNET_ROOT_PATH}/${accountIndex}'`; + + return path; +} +module.exports = getBIP44AccountPath; diff --git a/tests/functional/keys.spec.js b/tests/functional/keys.spec.js new file mode 100644 index 00000000..ebe0f756 --- /dev/null +++ b/tests/functional/keys.spec.js @@ -0,0 +1,359 @@ +const { expect } = require('chai'); +const { HDPrivateKey, HDPublicKey, Mnemonic } = require('@dashevo/dashcore-lib'); +const { KeyChain, KeyChainStore, CONSTANTS: { BIP44_ADDRESS_GAP, BIP44_TESTNET_ROOT_PATH } } = require('../../src'); +const getBIP44AccountPath = require("../../src/utils/bip44/getBIP44AccountPath"); + +const mnemonic = new Mnemonic('one dumb media liquid zero apple jeans nurse click busy cannon number'); + +const DPFriendReceivingHDPrivateKey = new HDPrivateKey('tprv8ZgxMBicQKsPfALquMaAd23GpFgRB7oJVa3pWPjLDf6rAhyNcttncn7hbALux3SUsohPuviHDJfC7LjZQgZjHhWsP1RYGx3vjkWuzXuZ9WZ'); +const DPFriendHDPublicKey = new HDPublicKey('tpubD6NzVbkrYhZ4XLcVv7LhkVJRkZVUy185BeeAoBD94uDoHTNMupQFGkAMgAkPKy6mb3CMSKmqMdJ6JMm4gy7tcfBADWRuiqLoGHtktaPc8Ep'); +describe('Keys - Functional', ()=>{ + describe('simple usage', ()=>{ + let walletKeyChainStore; + let walletKeyChain; + it('should be able to setup a walletKeyChain to a walletKeyChainStore ', function () { + walletKeyChainStore = new KeyChainStore(); + walletKeyChain = new KeyChain({ + network: 'testnet', + mnemonic + }); + expect(walletKeyChain.rootKeyType).to.equal(HDPrivateKey.name); + expect(walletKeyChain.network).to.equal('testnet'); + expect(walletKeyChain.rootKey.toString()).to.equal('tprv8ZgxMBicQKsPeu48xtibzy9daEN1arEjRCe6iSZp2rXxV8pk27SPYvEyYirgMJnS8FdJREBddHW3nGckS9PT284hPVGCfDoUMiXxhaSXQvn'); + + const walletKeyChain2 = new KeyChain({ + network: 'livenet', + mnemonic + }); + expect(walletKeyChain2.rootKey.toString()).to.equal('xprv9s21ZrQH143K45pcJKs6qKXeG6woMLCj5eiyr29MYt3UhY5f2k6e3AsXdYh2LwQ7kp6XR8ZsTvvFKR51Jw3WD4o6rr3tzs5RScnYFs46Apa'); + + walletKeyChainStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }); + expect(walletKeyChainStore.getMasterKeyChain()).to.deep.equal(walletKeyChain) + }); + it('should get any accountKeyChainStore and its accountKeyChain', function () { + const accountKeyChainStore0 = walletKeyChainStore.makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet')); + const accountKeyChain0 = accountKeyChainStore0.getMasterKeyChain(); + expect(accountKeyChain0.rootKey.toString()).to.equal('tprv8gNcoB29rES5PBtjviTp3iK4Vu4BA4rrxsU6gVtjzRwnSDhAQVx5WH32U6Z76Cmge1eBrfaaku9tJonAStBPDvYn9bRQEFi5d4nU9TZ5KK7') + + const accountKeyChainStore1 = walletKeyChainStore.makeChildKeyChainStore(getBIP44AccountPath(1, 'testnet')); + const accountKeyChain1 = accountKeyChainStore1.getMasterKeyChain(); + expect(accountKeyChain1.rootKey.toString()).to.equal('tprv8gNcoB29rES5PR9P3uJixRXtB7VjamA9QM6gKXmw8gxoGVbqfJsi1U6A92gpdegjAYx4PJ5HLzB33MdPZnpsq6ejYcnb5YjfZsADNrDmebA') + }); + + it('should get any address', function () { + const accountKeyChain0 = walletKeyChainStore + .makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet')) + .getMasterKeyChain(); + + const accountExternalAddress0 = accountKeyChain0.getForPath(`m/0/0`).address; + expect(accountExternalAddress0.toString()).to.equal('yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj') + const accountInternalAddress0 = accountKeyChain0.getForPath(`m/1/0`).address; + expect(accountInternalAddress0.toString()).to.equal('yjcnQuzZwhrFnUDA2Kj4GbtTjB7qpZaqq6') + }); + it('should get the signing key for an address', function () { + const accountKeyChain0 = walletKeyChainStore + .makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet')) + .getMasterKeyChain(); + + const accountExternalAddress0 = accountKeyChain0.getForPath(`m/0/0`).address; + + const signingKey = accountKeyChain0.getForAddress(accountExternalAddress0).key; + expect(signingKey.toString()).to.equal('tprv8jCCNzw9WdTAroytWTx4nJcWgykYLB9bnL8MWk62yJW5WSoznEoUVNzZtFPYTHQURxiAHh3yLQi5v91jtbW7AxDfu14wy78V59spYG5ETAc') + expect(signingKey.privateKey.toString()).to.equal('bdf5982ff67da261a31451933636825021e67be9289c9ec522087788f5593098') + }); + }) + describe('Gapped usage with watching and marking of address', ()=>{ + const walletKeyChainStore = new KeyChainStore(); + const walletKeyChain = new KeyChain({ + network: 'testnet', + mnemonic, + }); + walletKeyChainStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }) + + let accountKeyChain0; + it('should set keychain with gap', function () { + const lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP + } + } + accountKeyChain0 = walletKeyChainStore + .makeChildKeyChainStore( + getBIP44AccountPath(0, 'testnet'), + { lookAheadOpts } + ) + .getMasterKeyChain(); + expect(accountKeyChain0.lookAheadOpts).to.deep.equal({isWatched:true, ...lookAheadOpts}) + expect(BIP44_ADDRESS_GAP).to.equal(20) + }); + + it('should have 20 internal and external set as issued address', function () { + expect(Array.from(accountKeyChain0.issuedPaths.keys())).to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19', 'm/1/0', + 'm/1/1', 'm/1/2', 'm/1/3', + 'm/1/4', 'm/1/5', 'm/1/6', + 'm/1/7', 'm/1/8', 'm/1/9', + 'm/1/10', 'm/1/11', 'm/1/12', + 'm/1/13', 'm/1/14', 'm/1/15', + 'm/1/16', 'm/1/17', 'm/1/18', + 'm/1/19' + ]) + }); + it('should have 20 internal and external to watched address', function () { + expect(accountKeyChain0.getWatchedAddresses()).to.deep.equal([ + 'yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj', + 'yZA33e7ojAvegoCELThDzt1Ywnpf86CDtc', + 'yZhFqYxNyvD4J4ySQzfCfLkGG3k9wnSx9C', + 'yfZ2NijF39BH11dLEzSZJhYSZSoeUHhKv2', + 'ydYVMsNn6QzzivjyqdqgRPT1yLh81ycZ3W', + 'yXd5TLmqBmPLQSpdGBwgB7JB34JBmWkWUp', + 'yaHEgNjWHXHFirCTZQkRkSiGhz6xaWN7he', + 'yWdHf3g4FhCfNbP6LoZTi12jy1u7hh5iZ9', + 'yj6WzZLtT1Nby9dsDNo988QzqAchjfz6cw', + 'yifEMSD9SQTu9tmyft4d3aTvQh82afxkdK', + 'yaXCzdyL4LCui8SffErsPSr7dU6jV3zftj', + 'yPfH7yfbsHkqSezzMfDbReyLaw1hcwtxHw', + 'ybmmVgnfpzsEVwk269vsBgkZdgBUbkbxcb', + 'ygDCyp8yndRq9bNZetuDLzC3uByuf2aH3a', + 'ycwrVbzQy8PPpaJME9uutAjy8vAuAKpNuF', + 'yjd8ThFK8oCK5TKk7inUtUWh2AtNTtcJyu', + 'yYcb4K5aba3a96WNxoeoaqRgfXdz8uTAtP', + 'ydVPiyNEvFqNWgdpXiavRrkm1NoJexw9op', + 'yQjqbHEcK2b8Tw18j4aMnLJWrBM3hMd2jY', + 'yMindyX74LaHdkdxGemyxjPWgmdaPAQHV5', + 'yjcnQuzZwhrFnUDA2Kj4GbtTjB7qpZaqq6', + 'ycMG3EvpsaodChznzoW6d6TK2AJCXCofvK', + 'yeBmB9zZm54xG13qPe4uouBee1P41NPoFG', + 'yMc5SbTnRwuoNyx4hpBkJ2yULdYG7cmPrR', + 'yQgnwDHQNm3zXY24MpSbxpkhmsYfTCV2uf', + 'yazGoXCSRj3d7H7y8D9FBo34KN1rxWRTxj', + 'yMNPrtNdAD5nET98tVqUHU4j64oAuwEdZT', + 'ySHYJjCXe2q5qv4WqDHssYBzhq1MUwNC94', + 'yi5LCMKMGGv8ELhec8hJHQzD7tgVZka74T', + 'yNKFzqLWQyQNoTCuzayLgzLtAkVr1Cr97A', + 'yRgg5gBFtzuvMUT8vZRUH5mJS3DV7Jr2r6', + 'yduxdiYt2GZEv77kdyuPWRwEnFJNnGPSWK', + 'yb45zLKCAEvMpKMmQ9k3vv81g63PDCe5Mk', + 'ySxrW1JpQwx9HN9cYGnsYX99Tie95KDJfK', + 'yVXV45Gybn7QpqoRVDC9cBSiQdTsJ4rHSE', + 'yaj2HUVuPzJdXcpDh8jYyeQ9TVLPAE2Caa', + 'yZBjfbFMih1tf2i7zABdeuJkeJ3PLD2ecY', + 'yW7iE3DmqpM1etQWquToC3LsSNGw8r3Z9r', + 'yasbGEiKerEZiYvQDB2YfDEqX8pzf4GBjm', + 'yivH7ouWmGkkpwHtXfQxYK3YM1ct15q75o' + ]); + }); + it('should mark address as used and generate as per gap', function () { + accountKeyChain0.markAddressAsUsed('yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj'); + expect(Array.from(accountKeyChain0.issuedPaths.keys())) + .to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19', 'm/1/0', + 'm/1/1', 'm/1/2', 'm/1/3', + 'm/1/4', 'm/1/5', 'm/1/6', + 'm/1/7', 'm/1/8', 'm/1/9', + 'm/1/10', 'm/1/11', 'm/1/12', + 'm/1/13', 'm/1/14', 'm/1/15', + 'm/1/16', 'm/1/17', 'm/1/18', + 'm/1/19', 'm/0/20' + ]) + expect(accountKeyChain0.getWatchedAddresses()).to.deep.equal([ + 'yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj', + 'yZA33e7ojAvegoCELThDzt1Ywnpf86CDtc', + 'yZhFqYxNyvD4J4ySQzfCfLkGG3k9wnSx9C', + 'yfZ2NijF39BH11dLEzSZJhYSZSoeUHhKv2', + 'ydYVMsNn6QzzivjyqdqgRPT1yLh81ycZ3W', + 'yXd5TLmqBmPLQSpdGBwgB7JB34JBmWkWUp', + 'yaHEgNjWHXHFirCTZQkRkSiGhz6xaWN7he', + 'yWdHf3g4FhCfNbP6LoZTi12jy1u7hh5iZ9', + 'yj6WzZLtT1Nby9dsDNo988QzqAchjfz6cw', + 'yifEMSD9SQTu9tmyft4d3aTvQh82afxkdK', + 'yaXCzdyL4LCui8SffErsPSr7dU6jV3zftj', + 'yPfH7yfbsHkqSezzMfDbReyLaw1hcwtxHw', + 'ybmmVgnfpzsEVwk269vsBgkZdgBUbkbxcb', + 'ygDCyp8yndRq9bNZetuDLzC3uByuf2aH3a', + 'ycwrVbzQy8PPpaJME9uutAjy8vAuAKpNuF', + 'yjd8ThFK8oCK5TKk7inUtUWh2AtNTtcJyu', + 'yYcb4K5aba3a96WNxoeoaqRgfXdz8uTAtP', + 'ydVPiyNEvFqNWgdpXiavRrkm1NoJexw9op', + 'yQjqbHEcK2b8Tw18j4aMnLJWrBM3hMd2jY', + 'yMindyX74LaHdkdxGemyxjPWgmdaPAQHV5', + 'yjcnQuzZwhrFnUDA2Kj4GbtTjB7qpZaqq6', + 'ycMG3EvpsaodChznzoW6d6TK2AJCXCofvK', + 'yeBmB9zZm54xG13qPe4uouBee1P41NPoFG', + 'yMc5SbTnRwuoNyx4hpBkJ2yULdYG7cmPrR', + 'yQgnwDHQNm3zXY24MpSbxpkhmsYfTCV2uf', + 'yazGoXCSRj3d7H7y8D9FBo34KN1rxWRTxj', + 'yMNPrtNdAD5nET98tVqUHU4j64oAuwEdZT', + 'ySHYJjCXe2q5qv4WqDHssYBzhq1MUwNC94', + 'yi5LCMKMGGv8ELhec8hJHQzD7tgVZka74T', + 'yNKFzqLWQyQNoTCuzayLgzLtAkVr1Cr97A', + 'yRgg5gBFtzuvMUT8vZRUH5mJS3DV7Jr2r6', + 'yduxdiYt2GZEv77kdyuPWRwEnFJNnGPSWK', + 'yb45zLKCAEvMpKMmQ9k3vv81g63PDCe5Mk', + 'ySxrW1JpQwx9HN9cYGnsYX99Tie95KDJfK', + 'yVXV45Gybn7QpqoRVDC9cBSiQdTsJ4rHSE', + 'yaj2HUVuPzJdXcpDh8jYyeQ9TVLPAE2Caa', + 'yZBjfbFMih1tf2i7zABdeuJkeJ3PLD2ecY', + 'yW7iE3DmqpM1etQWquToC3LsSNGw8r3Z9r', + 'yasbGEiKerEZiYvQDB2YfDEqX8pzf4GBjm', + 'yivH7ouWmGkkpwHtXfQxYK3YM1ct15q75o', + 'yNti2kmrf8rXDvqB8ydSbwpkfuVEb2HUrQ' + ]); + }); + }); + describe('DashPay - ContactKeyChain', ()=>{ + const keyChainStore = new KeyChainStore(); + const walletKeyChain = new KeyChain({ + network: 'testnet', + mnemonic, + }); + const lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP + } + } + keyChainStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }) + const accountKeyChainStore = keyChainStore + .makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet'), {lookAheadOpts}) + + it('should add a receiving keychain', function () { + const lookAheadOpts = { + paths:{ + 'm/0': 10, + 'm/1': 10 + }, + } + const contactReceivingKeyChain = new KeyChain({ + network: 'testnet', + HDPrivateKey: DPFriendReceivingHDPrivateKey, + lookAheadOpts + }); + accountKeyChainStore.addKeyChain(contactReceivingKeyChain) + expect(Array.from(contactReceivingKeyChain.issuedPaths.keys())).to.deep.equal([ + 'm/0/0', + 'm/0/1', + 'm/0/2', + 'm/0/3', + 'm/0/4', + 'm/0/5', + 'm/0/6', + 'm/0/7', + 'm/0/8', + 'm/0/9', + 'm/1/0', + 'm/1/1', + 'm/1/2', + 'm/1/3', + 'm/1/4', + 'm/1/5', + 'm/1/6', + 'm/1/7', + 'm/1/8', + 'm/1/9' + ]) + expect(contactReceivingKeyChain.getWatchedAddresses()).to.deep.equal([ + 'ygXopQ36SWcK56fy6rD6qiogZcRv3HxMRw', + 'ygeagGpXd8tWFCNSYjYs1PnGKVSgvg5ndr', + 'yddpt9d3JttZeRHtuXMmp2pqiBmZRpf5bM', + 'yg61bJDLee3VRErvm5zVi2f7bBPQkVHSUo', + 'yT1sPoqaWf4Yu4Ddm3huGc72DBixLsQVD5', + 'yPvPxvoWFn1KCGRmtqwAzHBaS8UDq2kj6U', + 'yNNmU45y9tmgdq8BLtZzNAYxSPovhnTnkA', + 'yS7LoFDvrNfa6uNqNr1rKRAUU7HRKux3rm', + 'yjGhDPFwKWvBH5RqhimJUeKfh4723SyZSw', + 'yUMVKzNBo29N9G8fYWpGH4t7ka7Pxoxp1g', + 'yjSZFPSMMjW94gxky8GzNrDmibvCZsHeev', + 'yVEJExicNKEwAQR6hZpoKVjDpS6KQTXxRT', + 'yae21tB2umekfyNSCRiv2efCuC5AuJxUjx', + 'yWKMoKkMs9cY5ZrG1T8TUv5XJngw32H3JP', + 'yXpTUWPFzAVgfNS31AH5SgNfcGm2uhhXej', + 'yfQdpiNbnPDPY7qm91R9jx865fdpgsQUt5', + 'yQpGvLZ1wg5rBTt6BANb5pHj7nVm1yGsom', + 'yUxmwPFvtSiCXySoQXNY5xhTU4RZrJdMLE', + 'yUHCHewhGZrpDgJEiA3QeZqabBu53u7WBD', + 'yLzzpBayHs94ZDHukJL6KTzkFPvfyE2SZc' + ]) + }); + it('should add a sending keychain', function () { + const lookAheadOpts = { + paths:{ + 'm/0': 10, + 'm/1': 10 + }, + } + const contactSendingKeyChain = new KeyChain({ + network: 'testnet', + HDPublicKey: DPFriendHDPublicKey, + lookAheadOpts + }); + accountKeyChainStore.addKeyChain(contactSendingKeyChain) + + expect(Array.from(contactSendingKeyChain.issuedPaths.keys())).to.deep.equal([ + 'm/0/0', + 'm/0/1', + 'm/0/2', + 'm/0/3', + 'm/0/4', + 'm/0/5', + 'm/0/6', + 'm/0/7', + 'm/0/8', + 'm/0/9', + 'm/1/0', + 'm/1/1', + 'm/1/2', + 'm/1/3', + 'm/1/4', + 'm/1/5', + 'm/1/6', + 'm/1/7', + 'm/1/8', + 'm/1/9' + ]) + expect(contactSendingKeyChain.getWatchedAddresses()).to.deep.equal([ + 'ycjHcm5BTWNgucy5dZGy8ELRvLptxZKvkr', + 'yMNYq1LoSLCH6Bta4T7hbDdNmsLNqi47Ua', + 'ybca2FKwup3y2fsfVz9KABx81JtDH7F4zX', + 'yQrtYuoWfKv6BKY1xBqQVLnGvf4up3TsWo', + 'yWhMUL65dFoPbCZyW286QAZCjdSmLVcqQa', + 'yanMQ79PN5nTK6UwhpdUpiPAkRYoAe5UbZ', + 'yYq1LRkdvDxF7Spk8KPyUmPmHdbf9qmW4E', + 'yVuNQ412G8JvGYmdamwcHPNTmUqzWemJMx', + 'ycodktzX9pYAE29vUs8bVQquAkVUbZVuv2', + 'ybPry3LB5RH4CabqqAjm3QkN9n7PtLQvzJ', + 'yUfPQaqcZ8cviuhrmvi8mU7XykYL3WfCse', + 'ySzzXYet9CdBCpYgGMBu2GKPbtjBhXkDoU', + 'yfz51NpWoR6JvnMhkB1RTKWaCvyXfmudio', + 'yVQ5CtUUSNLEHSzR1EJ1y8GNEWorCP8ape', + 'ycEzBg7S85Qk13CjbfJzmtDda8sQKQc33Z', + 'yVrZBeUEYGNw3jY7PX37zcGh7t1qzvYbJc', + 'yVtfJdLKxhyoEHAuhwDSdd1UZcHQuVZH6D', + 'yZbCr237LvakaR2UHW8aqiQztX9xXSWK4o', + 'ySw8JxoCuqvzb5AWV8ZY7RkSEvzzh2bpu2', + 'yRZ2WPrNeTHH1G7nFD63Su5qwhfuKMruLU' + ]) + }); + it('should get watched address from all keychain', function () { + const addresses = []; + for(let [keychainId, keychain] of accountKeyChainStore.keyChains){ + addresses.push(...keychain.getWatchedAddresses()) + } + expect(addresses.length).to.equal(80); + }); + }) +}); + From 95526da497d457e20a4c6ea34874175328d43aff Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 15 Nov 2021 05:45:50 +0100 Subject: [PATCH 14/40] tests: update tests --- src/types/KeyChain/KeyChain.spec.js | 63 ++++++++++--------- src/types/KeyChain/methods/getForPath.js | 12 +++- .../KeyChain/methods/getHardenedBIP44HDKey.js | 5 +- .../methods/getHardenedDIP9FeatureHDKey.js | 5 +- src/types/KeyChain/methods/maybeLookAhead.js | 1 + 5 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 3adf98ed..b804d18f 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -84,14 +84,19 @@ describe('Keychain', function suite() { const keyForChild = keychain2.getForPath(0).key; expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); }); - it('should add keys to watched keys', ()=>{ - const keys = [keychain.getForPath(0).key, keychain.getForPath(1).key, keychain.getForPath(2).key]; - expect(keychain.addKeysToWatchedKeys(keys)).to.equal(3); - }); - it('should get watched keys', function () { - const watchedKeys = keychain.getWatchedKeys(); - let expectedWatchedKeys = [keychain.getForPath(0), keychain.getForPath(1), keychain.getForPath(2)]; - expect(watchedKeys).to.deep.equal(expectedWatchedKeys); + it('should mark address watched and get watched addresses', function () { + const key0 = keychain.getForPath(0); + key0.isWatched = true; + const key1 = keychain.getForPath(1, { isWatched: true }); + const key2 = keychain.getForPath(2, { isWatched: true }); + + const watchedAddresses = keychain.getWatchedAddresses(); + let expectedWatchedAddresses = [ + keychain.getForPath(0).address.toString(), + keychain.getForPath(1).address.toString(), + keychain.getForPath(2).address.toString() + ]; + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); }); it('should get watched addresses', function () { const watchedAddresses = keychain.getWatchedAddresses(); @@ -102,31 +107,31 @@ describe('Keychain', function suite() { ] expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); }); - it('should get watched public keys', function () { - const watchedPubKeys = keychain.getWatchedPublicKeys(); - const expectedWatchedPubKeys = [ - '03e6ab8177a7ca2699da4f83ca3c27768fb88b70ae9d6bde1cba8de88355ccf199', - '0246a870b65153b98e453ff08f7198d06bce2a790286f44d90929aceafafa0673f', - '025ad16f78f67801e52abe5b512d66e3896d4c2fa3ca3150349437a5dd13519967' - ] - expect(watchedPubKeys).to.deep.equal(expectedWatchedPubKeys) - }); - it('should remove a key from watched keys', function () { - let watchedKeys = [keychain.getForPath(0), keychain.getForPath(1), keychain.getForPath(2)]; - expect(keychain.removeKeysToWatchedKeys(watchedKeys[1])).to.equal(1); - expect(keychain.getWatchedKeys().length).to.equal(2); - const updatedWatchedKeys = keychain.getWatchedKeys(); - const expectedUpdatedWatchedKeys = [watchedKeys[0], watchedKeys[2]] - expect(updatedWatchedKeys.map((key)=>key.toString())).to.deep.equal(expectedUpdatedWatchedKeys.map((key)=>key.toString())); + // it('should get watched public keys', function () { + // const watchedPubKeys = keychain.getWatchedPublicKeys(); + // const expectedWatchedPubKeys = [ + // '03e6ab8177a7ca2699da4f83ca3c27768fb88b70ae9d6bde1cba8de88355ccf199', + // '0246a870b65153b98e453ff08f7198d06bce2a790286f44d90929aceafafa0673f', + // '025ad16f78f67801e52abe5b512d66e3896d4c2fa3ca3150349437a5dd13519967' + // ] + // expect(watchedPubKeys).to.deep.equal(expectedWatchedPubKeys) + // }); + it('should remove an address from watched addresses', function () { + const data0 = keychain.getForPath(0, { isWatched: false }); + const data1 = keychain.getForPath(1); + const data2 = keychain.getForPath(2); + data2.isWatched = false; + + expect(keychain.getWatchedAddresses().length).to.equal(1); }); it('should get address for path', function (){ - const address0_1 = keychain.getAddressForPath(1); + const address0_1 = keychain.getForPath(1).address; expect(address0_1.toString()).to.equal('yhFX5rseJPitV45HUCaa9haeGHtLuooBaq') }) it('should mark address as used', function () { - const address0_0 = keychain.getAddressForPath(0); + const address0_0 = keychain.getForPath(0).address; keychain.markAddressAsUsed(address0_0); - expect(keychain.issuedAddress.get(0).isUsed).to.equal(true) + expect(keychain.issuedPaths.get(0).isUsed).to.equal(true) }); }); describe('Keychain - HDPublicKey', function suite(){ @@ -142,11 +147,11 @@ describe('Keychain - HDPublicKey', function suite(){ expect(hdpubKeyChain.getRootKey().toString()).to.equal(hdPublicKey); }); it('should derivate', function () { - const key0_1 = hdpubKeyChain.getForPath(1); + const key0_1 = hdpubKeyChain.getForPath(1).key; expect(key0_1.publicKey.toAddress(hdpubKeyChain.network).toString()).to.equal('XoL5LcBiDWcj6L7fFwytsFoX5Vz7BVXw9w') }); it('should get address for path', function (){ - const address0_1 = hdpubKeyChain.getAddressForPath(2); + const address0_1 = hdpubKeyChain.getForPath(2).address; expect(address0_1.toString()).to.equal('XwAzpxQKbgebaLiadq1c6rDeFJ4FKPUufy') }) }) diff --git a/src/types/KeyChain/methods/getForPath.js b/src/types/KeyChain/methods/getForPath.js index ccb20fea..e16e93f9 100644 --- a/src/types/KeyChain/methods/getForPath.js +++ b/src/types/KeyChain/methods/getForPath.js @@ -4,15 +4,23 @@ function getForPath(path, opts = {}) { throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); } + let data; if (this.issuedPaths.has(path)) { - return this.issuedPaths.get(path); + data = this.issuedPaths.get(path); + if (opts && opts.isWatched !== undefined && data.isWatched !== opts.isWatched) { + data.isWatched = opts.isWatched; + } + if (opts && opts.isUsed !== undefined && data.isUsed !== opts.isUsed) { + data.isUsed = opts.isUsed; + } + return data; } const key = this.rootKey.derive(path); const isUsed = (opts && opts.isUsed !== undefined) ? opts.isUsed : false; const isWatched = (opts && opts.isWatched !== undefined) ? opts.isWatched : false; - const data = { + data = { path, key, isUsed, diff --git a/src/types/KeyChain/methods/getHardenedBIP44HDKey.js b/src/types/KeyChain/methods/getHardenedBIP44HDKey.js index 4c45a08f..48da61c1 100644 --- a/src/types/KeyChain/methods/getHardenedBIP44HDKey.js +++ b/src/types/KeyChain/methods/getHardenedBIP44HDKey.js @@ -2,11 +2,10 @@ const { BIP44_TESTNET_ROOT_PATH, BIP44_LIVENET_ROOT_PATH } = require('../../../C /** * Return a safier root keys to derivate from - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys * @return {HDPrivateKey|HDPublicKey} */ -function getHardenedBIP44HDKey(type = 'HDPrivateKey') { +function getHardenedBIP44HDKey() { const pathRoot = (this.network.toString() === 'testnet') ? BIP44_TESTNET_ROOT_PATH : BIP44_LIVENET_ROOT_PATH; - return this.getKeyForPath(pathRoot, type); + return this.getForPath(pathRoot).key; } module.exports = getHardenedBIP44HDKey; diff --git a/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js b/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js index 7a77803a..422c9fab 100644 --- a/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js +++ b/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js @@ -2,11 +2,10 @@ const { DIP9_LIVENET_ROOT_PATH, DIP9_TESTNET_ROOT_PATH } = require('../../../CON /** * Return a safier root path to derivate from - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys * @return {HDPrivateKey|HDPublicKey} */ -function getHardenedDIP9FeatureHDKey(type = 'HDPrivateKey') { +function getHardenedDIP9FeatureHDKey() { const pathRoot = (this.network.toString() === 'testnet') ? DIP9_TESTNET_ROOT_PATH : DIP9_LIVENET_ROOT_PATH; - return this.getKeyForPath(pathRoot, type); + return this.getForPath(pathRoot).key; } module.exports = getHardenedDIP9FeatureHDKey; diff --git a/src/types/KeyChain/methods/maybeLookAhead.js b/src/types/KeyChain/methods/maybeLookAhead.js index 47c5b828..f5a18697 100644 --- a/src/types/KeyChain/methods/maybeLookAhead.js +++ b/src/types/KeyChain/methods/maybeLookAhead.js @@ -2,6 +2,7 @@ function maybeLookAhead() { const { lookAheadOpts } = this; const generatedPaths = {}; + if (Object.keys(lookAheadOpts.paths).length === 0) return; const usedPaths = [...this.issuedPaths.entries()] .filter(([, el]) => el.isUsed === true) .map(([path]) => path); From 9475f3620b188f4fe26f5a407241b08a4ae31691 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 15 Nov 2021 14:01:37 +0100 Subject: [PATCH 15/40] feat: update local usage --- src/CONSTANTS.js | 2 + .../methods/getAddressesToSync.js | 4 +- src/types/Account/Account.js | 23 ++++++- .../Account/methods/createTransaction.js | 2 +- .../Account/methods/createTransaction.spec.js | 2 +- src/types/Account/methods/encrypt.spec.js | 2 +- src/types/Account/methods/generateAddress.js | 67 +++++++++++++------ src/types/Account/methods/getPrivateKeys.js | 2 +- src/types/Account/methods/sign.js | 2 +- src/types/Identities/Identities.js | 2 +- src/types/KeyChain/methods/maybeLookAhead.js | 11 +-- src/types/Wallet/Wallet.js | 3 +- src/types/Wallet/methods/fromAddress.js | 2 +- src/types/Wallet/methods/fromHDPrivateKey.js | 2 +- src/types/Wallet/methods/fromHDPublicKey.js | 2 +- src/types/Wallet/methods/fromMnemonic.js | 2 +- src/types/Wallet/methods/fromPrivateKey.js | 2 +- src/types/Wallet/methods/fromPublicKey.js | 2 +- 18 files changed, 91 insertions(+), 43 deletions(-) diff --git a/src/CONSTANTS.js b/src/CONSTANTS.js index 87edf80f..ac41f49d 100644 --- a/src/CONSTANTS.js +++ b/src/CONSTANTS.js @@ -46,7 +46,9 @@ const CONSTANTS = { PRIVATEKEY: 'privateKey', // TODO: DEPRECATE. SINGLE_ADDRESS: 'single_address', + // TODO: DEPRECATE. HDWALLET: 'hdwallet', + HDPRIVATE: 'hdprivate', HDPUBLIC: 'hdpublic', }, // List of account function and properties that can be injected in a plugin diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js index 83c58c4c..e87d86e1 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js @@ -1,5 +1,5 @@ -module.exports = function getAddressesToSync(opts = {}) { +module.exports = function getAddressesToSync() { return this.keyChainStore.getKeyChains() - .map((keychain) => keychain.getWatchedAddresses(opts)) + .map((keychain) => keychain.getWatchedAddresses()) .reduce((pre, cur) => pre.concat(cur)); }; diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 574d28e5..cc9ba923 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -1,7 +1,7 @@ const _ = require('lodash'); const EventEmitter = require('events'); const logger = require('../../logger'); -const { WALLET_TYPES } = require('../../CONSTANTS'); +const { WALLET_TYPES, BIP44_ADDRESS_GAP } = require('../../CONSTANTS'); const { is } = require('../../utils'); const EVENTS = require('../../EVENTS'); const Wallet = require('../Wallet/Wallet'); @@ -136,7 +136,26 @@ class Account extends EventEmitter { throw new Error(`Invalid wallet type ${this.walletType}`); } - this.keyChainStore = wallet.keyChainStore; + let keyChainStorePath = this.index; + const keyChainStoreOpts = {}; + + if (this.walletType.includes([ + WALLET_TYPES.HDWALLET, + WALLET_TYPES.HDPUBLIC, + WALLET_TYPES.PRIVATEKEY]) + ) { + keyChainStorePath = this.BIP44PATH; + keyChainStoreOpts.lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP, + }, + }; + } + + this.keyChainStore = wallet + .keyChainStore + .makeChildKeyChainStore(keyChainStorePath, keyChainStoreOpts); this.cacheTx = (opts.cacheTx) ? opts.cacheTx : defaultOptions.cacheTx; this.cacheBlockHeaders = (opts.cacheBlockHeaders) diff --git a/src/types/Account/methods/createTransaction.js b/src/types/Account/methods/createTransaction.js index 078de7fd..71334ed4 100644 --- a/src/types/Account/methods/createTransaction.js +++ b/src/types/Account/methods/createTransaction.js @@ -117,7 +117,7 @@ function createTransaction(opts = {}) { } }); try { - const signedTx = this.keyChainStore.getWalletKeyChain().sign( + const signedTx = this.keyChainStore.getMasterKeyChain().sign( tx, transformedPrivateKeys, crypto.Signature.SIGHASH_ALL, diff --git a/src/types/Account/methods/createTransaction.spec.js b/src/types/Account/methods/createTransaction.spec.js index 88198a7e..8b78fc27 100644 --- a/src/types/Account/methods/createTransaction.spec.js +++ b/src/types/Account/methods/createTransaction.spec.js @@ -63,7 +63,7 @@ describe('Account - createTransaction', function suite() { } }, keyChainStore: { - getWalletKeyChain:() => { + getMasterKeyChain:() => { return { sign: (tx, privateKeys) => tx.sign(privateKeys), } diff --git a/src/types/Account/methods/encrypt.spec.js b/src/types/Account/methods/encrypt.spec.js index 11d5ed8d..facc317e 100644 --- a/src/types/Account/methods/encrypt.spec.js +++ b/src/types/Account/methods/encrypt.spec.js @@ -29,7 +29,7 @@ describe('Account - encrypt', function suite() { const secret = 'secret'; it('should encrypt extPubKey with aes', () => { - const extPubKey = account.keyChainStore.getWalletKeyChain().getKeyForPath(derivationPath, 'HDPublicKey').toString(); + const extPubKey = account.keyChainStore.getMasterKeyChain().getForPath(derivationPath).key.toString(); const encryptedExtPubKey = account.encrypt('aes', extPubKey, secret).toString(); const bytes = CryptoJS.AES.decrypt(encryptedExtPubKey, secret); const decrypted = bytes.toString(CryptoJS.enc.Utf8); diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index 851d0ff1..d4eba6a4 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -13,50 +13,75 @@ function generateAddress(path, isWatchedAddress = true) { let index = 0; let privateKey; let address; + let keyData; const { network } = this; switch (this.walletType) { case WALLET_TYPES.ADDRESS: - address = this.keyChainStore.getWalletKeyChain().rootKey; + address = this.keyChainStore.getMasterKeyChain().rootKey; + if (isWatchedAddress) { + this.keyChainStore.issuedPaths.set(0, { + path: 0, + address, + isUsed: false, + isWatched: true, + }); + } break; case WALLET_TYPES.PUBLICKEY: - address = this.keyChainStore.getWalletKeyChain().rootKey.toAddress(network).toString(); + // eslint-disable-next-line no-case-declarations + const { rootKey } = this.keyChainStore.getMasterKeyChain(); + address = rootKey.toAddress(network).toString(); if (isWatchedAddress) { - this.keyChainStore.getWalletKeyChain() - .addKeysToWatchedKeys(this.keyChainStore.getWalletKeyChain().rootKey); + this.keyChainStore.issuedPaths.set(0, { + key: rootKey, + path: 0, + address, + isUsed: false, + isWatched: true, + }); } break; + case WALLET_TYPES.HDPRIVATE: case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring index = parseInt(path.toString().split('/')[5], 10); - privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(path); - if (isWatchedAddress) { - this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); - } - address = privateKey.publicKey.toAddress(network).toString(); + keyData = this.keyChainStore + .getMasterKeyChain() + .getForPath(path, { isWatched: isWatchedAddress }); + address = keyData.address; + privateKey = keyData.key; break; case WALLET_TYPES.HDPUBLIC: index = parseInt(path.toString().split('/')[5], 10); - privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(index); - if (isWatchedAddress) { - this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); - } - address = privateKey.publicKey.toAddress(network).toString(); + // eslint-disable-next-line no-case-declarations + keyData = this.keyChainStore + .getMasterKeyChain() + .getForPath(path, { isWatched: isWatchedAddress }); + privateKey = keyData.key; + address = keyData.address; break; // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: - privateKey = this.keyChainStore.getWalletKeyChain().rootKey; + privateKey = this.keyChainStore.getMasterKeyChain().rootKey; + address = privateKey.publicKey.toAddress(network).toString(); + if (isWatchedAddress) { - this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); + this.keyChainStore + .issuedPaths.set(0, { + key: privateKey, + path: 0, + address, + isUsed: false, + isWatched: true, + }); } - address = privateKey.publicKey.toAddress(network).toString(); break; default: - privateKey = this.keyChainStore.getWalletKeyChain().getKeyForPath(path.toString()); - if (isWatchedAddress) { - this.keyChainStore.getWalletKeyChain().addKeysToWatchedKeys(privateKey); - } + privateKey = this.keyChainStore + .getMasterKeyChain() + .getKeyForPath(path.toString(), { isWatched: isWatchedAddress }).key; address = privateKey.publicKey.toAddress(network).toString(); } diff --git a/src/types/Account/methods/getPrivateKeys.js b/src/types/Account/methods/getPrivateKeys.js index c455bd78..3a3ccb50 100644 --- a/src/types/Account/methods/getPrivateKeys.js +++ b/src/types/Account/methods/getPrivateKeys.js @@ -18,7 +18,7 @@ function getPrivateKeys(addressList) { paths.forEach((path) => { const address = self.store.wallets[walletId].addresses[subwallet][path]; if (addresses.includes(address.address)) { - const privateKey = self.keyChainStore.getWalletKeyChain().getKeyForPath(path); + const privateKey = self.keyChainStore.getMasterKeyChain().getForPath(path).key; privKeys = privKeys.concat([privateKey]); } }); diff --git a/src/types/Account/methods/sign.js b/src/types/Account/methods/sign.js index b1c32a76..33756ea2 100644 --- a/src/types/Account/methods/sign.js +++ b/src/types/Account/methods/sign.js @@ -30,5 +30,5 @@ module.exports = function sign(object, privateKeys = [], sigType) { }); } - return this.keyChainStore.getWalletKeyChain().sign(object, privateKeys, sigType); + return this.keyChainStore.getMasterKeyChain().sign(object, privateKeys, sigType); }; diff --git a/src/types/Identities/Identities.js b/src/types/Identities/Identities.js index 0f2215e3..f457fe9f 100644 --- a/src/types/Identities/Identities.js +++ b/src/types/Identities/Identities.js @@ -10,7 +10,7 @@ class Identities { this.storage = wallet.storage; - this.keyChain = wallet.keyChainStore.getWalletKeyChain(); + this.keyChain = wallet.keyChainStore.getMasterKeyChain(); } } diff --git a/src/types/KeyChain/methods/maybeLookAhead.js b/src/types/KeyChain/methods/maybeLookAhead.js index f5a18697..5f77adfb 100644 --- a/src/types/KeyChain/methods/maybeLookAhead.js +++ b/src/types/KeyChain/methods/maybeLookAhead.js @@ -1,8 +1,11 @@ function maybeLookAhead() { const { lookAheadOpts } = this; - const generatedPaths = {}; + const generatedPaths = []; + + if (Object.keys(lookAheadOpts.paths).length === 0) { + return generatedPaths; + } - if (Object.keys(lookAheadOpts.paths).length === 0) return; const usedPaths = [...this.issuedPaths.entries()] .filter(([, el]) => el.isUsed === true) .map(([path]) => path); @@ -26,7 +29,6 @@ function maybeLookAhead() { .forEach(([basePath]) => { lastUsedIndexes[basePath] = -1; lastGeneratedIndexes[basePath] = -1; - generatedPaths[basePath] = 0; }); Object @@ -72,8 +74,7 @@ function maybeLookAhead() { let index = lastIndex + 1; index <= lastIndexToGenerate; index += 1) { - generatedPaths[basePath] += 1; - this.getForPath(`${basePath}/${index}`, { isWatched }); + generatedPaths.push(this.getForPath(`${basePath}/${index}`, { isWatched })); } } } diff --git a/src/types/Wallet/Wallet.js b/src/types/Wallet/Wallet.js index bffff130..c2eb9d74 100644 --- a/src/types/Wallet/Wallet.js +++ b/src/types/Wallet/Wallet.js @@ -3,6 +3,8 @@ const { PrivateKey, Networks } = require('@dashevo/dashcore-lib'); const EventEmitter = require('events'); const _ = require('lodash'); const Storage = require('../Storage/Storage'); +const KeyChainStore = require('../KeyChainStore/KeyChainStore'); +const KeyChain = require('../KeyChain/KeyChain'); const { generateNewMnemonic, } = require('../../utils'); @@ -173,7 +175,6 @@ class Wallet extends EventEmitter { this.accounts = []; this.interface = opts.interface; - // Suppressed global require to avoid cyclic dependencies // eslint-disable-next-line global-require const Identities = require('../Identities/Identities'); diff --git a/src/types/Wallet/methods/fromAddress.js b/src/types/Wallet/methods/fromAddress.js index e24064f2..44cdc055 100644 --- a/src/types/Wallet/methods/fromAddress.js +++ b/src/types/Wallet/methods/fromAddress.js @@ -14,5 +14,5 @@ module.exports = function fromAddress(address) { const keyChain = new KeyChain({ address }); this.keyChainStore = new KeyChainStore(); - this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromHDPrivateKey.js b/src/types/Wallet/methods/fromHDPrivateKey.js index 3976f708..90b6f1a9 100644 --- a/src/types/Wallet/methods/fromHDPrivateKey.js +++ b/src/types/Wallet/methods/fromHDPrivateKey.js @@ -18,5 +18,5 @@ module.exports = function fromHDPrivateKey(hdPrivateKey) { const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); this.keyChainStore = new KeyChainStore(); - this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromHDPublicKey.js b/src/types/Wallet/methods/fromHDPublicKey.js index 97c2bc89..aa6a8407 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.js +++ b/src/types/Wallet/methods/fromHDPublicKey.js @@ -17,5 +17,5 @@ module.exports = function fromHDPublicKey(_hdPublicKey) { const keyChain = new KeyChain({ HDPublicKey: this.HDPublicKey }); this.keyChainStore = new KeyChainStore(); - this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromMnemonic.js b/src/types/Wallet/methods/fromMnemonic.js index ede7a240..fe9a1cdb 100644 --- a/src/types/Wallet/methods/fromMnemonic.js +++ b/src/types/Wallet/methods/fromMnemonic.js @@ -21,5 +21,5 @@ module.exports = function fromMnemonic(mnemonic) { this.keyChainStore = new KeyChainStore(); const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); - this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPrivateKey.js b/src/types/Wallet/methods/fromPrivateKey.js index d6abb9a5..36f8c275 100644 --- a/src/types/Wallet/methods/fromPrivateKey.js +++ b/src/types/Wallet/methods/fromPrivateKey.js @@ -15,5 +15,5 @@ module.exports = function fromPrivateKey(privateKey) { const keyChain = new KeyChain({ privateKey }); this.keyChainStore = new KeyChainStore(); - this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPublicKey.js b/src/types/Wallet/methods/fromPublicKey.js index 907a0600..ed00b585 100644 --- a/src/types/Wallet/methods/fromPublicKey.js +++ b/src/types/Wallet/methods/fromPublicKey.js @@ -15,5 +15,5 @@ module.exports = function fromPublicKey(publicKey) { const keyChain = new KeyChain({ publicKey }); this.keyChainStore = new KeyChainStore(); - this.keyChainStore.addKeyChain(keyChain, { isWalletKeyChain: true }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; From afb46ef54485bda84d93bf007fff8f7f3f056c32 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 15 Nov 2021 14:13:51 +0100 Subject: [PATCH 16/40] tests: update tests --- .../methods/getAddressesToSync.spec.js | 16 ++++++++-------- src/types/Wallet/Wallet.spec.js | 14 +++++++------- src/types/Wallet/methods/fromAddress.spec.js | 4 ++-- .../Wallet/methods/fromHDPrivateKey.spec.js | 2 +- src/types/Wallet/methods/fromHDPublicKey.spec.js | 4 ++-- src/types/Wallet/methods/fromMnemonic.spec.js | 8 ++++---- src/types/Wallet/methods/fromPrivateKey.spec.js | 4 ++-- src/types/Wallet/methods/fromPublicKey.spec.js | 4 ++-- src/types/Wallet/methods/fromSeed.spec.js | 4 ++-- 9 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js index 07338a6c..4a6f9e6c 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js @@ -14,13 +14,13 @@ const keychainHDPrivate1 = new KeyChain({HDPrivateKey: hdprivateKey1}); const keychainHDPublic1 = new KeyChain({HDPublicKey: hdpublicKey1}); const keychainStorePrivateKeyWallet = new KeyChainStore(); -keychainStorePrivateKeyWallet.addKeyChain(keychainPrivate1, { isWalletKeyChain: true}); -keychainPrivate1.addKeysToWatchedKeys(keychainPrivate1.getRootKey()) +keychainStorePrivateKeyWallet.addKeyChain(keychainPrivate1, { isMasterKeyChain: true}); +keychainPrivate1.getForPath(0, { isWatched: true }) const keychainStoreHDPrivateKeyWallet = new KeyChainStore(); -keychainStoreHDPrivateKeyWallet.addKeyChain(keychainHDPrivate1, { isWalletKeyChain: true}); -keychainHDPrivate1.addKeysToWatchedKeys(keychainHDPrivate1.getKeyForPath(`m/44'/1'/0/0`)) -keychainHDPrivate1.addKeysToWatchedKeys(keychainHDPrivate1.getKeyForPath(`m/44'/1'/0/1`)) +keychainStoreHDPrivateKeyWallet.addKeyChain(keychainHDPrivate1, { isMasterKeyChain: true}); +keychainHDPrivate1.getForPath(`m/0/0`, { isWatched: true }) +keychainHDPrivate1.getForPath(`m/0/1`, { isWatched: true }) const mockedStore1 = { @@ -146,10 +146,10 @@ const mockSelfIndex0 = { describe('TransactionSyncStreamWorker#getAddressesToSync', function suite() { it('should correctly fetch addresses to sync', async () => { - const addressesIndex0 = getAddressesToSync.call(mockSelfIndex0, { network: 'testnet'} ); + const addressesIndex0 = getAddressesToSync.call(mockSelfIndex0); expect(addressesIndex0).to.deep.equal([ - 'yWRLbb7nke6NWGBCZf4GAPn1UcuSSdTr4g', - 'yP7ZfDzfFR4FJATHcfP8XDac1mLdqbscs5' + 'Xpkr9M3DP8RgcWw4SHUW75PYtmU1Lh5Ss2', + 'Xp1kwhXoUVHKRKmoXt3dB4i4KhryHSYjtW' ]) const addressesIndex2 = getAddressesToSync.call(mockSelfPrivateKeyType ); diff --git a/src/types/Wallet/Wallet.spec.js b/src/types/Wallet/Wallet.spec.js index e187a140..a6d416bf 100644 --- a/src/types/Wallet/Wallet.spec.js +++ b/src/types/Wallet/Wallet.spec.js @@ -23,7 +23,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); - expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -50,7 +50,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -77,7 +77,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -95,7 +95,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('HDPublicKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPublicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -112,7 +112,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('privateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('privateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -132,7 +132,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('publicKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('publicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -149,7 +149,7 @@ describe('Wallet - class', function suite() { expect(wallet2.plugins).to.be.deep.equal({}); expect(wallet2.accounts).to.be.deep.equal([]); expect(wallet2.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet2.keyChainStore.getWalletKeyChain().rootKeyType).to.be.deep.equal('publicKey'); + expect(wallet2.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('publicKey'); expect(wallet2.passphrase).to.be.deep.equal(null); expect(wallet2.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet2.injectDefaultPlugins).to.be.deep.equal(true); diff --git a/src/types/Wallet/methods/fromAddress.spec.js b/src/types/Wallet/methods/fromAddress.spec.js index 62cb1bd8..f0839308 100644 --- a/src/types/Wallet/methods/fromAddress.spec.js +++ b/src/types/Wallet/methods/fromAddress.spec.js @@ -19,7 +19,7 @@ describe('Wallet - fromAddress', function suite() { expect(self1.mnemonic).to.equal(null); expect(self1.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('address'); expect(keyChain.rootKey).to.equal(cR4t6ePublicKey.toAddress().toString()); @@ -29,7 +29,7 @@ describe('Wallet - fromAddress', function suite() { expect(self2.mnemonic).to.equal(null); expect(self2.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - const keyChain2 = self2.keyChainStore.getWalletKeyChain() + const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('address'); expect(keyChain.rootKey).to.equal(cR4t6ePublicKey.toAddress().toString()); }); diff --git a/src/types/Wallet/methods/fromHDPrivateKey.spec.js b/src/types/Wallet/methods/fromHDPrivateKey.spec.js index 1b3c5f2f..c947a076 100644 --- a/src/types/Wallet/methods/fromHDPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromHDPrivateKey.spec.js @@ -17,7 +17,7 @@ describe('Wallet - fromHDPrivateKey', function suite() { expect(self1.mnemonic).to.equal(null); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); }); diff --git a/src/types/Wallet/methods/fromHDPublicKey.spec.js b/src/types/Wallet/methods/fromHDPublicKey.spec.js index d58923ef..36177f78 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.spec.js +++ b/src/types/Wallet/methods/fromHDPublicKey.spec.js @@ -31,7 +31,7 @@ describe('Wallet - HDPublicKey', function suite() { expect(mockOpts1.HDPublicKey.toString()).to.equal(gatherTestnet.external.hdpubkey); expect(new Dashcore.HDPublicKey(mockOpts1.HDPublicKey)).to.equal(mockOpts1.HDPublicKey); - const keyChain = mockOpts1.keyChainStore.getWalletKeyChain() + const keyChain = mockOpts1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPublicKey'); expect(keyChain.rootKey).to.deep.equal(Dashcore.HDPublicKey(gatherTestnet.external.hdpubkey)); }); @@ -47,7 +47,7 @@ describe('Wallet - HDPublicKey', function suite() { expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - const keyChain = wallet1.keyChainStore.getWalletKeyChain() + const keyChain = wallet1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.be.deep.equal('HDPublicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); diff --git a/src/types/Wallet/methods/fromMnemonic.spec.js b/src/types/Wallet/methods/fromMnemonic.spec.js index fa9f374b..6ceebe2c 100644 --- a/src/types/Wallet/methods/fromMnemonic.spec.js +++ b/src/types/Wallet/methods/fromMnemonic.spec.js @@ -21,7 +21,7 @@ describe('Wallet - fromMnemonic', function suite() { expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); expect(keyChain.network.name).to.equal('livenet'); expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); @@ -32,7 +32,7 @@ describe('Wallet - fromMnemonic', function suite() { expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); - const keyChain2 = self2.keyChainStore.getWalletKeyChain() + const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.network.name).to.equal('testnet'); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); @@ -65,7 +65,7 @@ describe('Wallet - fromMnemonic - with passphrase', function suite() { expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); expect(keyChain.network.name).to.equal('livenet'); expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); @@ -82,7 +82,7 @@ describe('Wallet - fromMnemonic - with passphrase', function suite() { expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); - const keyChain2 = self2.keyChainStore.getWalletKeyChain() + const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); expect(keyChain2.network.name).to.equal('testnet'); expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); diff --git a/src/types/Wallet/methods/fromPrivateKey.spec.js b/src/types/Wallet/methods/fromPrivateKey.spec.js index cf7fa70c..222cc0e0 100644 --- a/src/types/Wallet/methods/fromPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromPrivateKey.spec.js @@ -16,7 +16,7 @@ describe('Wallet - fromPrivateKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self1.mnemonic).to.equal(null); expect(self1.privateKey).to.equal(cR4t6eFixture.privateKey); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('privateKey'); expect(keyChain.rootKey).to.equal(cR4t6eFixture.privateKey); @@ -25,7 +25,7 @@ describe('Wallet - fromPrivateKey', function suite() { expect(self2.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self2.mnemonic).to.equal(null); expect(self2.privateKey).to.equal(cR4t6eFixture.privateKey); - const keyChain2 = self2.keyChainStore.getWalletKeyChain() + const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('privateKey'); expect(keyChain2.rootKey).to.equal(cR4t6eFixture.privateKey); }); diff --git a/src/types/Wallet/methods/fromPublicKey.spec.js b/src/types/Wallet/methods/fromPublicKey.spec.js index eed8797c..68c4dbc6 100644 --- a/src/types/Wallet/methods/fromPublicKey.spec.js +++ b/src/types/Wallet/methods/fromPublicKey.spec.js @@ -18,7 +18,7 @@ describe('Wallet - fromPublicKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.PUBLICKEY); expect(self1.mnemonic).to.equal(null); expect(self1.publicKey).to.equal(cR4t6ePublicKey); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('publicKey'); expect(keyChain.rootKey).to.equal(cR4t6ePublicKey); @@ -27,7 +27,7 @@ describe('Wallet - fromPublicKey', function suite() { expect(self2.walletType).to.equal(WALLET_TYPES.PUBLICKEY); expect(self2.mnemonic).to.equal(null); expect(self2.publicKey).to.equal(cR4t6ePublicKey.toString()); - const keyChain2 = self2.keyChainStore.getWalletKeyChain() + const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('publicKey'); expect(keyChain2.rootKey).to.equal(cR4t6ePublicKey.toString()); }); diff --git a/src/types/Wallet/methods/fromSeed.spec.js b/src/types/Wallet/methods/fromSeed.spec.js index 5c3efc7a..289c9ce3 100644 --- a/src/types/Wallet/methods/fromSeed.spec.js +++ b/src/types/Wallet/methods/fromSeed.spec.js @@ -19,7 +19,7 @@ describe('Wallet - fromSeed', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(null); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - const keyChain = self1.keyChainStore.getWalletKeyChain() + const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); @@ -32,7 +32,7 @@ describe('Wallet - fromSeed', function suite() { expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(null); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - const keyChain2 = self2.keyChainStore.getWalletKeyChain() + const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); }); From a43369a53143346cffca47d278b1d5d8135b240f Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Thu, 18 Nov 2021 12:22:38 +0100 Subject: [PATCH 17/40] fix: integration issues --- src/types/Account/Account.js | 5 ++--- src/types/Account/methods/generateAddress.js | 8 ++++---- src/types/Account/methods/getAddress.js | 11 ++++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index cc9ba923..69b263a4 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -139,10 +139,10 @@ class Account extends EventEmitter { let keyChainStorePath = this.index; const keyChainStoreOpts = {}; - if (this.walletType.includes([ + if ([ WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC, - WALLET_TYPES.PRIVATEKEY]) + WALLET_TYPES.PRIVATEKEY].includes(this.walletType) ) { keyChainStorePath = this.BIP44PATH; keyChainStoreOpts.lookAheadOpts = { @@ -152,7 +152,6 @@ class Account extends EventEmitter { }, }; } - this.keyChainStore = wallet .keyChainStore .makeChildKeyChainStore(keyChainStorePath, keyChainStoreOpts); diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index d4eba6a4..19f71f6b 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -45,11 +45,11 @@ function generateAddress(path, isWatchedAddress = true) { case WALLET_TYPES.HDPRIVATE: case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring - index = parseInt(path.toString().split('/')[5], 10); + index = parseInt(path.toString().split('/')[2], 10); keyData = this.keyChainStore .getMasterKeyChain() .getForPath(path, { isWatched: isWatchedAddress }); - address = keyData.address; + address = keyData.address.toString(); privateKey = keyData.key; break; case WALLET_TYPES.HDPUBLIC: @@ -59,12 +59,12 @@ function generateAddress(path, isWatchedAddress = true) { .getMasterKeyChain() .getForPath(path, { isWatched: isWatchedAddress }); privateKey = keyData.key; - address = keyData.address; + address = keyData.address.toString(); break; // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: - privateKey = this.keyChainStore.getMasterKeyChain().rootKey; + privateKey = this.keyChainStore.getMasterKeyChain().rootKey.toString(); address = privateKey.publicKey.toAddress(network).toString(); if (isWatchedAddress) { diff --git a/src/types/Account/methods/getAddress.js b/src/types/Account/methods/getAddress.js index d4771dc7..ed89c24f 100644 --- a/src/types/Account/methods/getAddress.js +++ b/src/types/Account/methods/getAddress.js @@ -1,6 +1,6 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); -const getTypePathFromWalletType = (walletType, addressType = 'external', index, BIP44PATH) => { +const getTypePathFromWalletType = (walletType, addressType = 'external', accountIndex, addressIndex) => { let type; let path; @@ -8,11 +8,11 @@ const getTypePathFromWalletType = (walletType, addressType = 'external', index, switch (walletType) { case WALLET_TYPES.HDWALLET: type = addressType; - path = `${BIP44PATH}/${addressTypeIndex}/${index}`; + path = `m/${addressTypeIndex}/${addressIndex}`; break; case WALLET_TYPES.HDPUBLIC: type = 'external'; - path = `${BIP44PATH}/${addressTypeIndex}/${index}`; + path = `m/${addressTypeIndex}/${addressIndex}`; break; case WALLET_TYPES.PUBLICKEY: case WALLET_TYPES.ADDRESS: @@ -30,8 +30,9 @@ const getTypePathFromWalletType = (walletType, addressType = 'external', index, * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) * @return */ -function getAddress(index = 0, _type = 'external') { - const { type, path } = getTypePathFromWalletType(this.walletType, _type, index, this.BIP44PATH); +function getAddress(addressIndex = 0, _type = 'external') { + const accountIndex = this.index; + const { type, path } = getTypePathFromWalletType(this.walletType, _type, accountIndex, addressIndex); const { wallets } = this.storage.getStore(); const matchingTypeAddresses = wallets[this.walletId].addresses[type]; From 3d2ddbead368b1d01ed1f24073d3dc910c0dac15 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 23 Nov 2021 01:50:43 +0100 Subject: [PATCH 18/40] feat: introduced ChainStore as new primitives --- src/types/ChainStore/ChainStore.js | 42 +++++++++++++ .../ChainStore/methods/considerTransaction.js | 63 +++++++++++++++++++ src/types/ChainStore/methods/exportState.js | 46 ++++++++++++++ src/types/ChainStore/methods/getAddress.js | 5 ++ .../ChainStore/methods/getBlockHeader.js | 5 ++ .../ChainStore/methods/getInstantLock.js | 5 ++ .../ChainStore/methods/getTransaction.js | 5 ++ src/types/ChainStore/methods/importAddress.js | 18 ++++++ .../ChainStore/methods/importBlockHeader.js | 5 ++ .../ChainStore/methods/importInstantLock.js | 5 ++ src/types/ChainStore/methods/importState.js | 42 +++++++++++++ .../ChainStore/methods/importTransaction.js | 19 ++++++ 12 files changed, 260 insertions(+) create mode 100644 src/types/ChainStore/ChainStore.js create mode 100644 src/types/ChainStore/methods/considerTransaction.js create mode 100644 src/types/ChainStore/methods/exportState.js create mode 100644 src/types/ChainStore/methods/getAddress.js create mode 100644 src/types/ChainStore/methods/getBlockHeader.js create mode 100644 src/types/ChainStore/methods/getInstantLock.js create mode 100644 src/types/ChainStore/methods/getTransaction.js create mode 100644 src/types/ChainStore/methods/importAddress.js create mode 100644 src/types/ChainStore/methods/importBlockHeader.js create mode 100644 src/types/ChainStore/methods/importInstantLock.js create mode 100644 src/types/ChainStore/methods/importState.js create mode 100644 src/types/ChainStore/methods/importTransaction.js diff --git a/src/types/ChainStore/ChainStore.js b/src/types/ChainStore/ChainStore.js new file mode 100644 index 00000000..f63d08b3 --- /dev/null +++ b/src/types/ChainStore/ChainStore.js @@ -0,0 +1,42 @@ +/** + * Hold information relatives to a specific chains such as + * - blockheaders + * - transactions + * - instantlocks + * + * It also keep a state for watched addresses with their current balance and utxos. + */ + +class ChainStore { + constructor(networkIdentifier) { + this.network = networkIdentifier; + + this.state = { + fees: { + minRelay: -1, + }, + blockHeight: 0, + blockHeaders: new Map(), + transactions: new Map(), + instantLocks: new Map(), + addresses: new Map(), + }; + } +} + +ChainStore.prototype.considerTransaction = require('./methods/considerTransaction'); + +ChainStore.prototype.exportState = require('./methods/exportState'); +ChainStore.prototype.importState = require('./methods/importState'); + +ChainStore.prototype.getAddress = require('./methods/getAddress'); +ChainStore.prototype.getBlockHeader = require('./methods/getBlockHeader'); +ChainStore.prototype.getInstantLock = require('./methods/getInstantLock'); +ChainStore.prototype.getTransaction = require('./methods/getTransaction'); + +ChainStore.prototype.importAddress = require('./methods/importAddress'); +ChainStore.prototype.importBlockHeader = require('./methods/importBlockHeader'); +ChainStore.prototype.importInstantLock = require('./methods/importInstantLock'); +ChainStore.prototype.importTransaction = require('./methods/importTransaction'); + +module.exports = ChainStore; diff --git a/src/types/ChainStore/methods/considerTransaction.js b/src/types/ChainStore/methods/considerTransaction.js new file mode 100644 index 00000000..965c1a06 --- /dev/null +++ b/src/types/ChainStore/methods/considerTransaction.js @@ -0,0 +1,63 @@ +const { Transaction } = require('@dashevo/dashcore-lib'); +const { is } = require('../../../utils'); +const { FETCHED_CONFIRMED_TRANSACTION, UPDATED_ADDRESS } = require('../../../EVENTS'); + +const { Output } = Transaction; + +function considerTransaction(transactionHash) { + const { transaction } = this.getTransaction(transactionHash); + + const { inputs, outputs } = transaction; + let outputIndex = -1; + + const processedAddressesForTx = {}; + + [...inputs, ...outputs].forEach((element) => { + const isOutput = (element instanceof Output); + if (isOutput) outputIndex += 1; + + if (element.script) { + const address = element.script.toAddress(this.network).toString(); + const watchedAddress = this.getAddress(address); + + if (watchedAddress) { + // If the transactions has already been processed in a previous insertion, + // we can skip the processing now, it's important to do so as we might consider + // the same transaction multiple times (e.g: on address import) + if (watchedAddress.transactions.includes(transactionHash)) { + return; + } + + // We mark our address as affected so we update the tx later on + if (!processedAddressesForTx[watchedAddress.address]) { + processedAddressesForTx[watchedAddress.address] = watchedAddress; + } + + if (!isOutput) { + const vin = element; + const utxoKey = `${vin.prevTxId.toString('hex')}-${vin.outputIndex}`; + if (watchedAddress.utxos[utxoKey]) { + const previousOutput = watchedAddress.utxos[utxoKey]; + watchedAddress.balanceSat -= previousOutput.satoshis; + delete watchedAddress.utxos[utxoKey]; + } + } else { + const vout = element; + + const utxoKey = `${transaction.hash}-${outputIndex}`; + if (!watchedAddress.utxos[utxoKey]) { + watchedAddress.utxos[utxoKey] = vout.toJSON(); + watchedAddress.balanceSat += vout.satoshis; + } + } + } + } + }); + + // As the same address can have one or more inputs and one or more outputs in the same tx + // we update it's transactions array as last step of importing + Object.values(processedAddressesForTx).forEach((addressObject) => { + addressObject.transactions.push(transaction.hash); + }); +} +module.exports = considerTransaction; diff --git a/src/types/ChainStore/methods/exportState.js b/src/types/ChainStore/methods/exportState.js new file mode 100644 index 00000000..e82e9188 --- /dev/null +++ b/src/types/ChainStore/methods/exportState.js @@ -0,0 +1,46 @@ +function exportState() { + const { network, state } = this; + const { + fees, + blockHeight, + blockHeaders, + transactions, + instantLocks, + addresses, + } = state; + + const serializedState = { + network, + state: { + fees, + blockHeight, + blockHeaders: {}, + transactions: {}, + instantLocks: {}, + addresses: {}, + }, + }; + + [...blockHeaders.entries()].forEach(([blockHeaderHash, blockHeader]) => { + serializedState.state.blockHeaders[blockHeaderHash] = blockHeader.toString(); + }); + + [...transactions.entries()].forEach(([transactionHash, { transaction, metadata }]) => { + serializedState.state.transactions[transactionHash] = { + transaction: transaction.toString(), + metadata, + }; + }); + + [...instantLocks.entries()].forEach(([transactionHash, instantLock]) => { + serializedState.state.instantLocks[transactionHash] = instantLock.toString(); + }); + + [...addresses.entries()].forEach(([address, addressObj]) => { + serializedState.state.addresses[address] = addressObj; + }); + + return serializedState; +} + +module.exports = exportState; diff --git a/src/types/ChainStore/methods/getAddress.js b/src/types/ChainStore/methods/getAddress.js new file mode 100644 index 00000000..1076c9ed --- /dev/null +++ b/src/types/ChainStore/methods/getAddress.js @@ -0,0 +1,5 @@ +function getAddress(address) { + return this.state.addresses.get(address.toString()); +} + +module.exports = getAddress; diff --git a/src/types/ChainStore/methods/getBlockHeader.js b/src/types/ChainStore/methods/getBlockHeader.js new file mode 100644 index 00000000..be7d9c57 --- /dev/null +++ b/src/types/ChainStore/methods/getBlockHeader.js @@ -0,0 +1,5 @@ +function getBlockHeader(blockHeaderHash) { + return this.state.blockHeaders.get(blockHeaderHash); +} + +module.exports = getBlockHeader; diff --git a/src/types/ChainStore/methods/getInstantLock.js b/src/types/ChainStore/methods/getInstantLock.js new file mode 100644 index 00000000..52338065 --- /dev/null +++ b/src/types/ChainStore/methods/getInstantLock.js @@ -0,0 +1,5 @@ +function getInstantLock(transactionHash) { + return this.state.instantLocks.get(transactionHash); +} + +module.exports = getInstantLock; diff --git a/src/types/ChainStore/methods/getTransaction.js b/src/types/ChainStore/methods/getTransaction.js new file mode 100644 index 00000000..7f97e871 --- /dev/null +++ b/src/types/ChainStore/methods/getTransaction.js @@ -0,0 +1,5 @@ +function getTransaction(transactionHash) { + return this.state.transactions.get(transactionHash); +} + +module.exports = getTransaction; diff --git a/src/types/ChainStore/methods/importAddress.js b/src/types/ChainStore/methods/importAddress.js new file mode 100644 index 00000000..82809c83 --- /dev/null +++ b/src/types/ChainStore/methods/importAddress.js @@ -0,0 +1,18 @@ +function importAddress(address) { + if (this.state.addresses.has(address.toString())) throw new Error('Address is already inserted'); + + this.state.addresses.set(address.toString(), { + address: address.toString(), + transactions: [], + utxos: {}, + balanceSat: 0, + unconfirmedBalanceSat: 0, + }); + + // We need to consider all previous transactions + [...this.state.transactions].forEach(([transactionHash]) => { + this.considerTransaction(transactionHash); + }); +} + +module.exports = importAddress; diff --git a/src/types/ChainStore/methods/importBlockHeader.js b/src/types/ChainStore/methods/importBlockHeader.js new file mode 100644 index 00000000..9d13c064 --- /dev/null +++ b/src/types/ChainStore/methods/importBlockHeader.js @@ -0,0 +1,5 @@ +function importBlockHeader(blockHeader) { + this.state.blockHeaders.set(blockHeader.hash, blockHeader); +} + +module.exports = importBlockHeader; diff --git a/src/types/ChainStore/methods/importInstantLock.js b/src/types/ChainStore/methods/importInstantLock.js new file mode 100644 index 00000000..c39d5d49 --- /dev/null +++ b/src/types/ChainStore/methods/importInstantLock.js @@ -0,0 +1,5 @@ +function importInstantLock(instantLock) { + this.state.instantLocks.set(instantLock.txid, instantLock); +} + +module.exports = importInstantLock; diff --git a/src/types/ChainStore/methods/importState.js b/src/types/ChainStore/methods/importState.js new file mode 100644 index 00000000..ab0feb9a --- /dev/null +++ b/src/types/ChainStore/methods/importState.js @@ -0,0 +1,42 @@ +const { + BlockHeader, + Transaction, + InstantLock, +} = require('@dashevo/dashcore-lib'); + +function importState(state) { + const { + network, + state: { + fees, + blockHeight, + blockHeaders, + transactions, + instantLocks, + addresses, + }, + } = state; + + this.network = network; + + this.state.fees = fees; + this.state.blockHeight = blockHeight; + + // We actually do not import address state, but import the address itself + // Which will force processing tx for each added address, therefore we might want to do + // import address as the first process done + Object.values(addresses).forEach(({ address }) => { + this.importAddress(address); + }); + + Object.values(blockHeaders).forEach((serializedBlockHeader) => { + this.importBlockHeader(new BlockHeader(Buffer.from(serializedBlockHeader, 'hex'))); + }); + Object.values(transactions).forEach(({ transaction: serializedTransaction, metadata }) => { + this.importTransaction(new Transaction(Buffer.from(serializedTransaction, 'hex')), metadata); + }); + Object.values(instantLocks).forEach((serializedInstantLock) => { + this.importTransaction(new InstantLock(Buffer.from(serializedInstantLock, 'hex'))); + }); +} +module.exports = importState; diff --git a/src/types/ChainStore/methods/importTransaction.js b/src/types/ChainStore/methods/importTransaction.js new file mode 100644 index 00000000..11400952 --- /dev/null +++ b/src/types/ChainStore/methods/importTransaction.js @@ -0,0 +1,19 @@ +const { Transaction } = require('@dashevo/dashcore-lib'); + +function importTransaction(transaction, metadata = {}) { + // Even if transaction is a transaction object, if manglized, + // it might end up not being a correct instanceof internally. + const normalizedTransaction = new Transaction(transaction); + this.state.transactions.set(normalizedTransaction.hash, { + transaction: normalizedTransaction, + metadata: { + blockHash: metadata.blockHash || null, + height: metadata.height || null, + isInstantLocked: metadata.isInstantLocked || null, + isChainLocked: metadata.isChainLocked || null, + }, + }); + this.considerTransaction(normalizedTransaction.hash); +} + +module.exports = importTransaction; From eb52ecff39cb2ca2b13d01238363025a462525ca Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sat, 27 Nov 2021 21:10:32 +0100 Subject: [PATCH 19/40] feat: introduce ChainStorage --- src/types/ChainStore/ChainStore.js | 2 ++ src/types/ChainStore/methods/considerTransaction.js | 4 +++- src/types/ChainStore/methods/getAddresses.js | 5 +++++ src/types/ChainStore/methods/importAddress.js | 4 +++- src/types/ChainStore/methods/importTransaction.js | 2 +- 5 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/types/ChainStore/methods/getAddresses.js diff --git a/src/types/ChainStore/ChainStore.js b/src/types/ChainStore/ChainStore.js index f63d08b3..a153188f 100644 --- a/src/types/ChainStore/ChainStore.js +++ b/src/types/ChainStore/ChainStore.js @@ -30,6 +30,8 @@ ChainStore.prototype.exportState = require('./methods/exportState'); ChainStore.prototype.importState = require('./methods/importState'); ChainStore.prototype.getAddress = require('./methods/getAddress'); +ChainStore.prototype.getAddresses = require('./methods/getAddresses'); + ChainStore.prototype.getBlockHeader = require('./methods/getBlockHeader'); ChainStore.prototype.getInstantLock = require('./methods/getInstantLock'); ChainStore.prototype.getTransaction = require('./methods/getTransaction'); diff --git a/src/types/ChainStore/methods/considerTransaction.js b/src/types/ChainStore/methods/considerTransaction.js index 965c1a06..d0b72204 100644 --- a/src/types/ChainStore/methods/considerTransaction.js +++ b/src/types/ChainStore/methods/considerTransaction.js @@ -1,10 +1,12 @@ const { Transaction } = require('@dashevo/dashcore-lib'); const { is } = require('../../../utils'); const { FETCHED_CONFIRMED_TRANSACTION, UPDATED_ADDRESS } = require('../../../EVENTS'); +const logger = require('../../../logger'); const { Output } = Transaction; function considerTransaction(transactionHash) { + logger.silly(`ChainStore - Considering transaction ${transactionHash}`); const { transaction } = this.getTransaction(transactionHash); const { inputs, outputs } = transaction; @@ -19,7 +21,6 @@ function considerTransaction(transactionHash) { if (element.script) { const address = element.script.toAddress(this.network).toString(); const watchedAddress = this.getAddress(address); - if (watchedAddress) { // If the transactions has already been processed in a previous insertion, // we can skip the processing now, it's important to do so as we might consider @@ -59,5 +60,6 @@ function considerTransaction(transactionHash) { Object.values(processedAddressesForTx).forEach((addressObject) => { addressObject.transactions.push(transaction.hash); }); + return processedAddressesForTx; } module.exports = considerTransaction; diff --git a/src/types/ChainStore/methods/getAddresses.js b/src/types/ChainStore/methods/getAddresses.js new file mode 100644 index 00000000..6d6b94bc --- /dev/null +++ b/src/types/ChainStore/methods/getAddresses.js @@ -0,0 +1,5 @@ +function getAddresses() { + return this.state.addresses; +} + +module.exports = getAddresses; diff --git a/src/types/ChainStore/methods/importAddress.js b/src/types/ChainStore/methods/importAddress.js index 82809c83..11f6e48b 100644 --- a/src/types/ChainStore/methods/importAddress.js +++ b/src/types/ChainStore/methods/importAddress.js @@ -1,6 +1,8 @@ +const logger = require('../../../logger'); + function importAddress(address) { + logger.silly(`ChainStore - import address ${address}`); if (this.state.addresses.has(address.toString())) throw new Error('Address is already inserted'); - this.state.addresses.set(address.toString(), { address: address.toString(), transactions: [], diff --git a/src/types/ChainStore/methods/importTransaction.js b/src/types/ChainStore/methods/importTransaction.js index 11400952..9bde724b 100644 --- a/src/types/ChainStore/methods/importTransaction.js +++ b/src/types/ChainStore/methods/importTransaction.js @@ -13,7 +13,7 @@ function importTransaction(transaction, metadata = {}) { isChainLocked: metadata.isChainLocked || null, }, }); - this.considerTransaction(normalizedTransaction.hash); + return this.considerTransaction(normalizedTransaction.hash); } module.exports = importTransaction; From 67c40dbadd43e2ee6b82faa1cb75872cd0986d66 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sat, 27 Nov 2021 21:10:46 +0100 Subject: [PATCH 20/40] feat: introduce IdentitiesStore --- src/types/IdentitiesStore/IdentitiesStore.js | 11 +++++++++++ .../methods/getIndexedIdentityIds.js | 6 +++++- .../methods/importIdentity.js} | 7 ++++--- 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 src/types/IdentitiesStore/IdentitiesStore.js rename src/types/{Storage => IdentitiesStore}/methods/getIndexedIdentityIds.js (53%) rename src/types/{Storage/methods/insertIdentityAtIndex.js => IdentitiesStore/methods/importIdentity.js} (81%) diff --git a/src/types/IdentitiesStore/IdentitiesStore.js b/src/types/IdentitiesStore/IdentitiesStore.js new file mode 100644 index 00000000..383eb3c6 --- /dev/null +++ b/src/types/IdentitiesStore/IdentitiesStore.js @@ -0,0 +1,11 @@ +class IdentitiesStore { + constructor() { + this.state = { + wallets: {}, + }; + } + + insertIdentityAtIndex() { + + } +} diff --git a/src/types/Storage/methods/getIndexedIdentityIds.js b/src/types/IdentitiesStore/methods/getIndexedIdentityIds.js similarity index 53% rename from src/types/Storage/methods/getIndexedIdentityIds.js rename to src/types/IdentitiesStore/methods/getIndexedIdentityIds.js index cb82397d..ef82fd14 100644 --- a/src/types/Storage/methods/getIndexedIdentityIds.js +++ b/src/types/IdentitiesStore/methods/getIndexedIdentityIds.js @@ -4,7 +4,11 @@ * @return {Array} */ function getIndexedIdentityIds(walletId) { - return this.store.wallets[walletId].identityIds; + return [...this.state.wallets[walletId].identities] + .map((identity) => { + + }); + return [...this.state.wallets[walletId].identities]; } module.exports = getIndexedIdentityIds; diff --git a/src/types/Storage/methods/insertIdentityAtIndex.js b/src/types/IdentitiesStore/methods/importIdentity.js similarity index 81% rename from src/types/Storage/methods/insertIdentityAtIndex.js rename to src/types/IdentitiesStore/methods/importIdentity.js index fd46c1f2..9940073e 100644 --- a/src/types/Storage/methods/insertIdentityAtIndex.js +++ b/src/types/IdentitiesStore/methods/importIdentity.js @@ -3,11 +3,12 @@ const IdentityReplaceError = require('../../../errors/IndentityIdReplaceError'); /** * * @param {string} walletId - * @param {string} identityId * @param {number} identityIndex + * @param {string} identityId * @return void */ -function insertIdentityAtIndex(walletId, identityId, identityIndex) { +function importIdentity(walletId, identityIndex, identityId) { + console.log(walletId, identityIndex, identityId); if (!this.store.wallets[walletId].identityIds) { this.store.wallets[walletId].identityIds = []; } @@ -22,4 +23,4 @@ function insertIdentityAtIndex(walletId, identityId, identityIndex) { this.lastModified = Date.now(); } -module.exports = insertIdentityAtIndex; +module.exports = importIdentity; From ceab12195f3f219fe76ec5686153ddd80d193371 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sat, 27 Nov 2021 21:11:18 +0100 Subject: [PATCH 21/40] feat!: Storage now only holds Account, Chain or Wallet Store --- src/types/Storage/.eslintrc | 5 - src/types/Storage/Storage.d.ts | 127 ------ src/types/Storage/Storage.js | 99 +---- src/types/Storage/Storage.spec.js | 106 ----- src/types/Storage/initialStore.json | 5 - .../Storage/methods/addNewTxToAddress.js | 30 -- .../Storage/methods/addNewTxToAddress.spec.js | 27 -- src/types/Storage/methods/announce.js | 27 -- src/types/Storage/methods/announce.spec.js | 6 - .../Storage/methods/calculateDuffBalance.js | 45 -- src/types/Storage/methods/clearAll.js | 11 - src/types/Storage/methods/clearAll.spec.js | 25 -- src/types/Storage/methods/configure.spec.js | 61 --- src/types/Storage/methods/createAccount.js | 28 -- .../Storage/methods/createAccountStore.js | 29 ++ src/types/Storage/methods/createChain.js | 25 -- src/types/Storage/methods/createChain.spec.js | 31 -- src/types/Storage/methods/createChainStore.js | 15 + .../Storage/methods/createSingleAddress.js | 27 -- src/types/Storage/methods/createWallet.js | 24 -- .../Storage/methods/createWallet.spec.js | 75 ---- .../Storage/methods/createWalletStore.js | 13 + src/types/Storage/methods/exportAccounts.js | 8 - src/types/Storage/methods/exportChains.js | 3 - .../Storage/methods/exportTransactions.js | 3 - src/types/Storage/methods/exportWallets.js | 3 - src/types/Storage/methods/getBlockHeader.js | 13 - src/types/Storage/methods/getChainStore.js | 4 + .../Storage/methods/getIdentityIdByIndex.js | 11 - src/types/Storage/methods/getInstantLock.js | 10 - src/types/Storage/methods/getStore.js | 8 - src/types/Storage/methods/getTransaction.js | 14 - .../Storage/methods/getTransaction.spec.js | 30 -- .../Storage/methods/getTransactionMetadata.js | 14 - .../methods/getTransactionMetadata.spec.js | 31 -- src/types/Storage/methods/getWalletStore.js | 4 + src/types/Storage/methods/importAccounts.js | 49 --- .../Storage/methods/importAccounts.spec.js | 63 --- src/types/Storage/methods/importAddress.js | 43 -- .../Storage/methods/importAddress.spec.js | 18 - src/types/Storage/methods/importAddresses.js | 31 -- .../Storage/methods/importAddresses.spec.js | 7 - .../Storage/methods/importBlockHeader.js | 30 -- src/types/Storage/methods/importChains.js | 10 - .../Storage/methods/importInstantLock.js | 9 - .../Storage/methods/importSingleAddress.js | 28 -- .../methods/importSingleAddress.spec.js | 8 - .../Storage/methods/importTransaction.js | 123 ------ .../Storage/methods/importTransaction.spec.js | 407 ------------------ .../Storage/methods/importTransactions.js | 34 -- .../methods/importTransactions.spec.js | 11 - .../Storage/methods/rehydrateState.spec.js | 8 - src/types/Storage/methods/saveState.spec.js | 8 - src/types/Storage/methods/searchAddress.js | 46 -- .../Storage/methods/searchAddress.spec.js | 8 - .../methods/searchAddressWithTx.spec.js | 8 - .../Storage/methods/searchAddressesWithTx.js | 36 -- .../Storage/methods/searchBlockHeader.js | 25 -- .../Storage/methods/searchBlockHeader.spec.js | 53 --- .../Storage/methods/searchTransaction.js | 22 - .../Storage/methods/searchTransaction.spec.js | 31 -- .../methods/searchTransactionMetadata.js | 22 - .../methods/searchTransactionMetadata.spec.js | 37 -- src/types/Storage/methods/searchWallet.js | 18 - .../Storage/methods/searchWallet.spec.js | 33 -- src/types/Storage/methods/startWorker.js | 11 - src/types/Storage/methods/startWorker.spec.js | 53 --- src/types/Storage/methods/stopWorker.js | 9 - src/types/Storage/methods/updateAddress.js | 123 ------ .../Storage/methods/updateAddress.spec.js | 35 -- .../Storage/methods/updateTransaction.js | 18 - .../Storage/methods/updateTransaction.spec.js | 70 --- src/types/WalletStore/WalletStore.js | 47 ++ 73 files changed, 126 insertions(+), 2463 deletions(-) delete mode 100644 src/types/Storage/.eslintrc delete mode 100644 src/types/Storage/Storage.d.ts delete mode 100644 src/types/Storage/Storage.spec.js delete mode 100644 src/types/Storage/initialStore.json delete mode 100644 src/types/Storage/methods/addNewTxToAddress.js delete mode 100644 src/types/Storage/methods/addNewTxToAddress.spec.js delete mode 100644 src/types/Storage/methods/announce.js delete mode 100644 src/types/Storage/methods/announce.spec.js delete mode 100644 src/types/Storage/methods/calculateDuffBalance.js delete mode 100644 src/types/Storage/methods/clearAll.js delete mode 100644 src/types/Storage/methods/clearAll.spec.js delete mode 100644 src/types/Storage/methods/configure.spec.js delete mode 100644 src/types/Storage/methods/createAccount.js create mode 100644 src/types/Storage/methods/createAccountStore.js delete mode 100644 src/types/Storage/methods/createChain.js delete mode 100644 src/types/Storage/methods/createChain.spec.js create mode 100644 src/types/Storage/methods/createChainStore.js delete mode 100644 src/types/Storage/methods/createSingleAddress.js delete mode 100644 src/types/Storage/methods/createWallet.js delete mode 100644 src/types/Storage/methods/createWallet.spec.js create mode 100644 src/types/Storage/methods/createWalletStore.js delete mode 100644 src/types/Storage/methods/exportAccounts.js delete mode 100644 src/types/Storage/methods/exportChains.js delete mode 100644 src/types/Storage/methods/exportTransactions.js delete mode 100644 src/types/Storage/methods/exportWallets.js delete mode 100644 src/types/Storage/methods/getBlockHeader.js create mode 100644 src/types/Storage/methods/getChainStore.js delete mode 100644 src/types/Storage/methods/getIdentityIdByIndex.js delete mode 100644 src/types/Storage/methods/getInstantLock.js delete mode 100644 src/types/Storage/methods/getStore.js delete mode 100644 src/types/Storage/methods/getTransaction.js delete mode 100644 src/types/Storage/methods/getTransaction.spec.js delete mode 100644 src/types/Storage/methods/getTransactionMetadata.js delete mode 100644 src/types/Storage/methods/getTransactionMetadata.spec.js create mode 100644 src/types/Storage/methods/getWalletStore.js delete mode 100644 src/types/Storage/methods/importAccounts.js delete mode 100644 src/types/Storage/methods/importAccounts.spec.js delete mode 100644 src/types/Storage/methods/importAddress.js delete mode 100644 src/types/Storage/methods/importAddress.spec.js delete mode 100644 src/types/Storage/methods/importAddresses.js delete mode 100644 src/types/Storage/methods/importAddresses.spec.js delete mode 100644 src/types/Storage/methods/importBlockHeader.js delete mode 100644 src/types/Storage/methods/importChains.js delete mode 100644 src/types/Storage/methods/importInstantLock.js delete mode 100644 src/types/Storage/methods/importSingleAddress.js delete mode 100644 src/types/Storage/methods/importSingleAddress.spec.js delete mode 100644 src/types/Storage/methods/importTransaction.js delete mode 100644 src/types/Storage/methods/importTransaction.spec.js delete mode 100644 src/types/Storage/methods/importTransactions.js delete mode 100644 src/types/Storage/methods/importTransactions.spec.js delete mode 100644 src/types/Storage/methods/rehydrateState.spec.js delete mode 100644 src/types/Storage/methods/saveState.spec.js delete mode 100644 src/types/Storage/methods/searchAddress.js delete mode 100644 src/types/Storage/methods/searchAddress.spec.js delete mode 100644 src/types/Storage/methods/searchAddressWithTx.spec.js delete mode 100644 src/types/Storage/methods/searchAddressesWithTx.js delete mode 100644 src/types/Storage/methods/searchBlockHeader.js delete mode 100644 src/types/Storage/methods/searchBlockHeader.spec.js delete mode 100644 src/types/Storage/methods/searchTransaction.js delete mode 100644 src/types/Storage/methods/searchTransaction.spec.js delete mode 100644 src/types/Storage/methods/searchTransactionMetadata.js delete mode 100644 src/types/Storage/methods/searchTransactionMetadata.spec.js delete mode 100644 src/types/Storage/methods/searchWallet.js delete mode 100644 src/types/Storage/methods/searchWallet.spec.js delete mode 100644 src/types/Storage/methods/startWorker.js delete mode 100644 src/types/Storage/methods/startWorker.spec.js delete mode 100644 src/types/Storage/methods/stopWorker.js delete mode 100644 src/types/Storage/methods/updateAddress.js delete mode 100644 src/types/Storage/methods/updateAddress.spec.js delete mode 100644 src/types/Storage/methods/updateTransaction.js delete mode 100644 src/types/Storage/methods/updateTransaction.spec.js create mode 100644 src/types/WalletStore/WalletStore.js diff --git a/src/types/Storage/.eslintrc b/src/types/Storage/.eslintrc deleted file mode 100644 index a8022ef2..00000000 --- a/src/types/Storage/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "import/newline-after-import": "off" - } -} diff --git a/src/types/Storage/Storage.d.ts b/src/types/Storage/Storage.d.ts deleted file mode 100644 index c41cc83b..00000000 --- a/src/types/Storage/Storage.d.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - AddressObj, - AddressType, - Mnemonic, - Network, - TransactionInfo, TransactionMetaData, TransactionsWithMetaData, - WalletObj, WalletType -} from "../types"; -import { BlockHeader } from "@dashevo/dashcore-lib"; -import { Account } from "../.."; -import { Transaction } from "@dashevo/dashcore-lib/typings/transaction/Transaction"; - - -export declare namespace Storage { - interface IStorageOptions { - rehydrate?: boolean; - autosave?: boolean; - autosaveIntervalTime?: number; - network?: Network; - } - interface store { - wallets: {}, - transactions: {}, - chains: {}, - } -} - - -export declare class Storage { - constructor(options?: Storage.IStorageOptions); - store: {}; - rehydrate: boolean; - autosave: boolean; - autosaveIntervalTime: number; - lastRehydrate: number|null; - lastSave: number|null; - lastModified: number|null; - network: Network; - mappedAddress: MappedAddressMap; - - addNewTxToAddress(tx: TransactionInfo, address: string): boolean; - announce(type: string, el: any): boolean; - calculateDuffBalance(walletId: number, accountId: number, type: string): number; - clearAll(): Promise; - configure(opts: { - rehydrate?: boolean, - autosave?: boolean, - adapter?: any - }): Promise; - createChain(network: string): boolean; - createWallet(walletId: string, network: Network, mnemonic?:Mnemonic, type?: WalletType ): boolean; - getBlockHeader(identifier: string|number): BlockHeader; - getIdentityIdByIndex(walletId: string, identityIndex: number): string|undefined; - getIndexedIdentityIds(walletId: string): Array; - getStore(): Storage.store; - getTransaction(txid: string): Transaction; - getTransactionMetadata(txid: string): TransactionMetaData; - importAccounts(accounts: Account|[Account], walletId: string): boolean; - importAddress(address: AddressObj, walletId: string): boolean; - importAddresses(addresses: [AddressObj], walletId: string): boolean; - importBlockHeader(blockHeader: BlockHeader, height: number): void; - importSingleAddress(singleAddress: AddressObj, walletId: string): boolean; - importTransaction(transaction: Transaction, metadata?: TransactionMetaData): void; - importTransactions(transactions: Transaction|[Transaction]|[TransactionsWithMetaData]): boolean; - insertIdentityIdAtIndex(walletId: string, identityId: string, identityIndex: number): void; - rehydrateState(): Promise; - saveState(): Promise; - searchAddress(address: string, forceLoop: boolean): AddressSearchResult; - searchAddressesWithTx(txid: string): AddressesSearchResult; - searchBlockHeader(identifier: string|number): BlockHeaderSearchResult; - searchTransaction(hash: string): TransactionSearchResult; - searchTransactionMetadata(hash: string): TransactionMetadataSearchResult; - searchWallet(walletId: string): WalletSearchResult; - startWorker(): void; - stopWorker(): boolean; - updateAddress(addressObj: AddressObj, walletId: string): boolean; - updateTransaction(transaction: Transaction): boolean; -} - -interface MappedAddressMap { - [pathName: string]: MappedAddress -} - -interface MappedAddress { - [path: string]: { - walletId: string, - type: AddressType, - path: string - }; -} - -interface WalletSearchResult { - walletId: number, - found: boolean, - result?: WalletObj -} - -interface TransactionSearchResult { - hash: number, - found: boolean, - result?: TransactionInfo -} -interface TransactionMetadataSearchResult { - hash: number, - found: boolean, - result?: TransactionMetaData -} - -interface AddressSearchResult { - address: string, - type: AddressType, - found: boolean, - path?: string, - result: AddressObj, - walletId: number -} -interface BlockHeaderSearchResult { - found: boolean, - result?: BlockHeader, - identifier: number|string -} - -interface AddressesSearchResult { - txid: number, - found: boolean, - results: [AddressObj] -} diff --git a/src/types/Storage/Storage.js b/src/types/Storage/Storage.js index cf166858..b7a0e439 100644 --- a/src/types/Storage/Storage.js +++ b/src/types/Storage/Storage.js @@ -1,95 +1,24 @@ const EventEmitter = require('events'); -const { cloneDeep, has } = require('lodash'); -const CONSTANTS = require('../../CONSTANTS'); - -const initialStore = { - wallets: {}, - transactions: {}, - transactionsMetadata: {}, - chains: {}, - instantLocks: {}, -}; -// eslint-disable-next-line no-underscore-dangle -const _defaultOpts = { - rehydrate: true, - autosave: true, - autosaveIntervalTime: CONSTANTS.STORAGE.autosaveIntervalTime, - network: 'testnet', -}; /** - * Handle all the storage logic, it's a wrapper around the adapters - * So all the needed methods should be provided by the Storage class and the access to the adapter - * should be limited. - * */ +* Handle all the storage logic, it's a wrapper around the adapters +* So all the needed methods should be provided by the Storage class and the access to the adapter +* should be limited. +* */ class Storage extends EventEmitter { - constructor(opts = JSON.parse(JSON.stringify(_defaultOpts))) { + constructor() { super(); - const defaultOpts = JSON.parse(JSON.stringify(_defaultOpts)); - - this.store = cloneDeep(initialStore); - this.rehydrate = has(opts, 'rehydrate') ? opts.rehydrate : defaultOpts.rehydrate; - this.autosave = has(opts, 'autosave') ? opts.autosave : defaultOpts.autosave; - this.autosaveIntervalTime = has(opts, 'autosaveIntervalTime') - ? opts.autosaveIntervalTime - : defaultOpts.autosaveIntervalTime; - - this.lastRehydrate = null; - this.lastSave = null; - this.lastModified = null; - this.network = has(opts, 'network') ? opts.network.toString() : defaultOpts.network; - - // Map an address to it's walletid/path/type schema (used by searchAddress for speedup) - this.mappedAddress = {}; - - // Map height to transaction ids to facilitate search. - this.mappedTransactionsHeight = {}; + this.wallets = new Map(); + this.chains = new Map(); + this.application = {}; } } -Storage.prototype.addNewTxToAddress = require('./methods/addNewTxToAddress'); -Storage.prototype.announce = require('./methods/announce'); -Storage.prototype.calculateDuffBalance = require('./methods/calculateDuffBalance'); -Storage.prototype.clearAll = require('./methods/clearAll'); -Storage.prototype.configure = require('./methods/configure'); -Storage.prototype.createAccount = require('./methods/createAccount'); -Storage.prototype.createChain = require('./methods/createChain'); -Storage.prototype.createSingleAddress = require('./methods/createSingleAddress'); -Storage.prototype.createWallet = require('./methods/createWallet'); - -Storage.prototype.exportAccounts = require('./methods/exportAccounts'); -Storage.prototype.exportChains = require('./methods/exportChains'); -Storage.prototype.exportTransactions = require('./methods/exportTransactions'); -Storage.prototype.exportWallets = require('./methods/exportWallets'); -Storage.prototype.getStore = require('./methods/getStore'); -Storage.prototype.getBlockHeader = require('./methods/getBlockHeader'); -Storage.prototype.getTransaction = require('./methods/getTransaction'); -Storage.prototype.getInstantLock = require('./methods/getInstantLock'); -Storage.prototype.importAccounts = require('./methods/importAccounts'); -Storage.prototype.importAddress = require('./methods/importAddress'); -Storage.prototype.importAddresses = require('./methods/importAddresses'); -Storage.prototype.importBlockHeader = require('./methods/importBlockHeader'); -Storage.prototype.importSingleAddress = require('./methods/importSingleAddress'); -Storage.prototype.importChains = require('./methods/importChains'); -Storage.prototype.importTransaction = require('./methods/importTransaction'); -Storage.prototype.importTransactions = require('./methods/importTransactions'); -Storage.prototype.importInstantLock = require('./methods/importInstantLock'); -Storage.prototype.rehydrateState = require('./methods/rehydrateState'); -Storage.prototype.saveState = require('./methods/saveState'); -Storage.prototype.searchAddress = require('./methods/searchAddress'); -Storage.prototype.searchAddressesWithTx = require('./methods/searchAddressesWithTx'); -Storage.prototype.searchBlockHeader = require('./methods/searchBlockHeader'); -Storage.prototype.searchTransaction = require('./methods/searchTransaction'); -Storage.prototype.searchTransactionMetadata = require('./methods/searchTransactionMetadata'); -Storage.prototype.searchWallet = require('./methods/searchWallet'); -Storage.prototype.updateAddress = require('./methods/updateAddress'); -Storage.prototype.updateTransaction = require('./methods/updateTransaction'); -Storage.prototype.startWorker = require('./methods/startWorker'); -Storage.prototype.stopWorker = require('./methods/stopWorker'); - -// Identities -Storage.prototype.insertIdentityIdAtIndex = require('./methods/insertIdentityAtIndex'); -Storage.prototype.getIdentityIdByIndex = require('./methods/getIdentityIdByIndex'); -Storage.prototype.getIndexedIdentityIds = require('./methods/getIndexedIdentityIds'); +Storage.prototype.configure = require('./methods/configure'); +Storage.prototype.createAccountStore = require('./methods/createAccountStore'); +Storage.prototype.createChainStore = require('./methods/createChainStore'); +Storage.prototype.createWalletStore = require('./methods/createWalletStore'); +Storage.prototype.getChainStore = require('./methods/getChainStore'); +Storage.prototype.getWalletStore = require('./methods/getWalletStore'); module.exports = Storage; diff --git a/src/types/Storage/Storage.spec.js b/src/types/Storage/Storage.spec.js deleted file mode 100644 index 621a01dc..00000000 --- a/src/types/Storage/Storage.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -const { expect } = require('chai'); - -const localForage = require('localforage'); -const Dashcore = require('@dashevo/dashcore-lib'); -const Storage = require('./Storage'); -const { CONFIGURED } = require('../../EVENTS'); - -describe('Storage - constructor', function suite() { - this.timeout(10000); - it('It should create a storage', () => { - const storage = new Storage(); - expect(storage.store).to.deep.equal({ wallets: {}, transactions: {}, transactionsMetadata: {}, chains: {}, instantLocks: {} }); - expect(storage.getStore()).to.deep.equal(storage.store); - expect(storage.rehydrate).to.equal(true); - expect(storage.autosave).to.equal(true); - expect(storage.lastRehydrate).to.equal(null); - expect(storage.lastSave).to.equal(null); - expect(storage.lastModified).to.equal(null); - storage.stopWorker(); - }); - it('should configure a storage with default adapter', async () => { - const storage = new Storage(); - let configuredEvent = false; - storage.on(CONFIGURED, () => configuredEvent = true); - await storage.configure(); - expect(storage.adapter).to.exist; - expect(storage.adapter.constructor.name).to.equal('InMem'); - expect(configuredEvent).to.equal(true); - storage.stopWorker(); - }); - it('should handle bad adapter', async function () { - if (process.browser){ - // Local forage is valid adapter on browser. - this.skip('LocalForage is a valid adapter on browser') - return; - } - const expectedException1 = 'Invalid Storage Adapter : No available storage method found.'; - const storageOpts1 = { adapter: localForage }; - const storage = new Storage(); - return storage.configure(storageOpts1).then( - () => Promise.reject(new Error('Expected method to reject.')), - (err) => expect(err).to.be.a('Error').with.property('message', expectedException1), - ).then(() => { - storage.stopWorker(); - }); - }); - it('should work on usage', async () => { - const storage = new Storage(); - await storage.configure(); - await storage.createChain(Dashcore.Networks.testnet); - - const defaultWalletId = 'squawk7700'; - const expectedStore1 = { - wallets: {}, - transactions: {}, - transactionsMetadata:{}, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - instantLocks: {} - }; - expect(storage.getStore()).to.deep.equal(expectedStore1); - - await storage.createWallet(); - const expectedStore2 = { - wallets: { - squawk7700: { - accounts: {}, - network: Dashcore.Networks.testnet.toString(), - mnemonic: null, - type: null, - identityIds: [], - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - transactions: {}, - transactionsMetadata: {}, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - instantLocks: {}, - }; - expect(storage.getStore()).to.deep.equal(expectedStore2); - expect(storage.store).to.deep.equal(expectedStore2); - - const account = {}; - await storage.importAccounts(account, defaultWalletId); - storage.stopWorker(); - }); -}); diff --git a/src/types/Storage/initialStore.json b/src/types/Storage/initialStore.json deleted file mode 100644 index 8b4f9541..00000000 --- a/src/types/Storage/initialStore.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "wallets": {}, - "transactions": {}, - "chains": {} -} \ No newline at end of file diff --git a/src/types/Storage/methods/addNewTxToAddress.js b/src/types/Storage/methods/addNewTxToAddress.js deleted file mode 100644 index a2dba1b5..00000000 --- a/src/types/Storage/methods/addNewTxToAddress.js +++ /dev/null @@ -1,30 +0,0 @@ -const { InvalidAddress, InvalidTransactionObject, StorageUnableToAddTransaction } = require('../../../errors'); -const { is } = require('../../../utils'); - -/** - * Add a new transaction to an address (push a tx) -* @param {TransactionInfo} tx -* @param {String} address -* @return {boolean} - */ -const addNewTxToAddress = function addNewTxToAddress(tx, address) { - if (!is.address(address)) throw new InvalidAddress(address); - if (!is.transactionObj(tx)) throw new InvalidTransactionObject(tx); - - const searchAddr = this.searchAddress(address); - const { walletId } = searchAddr; - - if (tx.address && tx.txid) { - const { type, path, found } = this.searchAddress(tx.address); - if (!found) { - throw new StorageUnableToAddTransaction(tx); - } - this.store.wallets[walletId].addresses[type][path].transactions.push(tx.txid); - // Because of the unclear state will force a refresh - this.store.wallets[walletId].addresses[type][path].fetchedLast = 0; - this.lastModified = +new Date(); - return true; - } - throw new StorageUnableToAddTransaction(tx); -}; -module.exports = addNewTxToAddress; diff --git a/src/types/Storage/methods/addNewTxToAddress.spec.js b/src/types/Storage/methods/addNewTxToAddress.spec.js deleted file mode 100644 index 03b20df4..00000000 --- a/src/types/Storage/methods/addNewTxToAddress.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -const { expect } = require('chai'); -const addNewTxToAddress = require('./addNewTxToAddress'); - -// FIXME : We only use this method in one specific case : From the SyncWorker when receiving from the WSock from insight -// THerefore we might want to remove our dependency on this method ? -describe('Storage - addNewTxToAddress', () => { - it('should add a new transaction to an address in store', () => { - // const self = { - // store: { - // wallets: { - // '123ae': { - // addresses: { - // internal: {}, - // external: {}, - // misc: {}, - // }, - // transactions:{ - // - // } - // }, - // }, - // }, - // }; - // - // addNewTxToAddress.call(self, fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e, '123ae'); - }); -}); diff --git a/src/types/Storage/methods/announce.js b/src/types/Storage/methods/announce.js deleted file mode 100644 index 8c35e6b7..00000000 --- a/src/types/Storage/methods/announce.js +++ /dev/null @@ -1,27 +0,0 @@ -const logger = require('../../../logger'); -const EVENTS = require('../../../EVENTS'); - -/** - * Used to announce some events. - * @param type - * @param el - * @return {boolean} - */ -const announce = function announce(type, el) { - switch (type) { - case EVENTS.BLOCK: - case EVENTS.BLOCKHEADER: - case EVENTS.BLOCKHEIGHT_CHANGED: - case EVENTS.CONFIRMED_BALANCE_CHANGED: - case EVENTS.UNCONFIRMED_BALANCE_CHANGED: - case EVENTS.FETCHED_UNCONFIRMED_TRANSACTION: - case EVENTS.FETCHED_CONFIRMED_TRANSACTION: - this.emit(type, { type, payload: el }); - break; - default: - this.emit(type, { type, payload: el }); - logger.warn('Storage - Not implemented, announce of ', type, el); - } - return true; -}; -module.exports = announce; diff --git a/src/types/Storage/methods/announce.spec.js b/src/types/Storage/methods/announce.spec.js deleted file mode 100644 index 55b7e6b5..00000000 --- a/src/types/Storage/methods/announce.spec.js +++ /dev/null @@ -1,6 +0,0 @@ -const { expect } = require('chai'); -const announce = require('./announce'); - -describe('Storage - announce', () => { - describe.skip('TODO test'); -}); diff --git a/src/types/Storage/methods/calculateDuffBalance.js b/src/types/Storage/methods/calculateDuffBalance.js deleted file mode 100644 index 8f8e7584..00000000 --- a/src/types/Storage/methods/calculateDuffBalance.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * - * @param walletId - The wallet Id where to perform the calculation - * @param accountIndex - The account Index where to perform the calculation - * @param type {{'confirmed','unconfirmed','total'}} Default: total. Calculate balance by utxo type. - * @return {number} Balance in duff - */ -module.exports = function calculateDuffBalance(walletId, accountIndex, type = 'total') { - let totalSat = 0; - if (walletId === undefined || accountIndex === undefined) { - throw new Error('Cannot calculate without walletId and accountIndex params'); - } - - const { addresses } = this.getStore().wallets[walletId]; - const subwallets = Object.keys(addresses); - subwallets.forEach((subwallet) => { - const paths = Object.keys(addresses[subwallet]) - // We filter out other potential account - .filter((el) => { - const splitted = el.split('/'); - const index = parseInt((splitted.length === 1) ? splitted[0] : splitted[3], 10); - return index === accountIndex; - }); - - paths.forEach((path) => { - const address = addresses[subwallet][path]; - const { balanceSat, unconfirmedBalanceSat } = address; - switch (type) { - case 'total': - totalSat += balanceSat + unconfirmedBalanceSat; - break; - case 'confirmed': - totalSat += balanceSat; - break; - case 'unconfirmed': - totalSat += unconfirmedBalanceSat; - break; - default: - throw new Error(`Unexpected balance type. Got ${type}`); - } - }); - }); - - return totalSat; -}; diff --git a/src/types/Storage/methods/clearAll.js b/src/types/Storage/methods/clearAll.js deleted file mode 100644 index c22c665b..00000000 --- a/src/types/Storage/methods/clearAll.js +++ /dev/null @@ -1,11 +0,0 @@ -const { cloneDeep } = require('lodash'); -const initialStore = require('../initialStore.json'); -/** - * Clear all the store and save the cleared store to the persistence adapter - * @return {Promise} - */ -const clearAll = async function clearAll() { - this.store = cloneDeep(initialStore); - return this.saveState(); -}; -module.exports = clearAll; diff --git a/src/types/Storage/methods/clearAll.spec.js b/src/types/Storage/methods/clearAll.spec.js deleted file mode 100644 index de4bfdd8..00000000 --- a/src/types/Storage/methods/clearAll.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -const { expect } = require('chai'); -const clearState = require('./clearAll'); - -describe('Storage - clearAll', function suite() { - this.timeout(10000); - it('should clear the whole state', () => { - let called = 0; - const self = { - saveState: () => called++, - store: { - stuff: {}, - transactions: { - nope: true, - }, - }, - }; - clearState.call(self); - expect(self.store).to.deep.equal({ - chains: {}, - transactions: {}, - wallets: {}, - }); - expect(called).to.equal(1); - }); -}); diff --git a/src/types/Storage/methods/configure.spec.js b/src/types/Storage/methods/configure.spec.js deleted file mode 100644 index 186f5adc..00000000 --- a/src/types/Storage/methods/configure.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -const { expect } = require('chai'); -const configure = require('./configure'); - -const noop = () => {}; - -describe('Storage - configure', async function suite() { - this.timeout(10000); - it('should set save/rehydrate settings', () => { - let rehydrated = 0; - - const self = { - emit: noop, - autosaveIntervalTime: 1000, - startWorker: noop, - rehydrateState: () => (rehydrated += 1), - rehydrate: true, - autosave: true, - }; - expect(rehydrated).to.equal(0); - return configure - .call(self) - .then(() => expect(self.autosave).to.equal(true)) - .then(() => expect(self.rehydrate).to.equal(true)) - .then(() => expect(rehydrated).to.equal(1)) - .then(() => configure - .call(self, { rehydrate: false, autosave: false }) - .then(() => expect(self.autosave).to.equal(false)) - .then(() => expect(self.rehydrate).to.equal(false)) - .then(() => expect(rehydrated).to.equal(1))); - }); - it('should successfully emit', () => { - const emitted = []; - const self = { - emit: (emitType) => (emitted.push(emitType)), - autosaveIntervalTime: 1000, - startWorker: noop, - }; - expect(emitted.length).to.equal(0); - - return configure - .call(self) - .then(() => expect(emitted).to.deep.equal(['CONFIGURED'])); - }); - it('should start the autosave worker if autosave is true', () => { - let workerStarted = false; - const self = { - rehydrate: false, - autosave: false, - autosaveIntervalTime: 1000, - startWorker: () => { workerStarted = true; }, - emit: noop, - }; - - return configure - .call(self) - .then(() => expect(workerStarted).to.equal(false)) - .then(() => configure - .call(self, { autosave: true }) - .then(() => expect(workerStarted).to.equal(true))); - }); -}); diff --git a/src/types/Storage/methods/createAccount.js b/src/types/Storage/methods/createAccount.js deleted file mode 100644 index 819a1737..00000000 --- a/src/types/Storage/methods/createAccount.js +++ /dev/null @@ -1,28 +0,0 @@ -const { hasProp } = require('../../../utils'); - -/** - * Create a new account into a wallet - * @param {string} walletId - * @param {string} path - * @param {string} network - * @param {string|null} [label] - * @return {boolean} - */ -module.exports = function createAccount(walletId, path, network, label = null) { - if (!hasProp(this.store.wallets, walletId.toString())) { - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId, network); - } - } - if (!hasProp(this.store.wallets[walletId].accounts, path.toString())) { - this.store.wallets[walletId].accounts[path.toString()] = { - label, - path, - network, - blockHeight: 0, // Used to keep track of local state sync of the account - }; - - return true; - } - return false; -}; diff --git a/src/types/Storage/methods/createAccountStore.js b/src/types/Storage/methods/createAccountStore.js new file mode 100644 index 00000000..3069a3b0 --- /dev/null +++ b/src/types/Storage/methods/createAccountStore.js @@ -0,0 +1,29 @@ +const { hasProp } = require('../../../utils'); + +/** + * Create a new account store into a wallet + * @param {string} walletId + * @param {string} path + * @param {string} network + * @param {string|null} [label] + * @return {boolean} + */ +module.exports = function createAccount(walletId, path, network, label = null) { + const walletStore = this.getWalletStore(walletId); + // if (!hasProp(this.store.wallets, walletId.toString())) { + // if (!this.searchWallet(walletId).found) { + // this.createWallet(walletId, network); + // } + // } + // if (!hasProp(this.store.wallets[walletId].accounts, path.toString())) { + // this.store.wallets[walletId].accounts[path.toString()] = { + // label, + // path, + // network, + // blockHeight: 0, // Used to keep track of local state sync of the account + // }; + // + // return true; + // } + // return false; +}; diff --git a/src/types/Storage/methods/createChain.js b/src/types/Storage/methods/createChain.js deleted file mode 100644 index 0fea70ed..00000000 --- a/src/types/Storage/methods/createChain.js +++ /dev/null @@ -1,25 +0,0 @@ -const { hasProp } = require('../../../utils'); - -/** - * Create when does not yet exist a chain in the store - * @param network - * @return {boolean} - */ -const createChain = function createChain(network) { - if (!hasProp(this.store.chains, network.toString())) { - this.store.chains[network.toString()] = { - name: network.toString(), - blockHeaders: {}, - // Map a blockheader to it's height (used by searchBlockheader for speed up the process) - mappedBlockHeaderHeights: {}, - blockHeight: -1, - fees: { - // feeRate expressed per kb - minRelay: -1, - }, - }; - return true; - } - return false; -}; -module.exports = createChain; diff --git a/src/types/Storage/methods/createChain.spec.js b/src/types/Storage/methods/createChain.spec.js deleted file mode 100644 index 71d2da15..00000000 --- a/src/types/Storage/methods/createChain.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const createChain = require('./createChain'); - -describe('Storage - createChain', function suite() { - this.timeout(10000); - it('should create a chain', () => { - const self = { - store: { chains: {} }, - }; - const testnet = 'testnet'; - - createChain.call(self, testnet); - - const expected = { - store: { - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - }, - }; - expect(self).to.be.deep.equal(expected); - }); -}); diff --git a/src/types/Storage/methods/createChainStore.js b/src/types/Storage/methods/createChainStore.js new file mode 100644 index 00000000..47ae5c2b --- /dev/null +++ b/src/types/Storage/methods/createChainStore.js @@ -0,0 +1,15 @@ +const ChainStore = require('../../ChainStore/ChainStore'); + +/** + * Create when does not yet exist a chainStore + * @param network + * @return {boolean} + */ +const createChainStore = function createChain(network) { + if (!this.chains.has(network.toString())) { + this.chains.set(network.toString(), new ChainStore(network.toString())); + return true; + } + return false; +}; +module.exports = createChainStore; diff --git a/src/types/Storage/methods/createSingleAddress.js b/src/types/Storage/methods/createSingleAddress.js deleted file mode 100644 index 62421610..00000000 --- a/src/types/Storage/methods/createSingleAddress.js +++ /dev/null @@ -1,27 +0,0 @@ -const { hasProp } = require('../../../utils'); - -/** - * Create a new account into a wallet - * @param {string} walletId - * @param {string} network - * @param {string|null} [label] - * @return {boolean} - */ -module.exports = function createAccount(walletId, network, label = null) { - if (!hasProp(this.store.wallets, walletId.toString())) { - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId, network); - } - } - - if (!hasProp(this.store.wallets[walletId].accounts, '0')) { - this.store.wallets[walletId].accounts['0'] = { - label, - network, - blockHeight: 0, // Used to keep track of local state sync of the account - }; - - return true; - } - return false; -}; diff --git a/src/types/Storage/methods/createWallet.js b/src/types/Storage/methods/createWallet.js deleted file mode 100644 index 7380cdbc..00000000 --- a/src/types/Storage/methods/createWallet.js +++ /dev/null @@ -1,24 +0,0 @@ -const Dashcore = require('@dashevo/dashcore-lib'); -const { hasProp } = require('../../../utils'); - -const { testnet } = Dashcore.Networks; -const createWallet = function createWallet(walletId = 'squawk7700', network = testnet.toString(), mnemonic = null, type = null) { - if (!hasProp(this.store.wallets, walletId)) { - this.store.wallets[walletId] = { - accounts: {}, - network, - mnemonic, - type, - identityIds: [], - addresses: { - external: {}, - internal: {}, - misc: {}, - }, - }; - this.createChain(network); - return true; - } - return false; -}; -module.exports = createWallet; diff --git a/src/types/Storage/methods/createWallet.spec.js b/src/types/Storage/methods/createWallet.spec.js deleted file mode 100644 index 9b799c12..00000000 --- a/src/types/Storage/methods/createWallet.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -const { expect } = require('chai'); -const Dashcore = require('@dashevo/dashcore-lib'); -const createWallet = require('./createWallet'); -const createChain = require('./createChain'); - -describe('Storage - createWallet', function suite() { - this.timeout(10000); - it('should create a wallet', () => { - const self = { - store: { wallets: {}, chains: {} }, - createChain, - }; - const walletid = '123ae'; - - createWallet.call(self, walletid); - - const expected = { - wallets: { - '123ae': { - accounts: {}, - network: Dashcore.Networks.testnet.toString(), - mnemonic: null, - type: null, - identityIds: [], - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - }; - expect(self.store).to.be.deep.equal(expected); - }); - it('should create a wallet without any walletId', () => { - const self = { - store: { wallets: {}, chains: {} }, - createChain, - }; - - createWallet.call(self); - - const expected = { - wallets: { - squawk7700: { - accounts: {}, - network: Dashcore.Networks.testnet.toString(), - mnemonic: null, - type: null, - identityIds: [], - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - }; - expect(self.store).to.be.deep.equal(expected); - }); -}); diff --git a/src/types/Storage/methods/createWalletStore.js b/src/types/Storage/methods/createWalletStore.js new file mode 100644 index 00000000..d3053b1a --- /dev/null +++ b/src/types/Storage/methods/createWalletStore.js @@ -0,0 +1,13 @@ +const Dashcore = require('@dashevo/dashcore-lib'); +const WalletStore = require('../../WalletStore/WalletStore'); + +const { testnet } = Dashcore.Networks; + +const createWalletStore = function createWallet(walletId = 'squawk7700', network = testnet.toString(), mnemonic = null, type = null) { + if (!this.wallets.has(walletId)) { + this.wallets.set(walletId, new WalletStore(walletId)); + return true; + } + return false; +}; +module.exports = createWalletStore; diff --git a/src/types/Storage/methods/exportAccounts.js b/src/types/Storage/methods/exportAccounts.js deleted file mode 100644 index 89969a73..00000000 --- a/src/types/Storage/methods/exportAccounts.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = function exportAccounts(walletId) { - if (!walletId) throw new Error('Expected to export account of a specific walletId'); - - if (!this.store.wallets[walletId]) { - throw new Error(`No wallet with the following walletId found in store : ${walletId}`); - } - return this.store.wallets[walletId].accounts; -}; diff --git a/src/types/Storage/methods/exportChains.js b/src/types/Storage/methods/exportChains.js deleted file mode 100644 index 0cef2b28..00000000 --- a/src/types/Storage/methods/exportChains.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportChains() { - return this.store.chains; -}; diff --git a/src/types/Storage/methods/exportTransactions.js b/src/types/Storage/methods/exportTransactions.js deleted file mode 100644 index 57daaf8e..00000000 --- a/src/types/Storage/methods/exportTransactions.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportTransactions() { - return this.store.transactions; -}; diff --git a/src/types/Storage/methods/exportWallets.js b/src/types/Storage/methods/exportWallets.js deleted file mode 100644 index b2ba7956..00000000 --- a/src/types/Storage/methods/exportWallets.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportWallets() { - return this.store.wallets; -}; diff --git a/src/types/Storage/methods/getBlockHeader.js b/src/types/Storage/methods/getBlockHeader.js deleted file mode 100644 index 4c69a0a4..00000000 --- a/src/types/Storage/methods/getBlockHeader.js +++ /dev/null @@ -1,13 +0,0 @@ -const { BlockHeaderNotInStore } = require('../../../errors'); - -/** - * @param identifier - block hash or height - * @return {BlockHeader} - */ -const getBlockHeader = function getBlockHeader(identifier) { - const search = this.searchBlockHeader(identifier); - if (!search.found) throw new BlockHeaderNotInStore(identifier); - return search.result; -}; - -module.exports = getBlockHeader; diff --git a/src/types/Storage/methods/getChainStore.js b/src/types/Storage/methods/getChainStore.js new file mode 100644 index 00000000..5255eb71 --- /dev/null +++ b/src/types/Storage/methods/getChainStore.js @@ -0,0 +1,4 @@ +function getChainStore(network) { + return this.chains.get(network); +} +module.exports = getChainStore; diff --git a/src/types/Storage/methods/getIdentityIdByIndex.js b/src/types/Storage/methods/getIdentityIdByIndex.js deleted file mode 100644 index 111d3a14..00000000 --- a/src/types/Storage/methods/getIdentityIdByIndex.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * - * @param {string} walletId - * @param {number} identityIndex - * @return {string|undefined} - */ -function getIdentityIdByIndex(walletId, identityIndex) { - return this.store.wallets[walletId].identityIds[identityIndex]; -} - -module.exports = getIdentityIdByIndex; diff --git a/src/types/Storage/methods/getInstantLock.js b/src/types/Storage/methods/getInstantLock.js deleted file mode 100644 index 333bbd8e..00000000 --- a/src/types/Storage/methods/getInstantLock.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * - * @param {string} transactionHash - * @return {InstantLock} - */ -function getInstantLock(transactionHash) { - return this.store.instantLocks[transactionHash]; -} - -module.exports = getInstantLock; diff --git a/src/types/Storage/methods/getStore.js b/src/types/Storage/methods/getStore.js deleted file mode 100644 index 8782c2bc..00000000 --- a/src/types/Storage/methods/getStore.js +++ /dev/null @@ -1,8 +0,0 @@ -const { cloneDeep } = require('lodash'); -/** - * Return the content of the store - * @return {Storage.store} - */ -module.exports = function getStore() { - return cloneDeep(this.store); -}; diff --git a/src/types/Storage/methods/getTransaction.js b/src/types/Storage/methods/getTransaction.js deleted file mode 100644 index 601bcf85..00000000 --- a/src/types/Storage/methods/getTransaction.js +++ /dev/null @@ -1,14 +0,0 @@ -const { TransactionNotInStore } = require('../../../errors'); - -/** - * Get a specific transaxtion by it's transaction id - * @param {string} txid - * @return {Transaction} - */ -const getTransaction = function getTransaction(txid) { - const search = this.searchTransaction(txid); - if (!search.found) throw new TransactionNotInStore(txid); - return search.result; -}; - -module.exports = getTransaction; diff --git a/src/types/Storage/methods/getTransaction.spec.js b/src/types/Storage/methods/getTransaction.spec.js deleted file mode 100644 index 4134d582..00000000 --- a/src/types/Storage/methods/getTransaction.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -const { expect } = require('chai'); -const getTransaction = require('./getTransaction'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -describe('Storage - getTransaction', function suite() { - this.timeout(10000); - it('should throw on failed fetching', () => { - const validTx = transactionsFixtures.valid.mainnet['4f71db0c4bf3e2769a3ebd2162753b54b33028e3287e45f93c5c7df8bac5ec7e']; - const exceptedException1 = `Transaction is not in store: ${validTx.txid}`; - const self = { - store: { - transactions: {}, - }, - searchTransaction: () => ({ found: false }), - }; - expect(() => getTransaction.call(self, validTx.txid)).to.throw(exceptedException1); - }); - it('should work', () => { - const validTx = transactionsFixtures.valid.mainnet['4f71db0c4bf3e2769a3ebd2162753b54b33028e3287e45f93c5c7df8bac5ec7e']; - const self = { - store: { - transactions: {}, - }, - searchTransaction: () => ({ found: true, result: validTx }), - }; - self.store.transactions[validTx.txid] = validTx; - const { txid } = validTx; - expect(getTransaction.call(self, txid)).to.deep.equal(validTx); - }); -}); diff --git a/src/types/Storage/methods/getTransactionMetadata.js b/src/types/Storage/methods/getTransactionMetadata.js deleted file mode 100644 index 63626b05..00000000 --- a/src/types/Storage/methods/getTransactionMetadata.js +++ /dev/null @@ -1,14 +0,0 @@ -const { TransactionMetadataNotInStore } = require('../../../errors'); - -/** - * Get a specific transaction metadata by it's transaction id - * @param {string} txid - * @return {TransactionMetaData} - */ -const getTransactionMetadata = function getTransactionMetadata(txid) { - const search = this.searchTransactionMetadata(txid); - if (!search.found) throw new TransactionMetadataNotInStore(txid); - return search.result; -}; - -module.exports = getTransactionMetadata; diff --git a/src/types/Storage/methods/getTransactionMetadata.spec.js b/src/types/Storage/methods/getTransactionMetadata.spec.js deleted file mode 100644 index 2624263f..00000000 --- a/src/types/Storage/methods/getTransactionMetadata.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const getTransactionMetadata = require('./getTransactionMetadata'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -describe('Storage - getTransactionMetadata', function suite() { - this.timeout(10000); - const validTxId = '1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'; - // const validTx = transactionsFixtures.valid.testnet[validTxId]; - const validMetadata = transactionsFixtures.valid.testnet.metadata[validTxId]; - - it('should throw on failed fetching', () => { - const exceptedException1 = `Transaction metadata is not in store: ${validTxId}`; - const self = { - store: { - transactionsMetadata: {}, - }, - searchTransactionMetadata: () => ({ found: false }), - }; - expect(() => getTransactionMetadata.call(self, validTxId)).to.throw(exceptedException1); - }); - it('should work', () => { - const validTx = transactionsFixtures.valid.mainnet['4f71db0c4bf3e2769a3ebd2162753b54b33028e3287e45f93c5c7df8bac5ec7e']; - const self = { - store: { - transactionsMetadata: {}, - }, - searchTransactionMetadata: () => ({ found: true, result: validMetadata }), - }; - expect(getTransactionMetadata.call(self, validTxId)).to.deep.equal(validMetadata); - }); -}); diff --git a/src/types/Storage/methods/getWalletStore.js b/src/types/Storage/methods/getWalletStore.js new file mode 100644 index 00000000..23c9d085 --- /dev/null +++ b/src/types/Storage/methods/getWalletStore.js @@ -0,0 +1,4 @@ +function getWalletStore(walletId) { + return this.wallets.get(walletId); +} +module.exports = getWalletStore; diff --git a/src/types/Storage/methods/importAccounts.js b/src/types/Storage/methods/importAccounts.js deleted file mode 100644 index 51b373b0..00000000 --- a/src/types/Storage/methods/importAccounts.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Import an array of accounts or a account object to the store - * @param {Account|[Account]} accounts - * @param {string} walletId - * @return {boolean} - */ -const importAccounts = function importAccounts(accounts, walletId) { - if (!walletId) throw new Error('Expected walletId to import addresses'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const accList = this.store.wallets[walletId].accounts; - - const type = accounts.constructor.name; - if (type === 'Object') { - if (accounts.path) { - if (!accList[accounts.path]) { - accList[accounts.path] = accounts; - this.lastModified = +new Date(); - } - } else { - const accountsPaths = Object.keys(accounts); - accountsPaths.forEach((path) => { - const el = accounts[path]; - if (el.path) { - if (!accList[el.path]) { - accList[el.path] = el; - this.lastModified = +new Date(); - } - } - }); - } - } else if (type === 'Array') { - accounts.forEach((account) => { - importAccounts.call(this, account, walletId); - }); - } else if (type === 'Account') { - const accObj = { - label: accounts.label, - path: accounts.BIP44PATH, - network: accounts.network, - }; - return importAccounts.call(this, accObj, walletId); - } else { - throw new Error('Invalid account. Cannot import.'); - } - return true; -}; -module.exports = importAccounts; diff --git a/src/types/Storage/methods/importAccounts.spec.js b/src/types/Storage/methods/importAccounts.spec.js deleted file mode 100644 index 361f8cfb..00000000 --- a/src/types/Storage/methods/importAccounts.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -const { expect } = require('chai'); -const importAccounts = require('./importAccounts'); -const Wallet = require('../../Wallet/Wallet'); -const EVENTS = require('../../../EVENTS'); - -describe('Storage - importAccounts', async function suite() { - this.timeout(15000); - it('should throw on failed import', () => { - const mockOpts1 = { }; - const walletId = '123ae'; - const exceptedException1 = 'Expected walletId to import addresses'; - - expect(() => importAccounts.call({})).to.throw(exceptedException1); - expect(() => importAccounts.call({}, walletId)).to.throw(exceptedException1); - }); - it('should create a wallet if not existing', (done) => { - const wallet = new Wallet({ offlineMode: true }); - wallet.storage.on(EVENTS.CONFIGURED, () => { - wallet.getAccount().then((acc)=>{ - let called = 0; - - const self = { - searchWallet: () => ({ found: false }), - createWallet: () => (called += 1), - store: { wallets: { } }, - }; - self.store.wallets[wallet.walletId] = { accounts: {} }; - importAccounts.call(self, acc, wallet.walletId); - - // Called twice because of recursivity. We have a Acc Instance here. - expect(called).to.be.equal(2); - wallet.disconnect(); - acc.disconnect(); - done(); - }); - }); - }); - it('should import an account', (done) => { - const wallet = new Wallet({ offlineMode: true }); - wallet.storage.on('CONFIGURED', () => { - wallet.getAccount().then((acc)=>{ - let called = 0; - - const self = { - searchWallet: () => ({ found: false }), - createWallet: () => (called += 1), - store: { wallets: { } }, - }; - acc.label = 'Heya!'; - self.store.wallets[wallet.walletId] = { accounts: {} }; - importAccounts.call(self, acc, wallet.walletId); - const walletsKeys = Object.keys(self.store.wallets); - expect(walletsKeys.length).to.equal(1); - expect(self.store.wallets[walletsKeys[0]].accounts['m/44\'/1\'/0\''].label).to.equal('Heya!'); - setTimeout(() => { - wallet.disconnect(); - acc.disconnect(); - done(); - }, 50); - }) - }); - }); -}); diff --git a/src/types/Storage/methods/importAddress.js b/src/types/Storage/methods/importAddress.js deleted file mode 100644 index 17886921..00000000 --- a/src/types/Storage/methods/importAddress.js +++ /dev/null @@ -1,43 +0,0 @@ -const { cloneDeep } = require('lodash'); -const { InvalidAddressObject } = require('../../../errors'); -const { is } = require('../../../utils'); -/** - * Import one address to the store - * @param {AddressObj} addressObj - * @param {string} walletId - * @return {boolean} - */ -const importAddress = function importAddress(addressObj, walletId) { - if (!walletId) throw new Error('Expected walletId to import addresses'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const addressesStore = this.store.wallets[walletId].addresses; - if (is.undef(walletId)) throw new Error('Expected walletId to import an address'); - if (!is.addressObj(addressObj)) throw new InvalidAddressObject(addressObj); - const { path } = addressObj; - const modifiedAddressObject = cloneDeep(addressObj); - const index = (addressObj.index !== undefined) ? addressObj.index : parseInt(path.split('/')[5], 10); - const typeInt = path.split('/')[4]; - let type; - switch (typeInt) { - case '0': - type = 'external'; - break; - case '1': - type = 'internal'; - break; - default: - type = 'misc'; - } - if (!modifiedAddressObject.index) modifiedAddressObject.index = index; - if (addressesStore[type][path]) { - if (addressesStore[type][path].fetchedLast < modifiedAddressObject.fetchedLast) { - this.updateAddress(modifiedAddressObject, walletId); - } - } else { - this.updateAddress(modifiedAddressObject, walletId); - } - return true; -}; -module.exports = importAddress; diff --git a/src/types/Storage/methods/importAddress.spec.js b/src/types/Storage/methods/importAddress.spec.js deleted file mode 100644 index 714f3cfd..00000000 --- a/src/types/Storage/methods/importAddress.spec.js +++ /dev/null @@ -1,18 +0,0 @@ -const { expect } = require('chai'); -const logger = require('../../../logger'); -const importAddress = require('./importAddress'); - -describe('Storage - importAddress', () => { - it('should throw on failed import', () => { - const walletId = '123ae'; - const exceptedException1 = 'Expected walletId to import addresses'; - - expect(() => importAddress.call({})).to.throw(exceptedException1); - expect(() => importAddress.call({}, walletId)).to.throw(exceptedException1); - }); - it('should import an address', () => { - logger.warn('FIXME'); - // const self = {}; - // importAddress.call(self, {}); - }); -}); diff --git a/src/types/Storage/methods/importAddresses.js b/src/types/Storage/methods/importAddresses.js deleted file mode 100644 index 5b2287d0..00000000 --- a/src/types/Storage/methods/importAddresses.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Import one or multiple addresses to the store - * @param {[AddressObj]} addresses - * @param {string} walletId - * @return {boolean} - */ -const importAddresses = function importAddresses(addresses, walletId) { - if (!walletId) throw new Error('Expected walletId to import addresses'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const type = addresses.constructor.name; - if (type === 'Object') { - if (addresses.path) { - const address = addresses; - this.importAddress(address, walletId); - } else { - const addressPaths = Object.keys(addresses); - addressPaths.forEach((path) => { - const address = addresses[path]; - this.importAddress(address, walletId); - }); - } - } else if (type === 'Array') { - throw new Error('Not implemented. Please create an issue on github if needed.'); - } else { - throw new Error('Not implemented. Please create an issue on github if needed.'); - } - return true; -}; -module.exports = importAddresses; diff --git a/src/types/Storage/methods/importAddresses.spec.js b/src/types/Storage/methods/importAddresses.spec.js deleted file mode 100644 index 7ad30ca8..00000000 --- a/src/types/Storage/methods/importAddresses.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -const { expect } = require('chai'); -const importAddresses = require('./importAddresses'); - -describe('Storage - importAddresses', () => { - it('should import an array of addresses', () => { - }); -}); diff --git a/src/types/Storage/methods/importBlockHeader.js b/src/types/Storage/methods/importBlockHeader.js deleted file mode 100644 index a96ca0f1..00000000 --- a/src/types/Storage/methods/importBlockHeader.js +++ /dev/null @@ -1,30 +0,0 @@ -const EVENTS = require('../../../EVENTS'); -/** - * This method is used to import a blockheader in Store. - * @param {BlockHeader} blockHeader - A Blockheader - * @param {number} height - */ -const importBlockHeader = function importBlockHeader(blockHeader, height) { - const self = this; - const { store, network } = this; - - const chainStore = store.chains[network]; - const { blockHeight: currentChainHeight } = store.chains[network]; - - if (!chainStore.blockHeaders[blockHeader.hash]) { - if (height) { - if (height > currentChainHeight) store.chains[network].blockHeight = height; - else { - store.chains[network].blockHeight += 1; - self.announce(EVENTS.BLOCKHEIGHT_CHANGED, store.chains[network].blockHeight); - } - } - const blockHeight = height || currentChainHeight; - - chainStore.blockHeaders[blockHeader.hash] = blockHeader; - chainStore.mappedBlockHeaderHeights[blockHeight] = blockHeader.hash; - - self.announce(EVENTS.BLOCKHEADER, blockHeader); - } -}; -module.exports = importBlockHeader; diff --git a/src/types/Storage/methods/importChains.js b/src/types/Storage/methods/importChains.js deleted file mode 100644 index df1afb97..00000000 --- a/src/types/Storage/methods/importChains.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Import chains to the store - * - * @param {object} chains - * @return {void} - */ -const importChains = function importChains(chains) { - Object.assign(this.store.chains, chains); -}; -module.exports = importChains; diff --git a/src/types/Storage/methods/importInstantLock.js b/src/types/Storage/methods/importInstantLock.js deleted file mode 100644 index 8ce00b63..00000000 --- a/src/types/Storage/methods/importInstantLock.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Imports instant lock to the storage - * @param {InstantLock} instantLock - */ -function importInstantLock(instantLock) { - this.store.instantLocks[instantLock.txid] = instantLock; -} - -module.exports = importInstantLock; diff --git a/src/types/Storage/methods/importSingleAddress.js b/src/types/Storage/methods/importSingleAddress.js deleted file mode 100644 index f408f472..00000000 --- a/src/types/Storage/methods/importSingleAddress.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * SingleAddress differs from importAddress is the type being linked to a - * single PrivateKey (when not a HDWallet). - * @param {AddressObj} singleAddress - * @param {string} walletId - * @returns {boolean} - */ -const importSingleAddress = function importSingleAddress(singleAddress, walletId) { - const type = singleAddress.constructor.name; - if (!walletId) throw new Error('Expected walletId to import single address'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const accList = this.store.wallets[walletId].accounts; - - if (type === 'Object') { - if (singleAddress.path) { - accList[singleAddress.path] = (singleAddress); - this.lastModified = +new Date(); - } - } else if (type === 'Array') { - throw new Error('Not implemented. Please create an issue on github if needed.'); - } else { - throw new Error('Invalid account. Cannot import.'); - } - return true; -}; -module.exports = importSingleAddress; diff --git a/src/types/Storage/methods/importSingleAddress.spec.js b/src/types/Storage/methods/importSingleAddress.spec.js deleted file mode 100644 index 44023d1d..00000000 --- a/src/types/Storage/methods/importSingleAddress.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const importSingleAddress = require('./importSingleAddress'); - -describe('Storage - importSingleAddress', () => { - it('should import a SingleAddress (non-bip44)', () => { - - }); -}); diff --git a/src/types/Storage/methods/importTransaction.js b/src/types/Storage/methods/importTransaction.js deleted file mode 100644 index 3c9943de..00000000 --- a/src/types/Storage/methods/importTransaction.js +++ /dev/null @@ -1,123 +0,0 @@ -const { Transaction } = require('@dashevo/dashcore-lib'); -const { Output } = Transaction; -const { InvalidDashcoreTransaction } = require('../../../errors'); -const { FETCHED_CONFIRMED_TRANSACTION } = require('../../../EVENTS'); - -const parseStringifiedTransaction = (stringified) => new Transaction(stringified); -/** - * This method is used to import a transaction in Store. - * if a transaction is already existing, we verify if the metadata needs an update as well. - * @param {Transaction/String} transaction - A valid Transaction - * @param {TransactionMetaData} transactionMetadata - Transaction Metadata - * @return void - */ -const importTransaction = function importTransaction(transaction, transactionMetadata) { - if (!(transaction instanceof Transaction)) { - try { - // eslint-disable-next-line no-param-reassign - transaction = parseStringifiedTransaction(transaction); - if (!transaction.hash || !transaction.inputs.length || !transaction.outputs.length) { - throw new InvalidDashcoreTransaction(transaction); - } - } catch (e) { - throw new InvalidDashcoreTransaction(transaction); - } - } - const { - store, - network, - mappedAddress, - mappedTransactionsHeight, - } = this; - const { transactions, transactionsMetadata } = store; - const { inputs, outputs } = transaction; - - let hasUpdateStorage = false; - let outputIndex = -1; - const processedAddressesForTx = {}; - - transactions[transaction.hash] = transaction; - if (transactionMetadata) { - const { height } = transactionMetadata; - if (Number.isInteger(height) && height !== 0) { - transactionsMetadata[transaction.hash] = transactionMetadata; - const mappedTransactionObject = { hash: transaction.hash, ...transactionMetadata }; - - if (mappedTransactionsHeight[height]) { - // If we had this transaction locally, and it might have not been final (confirmed) - // We require to look if it previously existed and need replace or to add it - const findIndex = mappedTransactionsHeight[height] - .findIndex((el) => el.hash === transaction.hash); - - if (findIndex >= 0) { - mappedTransactionsHeight[height][findIndex] = mappedTransactionObject; - } else { - mappedTransactionsHeight[height].push(mappedTransactionObject); - } - } else { - mappedTransactionsHeight[height] = ([mappedTransactionObject]); - } - } - } - - // even if we had this transaction locally, we need to - // process it to ensure no new address (BIP44) needs to be generated - [...inputs, ...outputs].forEach((element) => { - const isOutput = (element instanceof Output); - if (isOutput) outputIndex += 1; - - if (element.script) { - const address = element.script.toAddress(network).toString(); - - if (mappedAddress && mappedAddress[address]) { - const { path, type, walletId } = mappedAddress[address]; - const addressObject = store.wallets[walletId].addresses[type][path]; - // If the transactions has already been processed in a previous insertion, - // we can skip the processing now - if (addressObject.transactions.includes(transaction.hash)) { - return; - } - - if (!addressObject.used) addressObject.used = true; - - // We mark our address as affected so we update the tx later on - if (!processedAddressesForTx[addressObject.address]) { - processedAddressesForTx[addressObject.address] = addressObject; - } - - if (!isOutput) { - const vin = element; - const utxoKey = `${vin.prevTxId.toString('hex')}-${vin.outputIndex}`; - if (addressObject.utxos[utxoKey]) { - const previousOutput = addressObject.utxos[utxoKey]; - addressObject.balanceSat -= previousOutput.satoshis; - delete addressObject.utxos[utxoKey]; - hasUpdateStorage = true; - } - } else { - const vout = element; - - const utxoKey = `${transaction.hash}-${outputIndex}`; - if (!addressObject.utxos[utxoKey]) { - addressObject.utxos[utxoKey] = vout; - addressObject.balanceSat += vout.satoshis; - hasUpdateStorage = true; - } - } - } - } - }); - - // As the same address can have one or more inputs and one or more outputs in the same tx - // we update it's transactions array as last step of importing - Object.values(processedAddressesForTx).forEach((addressObject) => { - addressObject.transactions.push(transaction.hash); - }); - - if (hasUpdateStorage) { - this.lastModified = +new Date(); - // Announce only confirmed transaction imported that are our. - this.announce(FETCHED_CONFIRMED_TRANSACTION, { transaction }); - } -}; -module.exports = importTransaction; diff --git a/src/types/Storage/methods/importTransaction.spec.js b/src/types/Storage/methods/importTransaction.spec.js deleted file mode 100644 index 8c37c9f7..00000000 --- a/src/types/Storage/methods/importTransaction.spec.js +++ /dev/null @@ -1,407 +0,0 @@ -const {expect} = require('chai'); -const importTransaction = require('./importTransaction'); -const transactionFixtures = require('../../../../fixtures/transactions'); -const {fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e} = transactionFixtures.valid.mainnet -const { Transaction, Script } = require('@dashevo/dashcore-lib'); - -const faltyTx = '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0602cc0c028800ffffffff0200902f50090000001976a91446e502918c04a65a3830ce89cc364b0cd301793388ac00e40b54020000001976a914ecfd5aaebcbb8f4791e716e188b20d4f0183265c88ac00000000460200cc0c0000be0c7d02ff51a9d30e39873ebb953d763595565fcbe0512a04bfa25ed0455e380000000000000000000000000000000000000000000000000000000000000000'; - -const tx = new Transaction({ - hash: 'ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b', - version: 3, - inputs: [ - { - prevTxId: '9f398515b6fc898ebf4e7b49bbfc4359b8c89f508c6cd677e53946bd86064b28', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b90121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b901 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: 'b812d9345fa8ea06af1d19b935eec65824d53779db74cd325690ad1d38a82757', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '483045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b0121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '72 0x3045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b01 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: '370b7bbd5b6e0de42a95d59e3277041ac20e945ffb93f56bb6984ba42f28a2ac', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf60121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf601 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - ], - outputs: [ - { - satoshis: 12999997493, - script: '76a9143ec33076ba72b36b66b7ec571dd7417abdeb76f888ac', - }, - ], - nLockTime: 0, -}); - -describe('Storage - importTransaction', function suite() { - this.timeout(10000); - it('should throw on failed import', () => { - const mockStorage = { - store:{ - transactions:{} - } - } - const mockOpts1 = {}; - const mockOpts2 = '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23'; - const mockOpts3 = {'688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23': {}}; - const mockOpts4 = {txid: '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23'}; - const mockOpts5 = {txid: '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23', vin: []}; - - const exceptedException1 = 'A Dashcore Transaction object or valid rawTransaction is required'; - - expect(() => importTransaction.call(mockStorage, mockOpts1)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts2)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts3)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts4)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts5)).to.throw(exceptedException1); - }); - it('should import a transaction', () => { - const mockedSearchAddress = () => ({found: false}); - let announceCalled = 0; - const self = { - store: { - wallets: { - 'db158d08df': { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - } - } - } - } - }, - transactions: {}, - chains: {testnet: {blockHeight: 50000}}, - }, - mappedAddress: { - 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB': {walletId: 'db158d08df', type: 'external', path: "m/44'/1'/0'/0/0"} - }, - network: 'testnet', - lastModified: 0, - searchAddress: mockedSearchAddress, - announce: (annType) => { - announceCalled += 1; - expect(annType).to.equal('FETCHED/CONFIRMED_TRANSACTION'); - }, - }; - importTransaction.call(self, tx); - importTransaction.call(self, tx); - const expectedStore = { - wallets: { - 'db158d08df': { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB', - transactions: ["ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b"], - balanceSat: 12999997493, - unconfirmedBalanceSat: 0, - utxos: {"ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b-0": tx.outputs[0]}, - fetchedLast: 0, - used: true - } - } - } - } - }, - - transactions: {ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b: tx}, - chains: {testnet: {blockHeight: 50000}}, - }; - const expectedMappedAddress = { - 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB': {walletId: 'db158d08df', type: 'external', path: "m/44'/1'/0'/0/0"} - }; - - expect(self.store).to.be.deep.equal(expectedStore); - expect(self.mappedAddress).to.be.deep.equal(expectedMappedAddress); - expect(self.lastModified).to.be.not.equal(0); - expect(announceCalled).to.be.equal(1); - }); - it('should impact input and output correctly', function () { - let announceCalled = 0; - const tx_79fd_1 = new Transaction('0200000002de85b10c3e4e95e94597969cd7ffda3f8dc9237d36b225326fc8b24ea895039c010000006a47304402206f3b27083662213cadcc8d511f991c6cd57a45374829f32c707f99b046aaa6e8022021bfda9808d3adda06c9535edbdfe419db27ce3cb628ab0e9b1e3eeba01732c1012103a65caff6ca4c0415a3ac182dfc2a6d3a4dceb98e8b831e71501df38aa156f2c1feffffff17bed9b68cd3c1077b1776936b78a3c964bfe27d695708a196d0f35f4dcd3cef000000006a47304402200926de33076dfd2f6a0c6830ff447a9adab4e3143f7f34883e96cb3a9513f20f0220535a42cf40c5ba6095779393f8c702c913ce2f2a62d45a2cd37c56ccdbe60445012102372247aa7ef740c54fd126f3080537be5f834f0c16ba20edfe671d7c9b538c67feffffff0240782715000000001976a914e8f859254d24c98f64253a0388ba81ef2c68712788ac60f72e9e030000001976a914300dccc87c4811311c94525c7b208fc371ab654088ac1b150000'); - const tx_79fd_2 = new Transaction('0300000001853852da3974e3e1f8548256e1930781700b2f2c6bf420c0284033d61e1f4092010000006b483045022100d601a80702d3d599b338992d05f3475688a7c5febdabf372a053aaf0cfccc1d20220284410a6095a9777ca9b0f1ca56b42f536735ab39c7158682e7dba6a47cbd50c0121033807498e192fde6bfe27933365227e262e12fbfcf4d7b37ecff100228a0b04a2ffffffff0210270000000000001976a914e330440072a28e1da250fb63f3cd07e3d5a9b6cc88ac59cf2e9e030000001976a914c0c59ed9a83f91d070876941a362ed18f1b9223788ac00000000'); - const mockedSelf = { - store: { - wallets: { - '79fd90175d': { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/1": { - path: "m/44'/1'/0'/0/1", - index: 1, - address: 'yh2i4JZ51rCFLbgk6RStaGKWB5JkQeeQYr', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/2": { - path: "m/44'/1'/0'/0/2", - index: 2, - address: 'yikykkDREFzxM7gNjxszrw2LYmJGHfJsdv', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - } - }, - internal: { - "m/44'/1'/0'/1/0": { - path: "m/44'/1'/0'/1/0", - index: 0, - address: 'ydtjKwwrxsq2Czeoeqk5ULoSXvdrnKWKWR', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/1": { - path: "m/44'/1'/0'/1/1", - index: 1, - address: 'yZ7zwKLSwuvZyYQXU2UGRPf1qr7nRqdH7b', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - } - } - } - }, - transactions: {}, - chains: {testnet: {blockHeight: 50000}}, - }, - mappedAddress: { - 'yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd': {walletId: '79fd90175d', type: 'external', path: "m/44'/1'/0'/0/0"}, - 'ydtjKwwrxsq2Czeoeqk5ULoSXvdrnKWKWR': {walletId: '79fd90175d', type: 'internal', path: "m/44'/1'/0'/1/0"}, - 'yh2i4JZ51rCFLbgk6RStaGKWB5JkQeeQYr': {walletId: '79fd90175d', type: 'external', path: "m/44'/1'/0'/0/1"}, - 'yZ7zwKLSwuvZyYQXU2UGRPf1qr7nRqdH7b': {walletId: '79fd90175d', type: 'internal', path: "m/44'/1'/0'/1/1"}, - 'yikykkDREFzxM7gNjxszrw2LYmJGHfJsdv': {walletId: '79fd90175d', type: 'external', path: "m/44'/1'/0'/0/2"} - }, - network: 'testnet', - lastModified: 0, - announce: (annType) => { - announceCalled += 1; - expect(annType).to.equal('FETCHED/CONFIRMED_TRANSACTION'); - }, - }; - importTransaction.call(mockedSelf, tx_79fd_1); - const expectedTransactionsAfterTx1 = { - '92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885': tx_79fd_1 - }; - const expectedExternalStoreAfterTx1 = { - "m/44'/1'/0'/0/0":{ - "path": "m/44'/1'/0'/0/0", - "index": 0, - "address": "yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd", - "transactions": ['92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885'], - "balanceSat": 15538780000, - "unconfirmedBalanceSat": 0, - "utxos": { - "92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885-1": new Transaction.Output({ - "satoshis": 15538780000, - "script": "76a914300dccc87c4811311c94525c7b208fc371ab654088ac" - }) - }, - "fetchedLast": 0, - "used": true - } - } - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx1); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx1["m/44'/1'/0'/0/0"]); - - // We need to ensure it do not duplicate UTXO or TXs - importTransaction.call(mockedSelf, tx_79fd_1); - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx1); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx1["m/44'/1'/0'/0/0"]); - - importTransaction.call(mockedSelf, tx_79fd_2); - const expectedTransactionsAfterTx2 = { - '92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885': tx_79fd_1, - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e': tx_79fd_2 - }; - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx2); - - const expectedExternalStoreAfterTx2 = { - "m/44'/1'/0'/0/0":{ - "path": "m/44'/1'/0'/0/0", - "index": 0, - "address": "yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd", - "transactions": [ - '92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885', - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e' - ], - "balanceSat": 0, - "unconfirmedBalanceSat": 0, - "utxos": {}, - "fetchedLast": 0, - "used": true - }, - "m/44'/1'/0'/0/1":{ - "path": "m/44'/1'/0'/0/1", - "index": 1, - "address": "yh2i4JZ51rCFLbgk6RStaGKWB5JkQeeQYr", - "transactions": [ - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e' - ], - "balanceSat": 10000, - "unconfirmedBalanceSat": 0, - "utxos": { - "df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e-0": new Transaction.Output({ - "satoshis": 10000, - "script": '76a914e330440072a28e1da250fb63f3cd07e3d5a9b6cc88ac' - }), - }, - "fetchedLast": 0, - "used": true - },"m/44'/1'/0'/1/0":{ - "path": "m/44'/1'/0'/1/0", - "index": 0, - "address": "ydtjKwwrxsq2Czeoeqk5ULoSXvdrnKWKWR", - "transactions": [ - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e' - ], - "balanceSat": 15538769753, - "unconfirmedBalanceSat": 0, - "utxos": { - "df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e-1": new Transaction.Output({ - "satoshis": 15538769753, - "script": '76a914c0c59ed9a83f91d070876941a362ed18f1b9223788ac' - }), - }, - "fetchedLast": 0, - "used": true - } - }; - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/0"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/1"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/1"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.internal["m/44'/1'/0'/1/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/1/0"]); - - importTransaction.call(mockedSelf, tx_79fd_2); - importTransaction.call(mockedSelf, tx_79fd_1); - importTransaction.call(mockedSelf, tx_79fd_2); - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx2); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/0"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/1"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/1"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.internal["m/44'/1'/0'/1/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/1/0"]); - - }); - it('should import transaction metadata', function () { - let announceCalled = 0; - - const mockedSelf = { - store: { - wallets: { - '60ee3a92b6': { - accounts:{ - "m/44'/1'/0'": { - label: null, - path: "m/44'/1'/0'", - network: 'testnet', - blockHeight: 552160 - } - }, - network: 'testnet', - mnemonic: null, - type: null, - identityIds: [], - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yd1ohc12LgCYp56CDuckTEHwoa6LbPghMd', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - }, - } - } - }, - transactionsMetadata: {}, - transactions: {}, - - chains: {testnet: { - mappedBlockHeaderHeights: { - '552160': '0000019156265f85695bf62285e32408d6057406b19374a57c009afa9116396f' - }, - blockHeight: 552160 - }}, - }, - mappedAddress: { - 'yd1ohc12LgCYp56CDuckTEHwoa6LbPghMd': {walletId: '60ee3a92b6', type: 'external', path: "m/44'/1'/0'/0/0"}, - }, - mappedTransactionsHeight: { - - }, - network: 'testnet', - lastModified: 0, - announce: (annType) => { - announceCalled += 1; - expect(annType).to.equal('FETCHED/CONFIRMED_TRANSACTION'); - }, - }; - - importTransaction.call(mockedSelf, transactionFixtures.valid.testnet["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"], transactionFixtures.valid.testnet.metadata["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"]); - - expect(mockedSelf.store.transactions['1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6']).to.exist; - const expectedTransaction = new Transaction(transactionFixtures.valid.testnet["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"]); - expect(mockedSelf.store.transactions['1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'].toString()).to.equal(expectedTransaction.toString()); - - expect(mockedSelf.store.transactionsMetadata).to.exist; - expect(mockedSelf.store.transactionsMetadata['1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6']).to.deep.equal({ - blockHash: '0000007a84abfe1d2b4201f4844bb1e59f24daf965c928281589269f281abc01', - height: 551438, - instantLocked: true, - chainLocked: true - }) - - expect(mockedSelf.mappedTransactionsHeight).to.exist; - const expectedMetadata = transactionFixtures.valid.testnet.metadata["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"]; - expectedMetadata.hash = '1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'; - expect(mockedSelf.mappedTransactionsHeight['551438']).to.deep.equal([expectedMetadata]); - }); -}); diff --git a/src/types/Storage/methods/importTransactions.js b/src/types/Storage/methods/importTransactions.js deleted file mode 100644 index a2e29244..00000000 --- a/src/types/Storage/methods/importTransactions.js +++ /dev/null @@ -1,34 +0,0 @@ -const { Transaction } = require('@dashevo/dashcore-lib'); - -/** - * Import an array of transactions or a transaction object to the store - * @param {[TransactionsWithMetaData][Transaction]|Transaction} transactions - * @return {number} - * */ -const importTransactions = function importTransactions(transactions) { - const type = transactions.constructor.name; - const self = this; - if (type === Transaction.name) { - self.importTransaction(transactions); - } else if (type === 'Object') { - const transactionsIds = Object.keys(transactions); - if (transactionsIds.length === 0) { - throw new Error('Invalid transaction'); - } - transactionsIds.forEach((id) => { - const transaction = transactions[id]; - self.importTransaction(transaction); - }); - } else if (type === 'Array') { - transactions.forEach((transactionData) => { - if (transactionData.constructor.name === 'Array') { - self.importTransaction(transactionData[0], transactionData[1]); - } else { - self.importTransaction(transactionData); - } - }); - } else { - throw new Error('Invalid transaction. Cannot import.'); - } -}; -module.exports = importTransactions; diff --git a/src/types/Storage/methods/importTransactions.spec.js b/src/types/Storage/methods/importTransactions.spec.js deleted file mode 100644 index 307a5c32..00000000 --- a/src/types/Storage/methods/importTransactions.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -const { expect } = require('chai'); -const importTransactions = require('./importTransactions'); - -describe('Storage - importTransactions', () => { - it('should import an array of transaction', () => { - - }); - it('should import an object with one or multiples transaction', () => { - - }); -}); diff --git a/src/types/Storage/methods/rehydrateState.spec.js b/src/types/Storage/methods/rehydrateState.spec.js deleted file mode 100644 index 310107f0..00000000 --- a/src/types/Storage/methods/rehydrateState.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const rehydrateState = require('./rehydrateState'); - -describe('Storage - rehydrateState', () => { - it('should rehydrate the state', () => { - - }); -}); diff --git a/src/types/Storage/methods/saveState.spec.js b/src/types/Storage/methods/saveState.spec.js deleted file mode 100644 index 261a5aa6..00000000 --- a/src/types/Storage/methods/saveState.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const saveState = require('./saveState'); - -describe('Storage - saveState', () => { - it('should state the state', () => { - - }); -}); diff --git a/src/types/Storage/methods/searchAddress.js b/src/types/Storage/methods/searchAddress.js deleted file mode 100644 index b19382c6..00000000 --- a/src/types/Storage/methods/searchAddress.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Search a specific address in the store - * @param {string} address - * @param {boolean} [forceLoop=false] - When set at true, will force a full search - * @return {AddressSearchResult} - */ -const searchAddress = function searchAddress(address, forceLoop = false) { - const search = { - address, - type: null, - found: false, - }; - const { store } = this; - if (forceLoop === true) { - // Look up by looping over all addresses todo:optimisation - const existingWallets = Object.keys(store.wallets); - existingWallets.forEach((walletId) => { - const existingTypes = Object.keys(store.wallets[walletId].addresses); - existingTypes.forEach((type) => { - const existingPaths = Object.keys(store.wallets[walletId].addresses[type]); - existingPaths.forEach((path) => { - const el = store.wallets[walletId].addresses[type][path]; - if (el.address === search.address) { - search.path = path; - search.type = type; - search.found = true; - search.result = el; - search.walletId = walletId; - } - }); - }); - }); - } else if (this.mappedAddress[address]) { - const { path, type, walletId } = this.mappedAddress[address]; - const el = store.wallets[walletId].addresses[type][path]; - - search.path = path; - search.type = type; - search.found = true; - search.result = el; - search.walletId = walletId; - } - - return search; -}; -module.exports = searchAddress; diff --git a/src/types/Storage/methods/searchAddress.spec.js b/src/types/Storage/methods/searchAddress.spec.js deleted file mode 100644 index 0d0b92ca..00000000 --- a/src/types/Storage/methods/searchAddress.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const searchAddress = require('./searchAddress'); - -describe('Storage - searchAddress', () => { - it('should search an address', () => { - - }); -}); diff --git a/src/types/Storage/methods/searchAddressWithTx.spec.js b/src/types/Storage/methods/searchAddressWithTx.spec.js deleted file mode 100644 index f8df278e..00000000 --- a/src/types/Storage/methods/searchAddressWithTx.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const searchAddressesWithTx = require('./searchAddressesWithTx'); - -describe('Storage - searchAddressesWithTx', () => { - it('should search an address having a specific tx', () => { - - }); -}); diff --git a/src/types/Storage/methods/searchAddressesWithTx.js b/src/types/Storage/methods/searchAddressesWithTx.js deleted file mode 100644 index 6aa36d62..00000000 --- a/src/types/Storage/methods/searchAddressesWithTx.js +++ /dev/null @@ -1,36 +0,0 @@ -const _ = require('lodash'); -/** - * Search an address having a specific txid - * todo : Handle when multiples one (inbound/outbound) - * @param {string} txid - * @return {AddressesSearchResult} - */ -const searchAddressesWithTx = function searchAddressesWithTx(txid) { - const search = { - txid, - results: [], - found: false, - }; - const store = this.getStore(); - - // Look up by looping over all addresses todo:optimisation - const existingWallets = Object.keys(store.wallets); - existingWallets.forEach((walletId) => { - if (_.has(store.wallets[walletId], 'addresses')) { - const existingTypes = Object.keys(store.wallets[walletId].addresses); - existingTypes.forEach((type) => { - const existingPaths = Object.keys(store.wallets[walletId].addresses[type]); - existingPaths.forEach((path) => { - const el = store.wallets[walletId].addresses[type][path]; - if (el.transactions.includes(search.txid)) { - search.results.push({ type, ...el }); - search.found = true; - } - }); - }); - } - }); - - return search; -}; -module.exports = searchAddressesWithTx; diff --git a/src/types/Storage/methods/searchBlockHeader.js b/src/types/Storage/methods/searchBlockHeader.js deleted file mode 100644 index c93463a1..00000000 --- a/src/types/Storage/methods/searchBlockHeader.js +++ /dev/null @@ -1,25 +0,0 @@ -const { is } = require('../../../utils'); -/** - * Search a specific blockheader in the store - * @param {string|number} identifier - block hash or height - * @return {BlockHeaderSearchResult} - */ -const searchBlockHeader = function searchBlockHeader(identifier) { - const store = this.getStore(); - const search = { - identifier, - found: false, - }; - const chainStore = store.chains[this.network.toString()]; - const blockheader = (is.num(identifier) - // eslint-disable-next-line no-underscore-dangle - ? chainStore.blockHeaders[chainStore.mappedBlockHeaderHeights[identifier]] - : chainStore.blockHeaders[identifier]); - - if (blockheader) { - search.found = true; - search.result = blockheader; - } - return search; -}; -module.exports = searchBlockHeader; diff --git a/src/types/Storage/methods/searchBlockHeader.spec.js b/src/types/Storage/methods/searchBlockHeader.spec.js deleted file mode 100644 index 137b3b2e..00000000 --- a/src/types/Storage/methods/searchBlockHeader.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -const {expect} = require('chai'); -const {BlockHeader} = require('@dashevo/dashcore-lib'); -const searchBlockHeader = require('./searchBlockHeader'); - -const existingBlockHeader = new BlockHeader.fromObject({ - hash: '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331', - version: 2, - prevHash: '00000ce430de949c85a145b02e33ebbaed3772dc8f3d668f66edc6852c24d002', - merkleRoot: '663360403b5fba9cd8744c3706f9660c7d3fee4e5a9ee98ce0ad5e5ad7824c1d', - time: 1398712821, - bits: 504365040, - nonce: 312363 -}); -const notExistingBlockHeader = new BlockHeader.fromObject({ - hash: '00000b526b34e733532d706c1f4cef93eefe707b87c2c3cb2978e1a84b97c501', - version: 2, - prevHash: '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331', - merkleRoot: 'c2ed22a3e6712b842359dfbb6f0a133ae122ffb601e4cf60e30b8c99f9438f4f', - time: 1398712821, - bits: 504365040, - nonce: 8325 -}) -describe('Storage - searchBlockHeader', function suite() { - this.timeout(10000); - it('should find a transaction', () => { - const self = { - network: 'testnet', - store: { - chains:{ - 'testnet':{ - blockHeaders: { - '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331': existingBlockHeader, - }, - } - }, - }, - }; - - self.getStore = () => self.store; - - const existingBlockHash = existingBlockHeader.hash; - const notExistingBlockHash = notExistingBlockHeader.hash; - const search = searchBlockHeader.call(self, existingBlockHash); - - expect(search.found).to.be.equal(true); - expect(search.identifier).to.be.equal(existingBlockHash); - expect(search.result).to.be.deep.equal(existingBlockHeader); - - const search2 = searchBlockHeader.call(self, notExistingBlockHash); - expect(search2.found).to.be.equal(false); - expect(search2.identifier).to.be.equal(notExistingBlockHash); - }); -}); diff --git a/src/types/Storage/methods/searchTransaction.js b/src/types/Storage/methods/searchTransaction.js deleted file mode 100644 index 1bd8b729..00000000 --- a/src/types/Storage/methods/searchTransaction.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Search a specific txid in the store - * @param {string} hash - * @return {TransactionSearchResult} - */ -const searchTransaction = function searchTransaction(hash) { - const search = { - hash, - found: false, - }; - const store = this.getStore(); - - if (store.transactions[hash]) { - const tx = store.transactions[hash]; - if (tx.hash === hash) { - search.found = true; - search.result = tx; - } - } - return search; -}; -module.exports = searchTransaction; diff --git a/src/types/Storage/methods/searchTransaction.spec.js b/src/types/Storage/methods/searchTransaction.spec.js deleted file mode 100644 index 831a804b..00000000 --- a/src/types/Storage/methods/searchTransaction.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const searchTransaction = require('./searchTransaction'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -const { faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60, fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e } = transactionsFixtures.valid.mainnet; -describe('Storage - searchTransaction', function suite() { - this.timeout(10000); - it('should find a transaction', () => { - const self = { - store: { - transactions: { - faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60, - }, - }, - }; - - self.getStore = () => self.store; - - const existingTxID = faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60.hash; - const notExistingTxID = fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e.hash; - const search = searchTransaction.call(self, existingTxID); - - expect(search.found).to.be.equal(true); - expect(search.hash).to.be.equal(existingTxID); - expect(search.result).to.be.equal(faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60); - - const search2 = searchTransaction.call(self, notExistingTxID); - expect(search2.found).to.be.equal(false); - expect(search2.hash).to.be.equal(notExistingTxID); - }); -}); diff --git a/src/types/Storage/methods/searchTransactionMetadata.js b/src/types/Storage/methods/searchTransactionMetadata.js deleted file mode 100644 index eae1b962..00000000 --- a/src/types/Storage/methods/searchTransactionMetadata.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Search a specific txid's metadata in the store - * @param {string} hash - * @return {TransactionMetadataSearchResult} - */ -const searchTransactionMetadata = function searchTransactionMetadata(hash) { - const search = { - hash, - found: false, - }; - const store = this.getStore(); - - if (store.transactionsMetadata[hash]) { - const txMetadata = store.transactionsMetadata[hash]; - if (txMetadata.hash === hash) { - search.found = true; - search.result = txMetadata; - } - } - return search; -}; -module.exports = searchTransactionMetadata; diff --git a/src/types/Storage/methods/searchTransactionMetadata.spec.js b/src/types/Storage/methods/searchTransactionMetadata.spec.js deleted file mode 100644 index 6cc013b5..00000000 --- a/src/types/Storage/methods/searchTransactionMetadata.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -const { expect } = require('chai'); -const searchTransactionMetadata = require('./searchTransactionMetadata'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -const validTxId = '1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'; -const validTx = transactionsFixtures.valid.testnet[validTxId]; -const validMetadata = transactionsFixtures.valid.testnet.metadata[validTxId]; -describe('Storage - searchTransactionMetadata', function suite() { - this.timeout(10000); - it('should find a transaction metadata', () => { - const self = { - store: { - transactions: { - "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6": validTx, - }, - transactionsMetadata: { - "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6": {hash:"1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6",...validMetadata} - } - }, - }; - - self.getStore = () => self.store; - - const existingTxID = "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"; - const notExistingTxID = "fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e"; - const search = searchTransactionMetadata.call(self, existingTxID); - - expect(search.found).to.be.equal(true); - expect(search.hash).to.be.equal(existingTxID); - const expectedResult = {hash: "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6", ...validMetadata}; - expect(search.result).to.be.deep.equal(expectedResult); - - const search2 = searchTransactionMetadata.call(self, notExistingTxID); - expect(search2.found).to.be.equal(false); - expect(search2.hash).to.be.equal(notExistingTxID); - }); -}); diff --git a/src/types/Storage/methods/searchWallet.js b/src/types/Storage/methods/searchWallet.js deleted file mode 100644 index 9719d057..00000000 --- a/src/types/Storage/methods/searchWallet.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Search a wallet in store based from it's walletId - * @param {string} walletId - * @return {WalletSearchResult} - */ -const searchWallet = function searchWallet(walletId) { - const search = { - walletId, - found: false, - }; - const store = this.getStore(); - if (store.wallets[walletId]) { - search.found = true; - search.result = store.wallets[walletId]; - } - return search; -}; -module.exports = searchWallet; diff --git a/src/types/Storage/methods/searchWallet.spec.js b/src/types/Storage/methods/searchWallet.spec.js deleted file mode 100644 index 4f40be20..00000000 --- a/src/types/Storage/methods/searchWallet.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -const { expect } = require('chai'); -const Dashcore = require('@dashevo/dashcore-lib'); -const searchWallet = require('./searchWallet'); - -describe('Storage - searchWallet', function suite() { - this.timeout(10000); - it('should find a wallet', () => { - const self = { - store: { - wallets: { - '123ae': { - accounts: {}, - network: Dashcore.Networks.testnet, - mnemonic: null, - type: null, - blockHeight: 0, - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - chains: { testnet: { name: 'testnet', blockHeight: -1 } }, - }, - }; - self.getStore = () => self.store; - - const existingWalletid = '123ae'; - const search = searchWallet.call(self, existingWalletid); - - expect(search.found).to.be.equal(true); - expect(search.walletId).to.be.equal(existingWalletid); - expect(search.result.accounts).to.be.deep.equal({}); - expect(search.result.addresses).to.be.deep.equal({ external: {}, internal: {}, misc: {} }); - }); -}); diff --git a/src/types/Storage/methods/startWorker.js b/src/types/Storage/methods/startWorker.js deleted file mode 100644 index 4976c0eb..00000000 --- a/src/types/Storage/methods/startWorker.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Allow to start the working interval (worker for saving state). - * @return {void} - */ -module.exports = function startWorker() { - this.interval = setInterval(() => { - if (this.lastModified > this.lastSave) { - this.saveState(); - } - }, this.autosaveIntervalTime); -}; diff --git a/src/types/Storage/methods/startWorker.spec.js b/src/types/Storage/methods/startWorker.spec.js deleted file mode 100644 index 0711f409..00000000 --- a/src/types/Storage/methods/startWorker.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -const { expect } = require('chai'); -const startWorker = require('./startWorker'); - -let testInterval = null; -const simulateChangeEvery = function (ms) { - const self = this; - testInterval = setInterval(() => { - self.lastModified = Date.now(); - }, ms); -}; -describe('Storage - startWorker', function suite() { - this.timeout(60000); - it('should set an interval', function () { - const defaultIntervalValue = 10000; - const self = { - autosaveIntervalTime: defaultIntervalValue, - }; - startWorker.call(self); - if (process.browser){ - this.skip('doesn\'t work in browser') - // Need to clear to not hang-on forever - clearInterval(self.interval); - return; - } - expect(self.interval.constructor.name).to.be.equal('Timeout'); - expect(self.interval._repeat).to.be.equal(defaultIntervalValue); // Timeout are null btw - clearInterval(self.interval); - }); - it('should work', async () => new Promise((res) => { - let saved = 0; - const self = { - saveState: () => { - saved += 1; - self.lastSave = Date.now(); - }, - autosaveIntervalTime: 500, - lastModified: Date.now(), - lastSave: 0, - }; - startWorker.call(self); - simulateChangeEvery.call(self, 200); - - setTimeout(() => { - clearInterval(self.interval); - testInterval = clearInterval(testInterval); - - expect(saved < 11).to.be.equal(true); - // First autosave + 9 induced changes - // However it can be less as we do not hard force the place in the event loop (simple setInterval) - res(expect(saved >= 8).to.be.equal(true)); - }, 5499); - })); -}); diff --git a/src/types/Storage/methods/stopWorker.js b/src/types/Storage/methods/stopWorker.js deleted file mode 100644 index f0aa6690..00000000 --- a/src/types/Storage/methods/stopWorker.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Allow to clear the working interval (worker). - * @return {boolean} - */ -module.exports = function stopWorker() { - clearInterval(this.interval); - this.interval = null; - return true; -}; diff --git a/src/types/Storage/methods/updateAddress.js b/src/types/Storage/methods/updateAddress.js deleted file mode 100644 index 5bff96fe..00000000 --- a/src/types/Storage/methods/updateAddress.js +++ /dev/null @@ -1,123 +0,0 @@ -const { cloneDeep, xor } = require('lodash'); -const { InvalidAddressObject, TransactionNotInStore } = require('../../../errors'); -const { is } = require('../../../utils'); -const EVENTS = require('../../../EVENTS'); - -/** -* Update a specific address information in the store -* @param {AddressObj} addressObj -* @param {string} walletId -* @return {boolean} -*/ -const updateAddress = function updateAddress(addressObj, walletId) { - if (!walletId) throw new Error('Expected walletId to update an address'); - if (!is.addressObj(addressObj)) throw new InvalidAddressObject(addressObj); - const { path } = addressObj; - if (!path) throw new Error('Expected path to update an address'); - const accountIndex = parseInt(path.split('/')[3], 10); - - const typeInt = path.split('/')[4]; - let type; - switch (typeInt) { - case '0': - type = 'external'; - break; - case '1': - type = 'internal'; - break; - default: - type = 'misc'; - } - const walletStore = this.store.wallets[walletId]; - const addressesStore = walletStore.addresses; - const previousObject = cloneDeep(addressesStore[type][path]); - - const newObject = cloneDeep(addressObj); - // We do not autorize to alter UTXO using this - // if(newObject.utxos.length==0 && previousObject.utxos.length>0){ - // - // } - // const currentBlockHeight = this.store.chains[walletStore.network].blockHeight; - - // We calculate here the balanceSat and unconfirmedBalanceSat of our addressObj - // We do that to avoid getBalance to be slow, so we have to keep that in mind or then - // Move that to an event type of calculation or somth - const { utxos } = newObject; - - const newObjectUtxosKeys = Object.keys(utxos); - if (newObjectUtxosKeys.length > 0) { - // we compare the diff between the two utxos sets - - const previousUTXOS = (previousObject !== undefined) ? previousObject.utxos : []; - - const newUtxos = xor(newObjectUtxosKeys, Object.keys(previousUTXOS)); - // Then we verify the outputs - newUtxos.forEach((utxoKey) => { - const [txid, outputIndex] = utxoKey.split('-'); - const utxo = utxos[utxoKey]; - utxo.txId = txid; - utxo.outputIndex = parseInt(outputIndex, 10); - - try { - this.getTransaction(txid); - // TODO : We removed here the confirmations verification - // We should ensure we had a locked block before being able to really spend those. - newObject.balanceSat += utxo.satoshis; - } catch (e) { - if (!(e instanceof TransactionNotInStore)) throw e; - } - }); - } - - // Check if there is a balance but no utxo. - addressesStore[type][path] = newObject; - if (previousObject === undefined) { - if (newObject.balanceSat > 0) { - this.announce( - EVENTS.CONFIRMED_BALANCE_CHANGED, - { - delta: newObject.balanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'confirmed') || newObject.unconfirmedBalanceSat, - // currentValue: newObject.balanceSat, - }, - ); - } - if (newObject.unconfirmedBalanceSat > 0) { - this.announce( - EVENTS.UNCONFIRMED_BALANCE_CHANGED, - { - delta: newObject.unconfirmedBalanceSat, - // currentValue: newObject.unconfirmedBalanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'unconfirmed'), - }, - ); - } - } else { - if (previousObject.balanceSat !== newObject.balanceSat) { - this.announce( - EVENTS.CONFIRMED_BALANCE_CHANGED, - { - delta: newObject.balanceSat - previousObject.balanceSat, - // currentValue: newObject.balanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'confirmed'), - }, - ); - } - if (previousObject.unconfirmedBalanceSat !== newObject.unconfirmedBalanceSat) { - this.announce(EVENTS.UNCONFIRMED_BALANCE_CHANGED, - { - delta: newObject.unconfirmedBalanceSat - previousObject.unconfirmedBalanceSat, - // currentValue: newObject.unconfirmedBalanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'unconfirmed'), - }); - } - } - - this.lastModified = Date.now(); - - if (!this.mappedAddress[newObject.address]) { - this.mappedAddress[newObject.address] = { walletId, type, path }; - } - return true; -}; -module.exports = updateAddress; diff --git a/src/types/Storage/methods/updateAddress.spec.js b/src/types/Storage/methods/updateAddress.spec.js deleted file mode 100644 index 0de48ab1..00000000 --- a/src/types/Storage/methods/updateAddress.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -const { expect } = require('chai'); -const updateAddress = require('./updateAddress'); -const orangeWStore = require('../../../../fixtures/walletStore').valid.orange.store; - -describe('Storage - updateAddress', function suite() { - this.timeout(10000); - it('should throw errors on failed update', () => { - const self = {}; - expect(() => updateAddress.call(self)).to.throw('Expected walletId to update an address'); - - expect(() => updateAddress.call(self, {}, '123ae')).to.throw('Address should have property path of type string'); - }); - it('should update an address', () => { - const self = { store: orangeWStore, mappedAddress: {} }; - const validAddrObj = { - path: "m/44'/1'/0'/0/0", - index: '0', - address: 'yLhsYLXW5sFHLDPLj2EHgrmQRhP712ANda', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: true, - }; - const validWalletId = Object.keys(orangeWStore.wallets)[0]; - updateAddress.call(self, validAddrObj, validWalletId); - const expectedMappedAddress = { yLhsYLXW5sFHLDPLj2EHgrmQRhP712ANda: { walletId: 'a3771aaf93', type: 'external', path: "m/44'/1'/0'/0/0" } }; - const expectedUpdatedAddress = { - path: "m/44'/1'/0'/0/0", index: '0', address: 'yLhsYLXW5sFHLDPLj2EHgrmQRhP712ANda', transactions: [], balanceSat: 0, unconfirmedBalanceSat: 0, utxos: {}, fetchedLast: 0, used: true, - }; - expect(self.mappedAddress).to.be.deep.equal(expectedMappedAddress); - expect(self.store.wallets.a3771aaf93.addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedUpdatedAddress); - }); -}); diff --git a/src/types/Storage/methods/updateTransaction.js b/src/types/Storage/methods/updateTransaction.js deleted file mode 100644 index 18c35950..00000000 --- a/src/types/Storage/methods/updateTransaction.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Update a specific transaction information in the store - * It do not handle any merging right now and write over previous data. - * @param {Transaction} transaction - * @return {boolean} - */ -const updateTransaction = function updateTransaction(transaction) { - if (!transaction) throw new Error('Expected a transaction to update'); - - const transactionStore = this.store.transactions; - const storeTx = transactionStore[transaction.hash]; - if (JSON.stringify(storeTx) !== JSON.stringify(transaction)) { - transactionStore[transaction.hash] = transaction; - this.lastModified = Date.now(); - } - return true; -}; -module.exports = updateTransaction; diff --git a/src/types/Storage/methods/updateTransaction.spec.js b/src/types/Storage/methods/updateTransaction.spec.js deleted file mode 100644 index 04c35917..00000000 --- a/src/types/Storage/methods/updateTransaction.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -const { expect } = require('chai'); -const updateTransaction = require('./updateTransaction'); -const orangeWStore = require('../../../../fixtures/walletStore').valid.orange.store; -const { Transaction } = require('@dashevo/dashcore-lib'); - -const tx = new Transaction({ - hash: 'ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b', - version: 3, - inputs: [ - { - prevTxId: '9f398515b6fc898ebf4e7b49bbfc4359b8c89f508c6cd677e53946bd86064b28', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b90121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b901 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: 'b812d9345fa8ea06af1d19b935eec65824d53779db74cd325690ad1d38a82757', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '483045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b0121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '72 0x3045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b01 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: '370b7bbd5b6e0de42a95d59e3277041ac20e945ffb93f56bb6984ba42f28a2ac', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf60121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf601 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - ], - outputs: [ - { - satoshis: 12999997493, - script: '76a9143ec33076ba72b36b66b7ec571dd7417abdeb76f888ac', - }, - ], -}); -describe('Storage - updateTransaction',function suite() { - this.timeout(10000); - it('should throw on failed update', () => { - const exceptedException1 = 'Expected a transaction to update'; - const self = { - store: { - transactions: {}, - }, - }; - - expect(() => updateTransaction.call(self, null)).to.throw(exceptedException1); - }); - it('should work', () => { - const self = { - store: { - transactions: { - ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b: tx, - }, - }, - }; - const txObj = new Transaction(tx); - txObj.nLockTime = 0; - const expected = { - ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b: txObj, - }; - - - const update = updateTransaction.call(self, txObj); - expect(update).to.equal(true); - expect(self.store.transactions).to.deep.equal(expected); - }); -}); diff --git a/src/types/WalletStore/WalletStore.js b/src/types/WalletStore/WalletStore.js new file mode 100644 index 00000000..cf05dbc7 --- /dev/null +++ b/src/types/WalletStore/WalletStore.js @@ -0,0 +1,47 @@ +const IdentityReplaceError = require('../../errors/IndentityIdReplaceError'); +const logger = require('../../logger'); + +class WalletStore { + constructor(walletId) { + this.walletId = walletId; + + this.state = { + mnemonic: null, + paths: new Map(), + identities: new Map(), + }; + } + + createPathState(path) { + logger.debug(`WalletStore - Creating path state ${path}`); + if (!this.state.paths.has(path)) { + this.state.paths.set(path, { + path, + addresses: {}, + }); + } + } + + getPathState(path) { + return this.state.paths.get(path); + } + + getIndexedIdentityIds() { + return [...this.state.identities.values()]; + } + + getIdentityIdByIndex(identityIndex) { + return this.state.identities.get(identityIndex); + } + + insertIdentityAtIndex(identityId, identityIndex) { + const existingId = this.getIdentityIdByIndex(identityIndex); + + if (Boolean(existingId) && existingId !== identityId) { + throw new IdentityReplaceError(`Trying to replace identity at index ${identityIndex}`); + } + + this.state.identities.set(identityIndex, identityId); + } +} +module.exports = WalletStore; From d40e9db00e4826a3ec6b250ae9c67917b553d157 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sat, 27 Nov 2021 21:13:19 +0100 Subject: [PATCH 22/40] feat(KeyChain): return newly issued paths on address marked --- .../methods/getTransaction.js | 4 +-- src/types/KeyChain/KeyChain.js | 2 +- src/types/KeyChain/methods/getForPath.js | 26 +++++++++++-------- src/types/KeyChain/methods/getIssuedPaths.js | 5 ++++ .../KeyChain/methods/markAddressAsUsed.js | 9 ++++++- src/types/KeyChain/methods/maybeLookAhead.js | 6 +++-- .../methods/makeChildKeyChainStore.js | 4 ++- 7 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 src/types/KeyChain/methods/getIssuedPaths.js diff --git a/src/transport/DAPIClientTransport/methods/getTransaction.js b/src/transport/DAPIClientTransport/methods/getTransaction.js index 936076c9..9f9194a3 100644 --- a/src/transport/DAPIClientTransport/methods/getTransaction.js +++ b/src/transport/DAPIClientTransport/methods/getTransaction.js @@ -24,8 +24,8 @@ module.exports = async function getTransaction(txid) { transaction: new Transaction(response.getTransaction()), blockHash: response.getBlockHash().toString('hex'), height, - instantLocked, - chainLocked, + isInstantLocked: instantLocked, + isChainLocked: chainLocked, }; } catch (e) { if (e instanceof NotFoundError) { diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 46da8eb6..88046ea4 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -87,7 +87,6 @@ class KeyChain { this.issuedPaths = new Map(); - this.watchedKeys = new Map(); this.maybeLookAhead(); } } @@ -99,6 +98,7 @@ KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedD KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); KeyChain.prototype.getRootKey = require('./methods/getRootKey'); KeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); +KeyChain.prototype.getIssuedPaths = require('./methods/getIssuedPaths'); KeyChain.prototype.maybeLookAhead = require('./methods/maybeLookAhead'); KeyChain.prototype.markAddressAsUsed = require('./methods/markAddressAsUsed'); KeyChain.prototype.sign = require('./methods/sign'); diff --git a/src/types/KeyChain/methods/getForPath.js b/src/types/KeyChain/methods/getForPath.js index e16e93f9..fcd2b032 100644 --- a/src/types/KeyChain/methods/getForPath.js +++ b/src/types/KeyChain/methods/getForPath.js @@ -1,12 +1,18 @@ +const logger = require('../../../logger'); + function getForPath(path, opts = {}) { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType)) { - if (path === 0 || path === '0') return this.getRootKey(); - throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); + const stringifiedPath = path.toString(); + logger.silly(`KeyChain.getForPath(${stringifiedPath})`); + const isUsed = (opts && opts.isUsed !== undefined) ? opts.isUsed : false; + const isWatched = (opts && opts.isWatched !== undefined) ? opts.isWatched : false; + const isDerivable = ['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType); + if (!isDerivable && stringifiedPath !== '0') { + throw new Error(`Wallet is not loaded from a mnemonic or a HDPrivateKey, impossible to derivate keys for path ${stringifiedPath}`); } let data; - if (this.issuedPaths.has(path)) { - data = this.issuedPaths.get(path); + if (this.issuedPaths.has(stringifiedPath)) { + data = this.issuedPaths.get(stringifiedPath); if (opts && opts.isWatched !== undefined && data.isWatched !== opts.isWatched) { data.isWatched = opts.isWatched; } @@ -16,20 +22,18 @@ function getForPath(path, opts = {}) { return data; } - const key = this.rootKey.derive(path); - const isUsed = (opts && opts.isUsed !== undefined) ? opts.isUsed : false; - const isWatched = (opts && opts.isWatched !== undefined) ? opts.isWatched : false; + const key = (isDerivable) ? this.rootKey.derive(stringifiedPath) : this.getRootKey(); data = { - path, + path: stringifiedPath, key, isUsed, isWatched, address: key.publicKey.toAddress(this.network), }; - this.issuedPaths.set(path, data); - return data; + this.issuedPaths.set(stringifiedPath, data); + return { hasBeenNewlyIssued: true, ...data }; } module.exports = getForPath; diff --git a/src/types/KeyChain/methods/getIssuedPaths.js b/src/types/KeyChain/methods/getIssuedPaths.js new file mode 100644 index 00000000..32475085 --- /dev/null +++ b/src/types/KeyChain/methods/getIssuedPaths.js @@ -0,0 +1,5 @@ +function getWatchedAddresses() { + return [...this.issuedPaths.values()]; +} + +module.exports = getWatchedAddresses; diff --git a/src/types/KeyChain/methods/markAddressAsUsed.js b/src/types/KeyChain/methods/markAddressAsUsed.js index 034ec5ff..90b04f2e 100644 --- a/src/types/KeyChain/methods/markAddressAsUsed.js +++ b/src/types/KeyChain/methods/markAddressAsUsed.js @@ -1,8 +1,15 @@ +const logger = require('../../../logger'); + function markAddressAsUsed(address) { + logger.silly(`KeyChain - Marking ${address} as used`); + const searchResult = [...this.issuedPaths.entries()] .find(([, el]) => el.address.toString() === address.toString()); + const [, addressData] = searchResult; + addressData.isUsed = true; - this.maybeLookAhead(); + + return this.maybeLookAhead(); } module.exports = markAddressAsUsed; diff --git a/src/types/KeyChain/methods/maybeLookAhead.js b/src/types/KeyChain/methods/maybeLookAhead.js index 5f77adfb..45967136 100644 --- a/src/types/KeyChain/methods/maybeLookAhead.js +++ b/src/types/KeyChain/methods/maybeLookAhead.js @@ -74,12 +74,14 @@ function maybeLookAhead() { let index = lastIndex + 1; index <= lastIndexToGenerate; index += 1) { - generatedPaths.push(this.getForPath(`${basePath}/${index}`, { isWatched })); + const pathData = this.getForPath(`${basePath}/${index}`, { isWatched }); + if (pathData.hasBeenNewlyIssued) { + generatedPaths.push(pathData); + } } } } }); - return generatedPaths; } module.exports = maybeLookAhead; diff --git a/src/types/KeyChainStore/methods/makeChildKeyChainStore.js b/src/types/KeyChainStore/methods/makeChildKeyChainStore.js index ac44faf7..b341184f 100644 --- a/src/types/KeyChainStore/methods/makeChildKeyChainStore.js +++ b/src/types/KeyChainStore/methods/makeChildKeyChainStore.js @@ -1,8 +1,10 @@ const { Networks } = require('@dashevo/dashcore-lib'); const { BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../../../CONSTANTS'); const KeyChain = require('../../KeyChain/KeyChain'); +const logger = require('../../../logger'); function makeChildKeyChainStore(path, opts) { + logger.debug(`KeyChainStore - make a child keychainstore for ${path}`); const masterKeyChain = this.getMasterKeyChain(); if (!masterKeyChain) throw new Error('Requires a master keychain to be added first.'); @@ -10,7 +12,7 @@ function makeChildKeyChainStore(path, opts) { const keyChainOpts = { network: masterKeyChain.network, ...opts }; // Accessing the type from getKeyForPath would behave on browser differently due to mangling. - keyChainOpts[masterKeyChain.rootKeyType] = this.getMasterKeyChain().getForPath(path).key; + keyChainOpts[masterKeyChain.rootKeyType] = masterKeyChain.getForPath(path).key; const childKeyChain = new KeyChain(keyChainOpts); childKeyChainStore.addKeyChain(childKeyChain, { isMasterKeyChain: true }); return childKeyChainStore; From 86a1b2d4f4b980b181c7f1f12da2f09d9ae8cf9d Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sat, 27 Nov 2021 21:14:11 +0100 Subject: [PATCH 23/40] feat: integrate store and keychains --- fixtures/chains/testnet/blockheaders.json | 12 ++ fixtures/chains/testnet/transactions.json | 14 ++ src/EVENTS.js | 1 + src/index.d.ts | 5 + src/index.js | 12 +- src/plugins/Plugins/ChainPlugin.js | 19 ++- src/plugins/Workers/IdentitySyncWorker.js | 3 +- .../TransactionSyncStreamWorker.js | 21 +-- .../handlers/onStreamError.js | 1 + .../methods/getLastSyncedBlockHeight.js | 8 +- .../methods/handleTransactionFromStream.js | 5 +- .../methods/processChunks.js | 15 +- .../methods/setLastSyncedBlockHeight.js | 12 +- src/types/Account/Account.js | 114 ++++++++----- src/types/Account/_initializeAccount.js | 69 ++++---- src/types/Account/methods/disconnect.spec.js | 1 - src/types/Account/methods/generateAddress.js | 50 +++--- src/types/Account/methods/getAddress.js | 47 ++---- src/types/Account/methods/getAddresses.js | 53 ++++-- .../Account/methods/getConfirmedBalance.js | 11 +- ...ce.spec.js => getConfirmedBalance.spec.js} | 3 +- src/types/Account/methods/getTotalBalance.js | 11 +- .../Account/methods/getTotalBalance.spec.js | 43 +++++ src/types/Account/methods/getTransaction.js | 15 +- .../Account/methods/getTransactionHistory.js | 122 +++++++------- src/types/Account/methods/getTransactions.js | 9 +- src/types/Account/methods/getUTXOS.js | 60 +++---- .../Account/methods/getUnconfirmedBalance.js | 10 +- .../methods/getUnconfirmedBalance.spec.js | 43 +++++ src/types/Account/methods/getUnusedAddress.js | 49 ++++-- .../Account/methods/getUnusedAddress.spec.js | 1 - .../Account/methods/importBlockHeader.js | 12 +- .../Account/methods/importTransactions.js | 42 ++--- src/types/Wallet/Wallet.js | 19 +-- src/types/Wallet/methods/fromAddress.js | 4 +- src/types/Wallet/methods/fromMnemonic.js | 8 +- src/types/Wallet/methods/fromPrivateKey.js | 4 +- src/types/Wallet/methods/fromPublicKey.js | 4 +- src/types/Wallet/methods/fromSeed.js | 4 +- src/types/types.d.ts | 8 +- src/utils/bip44/ensureAddressesToGapLimit.js | 1 - src/utils/calculateDuffBalance.js | 62 +++++++ src/utils/index.js | 2 + tests/functional/chainStore.spec.js | 153 ++++++++++++++++++ tests/functional/identitiesStore.spec.js | 24 +++ tests/functional/offlineWallet.spec.js | 83 ++++++++++ tests/functional/storage.spec.js | 35 ++++ tests/functional/walletStore.spec.js | 14 ++ 48 files changed, 929 insertions(+), 389 deletions(-) create mode 100644 fixtures/chains/testnet/blockheaders.json create mode 100644 fixtures/chains/testnet/transactions.json rename src/types/Account/methods/{getBalance.spec.js => getConfirmedBalance.spec.js} (94%) create mode 100644 src/types/Account/methods/getTotalBalance.spec.js create mode 100644 src/types/Account/methods/getUnconfirmedBalance.spec.js create mode 100644 src/utils/calculateDuffBalance.js create mode 100644 tests/functional/chainStore.spec.js create mode 100644 tests/functional/identitiesStore.spec.js create mode 100644 tests/functional/offlineWallet.spec.js create mode 100644 tests/functional/storage.spec.js create mode 100644 tests/functional/walletStore.spec.js diff --git a/fixtures/chains/testnet/blockheaders.json b/fixtures/chains/testnet/blockheaders.json new file mode 100644 index 00000000..d58e0885 --- /dev/null +++ b/fixtures/chains/testnet/blockheaders.json @@ -0,0 +1,12 @@ +[ + { + "height": 600000, + "blockheader": "00000020a69faca490012f4371e4bd40a40167809e6fe1b03c9c5dcb7df247f2fa000000b4b8c2d4a78fb35e50a4eda0bd3a5b9b32f4d739b28a4266f24775b3b79d5b61c73275610e04021ea74d0000", + "hash":"000000de786e659950e0f27681faf1a91871d15de264d0b769cb5941c1d807c3" + }, + { + "height": 610000, + "blockheader": "00000020af5155e1f8d5b60fb247ac9bb8badbdce1fa72302336c7daab53585caf0000006d495b9629989ac3699d528956b1f617fb51a199ef4277c3f45f4606aba8e461d1c78a61210b011e94ef0000", + "hash":"00000094d124cfb68d6d59ffaec9f7d63965cb894855684e23a586274b49708f" + } +] \ No newline at end of file diff --git a/fixtures/chains/testnet/transactions.json b/fixtures/chains/testnet/transactions.json new file mode 100644 index 00000000..5fd376a7 --- /dev/null +++ b/fixtures/chains/testnet/transactions.json @@ -0,0 +1,14 @@ +[ + { + "blockHash": "000000b2537d9b3468e02dc6f49fb8bbb5c9d8b77895df1e76816fbad5555d7d", + "transaction": "02000000019583d226737edfad682cc1f0297f688bfca4c0046062ed3e88fa47dd47eab2b2010000006a473044022021550232cc659fa412deb2ea98956faf9ff0efea3a04029bb63022f6809105ce0220230a3f955a3ee7563f5b006342d8db940f3b29f4e312701bf51720575c7a7a8a012103f3d2b2ddfe6ffad2b8fc1708f2eafadfed36bf31a05dfa04b1fc176526475b0ffeffffff02a003d806000000001976a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac4c5bc92b250000001976a9143e89746b9aa52703ab784bc0df467b160406ffb988acb56b0900", + "height": 617398, + "isInstantLocked": true, + "isChainLocked": true + }, + { + "transaction": "0200000001c7d66bb85e0069c221b44b07f49f52cc4f2e54f70e14430b94888327763a66a9010000006b483045022100da5b319f73e6adfee751f33308f5a8c1fceeab2683e15e132d79053b3118639602204262022fb85f88d9802649a289a1134b678efcf708faaeae8f101e8eab785054012102bc626898b49f31f5194de7bc68004401639a20cfa82e4c2eac9684a91fc47a57feffffff0270f2f605000000001976a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac912e250c250000001976a91415e1edb5c5d9e67d0e36f94343b3eff26bb76d1088ac266e0900", + "blockHash":"0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7", + "height": 618023 + } +] \ No newline at end of file diff --git a/src/EVENTS.js b/src/EVENTS.js index 7be5a5ed..1f6448ff 100644 --- a/src/EVENTS.js +++ b/src/EVENTS.js @@ -10,6 +10,7 @@ module.exports = { TRANSACTION: 'transaction', BLOCKHEADER: 'blockheader', FETCHED_ADDRESS: 'FETCHED/ADDRESS', + UPDATED_ADDRESS: 'UPDATED/ADDRESS', ERROR_UPDATE_ADDRESS: 'ERROR/UPDATE_ADDRESS', FETCHED_TRANSACTION: 'FETCHED/TRANSACTION', FETCHED_UNCONFIRMED_TRANSACTION: 'FETCHED/UNCONFIRMED_TRANSACTION', diff --git a/src/index.d.ts b/src/index.d.ts index 7ac3af55..20ab7dd0 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,8 +4,11 @@ import { Account } from "./types/Account/Account"; import { Wallet } from "./types/Wallet/Wallet"; import { Identities } from "./types/Identities/Identities"; +import { IdentitiesStore } from "./types/IdentitiesStore/IdentitiesStore"; import { KeyChain } from "./types/KeyChain/KeyChain"; import { KeyChainStore } from "./types/KeyChainStore/KeyChainStore"; +import { Storage } from "./types/Storage/Storage"; +import { ChainStore } from "./types/ChainStore/ChainStore"; import CONSTANTS from "./CONSTANTS"; import EVENTS from "./EVENTS"; import utils from "./utils"; @@ -14,9 +17,11 @@ import plugins from "./plugins"; export { Account, Wallet, + ChainStore, KeyChain, KeyChainStore, Identities, + IdentitiesStore, EVENTS, CONSTANTS, utils, diff --git a/src/index.js b/src/index.js index a2858bf4..5919e2bd 100644 --- a/src/index.js +++ b/src/index.js @@ -2,22 +2,30 @@ // polyfill included here. Making it work with webpack is rather tricky, so it is used as per // documentation: https://github.com/YuzuJS/setImmediate#usage require('setimmediate'); -const Wallet = require('./types/Wallet/Wallet'); const Account = require('./types/Account/Account'); +const ChainStore = require('./types/ChainStore/ChainStore'); const Identities = require('./types/Identities/Identities'); +const IdentitiesStore = require('./types/IdentitiesStore/IdentitiesStore'); const KeyChain = require('./types/KeyChain/KeyChain'); const KeyChainStore = require('./types/KeyChainStore/KeyChainStore'); +const Storage = require('./types/Storage/Storage'); +const Wallet = require('./types/Wallet/Wallet'); +const WalletStore = require('./types/WalletStore/WalletStore'); const EVENTS = require('./EVENTS'); const CONSTANTS = require('./CONSTANTS'); const utils = require('./utils'); const plugins = require('./plugins'); module.exports = { - Wallet, Account, + ChainStore, Identities, + IdentitiesStore, KeyChain, KeyChainStore, + Storage, + Wallet, + WalletStore, EVENTS, CONSTANTS, utils, diff --git a/src/plugins/Plugins/ChainPlugin.js b/src/plugins/Plugins/ChainPlugin.js index af0d1c45..9c659c37 100644 --- a/src/plugins/Plugins/ChainPlugin.js +++ b/src/plugins/Plugins/ChainPlugin.js @@ -33,7 +33,10 @@ class ChainPlugin extends StandardPlugin { */ async execBlockListener() { const self = this; - const { network } = this.storage.store.wallets[this.walletId]; + const { network } = this.storage.application; + const chainStore = this.storage.getChainStore(network); + + // const { network } = this.storage.store.wallets[this.walletId]; if (!this.isSubscribedToBlocks) { self.transport.on(EVENTS.BLOCK, async (ev) => { @@ -41,7 +44,7 @@ class ChainPlugin extends StandardPlugin { const { payload: block } = ev; this.parentEvents.emit(EVENTS.BLOCK, { type: EVENTS.BLOCK, payload: block }); // We do not announce BLOCKHEADER as this is done by Storage - await self.storage.importBlockHeader(block.header); + await chainStore.importBlockHeader(block.header); }); self.transport.on(EVENTS.BLOCKHEIGHT_CHANGED, async (ev) => { const { payload: blockheight } = ev; @@ -50,7 +53,7 @@ class ChainPlugin extends StandardPlugin { type: EVENTS.BLOCKHEIGHT_CHANGED, payload: blockheight, }); - this.storage.store.chains[network.toString()].blockHeight = blockheight; + chainStore.state.blockHeight = blockheight; logger.debug(`ChainPlugin - setting chain blockheight ${blockheight}`); }); await self.transport.subscribeToBlocks(); @@ -69,20 +72,20 @@ class ChainPlugin extends StandardPlugin { return false; } + const { network } = this.storage.application; + const chainStore = this.storage.getChainStore(network); const { chain: { blocksCount: blocks }, network: { fee: { relay } } } = res; - const { network } = this.storage.store.wallets[this.walletId]; - logger.debug('ChainPlugin - Setting up starting blockHeight', blocks); - this.storage.store.chains[network.toString()].blockHeight = blocks; + chainStore.state.blockHeight = blocks; if (relay) { - this.storage.store.chains[network.toString()].fees.minRelay = dashToDuffs(relay); + chainStore.state.fees.minRelay = dashToDuffs(relay); } const bestBlock = await this.transport.getBlockHeaderByHeight(blocks); - await this.storage.importBlockHeader(bestBlock); + await chainStore.importBlockHeader(bestBlock); return true; } diff --git a/src/plugins/Workers/IdentitySyncWorker.js b/src/plugins/Workers/IdentitySyncWorker.js index c11cdd4e..1e32515b 100644 --- a/src/plugins/Workers/IdentitySyncWorker.js +++ b/src/plugins/Workers/IdentitySyncWorker.js @@ -26,7 +26,8 @@ class IdentitySyncWorker extends Worker { } async execute() { - const indexedIds = await this.storage.getIndexedIdentityIds(this.walletId); + const walletStore = this.storage.getWalletStore(this.walletId); + const indexedIds = await walletStore.getIndexedIdentityIds(); // Add gaps to empty indices const unusedIndices = []; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index 0f17d76a..a322c35e 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -125,7 +125,6 @@ class TransactionSyncStreamWorker extends Worker { .getMessagesList() .map((instantSendLock) => new InstantLock(Buffer.from(instantSendLock))); } - return walletTransactions; } @@ -136,7 +135,7 @@ class TransactionSyncStreamWorker extends Worker { const { skipSynchronizationBeforeHeight, skipSynchronization, - } = (this.storage.store.syncOptions || {}); + } = (this.storage.application.syncOptions || {}); if (skipSynchronization) { logger.debug('TransactionSyncStreamWorker - Wallet created from a new mnemonic. Sync from the best block height.'); @@ -239,25 +238,15 @@ class TransactionSyncStreamWorker extends Worker { } setLastSyncedBlockHash(hash) { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; - - const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; + const applicationStore = this.storage.application; - accountStore.blockHash = hash; + applicationStore.blockHash = hash; - return accountStore.blockHash; + return applicationStore.blockHash; } getLastSyncedBlockHash() { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; - - const { blockHash } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; + const { blockHash } = this.storage.application; return blockHash; } diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js b/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js index fb3577c9..624c5821 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js @@ -2,6 +2,7 @@ const logger = require('../../../../logger'); function onStreamError(error, reject) { logger.silly('TransactionSyncStreamWorker - end stream on error'); + logger.silly(error.message); reject(error); } module.exports = onStreamError; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js index ef9ca277..85e0a4ae 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js @@ -1,15 +1,9 @@ -const { WALLET_TYPES } = require('../../../../CONSTANTS'); /** * Return last synced block height * @return {number} */ module.exports = function getLastSyncedBlockHeight() { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; - - let { blockHeight } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; + let { blockHeight } = this.storage.application; // Fix Genesis issue on DCore if (blockHeight === 0) blockHeight = 1; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js index 299e4c48..eca6ee66 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js @@ -7,7 +7,6 @@ async function handleTransactionFromStream(transaction) { // eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-underscore-dangle const transactionHash = transaction.hash; - this.pendingRequest[transactionHash] = { isProcessing: true, type: 'transaction' }; // eslint-disable-next-line no-await-in-loop const getTransactionResponse = await this.transport.getTransaction(transactionHash); @@ -62,8 +61,8 @@ async function handleTransactionFromStream(transaction) { const metadata = { blockHash: getTransactionResponse.blockHash, height: getTransactionResponse.height, - instantLocked: getTransactionResponse.instantLocked, - chainLocked: getTransactionResponse.chainLocked, + instantLocked: getTransactionResponse.isInstantLocked, + chainLocked: getTransactionResponse.isChainLocked, }; delete this.pendingRequest[transactionHash]; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js index 19e51bff..3229db4e 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js @@ -27,11 +27,12 @@ async function processChunks(dataChunk) { .filterWalletTransactions(transactionsFromResponse, addresses, network); if (walletTransactions.transactions.length) { + // Normalizing format of transaction for account.importTransactions + const walletTransactionWithoutMetadata = walletTransactions.transactions.map((tx) => [tx]); // When a transaction exist, there is multiple things we need to do : // 1) The transaction itself needs to be imported const addressesGeneratedCount = await self - .importTransactions(walletTransactions.transactions); - + .importTransactions(walletTransactionWithoutMetadata); // 2) Transaction metadata need to be fetched and imported as well. // as such event might happen in the future // As we require height information, we fetch transaction using client @@ -80,10 +81,10 @@ async function processChunks(dataChunk) { // Wrapping `cancel` in `setImmediate` due to bug with double-free // explained here (https://github.com/grpc/grpc-node/issues/1652) // and here (https://github.com/nodejs/node/issues/38964) - await new Promise((resolveCancel) => setImmediate(() => { - self.stream.cancel(); - resolveCancel(); - })); + // await new Promise((resolveCancel) => setImmediate(() => { + // self.stream.cancel(); + // resolveCancel(); + // })); } } } @@ -95,7 +96,7 @@ async function processChunks(dataChunk) { if (merkleBlockFromResponse) { // Reverse hashes, as they're little endian in the header const transactionsInHeader = merkleBlockFromResponse.hashes.map((hashHex) => Buffer.from(hashHex, 'hex').reverse().toString('hex')); - const transactionsInWallet = Object.keys(self.storage.getStore().transactions); + const transactionsInWallet = [...self.storage.getChainStore(self.network).state.transactions.keys()]; const isTruePositive = isAnyIntersection(transactionsInHeader, transactionsInWallet); if (isTruePositive) { self.importBlockHeader(merkleBlockFromResponse.header); diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js index 19bd654a..05579ea3 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js @@ -7,14 +7,8 @@ const { WALLET_TYPES } = require('../../../../CONSTANTS'); * @return {number} */ module.exports = function setLastSyncedBlockHeight(blockHeight) { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; + const applicationStore = this.storage.application; + applicationStore.blockHeight = blockHeight; - const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; - - accountStore.blockHeight = blockHeight; - - return accountStore.blockHeight; + return applicationStore.blockHeight; }; diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 69b263a4..3c0aaa7b 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -94,6 +94,7 @@ class Account extends EventEmitter { this.storage = wallet.storage; // Forward all storage event + this.storage.on(EVENTS.UPDATED_ADDRESS, (ev) => this.emit(ev.type, ev)); this.storage.on(EVENTS.CONFIGURED, (ev) => this.emit(ev.type, ev)); this.storage.on(EVENTS.REHYDRATE_STATE_FAILED, (ev) => this.emit(ev.type, ev)); this.storage.on(EVENTS.REHYDRATE_STATE_SUCCESS, (ev) => this.emit(ev.type, ev)); @@ -114,48 +115,75 @@ class Account extends EventEmitter { } switch (this.walletType) { case WALLET_TYPES.HDWALLET: - case WALLET_TYPES.HDPUBLIC: - this.storage.createAccount( - this.walletId, - this.BIP44PATH, - this.network, - this.label, - ); + this.accountPath = getBIP44Path(this.network, this.index); + // this.storage + // .getWalletStore(this.walletId) + // .createPathState(this.BIP44PATH); + // this.storage.createAccount( + // this.walletId, + // this.BIP44PATH, + // this.network, + // this.label, + // ); break; + case WALLET_TYPES.HDPUBLIC: case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.PUBLICKEY: case WALLET_TYPES.ADDRESS: case WALLET_TYPES.SINGLE_ADDRESS: - this.storage.createSingleAddress( - this.walletId, - this.network, - this.label, - ); + this.accountPath = 'm/0'; + // this.storage + // .getWalletStore(this.walletId) + // .createPathState(this); + // this.storage.createSingleAddress( + // this.walletId, + // this.network, + // this.label, + // ); break; default: throw new Error(`Invalid wallet type ${this.walletType}`); } + this.storage + .getWalletStore(this.walletId) + .createPathState(this.accountPath); + let keyChainStorePath = this.index; const keyChainStoreOpts = {}; - if ([ - WALLET_TYPES.HDWALLET, - WALLET_TYPES.HDPUBLIC, - WALLET_TYPES.PRIVATEKEY].includes(this.walletType) - ) { - keyChainStorePath = this.BIP44PATH; - keyChainStoreOpts.lookAheadOpts = { - paths: { - 'm/0': BIP44_ADDRESS_GAP, - 'm/1': BIP44_ADDRESS_GAP, - }, - }; + switch (this.walletType) { + case WALLET_TYPES.HDPUBLIC: + keyChainStorePath = this.accountPath; + keyChainStoreOpts.lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + }, + }; + break; + case WALLET_TYPES.HDWALLET: + case WALLET_TYPES.HDPRIVATE: + keyChainStorePath = this.BIP44PATH; + keyChainStoreOpts.lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP, + }, + }; + break; + default: + break; } + this.keyChainStore = wallet .keyChainStore .makeChildKeyChainStore(keyChainStorePath, keyChainStoreOpts); + // This forces keychainStore to set to issued key what is already its masterkey + if ([WALLET_TYPES.PUBLICKEY, WALLET_TYPES.PRIVATEKEY].includes(this.walletType)) { + this.keyChainStore.getMasterKeyChain().getForPath('0'); + } + this.cacheTx = (opts.cacheTx) ? opts.cacheTx : defaultOptions.cacheTx; this.cacheBlockHeaders = (opts.cacheBlockHeaders) ? opts.cacheBlockHeaders @@ -167,25 +195,25 @@ class Account extends EventEmitter { watchers: {}, }; - // Handle import of cache - if (opts.cache) { - if (opts.cache.addresses) { - try { - this.storage.importAddresses(opts.cache.addresses, this.walletId); - } catch (e) { - this.disconnect(); - throw e; - } - } - if (opts.cache.transactions) { - try { - this.storage.importTransactions(opts.cache.transactions); - } catch (e) { - this.disconnect(); - throw e; - } - } - } + // // Handle import of cache + // if (opts.cache) { + // if (opts.cache.addresses) { + // try { + // this.storage.importAddresses(opts.cache.addresses, this.walletId); + // } catch (e) { + // this.disconnect(); + // throw e; + // } + // } + // if (opts.cache.transactions) { + // try { + // this.storage.importTransactions(opts.cache.transactions); + // } catch (e) { + // this.disconnect(); + // throw e; + // } + // } + // } this.emit(EVENTS.CREATED, { type: EVENTS.CREATED, payload: null }); } diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index 858e7488..67dae65e 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -3,10 +3,35 @@ const EVENTS = require('../../EVENTS'); const { WALLET_TYPES } = require('../../CONSTANTS'); const preparePlugins = require('./_preparePlugins'); const ensureAddressesToGapLimit = require('../../utils/bip44/ensureAddressesToGapLimit'); +const { UPDATED_ADDRESS } = require('../../EVENTS'); +const { is } = require('../../utils'); // eslint-disable-next-line no-underscore-dangle async function _initializeAccount(account, userUnsafePlugins) { const self = account; + + function markAddressAsUsed(props) { + const { address } = props.payload; + // This works if the TX cames from our main address, but not in all cases... + self.keyChainStore + .getMasterKeyChain() + .markAddressAsUsed(address); + } + + self.on(UPDATED_ADDRESS, markAddressAsUsed); + + const accountStore = account.storage + .getWalletStore(account.walletId) + .getPathState(account.accountPath); + + const chainStore = account.storage.getChainStore(account.network); + + const issuedPaths = account.keyChainStore.getMasterKeyChain().getIssuedPaths(); + issuedPaths.forEach((issuedPath) => { + accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); + chainStore.importAddress(issuedPath.address.toString()); + }); + // We run faster in offlineMode to speed up the process when less happens. const readinessIntervalTime = (account.offlineMode) ? 50 : 200; // TODO: perform rejection with a timeout @@ -14,16 +39,16 @@ async function _initializeAccount(account, userUnsafePlugins) { return new Promise(async (resolve, reject) => { try { if (account.injectDefaultPlugins) { - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { - ensureAddressesToGapLimit( - account.store.wallets[account.walletId], - account.walletType, - account.index, - account.getAddress.bind(account), - ); - } else { - await account.getAddress('0'); // We force what is usually done by the BIP44Worker. - } + // if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { + // ensureAddressesToGapLimit( + // account.store.wallets[account.walletId], + // account.walletType, + // account.index, + // account.getAddress.bind(account), + // ); + // } else { + // await account.getAddress('0'); // We force what is usually done by the BIP44Worker. + // } } // Will sort and inject plugins. @@ -68,35 +93,11 @@ async function _initializeAccount(account, userUnsafePlugins) { // while SyncWorker fetch'em on network clearInterval(self.readinessInterval); - switch (account.walletType) { - case WALLET_TYPES.PRIVATEKEY: - case WALLET_TYPES.SINGLE_ADDRESS: - account.generateAddress(0); - sendReady(); - return resolve(true); - case WALLET_TYPES.PUBLICKEY: - case WALLET_TYPES.ADDRESS: - account.generateAddress(0); - sendReady(); - return resolve(true); - default: - break; - } - if (!account.injectDefaultPlugins) { sendReady(); return resolve(true); } - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { - ensureAddressesToGapLimit( - account.store.wallets[account.walletId], - account.walletType, - account.index, - account.getAddress.bind(account), - ); - } - sendReady(); return resolve(true); } diff --git a/src/types/Account/methods/disconnect.spec.js b/src/types/Account/methods/disconnect.spec.js index 0e2036fb..aa0fcfe3 100644 --- a/src/types/Account/methods/disconnect.spec.js +++ b/src/types/Account/methods/disconnect.spec.js @@ -33,7 +33,6 @@ describe('Account - disconnect', function suite() { it('should disconnect to stream and worker', async () => { expect(transportConnected).to.equal(true); await disconnect.call(self); - // console.log(self, transportConnected, emitted); expect(transportConnected).to.equal(false); expect(emitted).to.deep.equal([ 'WORKER/DUMMYWORKER/STARTING', diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index 19f71f6b..c24169b0 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -6,14 +6,13 @@ const { is } = require('../../../utils'); * Generate an address from a path and import it to the store * @param {string} path * @param {boolean} [isWatchedAddress=true] - if the address will be watched - * @return {AddressObj} Address information + * @return {AddressInfo} Address information * */ function generateAddress(path, isWatchedAddress = true) { if (is.undefOrNull(path)) throw new Error('Expected path to generate an address'); let index = 0; - let privateKey; let address; - let keyData; + let keyPathData; const { network } = this; switch (this.walletType) { @@ -46,59 +45,48 @@ function generateAddress(path, isWatchedAddress = true) { case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring index = parseInt(path.toString().split('/')[2], 10); - keyData = this.keyChainStore + keyPathData = this.keyChainStore .getMasterKeyChain() .getForPath(path, { isWatched: isWatchedAddress }); - address = keyData.address.toString(); - privateKey = keyData.key; + address = keyPathData.address.toString(); break; case WALLET_TYPES.HDPUBLIC: index = parseInt(path.toString().split('/')[5], 10); // eslint-disable-next-line no-case-declarations - keyData = this.keyChainStore + keyPathData = this.keyChainStore .getMasterKeyChain() .getForPath(path, { isWatched: isWatchedAddress }); - privateKey = keyData.key; - address = keyData.address.toString(); + address = keyPathData.address.toString(); break; // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: - privateKey = this.keyChainStore.getMasterKeyChain().rootKey.toString(); - address = privateKey.publicKey.toAddress(network).toString(); - - if (isWatchedAddress) { - this.keyChainStore - .issuedPaths.set(0, { - key: privateKey, - path: 0, - address, - isUsed: false, - isWatched: true, - }); - } - break; default: - privateKey = this.keyChainStore + keyPathData = this.keyChainStore .getMasterKeyChain() - .getKeyForPath(path.toString(), { isWatched: isWatchedAddress }).key; - address = privateKey.publicKey.toAddress(network).toString(); + .getForPath(path, { isWatched: isWatchedAddress }); + address = keyPathData.address.toString(); + break; } const addressData = { path: path.toString(), index, address, - // privateKey, transactions: [], + utxos: {}, balanceSat: 0, unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false, }; - this.storage.importAddresses(addressData, this.walletId); + const accountStore = this.storage + .getWalletStore(this.walletId) + .getPathState(this.accountPath); + + const chainStore = this.storage.getChainStore(this.network); + + accountStore.addresses[addressData.path] = addressData.address.toString(); + chainStore.importAddress(addressData.address.toString()); this.emit(EVENTS.GENERATED_ADDRESS, { type: EVENTS.GENERATED_ADDRESS, payload: addressData }); return addressData; } diff --git a/src/types/Account/methods/getAddress.js b/src/types/Account/methods/getAddress.js index ed89c24f..86efad07 100644 --- a/src/types/Account/methods/getAddress.js +++ b/src/types/Account/methods/getAddress.js @@ -1,41 +1,26 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); -const getTypePathFromWalletType = (walletType, addressType = 'external', accountIndex, addressIndex) => { - let type; - let path; - - const addressTypeIndex = (addressType === 'external') ? 0 : 1; - switch (walletType) { - case WALLET_TYPES.HDWALLET: - type = addressType; - path = `m/${addressTypeIndex}/${addressIndex}`; - break; - case WALLET_TYPES.HDPUBLIC: - type = 'external'; - path = `m/${addressTypeIndex}/${addressIndex}`; - break; - case WALLET_TYPES.PUBLICKEY: - case WALLET_TYPES.ADDRESS: - case WALLET_TYPES.PRIVATEKEY: - case WALLET_TYPES.SINGLE_ADDRESS: - default: - type = 'misc'; - path = '0'; - } - return { type, path }; -}; /** * Get a specific addresss based on the index and type of address. * @param {number} index - The index on the type - * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) + * @param {AddressType} [addressType="external"] - Type of the address (external, internal, misc) * @return */ -function getAddress(addressIndex = 0, _type = 'external') { - const accountIndex = this.index; - const { type, path } = getTypePathFromWalletType(this.walletType, _type, accountIndex, addressIndex); +function getAddress(addressIndex = 0, addressType = 'external') { + const addressTypeIndex = (addressType === 'external') ? 0 : 1; + + const { addresses } = this.storage.getWalletStore(this.walletId).getPathState(this.accountPath); + const addressPath = ([WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(this.walletType)) + ? `m/${addressTypeIndex}/${addressIndex}` : '0'; + + const address = addresses[addressPath]; + if (!address) return this.generateAddress(addressPath); - const { wallets } = this.storage.getStore(); - const matchingTypeAddresses = wallets[this.walletId].addresses[type]; - return (matchingTypeAddresses[path]) ? matchingTypeAddresses[path] : this.generateAddress(path); + const chainStore = this.storage.getChainStore(this.network); + return { + index: addressIndex, + path: addressPath, + ...chainStore.getAddress(address), + }; } module.exports = getAddress; diff --git a/src/types/Account/methods/getAddresses.js b/src/types/Account/methods/getAddresses.js index 92a64e65..8ea2d9c5 100644 --- a/src/types/Account/methods/getAddresses.js +++ b/src/types/Account/methods/getAddresses.js @@ -2,20 +2,47 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); /** * Get all the addresses from the store from a given type - * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) + * @param {AddressType} [addressType="external"] - Type of the address (external, internal, misc) * @return {[AddressObj]} address - All address matching the type */ -function getAddresses(_type = 'external') { - const miscTypes = [ - WALLET_TYPES.SINGLE_ADDRESS, - WALLET_TYPES.PUBLICKEY, - WALLET_TYPES.PRIVATEKEY, - WALLET_TYPES.ADDRESS, - ]; - const walletType = (miscTypes.includes(this.walletType)) - ? 'misc' - : ((_type) || 'external'); - const store = this.storage.getStore(); - return store.wallets[this.walletId].addresses[walletType]; +function getAddresses(addressType = 'external') { + // const miscTypes = [ + // WALLET_TYPES.SINGLE_ADDRESS, + // WALLET_TYPES.PUBLICKEY, + // WALLET_TYPES.PRIVATEKEY, + // WALLET_TYPES.ADDRESS, + // ]; + // const walletType = (miscTypes.includes(this.walletType)) + // ? 'misc' + // : ((_type) || 'external'); + // const store = this.storage.getStore(); + // return store.wallets[this.walletId].addresses[walletType]; + const addressTypeIndex = (addressType === 'external') ? 0 : 1; + + const { addresses } = this.storage + .getWalletStore(this.walletId) + .getPathState(this.accountPath); + + const chainStore = this.storage.getChainStore(this.network); + + const baseAddressPath = ([WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(this.walletType)) + ? `m/${addressTypeIndex}` : '0'; + + const typedAddresses = {}; + + Object + .entries(addresses) + .forEach(([path, address]) => { + if (path.startsWith(baseAddressPath)) { + const index = parseInt(path.split('/').slice(-1)[0], 10); + typedAddresses[path] = { + index, + path, + ...chainStore.getAddress(address), + }; + } + }); + + return typedAddresses; } module.exports = getAddresses; diff --git a/src/types/Account/methods/getConfirmedBalance.js b/src/types/Account/methods/getConfirmedBalance.js index c84a71c4..158ca50f 100644 --- a/src/types/Account/methods/getConfirmedBalance.js +++ b/src/types/Account/methods/getConfirmedBalance.js @@ -1,4 +1,4 @@ -const { duffsToDash } = require('../../../utils'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); /** * Return the confirmed balance of an account. @@ -7,10 +7,13 @@ const { duffsToDash } = require('../../../utils'); */ function getConfirmedBalance(displayDuffs = true) { const { - walletId, storage, + walletId, storage, accountPath, network, } = this; - const accountIndex = this.index; - const totalSat = storage.calculateDuffBalance(walletId, accountIndex, 'confirmed'); + + const { addresses } = storage.getWalletStore(walletId).getPathState(accountPath); + + const chainStore = storage.getChainStore(network); + const totalSat = (calculateDuffBalance(Object.values(addresses), chainStore, 'confirmed')); return (displayDuffs) ? totalSat : duffsToDash(totalSat); } diff --git a/src/types/Account/methods/getBalance.spec.js b/src/types/Account/methods/getConfirmedBalance.spec.js similarity index 94% rename from src/types/Account/methods/getBalance.spec.js rename to src/types/Account/methods/getConfirmedBalance.spec.js index 9ade6080..c5db8209 100644 --- a/src/types/Account/methods/getBalance.spec.js +++ b/src/types/Account/methods/getConfirmedBalance.spec.js @@ -3,7 +3,8 @@ const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapsho const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const calculateDuffBalance = require('../../Storage/methods/calculateDuffBalance'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); + let mockedWallet; describe('Account - getTotalBalance', function suite() { diff --git a/src/types/Account/methods/getTotalBalance.js b/src/types/Account/methods/getTotalBalance.js index 8b490ac8..9c016154 100644 --- a/src/types/Account/methods/getTotalBalance.js +++ b/src/types/Account/methods/getTotalBalance.js @@ -1,4 +1,4 @@ -const { duffsToDash } = require('../../../utils'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); /** * Return the total balance of an account (confirmed + unconfirmed). @@ -7,9 +7,14 @@ const { duffsToDash } = require('../../../utils'); */ function getTotalBalance(displayDuffs = true) { const { - walletId, storage, index, + walletId, storage, accountPath, network, } = this; - const totalSat = storage.calculateDuffBalance(walletId, index, 'total'); + + const { addresses } = storage.getWalletStore(walletId).getPathState(accountPath); + + const chainStore = storage.getChainStore(network); + + const totalSat = (calculateDuffBalance(Object.values(addresses), chainStore, 'total')); return (displayDuffs) ? totalSat : duffsToDash(totalSat); } diff --git a/src/types/Account/methods/getTotalBalance.spec.js b/src/types/Account/methods/getTotalBalance.spec.js new file mode 100644 index 00000000..c5db8209 --- /dev/null +++ b/src/types/Account/methods/getTotalBalance.spec.js @@ -0,0 +1,43 @@ +const { expect } = require('chai'); +const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); +const getTotalBalance = require('./getTotalBalance'); +const getConfirmedBalance = require('./getConfirmedBalance'); +const getUnconfirmedBalance = require('./getUnconfirmedBalance'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); + + +let mockedWallet; +describe('Account - getTotalBalance', function suite() { + this.timeout(10000); + before(() => { + const storageHDW = { + store: mockedStore, + calculateDuffBalance, + getStore: () => mockedStore, + mappedAddress: {}, + }; + const walletId = Object.keys(mockedStore.wallets)[0]; + mockedWallet = { + walletId, + index: 0, + storage: storageHDW, + }; + }); + it('should correctly get the balance', async () => { + const balance = await getTotalBalance.call(mockedWallet); + expect(balance).to.equal(184499999506); + }); + it('should correctly get the balance confirmed only', async () => { + const balance = await getConfirmedBalance.call(mockedWallet); + expect(balance).to.equal(184499999506); + }); + it('should correctly get the balance dash value instead of duff', async () => { + const balanceTotalDash = await getTotalBalance.call(mockedWallet, false); + const balanceUnconfDash = await getUnconfirmedBalance.call(mockedWallet, false); + const balanceConfDash = await getConfirmedBalance.call(mockedWallet, false); + + expect(balanceTotalDash).to.equal(1844.99999506); + expect(balanceUnconfDash).to.equal(0); + expect(balanceConfDash).to.equal(1844.99999506); + }); +}); diff --git a/src/types/Account/methods/getTransaction.js b/src/types/Account/methods/getTransaction.js index 4c7dce06..da7a0970 100644 --- a/src/types/Account/methods/getTransaction.js +++ b/src/types/Account/methods/getTransaction.js @@ -6,15 +6,14 @@ const EVENTS = require('../../../EVENTS'); * @return {Promise<{metadata: TransactionMetaData|null, transaction: Transaction}>} */ async function getTransaction(txid = null) { - const searchTransaction = await this.storage.searchTransaction(txid); - const searchTransactionMetadata = await this.storage.searchTransactionMetadata(txid); - if (searchTransaction.found) { - const searchResult = { transaction: searchTransaction.result, metadata: null }; - if (searchTransactionMetadata.found) { - searchResult.metadata = searchTransactionMetadata.result; - } - return searchResult; + const { storage, network } = this; + const chainStore = storage.getChainStore(network); + const searchedTransaction = chainStore.getTransaction(txid); + + if (searchedTransaction) { + return searchedTransaction; } + const getTransactionResponse = await this.transport.getTransaction(txid); if (!getTransactionResponse) return null; const { diff --git a/src/types/Account/methods/getTransactionHistory.js b/src/types/Account/methods/getTransactionHistory.js index 3118f363..e0d13254 100644 --- a/src/types/Account/methods/getTransactionHistory.js +++ b/src/types/Account/methods/getTransactionHistory.js @@ -25,73 +25,77 @@ function getTransactionHistory() { } = this; const transactions = this.getTransactions(); - const store = storage.getStore(); - const chainStore = store.chains[network.toString()]; + const chainStore = storage.getChainStore(network); const { blockHeaders } = chainStore; - const { wallets: walletStore, transactionsMetadata } = store; + // const { wallets: walletStore, transactionsMetadata } = store; - const accountStore = walletStore[walletId]; + // const accountStore = walletStore[walletId]; // In store, not all transaction are specific to this account, we filter our transactions. - const filteredTransactions = filterTransactions( - accountStore, - walletType, - accountIndex, - transactions, - ); - const filteredTransactionsWithMetadata = extendTransactionsWithMetadata( - filteredTransactions, - transactionsMetadata, - ); - - const categorizedTransactions = categorizeTransactions( - filteredTransactionsWithMetadata, - accountStore, - accountIndex, - walletType, - network, - ); - - const sortedCategorizedTransactions = categorizedTransactions.sort(sortByHeightDescending); - - each(sortedCategorizedTransactions, (categorizedTransaction) => { - const { - transaction, - from, - to, - type, - isChainLocked, - isInstantLocked, - } = categorizedTransaction; - - const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash : null; - - // To get time of block, let's find the blockheader. - const blockHeader = blockHeaders[blockHash]; - - // If it's unconfirmed, we won't have a blockHeader nor it's time. - const time = blockHeader ? blockHeader.time : -1; - - const normalizedTransactionHistory = { - // Would require knowing the vout of this vin to determinate inputAmount. - // This information could be fetched, but the necessity vs the cost is questionable. - // fees: calculateTransactionFees(categorizedTransaction.transaction), - from, - to, - type, - time, - txId: transaction.hash, - blockHash, - isChainLocked, - isInstantLocked, - }; - - transactionHistory.push(normalizedTransactionHistory); + // const filteredTransactions = filterTransactions( + // accountStore, + // walletType, + // accountIndex, + // transactions, + // ); + // const filteredTransactionsWithMetadata = extendTransactionsWithMetadata( + // filteredTransactions, + // transactionsMetadata, + // ); + + const transactionsWithMetadata = []; + transactions.forEach((transactionId) => { + console.log(chainStore.getTransaction(transactionId)); }); + + // const categorizedTransactions = categorizeTransactions( + // transactionsWithMetadata, + // accountStore, + // accountIndex, + // walletType, + // network, + // ); + // + // const sortedCategorizedTransactions = categorizedTransactions.sort(sortByHeightDescending); + // + // each(sortedCategorizedTransactions, (categorizedTransaction) => { + // const { + // transaction, + // from, + // to, + // type, + // isChainLocked, + // isInstantLocked, + // } = categorizedTransaction; + // + // const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash : null; + // + // To get time of block, let's find the blockheader. + // const blockHeader = blockHeaders[blockHash]; + // + // If it's unconfirmed, we won't have a blockHeader nor it's time. + // const time = blockHeader ? blockHeader.time : -1; + + // const normalizedTransactionHistory = { + // Would require knowing the vout of this vin to determinate inputAmount. + // This information could be fetched, but the necessity vs the cost is questionable. + // fees: calculateTransactionFees(categorizedTransaction.transaction), + // from, + // to, + // type, + // time, + // txId: transaction.hash, + // blockHash, + // isChainLocked, + // isInstantLocked, + // }; + + // transactionHistory.push(normalizedTransactionHistory); + // }); // Sort by decreasing time. - return transactionHistory.sort(sortbyTimeDescending); + // return transactionHistory.sort(sortbyTimeDescending); } module.exports = getTransactionHistory; diff --git a/src/types/Account/methods/getTransactions.js b/src/types/Account/methods/getTransactions.js index 453fe388..59267b00 100644 --- a/src/types/Account/methods/getTransactions.js +++ b/src/types/Account/methods/getTransactions.js @@ -3,6 +3,11 @@ * @return {[Transaction]} transactions - All transaction in the store */ module.exports = function getTransactions() { - const store = this.storage.getStore(); - return store.transactions; + const chainStore = this.storage.getChainStore(this.network); + const transactions = []; + const { addresses } = this.storage.getWalletStore(this.walletId).getPathState(this.accountPath); + Object.values(addresses).forEach((address) => { + transactions.push(...chainStore.getAddress(address).transactions); + }); + return transactions; }; diff --git a/src/types/Account/methods/getUTXOS.js b/src/types/Account/methods/getUTXOS.js index d0dcfcd6..eab227c0 100644 --- a/src/types/Account/methods/getUTXOS.js +++ b/src/types/Account/methods/getUTXOS.js @@ -11,44 +11,32 @@ const { WALLET_TYPES, COINBASE_MATURITY } = require('../../../CONSTANTS'); function getUTXOS(options = { coinbaseMaturity: COINBASE_MATURITY, }) { - const self = this; const { walletId, network, - BIP44PATH, - walletType, } = this; const utxos = []; - const isHDWallet = [WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(walletType); - const currentBlockHeight = this.store.chains[network].blockHeight; + const chainStore = this.storage.getChainStore(network); + const accountState = this.storage.getWalletStore(walletId).getPathState(this.accountPath); + const currentBlockHeight = chainStore.blockHeight; - for (const addressType in this.store.wallets[walletId].addresses) { - if (!addressType || !['external', 'internal', 'misc'].includes(addressType)) { - continue; - } - for (const path in self.store.wallets[walletId].addresses[addressType]) { - if (!path) continue; - const address = self.store.wallets[walletId].addresses[addressType][path]; - - if (isHDWallet && !path.startsWith(BIP44PATH)) continue; - - for (const identifier in address.utxos) { - if (!identifier) continue; - const [txid, outputIndex] = identifier.split('-'); - const transaction = new Transaction(this.store.transactions[txid]); - - if (transaction.isCoinbase()) { - // If the transaction is not a special transaction, we can't check its - // maturity at the moment of writing this comment. - // The wallet library doesn't maintain the header chain and thus we can - // figure out the height only from the payload, but old coinbase transactions - // doesn't have a payload. - if (!transaction.isSpecialTransaction()) { - continue; - } + Object.values(accountState.addresses).forEach((address) => { + const addressData = chainStore.getAddress(address); + const utxosKeys = Object.keys(addressData.utxos); + utxosKeys.forEach((utxoIdentifier) => { + let skipUtxo = false; + const [txid, outputIndex] = utxoIdentifier.split('-'); + const { transaction } = chainStore.getTransaction(txid); + if (transaction.isCoinbase()) { + // If the transaction is not a special transaction, we can't check its + // maturity at the moment of writing this comment. + // The wallet library doesn't maintain the header chain and thus we can + // figure out the height only from the payload, but old coinbase transactions + // doesn't have a payload. + if (transaction.isSpecialTransaction()) { const transactionHeight = (this.store.transactionsMetadata[txid]) ? this.store.transactionsMetadata[txid].height : transaction.extraPayload.height; @@ -56,22 +44,24 @@ function getUTXOS(options = { // We check maturity is at least 100 blocks. // another way is to just read _scriptBuffer height value. if (transactionHeight + options.coinbaseMaturity > currentBlockHeight) { - continue; + skipUtxo = true; } } + } + if (!skipUtxo) { utxos.push(new Transaction.UnspentOutput( { txId: txid, vout: parseInt(outputIndex, 10), - script: address.utxos[identifier].script, - satoshis: address.utxos[identifier].satoshis, - address: new Address(address.address, network), + script: addressData.utxos[utxoIdentifier].script, + satoshis: addressData.utxos[utxoIdentifier].satoshis, + address: new Address(addressData.address, network), }, )); } - } - } + }); + }); return utxos.sort((a, b) => b.satoshis - a.satoshis); } diff --git a/src/types/Account/methods/getUnconfirmedBalance.js b/src/types/Account/methods/getUnconfirmedBalance.js index 6b320a0f..9833525b 100644 --- a/src/types/Account/methods/getUnconfirmedBalance.js +++ b/src/types/Account/methods/getUnconfirmedBalance.js @@ -1,4 +1,4 @@ -const { duffsToDash } = require('../../../utils'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); /** * Return the total balance of unconfirmed utxo @@ -7,11 +7,13 @@ const { duffsToDash } = require('../../../utils'); */ function getUnconfirmedBalance(displayDuffs = true) { const { - walletId, storage, + walletId, storage, accountPath, network, } = this; - const accountIndex = this.index; - const totalSat = storage.calculateDuffBalance(walletId, accountIndex, 'unconfirmed'); + const { addresses } = storage.getWalletStore(walletId).getPathState(accountPath); + + const chainStore = storage.getChainStore(network); + const totalSat = (calculateDuffBalance(Object.values(addresses), chainStore, 'unconfirmed')); return (displayDuffs) ? totalSat : duffsToDash(totalSat); } diff --git a/src/types/Account/methods/getUnconfirmedBalance.spec.js b/src/types/Account/methods/getUnconfirmedBalance.spec.js new file mode 100644 index 00000000..c5db8209 --- /dev/null +++ b/src/types/Account/methods/getUnconfirmedBalance.spec.js @@ -0,0 +1,43 @@ +const { expect } = require('chai'); +const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); +const getTotalBalance = require('./getTotalBalance'); +const getConfirmedBalance = require('./getConfirmedBalance'); +const getUnconfirmedBalance = require('./getUnconfirmedBalance'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); + + +let mockedWallet; +describe('Account - getTotalBalance', function suite() { + this.timeout(10000); + before(() => { + const storageHDW = { + store: mockedStore, + calculateDuffBalance, + getStore: () => mockedStore, + mappedAddress: {}, + }; + const walletId = Object.keys(mockedStore.wallets)[0]; + mockedWallet = { + walletId, + index: 0, + storage: storageHDW, + }; + }); + it('should correctly get the balance', async () => { + const balance = await getTotalBalance.call(mockedWallet); + expect(balance).to.equal(184499999506); + }); + it('should correctly get the balance confirmed only', async () => { + const balance = await getConfirmedBalance.call(mockedWallet); + expect(balance).to.equal(184499999506); + }); + it('should correctly get the balance dash value instead of duff', async () => { + const balanceTotalDash = await getTotalBalance.call(mockedWallet, false); + const balanceUnconfDash = await getUnconfirmedBalance.call(mockedWallet, false); + const balanceConfDash = await getConfirmedBalance.call(mockedWallet, false); + + expect(balanceTotalDash).to.equal(1844.99999506); + expect(balanceUnconfDash).to.equal(0); + expect(balanceConfDash).to.equal(1844.99999506); + }); +}); diff --git a/src/types/Account/methods/getUnusedAddress.js b/src/types/Account/methods/getUnusedAddress.js index cdd6334c..9a544719 100644 --- a/src/types/Account/methods/getUnusedAddress.js +++ b/src/types/Account/methods/getUnusedAddress.js @@ -10,27 +10,44 @@ function getUnusedAddress(type = 'external', skip = 0) { let unused = { address: '', }; - let skipped = 0; + const skipped = 0; const { walletId } = this; const accountIndex = this.index; - const keys = Object.keys(this.store.wallets[walletId].addresses[type]) - // We filter out other potential account - .filter((el) => parseInt(el.split('/')[3], 10) === accountIndex); - for (let i = 0; i < keys.length; i += 1) { - const key = keys[i]; - const el = (this.store.wallets[walletId].addresses[type][key]); + const { addresses } = this.storage.getWalletStore(walletId).getPathState(this.accountPath); - if (!el || !el.address || el.address === '') { - logger.warn('getUnusedAddress received an empty one.', el, i, skipped); - } - unused = el; - if (el.used === false) { - if (skipped < skip) { - skipped += 1; - } else { - break; + const chainStore = this.storage.getChainStore(this.network); + + // We sort by type + const sortedAddresses = { + external: {}, + internal: {}, + }; + Object + .keys(addresses) + .forEach((path) => { + const splittedPath = path.split('/'); + let pathType = 'external'; + if (splittedPath.length > 1) { + pathType = (splittedPath[splittedPath.length - 2] === '0') ? 'external' : 'internal'; } + sortedAddresses[pathType][path] = addresses[path]; + }); + + const keys = Object.keys(sortedAddresses[type]); + + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + const address = (sortedAddresses[type][key]); + const addressState = chainStore.getAddress(address); + if (!addressState || addressState.transactions.length === 0) { + const keychainData = this.keyChainStore.getMasterKeyChain().getForPath(key); + unused = { + address: keychainData.address.toString(), + path: key, + index: parseInt(key.split('/').splice(-1)[0], 10), + }; + break; } } diff --git a/src/types/Account/methods/getUnusedAddress.spec.js b/src/types/Account/methods/getUnusedAddress.spec.js index 91d0f440..bc6dc99c 100644 --- a/src/types/Account/methods/getUnusedAddress.spec.js +++ b/src/types/Account/methods/getUnusedAddress.spec.js @@ -33,7 +33,6 @@ describe('Account - getUnusedAddress', function suite() { const unusedAddressExternal = getUnusedAddress.call(self); const unusedAddressInternal = getUnusedAddress.call(self, 'internal'); - // console.log(mockedStore.wallets[self.walletId].addresses.internal) expect(unusedAddressExternal).to.be.deep.equal({ address: 'yaVrJ5dgELFkYwv6AydDyGPAJQ5kTJXyAN', balanceSat: 0, diff --git a/src/types/Account/methods/importBlockHeader.js b/src/types/Account/methods/importBlockHeader.js index 01e4517a..79c38b4f 100644 --- a/src/types/Account/methods/importBlockHeader.js +++ b/src/types/Account/methods/importBlockHeader.js @@ -15,16 +15,14 @@ module.exports = async function importBlockHeader(blockHeader) { // knowing the following blockHeight blockheader's prevHash value // const previousHash = blockHeader.prevHash.reverse().toString('hex'); const { - walletId, BIP44PATH, index, store, storage, walletType, + walletId, BIP44PATH, index, store, storage, network, walletType, } = this; - const localWalletStore = store.wallets[walletId]; - const localAccountStore = ([WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(walletType)) - ? localWalletStore.accounts[BIP44PATH.toString()] - : localWalletStore.accounts[index.toString()]; + const applicationStore = storage.application; + const chainStore = storage.getChainStore(network); - localAccountStore.blockHash = blockHeader.id; + applicationStore.blockHash = blockHeader.id; - storage.importBlockHeader(blockHeader); + chainStore.importBlockHeader(blockHeader); logger.silly(`Account.importBlockHeader(${blockHeader.id})`); }; diff --git a/src/types/Account/methods/importTransactions.js b/src/types/Account/methods/importTransactions.js index 2e3bb0dd..be26ef68 100644 --- a/src/types/Account/methods/importTransactions.js +++ b/src/types/Account/methods/importTransactions.js @@ -8,30 +8,36 @@ const ensureAddressesToGapLimit = require('../../../utils/bip44/ensureAddressesT * @param transactions * @returns {Promise} */ -module.exports = async function importTransactions(transactions) { +module.exports = async function importTransactions(transactionsWithMayBeMetadata) { const { - walletType, - walletId, - index, - store, storage, - getAddress, + network, + walletId, + accountPath, + keyChainStore, } = this; - const localWalletStore = store.wallets[walletId]; + const chainStore = storage.getChainStore(network); + const accountStore = storage + .getWalletStore(walletId) + .getPathState(accountPath); - storage.importTransactions(transactions); - logger.silly(`Account.importTransactions(len: ${transactions.length})`); + const masterKeyChain = keyChainStore.getMasterKeyChain(); - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(walletType)) { - // After each imports, we will need to ensure we keep our gap of 20 unused addresses - return ensureAddressesToGapLimit( - localWalletStore, - walletType, - index, - getAddress.bind(this), - ); - } + transactionsWithMayBeMetadata.forEach(([transaction, metadata]) => { + const affectedAddressesData = chainStore.importTransaction(transaction, metadata); + const affectedAddresses = Object.keys(affectedAddressesData); + logger.silly(`Account.importTransactions - Import ${transaction.hash} to chainStore. ${affectedAddresses.length} addresses affected.`); + affectedAddresses.forEach((address) => { + const issuedPaths = masterKeyChain.markAddressAsUsed(address); + logger.silly(`Account.importTransactions - newly issued paths ${issuedPaths.length}`); + issuedPaths.forEach((issuedPath) => { + accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); + chainStore.importAddress(issuedPath.address.toString()); + }); + }); + }); + logger.silly(`Account.importTransactions(len: ${transactionsWithMayBeMetadata.length})`); return 0; }; diff --git a/src/types/Wallet/Wallet.js b/src/types/Wallet/Wallet.js index c2eb9d74..2e11fb7c 100644 --- a/src/types/Wallet/Wallet.js +++ b/src/types/Wallet/Wallet.js @@ -88,21 +88,21 @@ class Wallet extends EventEmitter { mnemonic = generateNewMnemonic(); createdFromNewMnemonic = true; } - this.fromMnemonic(mnemonic); + this.fromMnemonic(mnemonic, this.network, this.passphrase); } else if ('seed' in opts) { - this.fromSeed(opts.seed); + this.fromSeed(opts.seed, this.network); } else if ('HDPrivateKey' in opts) { this.fromHDPrivateKey(opts.HDPrivateKey); } else if ('privateKey' in opts) { this.fromPrivateKey((opts.privateKey === null) ? new PrivateKey(network).toString() - : opts.privateKey); + : opts.privateKey, this.network); } else if ('publicKey' in opts) { - this.fromPublicKey(opts.publicKey); + this.fromPublicKey(opts.publicKey, this.network); } else if ('HDPublicKey' in opts) { this.fromHDPublicKey(opts.HDPublicKey); } else if ('address' in opts) { - this.fromAddress(opts.address); + this.fromAddress(opts.address, this.network); } else { this.fromMnemonic(generateNewMnemonic()); createdFromNewMnemonic = true; @@ -114,21 +114,22 @@ class Wallet extends EventEmitter { this.storage = new Storage({ rehydrate: true, autosave: true, - network, }); this.storage.configure({ adapter: opts.adapter, }); - this.store = this.storage.store; + this.storage.application.network = this.network; + this.storage.createWalletStore(this.walletId); + this.storage.createChainStore(this.network); if (createdFromNewMnemonic) { // As it is pretty complicated to pass any of wallet options // to a specific plugin, using `store` as an options mediator // is easier. - this.store.syncOptions = { + this.storage.application.syncOptions = { skipSynchronization: true, }; @@ -137,7 +138,7 @@ class Wallet extends EventEmitter { + ' created from the new mnemonic'); } } else if (this.unsafeOptions.skipSynchronizationBeforeHeight) { - this.store.syncOptions = { + this.storage.application.syncOptions = { skipSynchronizationBeforeHeight: this.unsafeOptions.skipSynchronizationBeforeHeight, }; } diff --git a/src/types/Wallet/methods/fromAddress.js b/src/types/Wallet/methods/fromAddress.js index 44cdc055..2397a91a 100644 --- a/src/types/Wallet/methods/fromAddress.js +++ b/src/types/Wallet/methods/fromAddress.js @@ -6,13 +6,13 @@ const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * @param address */ -module.exports = function fromAddress(address) { +module.exports = function fromAddress(address, network) { if (!is.address(address)) throw new Error('Expected a valid address (typeof Address or String)'); this.walletType = WALLET_TYPES.ADDRESS; this.mnemonic = null; this.address = address.toString(); - const keyChain = new KeyChain({ address }); + const keyChain = new KeyChain({ address, network }); this.keyChainStore = new KeyChainStore(); this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromMnemonic.js b/src/types/Wallet/methods/fromMnemonic.js index fe9a1cdb..1e2cbdda 100644 --- a/src/types/Wallet/methods/fromMnemonic.js +++ b/src/types/Wallet/methods/fromMnemonic.js @@ -10,14 +10,16 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) * @param mnemonic */ -module.exports = function fromMnemonic(mnemonic) { +module.exports = function fromMnemonic(mnemonic, network, passphrase = '') { if (!is.mnemonic(mnemonic)) { throw new Error('Expected a valid mnemonic (typeof String or Mnemonic)'); } const trimmedMnemonic = mnemonic.toString().trim(); this.walletType = WALLET_TYPES.HDWALLET; - this.mnemonic = trimmedMnemonic; // todo : What about without this ? - this.HDPrivateKey = mnemonicToHDPrivateKey(trimmedMnemonic, this.network, this.passphrase); + // As we do not require the mnemonic except in this.exportWallet + // users of wallet-lib are free to clear this prop at anytime. + this.mnemonic = trimmedMnemonic; + this.HDPrivateKey = mnemonicToHDPrivateKey(trimmedMnemonic, network, passphrase); this.keyChainStore = new KeyChainStore(); const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); diff --git a/src/types/Wallet/methods/fromPrivateKey.js b/src/types/Wallet/methods/fromPrivateKey.js index 36f8c275..870af993 100644 --- a/src/types/Wallet/methods/fromPrivateKey.js +++ b/src/types/Wallet/methods/fromPrivateKey.js @@ -7,13 +7,13 @@ const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) * @param privateKey */ -module.exports = function fromPrivateKey(privateKey) { +module.exports = function fromPrivateKey(privateKey, network) { if (!is.privateKey(privateKey)) throw new Error('Expected a valid private key (typeof PrivateKey or String)'); this.walletType = WALLET_TYPES.PRIVATEKEY; this.mnemonic = null; this.privateKey = privateKey; - const keyChain = new KeyChain({ privateKey }); + const keyChain = new KeyChain({ privateKey, network }); this.keyChainStore = new KeyChainStore(); this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPublicKey.js b/src/types/Wallet/methods/fromPublicKey.js index ed00b585..ffbf4134 100644 --- a/src/types/Wallet/methods/fromPublicKey.js +++ b/src/types/Wallet/methods/fromPublicKey.js @@ -7,13 +7,13 @@ const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) * @param privateKey */ -module.exports = function fromPublicKey(publicKey) { +module.exports = function fromPublicKey(publicKey, network) { if (!is.publicKey(publicKey)) throw new Error('Expected a valid public key (typeof PublicKey or String)'); this.walletType = WALLET_TYPES.PUBLICKEY; this.mnemonic = null; this.publicKey = publicKey; - const keyChain = new KeyChain({ publicKey }); + const keyChain = new KeyChain({ publicKey, network }); this.keyChainStore = new KeyChainStore(); this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromSeed.js b/src/types/Wallet/methods/fromSeed.js index 978adc9e..038fc138 100644 --- a/src/types/Wallet/methods/fromSeed.js +++ b/src/types/Wallet/methods/fromSeed.js @@ -8,7 +8,7 @@ const { * fixme: Term seed is often use, but we might want to rename to fromHDPrivateKey * @param seed */ -module.exports = function fromSeed(seed) { +module.exports = function fromSeed(seed, network) { if (!is.seed(seed)) throw new Error('Expected a valid seed (typeof string)'); - return this.fromHDPrivateKey(seedToHDPrivateKey(seed, this.network)); + return this.fromHDPrivateKey(seedToHDPrivateKey(seed, network)); }; diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 103a3e8f..1673d707 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -91,13 +91,13 @@ export declare type broadcastTransactionOpts = T & { export declare type AddressInfo = T & { path: string; address: string; - balanceSat: number; index: number; - fetchedLast:number; + transaction: [object]; + balanceSat: number; unconfirmedBalanceSat: number; - transaction: object; - used:boolean; utxos:[object] + fetchedLast:number; + used:boolean; } export declare type Network = "livenet" | "testnet" | "evonet" | "regtest" | "local" | "devnet" | "mainnet"; diff --git a/src/utils/bip44/ensureAddressesToGapLimit.js b/src/utils/bip44/ensureAddressesToGapLimit.js index 020fab42..a4c84cbc 100644 --- a/src/utils/bip44/ensureAddressesToGapLimit.js +++ b/src/utils/bip44/ensureAddressesToGapLimit.js @@ -86,7 +86,6 @@ function ensureAccountAddressesToGapLimit(walletStore, walletType, accountIndex, gapBetweenLastUsedAndLastGenerated.internal = lastGeneratedIndexes.internal - lastUsedIndexes.internal; addressesToGenerate.internal = BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.internal; } - Object.entries(addressesToGenerate) .forEach(([typeToGenerate, numberToGenerate]) => { if (numberToGenerate > 0) { diff --git a/src/utils/calculateDuffBalance.js b/src/utils/calculateDuffBalance.js new file mode 100644 index 00000000..b5c7da96 --- /dev/null +++ b/src/utils/calculateDuffBalance.js @@ -0,0 +1,62 @@ +/** + * + * @param walletId - The wallet Id where to perform the calculation + * @param accountIndex - The account Index where to perform the calculation + * @param type {{'confirmed','unconfirmed','total'}} Default: total. Calculate balance by utxo type. + * @return {number} Balance in duff + */ +module.exports = function calculateDuffBalance(addresses, chainStore, type = 'total') { + let totalSat = 0; + + addresses.forEach((address) => { + const addressData = chainStore.getAddress(address); + switch (type) { + case 'total': + totalSat += addressData.balanceSat + addressData.unconfirmedBalanceSat; + break; + case 'confirmed': + totalSat += addressData.balanceSat; + break; + case 'unconfirmed': + totalSat += addressData.unconfirmedBalanceSat; + break; + default: + throw new Error(`Unexpected balance type. Got ${type}`); + } + }); + // if (walletId === undefined || accountIndex === undefined) { + // throw new Error('Cannot calculate without walletId and accountIndex params'); + // } + // + // const { addresses } = this.getStore().wallets[walletId]; + // const subwallets = Object.keys(addresses); + // subwallets.forEach((subwallet) => { + // const paths = Object.keys(addresses[subwallet]) + // // We filter out other potential account + // .filter((el) => { + // const splitted = el.split('/'); + // const index = parseInt((splitted.length === 1) ? splitted[0] : splitted[3], 10); + // return index === accountIndex; + // }); + // + // paths.forEach((path) => { + // const address = addresses[subwallet][path]; + // const { balanceSat, unconfirmedBalanceSat } = address; + // switch (type) { + // case 'total': + // totalSat += balanceSat + unconfirmedBalanceSat; + // break; + // case 'confirmed': + // totalSat += balanceSat; + // break; + // case 'unconfirmed': + // totalSat += unconfirmedBalanceSat; + // break; + // default: + // throw new Error(`Unexpected balance type. Got ${type}`); + // } + // }); + // }); + + return totalSat; +}; diff --git a/src/utils/index.js b/src/utils/index.js index ac209d5d..c23bb6a6 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,7 @@ const extendTransactionsWithMetadata = require('./extendTransactionsWithMetadata'); const calculateTransactionFees = require('./calculateTransactionFees'); const categorizeTransactions = require('./categorizeTransactions'); +const calculateDuffBalance = require('./calculateDuffBalance'); const filterTransactions = require('./filterTransactions'); const { hash, doubleSha256, sha256 } = require('./crypto'); const { varIntSizeBytesFromLength } = require('./varInt'); @@ -29,6 +30,7 @@ module.exports = { calculateTransactionFees, categorizeTransactions, mnemonicToHDPrivateKey, + calculateDuffBalance, generateNewMnemonic, seedToHDPrivateKey, mnemonicToWalletId, diff --git a/tests/functional/chainStore.spec.js b/tests/functional/chainStore.spec.js new file mode 100644 index 00000000..3a7aedef --- /dev/null +++ b/tests/functional/chainStore.spec.js @@ -0,0 +1,153 @@ +const { expect } = require('chai'); +const { ChainStore } = require('../../src'); +const { BlockHeader,Transaction } = require("@dashevo/dashcore-lib"); + +let testnetChainStore; +const testnetBlockHeadersFixtures = require('../../fixtures/chains/testnet/blockheaders.json'); +const testnetTransactionsFixtures = require('../../fixtures/chains/testnet/transactions.json'); + +describe('ChainStore - Functional', ()=>{ + describe('simple usage', ()=>{ + it('should create a testnet chain store', function () { + testnetChainStore = new ChainStore('testnet'); + expect(testnetChainStore.network).to.equal('testnet') + }); + it('should have a initial state', function () { + expect(testnetChainStore.state.blockHeight).to.equal(0); + expect(testnetChainStore.state.fees).to.deep.equal({ + minRelay: -1 + }); + }); + it('should allow to import a blockHeaders', function () { + const stringifiedBlockHeader = testnetBlockHeadersFixtures[0].blockheader; + const blockHeader = new BlockHeader(Buffer.from(stringifiedBlockHeader, 'hex')); + testnetChainStore.importBlockHeader(blockHeader) + expect(testnetChainStore.getBlockHeader(blockHeader.hash).toString()).to.equal(stringifiedBlockHeader) + }); + it('should allow to watch for an address state', function () { + testnetChainStore.importAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq'); + const expectedGetAddress = { + "address": "yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq", + "balanceSat": 0, + "transactions": [], + "unconfirmedBalanceSat": 0, + "utxos": {} + } + expect(testnetChainStore.getAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq')).to.deep.equal(expectedGetAddress) + }); + it('should allow to import a transaction with metadata', function () { + const { blockHash, height, isInstantLocked, isChainLocked } = testnetTransactionsFixtures[0] + const stringifiedTransaction = testnetTransactionsFixtures[0].transaction; + const metadata = { + blockHash, + height, + isInstantLocked, + isChainLocked + } + const transaction = new Transaction(Buffer.from(stringifiedTransaction, 'hex')); + testnetChainStore.importTransaction(transaction, metadata) + expect(testnetChainStore.getTransaction(transaction.hash).transaction.toString()).to.equal(stringifiedTransaction) + expect(testnetChainStore.getTransaction(transaction.hash).metadata).to.deep.equal({ + blockHash, + height, + isInstantLocked, + isChainLocked + }) + }); + it('should have update address state', function () { + const expectedGetAddress = { + "address": "yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq", + "balanceSat": 114820000, + "transactions": [ + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353", + ], + "unconfirmedBalanceSat": 0, + "utxos": { + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353-0": { + "satoshis": 114820000, + "script": "76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac" + } + } + } + expect(testnetChainStore.getAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq')).to.deep.equal(expectedGetAddress) + }); + it('should allow to import a transaction without metadata', function () { + const stringifiedTransaction = testnetTransactionsFixtures[1].transaction; + const transaction = new Transaction(Buffer.from(stringifiedTransaction, 'hex')); + testnetChainStore.importTransaction(transaction) + expect(testnetChainStore.getTransaction(transaction.hash).transaction.toString()).to.equal(stringifiedTransaction) + expect(testnetChainStore.getTransaction(transaction.hash).metadata).to.deep.equal({ + blockHash: null, + height: null, + isInstantLocked: null, + isChainLocked: null + }) + }); + it('should have update address state', function () { + const expectedGetAddress = { + "address": "yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq", + "balanceSat": 214890000, + "transactions": [ + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353", + "61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c" + ], + "unconfirmedBalanceSat": 0, + "utxos": { + "61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c-0": { + "satoshis": 100070000, + "script": "76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac" + }, + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353-0": { + "satoshis": 114820000, + "script": "76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac" + } + } + } + expect(testnetChainStore.getAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq')).to.deep.equal(expectedGetAddress) + }); + it('should update a transaction', function () { + const stringifiedTransaction = testnetTransactionsFixtures[1].transaction; + const transaction = new Transaction(Buffer.from(stringifiedTransaction, 'hex')); + testnetChainStore.importTransaction(transaction, { + blockHash: '0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7', + height: 618023, + isInstantLocked: true, + isChainLocked: true + }) + expect(testnetChainStore.getTransaction(transaction.hash).transaction.toString()).to.equal(stringifiedTransaction) + expect(testnetChainStore.getTransaction(transaction.hash).metadata).to.deep.equal({ + blockHash: '0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7', + height: 618023, + isInstantLocked: true, + isChainLocked: true + }) + }); + // it('should also have updated watched address', function () { + // + // }); + // it('should allow to import an instantLock for a transaction', function () { + // + // }); + // it('should have updated address state with locks', function () { + // + // }); + let exportedState; + it('should allow to export', function () { + exportedState = testnetChainStore.exportState() + }); + it('should allow to import store', function () { + const importedChainStore = new ChainStore(); + importedChainStore.importState(exportedState) + expect(importedChainStore.exportState()).to.deep.equal(exportedState) + }); + it('should consider previous transaction when address is added afterwards', function () { + const importedChainStore = new ChainStore(); + const modifiedState = JSON.parse(JSON.stringify(exportedState)); + modifiedState.state.addresses = {}; + importedChainStore.importState(modifiedState) + importedChainStore.importAddress("yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq") + expect(importedChainStore.exportState()).to.deep.equal(exportedState) + }); + }) +}); + diff --git a/tests/functional/identitiesStore.spec.js b/tests/functional/identitiesStore.spec.js new file mode 100644 index 00000000..ac8c2d83 --- /dev/null +++ b/tests/functional/identitiesStore.spec.js @@ -0,0 +1,24 @@ +//[ +// "9Gk9T5mJY9j3dDX1D1tG5WYaV8g6zQTS2ocFFXe6NCrq", +// null, +// "HZJywfYZ87fdJFLkp7wtnTfS29zpvR63f21gqaajLYx6" +// ] + +const { expect } = require('chai'); +const { IdentitiesStore } = require('../../src'); +// const { BlockHeader,Transaction } = require("@dashevo/dashcore-lib"); +// +let identitiesStore; +// const testnetBlockHeadersFixtures = require('../../fixtures/chains/testnet/blockheaders.json'); +// const testnetTransactionsFixtures = require('../../fixtures/chains/testnet/transactions.json'); + +describe('IdentitiesStore - Functional', ()=> { + describe('simple usage', () => { + it('should create an identityStore', function () { + identitiesStore = new IdentitiesStore(); + }); + it('should import an identity', function () { + identitiesStore.importIdentity() + }); + }) +}) \ No newline at end of file diff --git a/tests/functional/offlineWallet.spec.js b/tests/functional/offlineWallet.spec.js new file mode 100644 index 00000000..3da8930c --- /dev/null +++ b/tests/functional/offlineWallet.spec.js @@ -0,0 +1,83 @@ +const { expect, use} = require('chai'); +const { Wallet } = require('../../src/index'); +const { Transaction } = require('@dashevo/dashcore-lib'); +let offlineWallet; +let account0; +describe('Wallet-lib - functional - offline Wallet', function suite() { + this.timeout(700000); + describe('Wallet', () => { + describe('Create a new offline wallet', () => { + it('should create a new offline wallet', () => { + offlineWallet = new Wallet({ + offlineMode: true, + mnemonic: 'replace eternal resource drill side kidney sudden thought account fog fluid wire' + }); + }); + }); + }); + + describe('Account', () => { + it('should allow to get the first account', async function () { + account0 = await offlineWallet.getAccount(); + expect(account0.offlineMode).to.equal(true) + expect(account0.accountPath).to.equal(`m/44'/1'/0'`) + }); + it('should get all addresses', function () { + const externalAddressesSet = account0.getAddresses(); + const internalAddressesSet = account0.getAddresses('internal'); + expect(Object.keys(externalAddressesSet)).to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19' + ]) + expect(Object.keys(internalAddressesSet)).to.deep.equal([ + 'm/1/0', 'm/1/1', 'm/1/2', + 'm/1/3', 'm/1/4', 'm/1/5', + 'm/1/6', 'm/1/7', 'm/1/8', + 'm/1/9', 'm/1/10', 'm/1/11', + 'm/1/12', 'm/1/13', 'm/1/14', + 'm/1/15', 'm/1/16', 'm/1/17', + 'm/1/18', 'm/1/19' + ]) + }); + it('should generate new address on tx affecting address', function () { + expect(account0.getUnusedAddress()).to.deep.equal({ + address: 'yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq', + path: 'm/0/0', + index: 0 + }); + const transactionsWithMetadata = [ + [ + new Transaction('0200000001c7d66bb85e0069c221b44b07f49f52cc4f2e54f70e14430b94888327763a66a9010000006b483045022100da5b319f73e6adfee751f33308f5a8c1fceeab2683e15e132d79053b3118639602204262022fb85f88d9802649a289a1134b678efcf708faaeae8f101e8eab785054012102bc626898b49f31f5194de7bc68004401639a20cfa82e4c2eac9684a91fc47a57feffffff0270f2f605000000001976a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac912e250c250000001976a91415e1edb5c5d9e67d0e36f94343b3eff26bb76d1088ac266e0900'), + {"blockHash":"0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7","height":618023,"instantLocked":true,"chainLocked":true} + ] + ] + account0.importTransactions(transactionsWithMetadata); + expect(account0.getUnusedAddress()).to.deep.equal({ + address: 'yWFHBxc6c9jmkL82v795PtJJteSkcVKbt5', + path: 'm/0/1', + index: 1 + }); + expect(account0.getTotalBalance()).to.equal(100070000); + expect(account0.getUTXOS().length).to.equal(1); + expect(account0.getUTXOS()[0].toString()).to.equal('61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c:0'); + expect(account0.getUTXOS()[0]).to.deep.equal(new Transaction.UnspentOutput({"address":"yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq","txid":"61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c","vout":0,"scriptPubKey":"76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac","amount":1.0007})) + }); + it('should have issued new addresses', function () { + const externalAddressesSet = account0.getAddresses(); + expect(Object.keys(externalAddressesSet)).to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19', 'm/0/20' + ]) + }); + }); +}); diff --git a/tests/functional/storage.spec.js b/tests/functional/storage.spec.js new file mode 100644 index 00000000..3fb7868c --- /dev/null +++ b/tests/functional/storage.spec.js @@ -0,0 +1,35 @@ +const { expect } = require('chai'); +const { Storage } = require('../../src'); + +let storage; +describe('Storage - Functional', ()=> { + describe('simple usage', () => { + it('should create a storage', function () { + storage = new Storage(); + }); + it('should create a chain', function () { + storage.createChainStore('testnet') + }); + it('should get a chain store', function () { + const chainStore = storage.getChainStore('testnet') + expect(chainStore.network).to.equal('testnet'); + expect(chainStore.state.blockHeight).to.equal(0); + }); + it('should create a wallet', function () { + storage.createWalletStore('73a5575413') + }); + it('should get a wallet store', function () { + const walletStore = storage.getWalletStore('73a5575413') + expect(walletStore.walletId).to.equal('73a5575413'); + }); + it('should create paths store', function () { + const walletStore = storage.getWalletStore('73a5575413') + // const publicKeyStore = walletStore.createPathStore(`m/0'`); + const accountStore = walletStore.createPathState(`m/44'/1'/0'`); + // const contactReceivingStore = walletStore.createPathStore(`m/9'/1'/15'/0'/123/456`); + }); + it('should ', function () { + + }); + }) +}) \ No newline at end of file diff --git a/tests/functional/walletStore.spec.js b/tests/functional/walletStore.spec.js new file mode 100644 index 00000000..61fbaa29 --- /dev/null +++ b/tests/functional/walletStore.spec.js @@ -0,0 +1,14 @@ +const { expect } = require('chai'); +const { WalletStore } = require('../../src'); + +let walletStore; +describe('WalletStore - Functional', ()=> { + describe('simple usage', () => { + it('should create a walletStore', function () { + walletStore = new WalletStore(); + }); + it('should create account', function () { + walletStore.createAccount() + }); + }) +}) \ No newline at end of file From d82fcabac977c871f3143f341412c56959fa7d65 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 28 Nov 2021 02:20:49 +0100 Subject: [PATCH 24/40] fix: get private keys --- src/types/Account/methods/getPrivateKeys.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/types/Account/methods/getPrivateKeys.js b/src/types/Account/methods/getPrivateKeys.js index 3a3ccb50..db821497 100644 --- a/src/types/Account/methods/getPrivateKeys.js +++ b/src/types/Account/methods/getPrivateKeys.js @@ -5,23 +5,18 @@ */ function getPrivateKeys(addressList) { let addresses = []; - let privKeys = []; + const privKeys = []; if (addressList.constructor.name === Object.name) { addresses = [addressList]; } else { addresses = addressList; } - const { walletId } = this; - const self = this; - const subwallets = Object.keys(this.store.wallets[walletId].addresses); - subwallets.forEach((subwallet) => { - const paths = Object.keys(self.store.wallets[walletId].addresses[subwallet]); - paths.forEach((path) => { - const address = self.store.wallets[walletId].addresses[subwallet][path]; - if (addresses.includes(address.address)) { - const privateKey = self.keyChainStore.getMasterKeyChain().getForPath(path).key; - privKeys = privKeys.concat([privateKey]); - } - }); + const { keyChainStore } = this; + + const keyChain = keyChainStore.getMasterKeyChain(); + + addresses.forEach((address) => { + const addressData = keyChain.getForAddress(address); + privKeys.push(addressData.key); }); return privKeys; From 9522989f10c0bac44994cb62d7e27622d5e81f73 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 28 Nov 2021 02:35:45 +0100 Subject: [PATCH 25/40] tests: update tests --- .../Account/methods/getTransaction.spec.js | 4 ---- .../methods/getTransactionHistory.spec.js | 5 ----- .../methods/getIdentityHDKeyById.spec.js | 2 -- .../Identities/methods/getIdentityIds.spec.js | 2 -- src/types/Wallet/methods/fromMnemonic.spec.js | 21 ++++++++----------- .../Wallet/methods/fromPrivateKey.spec.js | 4 ++-- .../Wallet/methods/fromPublicKey.spec.js | 4 ++-- src/types/Wallet/methods/fromSeed.spec.js | 2 +- tests/functional/offlineWallet.spec.js | 13 ++++++++++-- 9 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/types/Account/methods/getTransaction.spec.js b/src/types/Account/methods/getTransaction.spec.js index 2a4f80cd..139b95ae 100644 --- a/src/types/Account/methods/getTransaction.spec.js +++ b/src/types/Account/methods/getTransaction.spec.js @@ -1,8 +1,6 @@ const { expect } = require('chai'); const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getTransaction = require('./getTransaction'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); -const searchTransactionMetadata = require('../../Storage/methods/searchTransactionMetadata'); let mockedWallet; let fetchTransactionInfoCalledNb = 0; @@ -13,8 +11,6 @@ describe('Account - getTransaction', function suite() { store: mockedStore, getStore: () => mockedStore, mappedAddress: {}, - searchTransaction, - searchTransactionMetadata, importTransactions: () => null, }; const walletId = Object.keys(mockedStore.wallets)[0]; diff --git a/src/types/Account/methods/getTransactionHistory.spec.js b/src/types/Account/methods/getTransactionHistory.spec.js index 6dd869b2..c34aaaae 100644 --- a/src/types/Account/methods/getTransactionHistory.spec.js +++ b/src/types/Account/methods/getTransactionHistory.spec.js @@ -3,11 +3,6 @@ const {Transaction, BlockHeader} = require('@dashevo/dashcore-lib'); const {WALLET_TYPES} = require('../../../CONSTANTS'); const getTransactions = require('./getTransactions'); const getTransactionHistory = require('./getTransactionHistory'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); -const getTransaction = require('../../Storage/methods/getTransaction'); -const getBlockHeader = require('../../Storage/methods/getBlockHeader'); -const searchBlockHeader = require('../../Storage/methods/searchBlockHeader'); -const searchAddress = require('../../Storage/methods/searchAddress'); const mockedStoreHDWallet = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); const mockedStoreSingleAddress = require('../../../../fixtures/da07-fullstore-snapshot-1548533266'); diff --git a/src/types/Identities/methods/getIdentityHDKeyById.spec.js b/src/types/Identities/methods/getIdentityHDKeyById.spec.js index e13dc087..146d670c 100644 --- a/src/types/Identities/methods/getIdentityHDKeyById.spec.js +++ b/src/types/Identities/methods/getIdentityHDKeyById.spec.js @@ -1,7 +1,6 @@ const { expect } = require('chai'); const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getIdentityHDKeyById = require('./getIdentityHDKeyById'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); let walletMock; let fetchTransactionInfoCalledNb = 0; @@ -14,7 +13,6 @@ describe('Wallet#getIdentityHDKeyById', function suite() { store: mockedStore, getStore: () => mockedStore, mappedAddress: {}, - searchTransaction, getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds, }; const walletId = Object.keys(mockedStore.wallets)[0]; diff --git a/src/types/Identities/methods/getIdentityIds.spec.js b/src/types/Identities/methods/getIdentityIds.spec.js index df90868e..df2e8871 100644 --- a/src/types/Identities/methods/getIdentityIds.spec.js +++ b/src/types/Identities/methods/getIdentityIds.spec.js @@ -1,7 +1,6 @@ const { expect } = require('chai'); const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getIdentityIds = require('./getIdentityIds'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); let mockedWallet; let fetchTransactionInfoCalledNb = 0; @@ -12,7 +11,6 @@ describe('Wallet#getIdentityIds', function suite() { store: mockedStore, getStore: () => mockedStore, mappedAddress: {}, - searchTransaction, getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds, }; const walletId = Object.keys(mockedStore.wallets)[0]; diff --git a/src/types/Wallet/methods/fromMnemonic.spec.js b/src/types/Wallet/methods/fromMnemonic.spec.js index 6ceebe2c..85f498c6 100644 --- a/src/types/Wallet/methods/fromMnemonic.spec.js +++ b/src/types/Wallet/methods/fromMnemonic.spec.js @@ -15,7 +15,7 @@ describe('Wallet - fromMnemonic', function suite() { const self1 = { network: 'livenet', }; - fromMnemonic.call(self1, knifeFixture.mnemonic); + fromMnemonic.call(self1, knifeFixture.mnemonic, 'livenet'); expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); @@ -23,7 +23,7 @@ describe('Wallet - fromMnemonic', function suite() { const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); - expect(keyChain.network.name).to.equal('livenet'); + expect(keyChain.network).to.equal('livenet'); expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); @@ -33,7 +33,7 @@ describe('Wallet - fromMnemonic', function suite() { expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); const keyChain2 = self2.keyChainStore.getMasterKeyChain() - expect(keyChain2.network.name).to.equal('testnet'); + expect(keyChain2.network).to.equal('testnet'); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); @@ -57,38 +57,35 @@ describe('Wallet - fromMnemonic - with passphrase', function suite() { this.timeout(10000); it('should correctly works with passphrase', () => { const self1 = { - network: 'livenet', - passphrase: knifeFixture.passphrase, }; - fromMnemonic.call(self1, knifeFixture.mnemonic); + fromMnemonic.call(self1, knifeFixture.mnemonic, 'livenet', knifeFixture.passphrase); expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); - expect(keyChain.network.name).to.equal('livenet'); + expect(keyChain.network).to.equal('livenet'); expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); const path1 = 'm/44\'/5\'/0\'/0/0'; - const pubKey1 = keyChain.getKeyForPath(path1).publicKey.toAddress(); + const pubKey1 = keyChain.getForPath(path1).key.publicKey.toAddress(); expect(new Dashcore.Address(pubKey1).toString()).to.equal('Xq3zjky18WjwAHpLgGLasvX5g8TeLRKaxt'); const self2 = { - passphrase: knifeFixture.passphrase, }; - fromMnemonic.call(self2, knifeFixture.mnemonic); + fromMnemonic.call(self2, knifeFixture.mnemonic, 'testnet', knifeFixture.passphrase); expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); - expect(keyChain2.network.name).to.equal('testnet'); + expect(keyChain2.network).to.equal('testnet'); expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); const path2 = 'm/44\'/1\'/0\'/0/0'; - const pubKey2 = keyChain2.getKeyForPath(path2).publicKey.toAddress(); + const pubKey2 = keyChain2.getForPath(path2).key.publicKey.toAddress(); expect(new Dashcore.Address(pubKey2, 'testnet').toString()).to.equal('yWYCH9XDRnpdNxh67jQJFkovToBVwWr8Ck'); }); }); diff --git a/src/types/Wallet/methods/fromPrivateKey.spec.js b/src/types/Wallet/methods/fromPrivateKey.spec.js index 222cc0e0..4b6ca5e8 100644 --- a/src/types/Wallet/methods/fromPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromPrivateKey.spec.js @@ -18,7 +18,7 @@ describe('Wallet - fromPrivateKey', function suite() { expect(self1.privateKey).to.equal(cR4t6eFixture.privateKey); const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('privateKey'); - expect(keyChain.rootKey).to.equal(cR4t6eFixture.privateKey); + expect(keyChain.rootKey.toWIF()).to.equal(cR4t6eFixture.privateKey); const self2 = {}; fromPrivateKey.call(self2, cR4t6eFixture.privateKey); @@ -27,7 +27,7 @@ describe('Wallet - fromPrivateKey', function suite() { expect(self2.privateKey).to.equal(cR4t6eFixture.privateKey); const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('privateKey'); - expect(keyChain2.rootKey).to.equal(cR4t6eFixture.privateKey); + expect(keyChain2.rootKey.toWIF()).to.equal(cR4t6eFixture.privateKey); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromPublicKey.spec.js b/src/types/Wallet/methods/fromPublicKey.spec.js index 68c4dbc6..07890f2d 100644 --- a/src/types/Wallet/methods/fromPublicKey.spec.js +++ b/src/types/Wallet/methods/fromPublicKey.spec.js @@ -20,7 +20,7 @@ describe('Wallet - fromPublicKey', function suite() { expect(self1.publicKey).to.equal(cR4t6ePublicKey); const keyChain = self1.keyChainStore.getMasterKeyChain() expect(keyChain.rootKeyType).to.equal('publicKey'); - expect(keyChain.rootKey).to.equal(cR4t6ePublicKey); + expect(keyChain.rootKey.toString()).to.equal(cR4t6ePublicKey.toString()); const self2 = {}; fromPublicKey.call(self2, cR4t6ePublicKey.toString()); @@ -29,7 +29,7 @@ describe('Wallet - fromPublicKey', function suite() { expect(self2.publicKey).to.equal(cR4t6ePublicKey.toString()); const keyChain2 = self2.keyChainStore.getMasterKeyChain() expect(keyChain2.rootKeyType).to.equal('publicKey'); - expect(keyChain2.rootKey).to.equal(cR4t6ePublicKey.toString()); + expect(keyChain2.rootKey.toString()).to.equal(cR4t6ePublicKey.toString()); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromSeed.spec.js b/src/types/Wallet/methods/fromSeed.spec.js index 289c9ce3..a0a9ec82 100644 --- a/src/types/Wallet/methods/fromSeed.spec.js +++ b/src/types/Wallet/methods/fromSeed.spec.js @@ -28,7 +28,7 @@ describe('Wallet - fromSeed', function suite() { network: 'mainnet', }; - fromSeed.call(self2, knifeFixture.seed); + fromSeed.call(self2, knifeFixture.seed, self2.network); expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(null); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); diff --git a/tests/functional/offlineWallet.spec.js b/tests/functional/offlineWallet.spec.js index 3da8930c..ac056f9e 100644 --- a/tests/functional/offlineWallet.spec.js +++ b/tests/functional/offlineWallet.spec.js @@ -64,8 +64,10 @@ describe('Wallet-lib - functional - offline Wallet', function suite() { }); expect(account0.getTotalBalance()).to.equal(100070000); expect(account0.getUTXOS().length).to.equal(1); - expect(account0.getUTXOS()[0].toString()).to.equal('61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c:0'); - expect(account0.getUTXOS()[0]).to.deep.equal(new Transaction.UnspentOutput({"address":"yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq","txid":"61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c","vout":0,"scriptPubKey":"76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac","amount":1.0007})) + const utxo = account0.getUTXOS()[0]; + expect(utxo.toString()).to.equal('61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c:0'); + expect(utxo).to.deep.equal(new Transaction.UnspentOutput({"address":"yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq","txid":"61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c","vout":0,"scriptPubKey":"76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac","amount":1.0007})) + expect(account0.getPrivateKeys([utxo.address.toString()])[0].toString()).to.equal('tprv8k5Lf2T5uY7BZVxCvssWR9txCj5rEvT19Nd291gfuDJf1wnibAB9GTRzke7FvwKnpXBYiYWvjthnnCWPFUvJbsN3StwcL63EnJNcMSmorfC') }); it('should have issued new addresses', function () { const externalAddressesSet = account0.getAddresses(); @@ -79,5 +81,12 @@ describe('Wallet-lib - functional - offline Wallet', function suite() { 'm/0/18', 'm/0/19', 'm/0/20' ]) }); + it('should be able to create and sign transaction', function () { + const tx = account0.createTransaction({ + recipient: 'yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq', + satoshis: 100000 + }) + expect(tx.isFullySigned()).to.equal(true); + }); }); }); From 0349d5a15be1b45320caa95769353ab93ee4ec1c Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Sun, 28 Nov 2021 03:44:27 +0100 Subject: [PATCH 26/40] fix: broadcastTransaction including chaining --- src/types/Account/Account.js | 3 +- .../Account/methods/broadcastTransaction.js | 70 +++++++++++++------ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 3c0aaa7b..921db321 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -251,7 +251,8 @@ class Account extends EventEmitter { * @param {InstantLock} instantLock */ importInstantLock(instantLock) { - this.storage.importInstantLock(instantLock); + const chainStore = this.storage.getChainStore(this.network); + chainStore.importInstantLock(instantLock); this.emit(Account.getInstantLockTopicName(instantLock.txid), instantLock); } diff --git a/src/types/Account/methods/broadcastTransaction.js b/src/types/Account/methods/broadcastTransaction.js index ed1b3cf4..75e39b0c 100644 --- a/src/types/Account/methods/broadcastTransaction.js +++ b/src/types/Account/methods/broadcastTransaction.js @@ -6,30 +6,63 @@ const { InvalidDashcoreTransaction, } = require('../../../errors'); -function impactAffectedInputs({ inputs }) { +function impactAffectedInputs({ transaction }) { const { - storage, walletId, + storage, network, } = this; + const { inputs, changeIndex } = transaction.toObject(); + const txid = transaction.hash; + + const addresses = storage.getChainStore(network).getAddresses(); // We iterate out input to substract their balance. inputs.forEach((input) => { - const potentiallySelectedAddresses = storage.searchAddressesWithTx(input.prevTxId); - // Fixme : If you want this check, you will need to modify fixtures of our tests. - // if (!potentiallySelectedAddresses.found) { - // throw new Error('Input is not part of that Wallet.'); - // } - potentiallySelectedAddresses.results.forEach((potentiallySelectedAddress) => { - const { type, path } = potentiallySelectedAddress; - if (potentiallySelectedAddress.utxos[`${input.prevTxId}-${input.outputIndex}`]) { - const inputUTXO = potentiallySelectedAddress.utxos[`${input.prevTxId}-${input.outputIndex}`]; - const address = storage.store.wallets[walletId].addresses[type][path]; + const potentiallySelectedAddresses = [...addresses] + .reduce((acc, [address, { transactions }]) => { + if (transactions.includes(input.prevTxId)) acc.push(address); + return acc; + }, []); + + potentiallySelectedAddresses.forEach((potentiallySelectedAddress) => { + // console.log(addresses.get(pot)); + const addressData = addresses.get(potentiallySelectedAddress); + if (addressData.utxos[`${input.prevTxId}-${input.outputIndex}`]) { + const inputUTXO = addressData.utxos[`${input.prevTxId}-${input.outputIndex}`]; + // const address = storage.store.wallets[walletId].addresses[type][path]; // Todo: This modify the balance of an address, we need a std method to do that instead. - address.balanceSat -= inputUTXO.satoshis; - delete address.utxos[`${input.prevTxId}-${input.outputIndex}`]; + addressData.balanceSat -= inputUTXO.satoshis; + delete addressData.utxos[`${input.prevTxId}-${input.outputIndex}`]; } }); }); + const changeOutput = transaction.getChangeOutput(); + if (changeOutput) { + const addressString = changeOutput.script.toAddress(network).toString(); + + const address = addresses.get(addressString); + const utxoKey = `${txid}-${changeIndex}`; + + /** + * In some cases, `Storage#importTransaction` function gets called before the + * `impactAffectedInputs`and this utxo being written as a confirmed one. + * Skip creation of the unconfirmed UTXOs for such cases. + */ + if (!address.utxos[utxoKey]) { + address.utxos[utxoKey] = new Dashcore.Transaction.UnspentOutput( + { + txId: txid, + vout: changeIndex, + script: changeOutput.script, + satoshis: changeOutput.satoshis, + address: addressString, + }, + ); + address.unconfirmedBalanceSat = changeOutput.satoshis; + address.used = true; + } + } + return true; } @@ -42,8 +75,7 @@ function impactAffectedInputs({ inputs }) { */ async function broadcastTransaction(transaction, options = {}) { const { network, storage } = this; - const { chains } = storage.getStore(); - if (!this.transport) throw new ValidTransportLayerRequired('broadcast'); + // if (!this.transport) throw new ValidTransportLayerRequired('broadcast'); // We still support having in rawtransaction, if this is the case // we first need to reform our object @@ -61,8 +93,7 @@ async function broadcastTransaction(transaction, options = {}) { throw new Error('Transaction not signed.'); } - const { inputs } = transaction.toObject(); - const { minRelay: minRelayFeeRate } = chains[network.toString()].fees; + const { minRelay: minRelayFeeRate } = storage.getChainStore(network).state.fees; // eslint-disable-next-line no-underscore-dangle const estimateKbSize = transaction._estimateSize() / 1000; @@ -78,9 +109,8 @@ async function broadcastTransaction(transaction, options = {}) { // We now need to impact/update our affected inputs // so we clear them out from UTXOset. impactAffectedInputs.call(this, { - inputs, + transaction, }); - return txid; } From f9eb15d9fe65b813ce7133507cbfa4d0a97dcaf6 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 29 Nov 2021 09:44:29 +0100 Subject: [PATCH 27/40] feat!: update plugin injection and additional test fixes --- src/CONSTANTS.js | 1 - src/plugins/Workers/IdentitySyncWorker.js | 11 +++--- .../methods/getTransaction.spec.js | 4 +- src/types/Account/Account.js | 4 +- src/types/Account/Account.spec.js | 15 ++++++-- .../Account/methods/broadcastTransaction.js | 2 +- .../methods/broadcastTransaction.spec.js | 38 ++++++------------- .../Account/methods/getUnusedIdentityIndex.js | 2 +- .../methods/importInstantLock.spec.js | 1 + src/types/Storage/Storage.js | 2 + src/types/WalletStore/WalletStore.js | 2 +- 11 files changed, 40 insertions(+), 42 deletions(-) create mode 100644 src/types/ChainStore/methods/importInstantLock.spec.js diff --git a/src/CONSTANTS.js b/src/CONSTANTS.js index ac41f49d..838efb8e 100644 --- a/src/CONSTANTS.js +++ b/src/CONSTANTS.js @@ -81,7 +81,6 @@ const CONSTANTS = { ], UNSAFE_PROPERTIES: [ 'storage', - 'keyChain', 'identities', ], SAFE_PROPERTIES: [ diff --git a/src/plugins/Workers/IdentitySyncWorker.js b/src/plugins/Workers/IdentitySyncWorker.js index 1e32515b..99b5f3db 100644 --- a/src/plugins/Workers/IdentitySyncWorker.js +++ b/src/plugins/Workers/IdentitySyncWorker.js @@ -99,11 +99,12 @@ class IdentitySyncWorker extends Worker { logger.silly(`IdentitySyncWorker - got ${Identifier.from(fetchedId)} at ${index}`); // eslint-disable-next-line no-await-in-loop - await this.storage.insertIdentityIdAtIndex( - this.walletId, - Identifier.from(fetchedId).toString(), - index, - ); + await this.storage + .getWalletStore(this.walletId) + .insertIdentityIdAtIndex( + Identifier.from(fetchedId).toString(), + index, + ); } logger.silly('IdentitySyncWorker - sync finished'); diff --git a/src/transport/DAPIClientTransport/methods/getTransaction.spec.js b/src/transport/DAPIClientTransport/methods/getTransaction.spec.js index bb2dea97..63fad293 100644 --- a/src/transport/DAPIClientTransport/methods/getTransaction.spec.js +++ b/src/transport/DAPIClientTransport/methods/getTransaction.spec.js @@ -39,8 +39,8 @@ describe('transports - DAPIClientTransport .getTransaction', function suite() { expect(res.transaction.hash).to.equal('2c0ee853b91b23d881f96f0128bbb5ebb90c9ef7e7bdb4eda360b0e5abf97239'); expect(res.blockHash).to.equal('4f46066bd50cc2684484407696b7949e82bd906ea92c040f59a97cba47ed8176'); expect(res.height).to.equal(42); - expect(res.instantLocked).to.equal(true); - expect(res.chainLocked).to.equal(false); + expect(res.isInstantLocked).to.equal(true); + expect(res.isChainLocked).to.equal(false); }); it('should return null if transaction if not found', async () => { diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 921db321..a3c2c117 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -272,10 +272,10 @@ class Account extends EventEmitter { */ waitForInstantLock(transactionHash, timeout = this.waitForInstantLockTimeout) { let rejectTimeout; - + const chainStore = this.storage.getChainStore(this.network); return Promise.race([ new Promise((resolve) => { - const instantLock = this.storage.getInstantLock(transactionHash); + const instantLock = chainStore.getInstantLock(transactionHash); if (instantLock != null) { clearTimeout(rejectTimeout); resolve(instantLock); diff --git a/src/types/Account/Account.spec.js b/src/types/Account/Account.spec.js index a7b01b6f..64dda6b0 100644 --- a/src/types/Account/Account.spec.js +++ b/src/types/Account/Account.spec.js @@ -7,6 +7,10 @@ const { WALLET_TYPES } = require('../../CONSTANTS'); const { Account, EVENTS } = require('../../index'); const EventEmitter = require('events'); const inMem = require('../../adapters/InMem'); +const Storage = require('../Storage/Storage'); +const {mock} = require("sinon"); +const KeyChainStore = require("../KeyChainStore/KeyChainStore"); +const KeyChain = require("../KeyChain/KeyChain"); const blockHeader = new Dashcore.BlockHeader.fromObject({ hash: '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331', version: 2, @@ -29,7 +33,7 @@ describe('Account - class', function suite() { const mockStorage = { on: emitter.on, emit: emitter.emit, - store: {}, + storage: new Storage(), getStore: () => {}, saveState: () => {}, stopWorker: () => {}, @@ -43,8 +47,13 @@ describe('Account - class', function suite() { this.walletType = WALLET_TYPES.HDWALLET; this.accounts = []; this.network = Dashcore.Networks.testnet; - this.storage = mockStorage; + this.storage = new Storage(); })()); + mocks.wallet.storage.application.network = mocks.wallet.network; + mocks.wallet.storage.createWalletStore(mocks.wallet.walletId); + mocks.wallet.storage.createChainStore(mocks.wallet.network); + mocks.wallet.keyChainStore = new KeyChainStore() + mocks.wallet.keyChainStore.addKeyChain(new KeyChain({mnemonic: fluidMnemonic.mnemonic}), { isMasterKeyChain: true }) }); it('should be specify on missing params', () => { const expectedException1 = 'Expected wallet to be passed as param'; @@ -96,7 +105,7 @@ describe('Account - class', function suite() { await account.on(EVENTS.BLOCKHEADER, ()=>{ done(); }); - account.storage.importBlockHeader(blockHeader); + account.storage.getChainStore(account.network).importBlockHeader(blockHeader); }) }); diff --git a/src/types/Account/methods/broadcastTransaction.js b/src/types/Account/methods/broadcastTransaction.js index 75e39b0c..0d11eba6 100644 --- a/src/types/Account/methods/broadcastTransaction.js +++ b/src/types/Account/methods/broadcastTransaction.js @@ -75,7 +75,7 @@ function impactAffectedInputs({ transaction }) { */ async function broadcastTransaction(transaction, options = {}) { const { network, storage } = this; - // if (!this.transport) throw new ValidTransportLayerRequired('broadcast'); + if (!this.transport) throw new ValidTransportLayerRequired('broadcast'); // We still support having in rawtransaction, if this is the case // we first need to reform our object diff --git a/src/types/Account/methods/broadcastTransaction.spec.js b/src/types/Account/methods/broadcastTransaction.spec.js index 0613a8e3..df89a2ca 100644 --- a/src/types/Account/methods/broadcastTransaction.spec.js +++ b/src/types/Account/methods/broadcastTransaction.spec.js @@ -4,7 +4,7 @@ const broadcastTransaction = require('./broadcastTransaction'); const validRawTxs = require('../../../../fixtures/rawtx').valid; const invalidRawTxs = require('../../../../fixtures/rawtx').invalid; const expectThrowsAsync = require('../../../utils/expectThrowsAsync'); - +const ChainStore = require('../../ChainStore/ChainStore'); const { PrivateKey } = Dashcore; describe('Account - broadcastTransaction', function suite() { @@ -14,12 +14,14 @@ describe('Account - broadcastTransaction', function suite() { let keysToSign; let oneToOneTx; let fee; + // const storage = new Storage(); + // storage.createChainStore('testnet') + // storage.getChainStore('testnet').state.fees.minRelay = 888; + const chainStore = new ChainStore('testnet'); + chainStore.state.fees.minRelay = 888; + chainStore.importAddress('yTBXsrcGw74yMUsK34fBKAWJx3RNCq97Aq'); const storage = { - getStore: ()=>({ - chains:{ - "testnet": { fees: { minRelay: 888 }} - } - }) + getChainStore:()=> chainStore } beforeEach(() => { utxos = [ @@ -87,11 +89,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: () => { searchCalled = +1; return { results: [] }; }, - }, + storage }; const tx = oneToOneTx; @@ -109,11 +107,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: (affectedTxId) => { searchCalled = +1; return { results: [] }; }, - }, + storage }; return broadcastTransaction @@ -132,11 +126,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: () => { searchCalled = +1; return { results: [] }; }, - }, + storage }; const tx = oneToOneTx; @@ -151,11 +141,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: () => { searchCalled = +1; return { results: [] }; }, - }, + storage }; const tx = oneToOneTx; diff --git a/src/types/Account/methods/getUnusedIdentityIndex.js b/src/types/Account/methods/getUnusedIdentityIndex.js index 7cf353d0..0e32b274 100644 --- a/src/types/Account/methods/getUnusedIdentityIndex.js +++ b/src/types/Account/methods/getUnusedIdentityIndex.js @@ -6,7 +6,7 @@ async function getUnusedIdentityIndex() { // Force identities sync before return unused index await this.getWorker('IdentitySyncWorker').execWorker(); - const identityIds = this.storage.getIndexedIdentityIds(this.walletId); + const identityIds = this.storage.getWalletStore(this.walletId).getIndexedIdentityIds(); const firstMissingIndex = identityIds.findIndex((identityId) => !identityId); diff --git a/src/types/ChainStore/methods/importInstantLock.spec.js b/src/types/ChainStore/methods/importInstantLock.spec.js new file mode 100644 index 00000000..86cfd344 --- /dev/null +++ b/src/types/ChainStore/methods/importInstantLock.spec.js @@ -0,0 +1 @@ +const is = '01424b1c86a93794b4cc8bf9391494faf6994fe6794747ba2bc415e7bcd353204301000000c518facee06d0dc8d5b71028ce949dc8ba99d95c023af2752421a3647f6ce668827fbd5503f9a48a73ab9e30e564cd8b95f6c603caa93deaf4c3836c89d19cb71edc6edab0eccafe42d415e5598fa92e15ff71d5e8a81a34cac04c2fea031cc2f9e653a2d2a7fa2ef1994dee8df60e031c29ab7481ccb1bd3a89e7576cb79880'; \ No newline at end of file diff --git a/src/types/Storage/Storage.js b/src/types/Storage/Storage.js index b7a0e439..019a5670 100644 --- a/src/types/Storage/Storage.js +++ b/src/types/Storage/Storage.js @@ -20,5 +20,7 @@ Storage.prototype.createChainStore = require('./methods/createChainStore'); Storage.prototype.createWalletStore = require('./methods/createWalletStore'); Storage.prototype.getChainStore = require('./methods/getChainStore'); Storage.prototype.getWalletStore = require('./methods/getWalletStore'); +Storage.prototype.rehydrateState = require('./methods/rehydrateState'); +Storage.prototype.saveState = require('./methods/saveState'); module.exports = Storage; diff --git a/src/types/WalletStore/WalletStore.js b/src/types/WalletStore/WalletStore.js index cf05dbc7..238eb6b3 100644 --- a/src/types/WalletStore/WalletStore.js +++ b/src/types/WalletStore/WalletStore.js @@ -34,7 +34,7 @@ class WalletStore { return this.state.identities.get(identityIndex); } - insertIdentityAtIndex(identityId, identityIndex) { + insertIdentityIdAtIndex(identityId, identityIndex) { const existingId = this.getIdentityIdByIndex(identityIndex); if (Boolean(existingId) && existingId !== identityId) { From d3dd9751a606fd1f5b402eb40ab20719a805b366 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 30 Nov 2021 13:46:37 +0100 Subject: [PATCH 28/40] feat(WalletStore): move to one file per methods + export/import --- src/types/WalletStore/WalletStore.js | 42 ++++--------------- .../WalletStore/methods/createPathState.js | 12 ++++++ src/types/WalletStore/methods/exportState.js | 14 +++++++ .../methods/getIdentityIdByIndex.js | 4 ++ .../methods/getIndexedIdentityIds.js | 4 ++ src/types/WalletStore/methods/getPathState.js | 4 ++ src/types/WalletStore/methods/importState.js | 7 ++++ .../methods/insertIdentityIdAtIndex.js | 12 ++++++ 8 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 src/types/WalletStore/methods/createPathState.js create mode 100644 src/types/WalletStore/methods/exportState.js create mode 100644 src/types/WalletStore/methods/getIdentityIdByIndex.js create mode 100644 src/types/WalletStore/methods/getIndexedIdentityIds.js create mode 100644 src/types/WalletStore/methods/getPathState.js create mode 100644 src/types/WalletStore/methods/importState.js create mode 100644 src/types/WalletStore/methods/insertIdentityIdAtIndex.js diff --git a/src/types/WalletStore/WalletStore.js b/src/types/WalletStore/WalletStore.js index 238eb6b3..b105d679 100644 --- a/src/types/WalletStore/WalletStore.js +++ b/src/types/WalletStore/WalletStore.js @@ -1,6 +1,3 @@ -const IdentityReplaceError = require('../../errors/IndentityIdReplaceError'); -const logger = require('../../logger'); - class WalletStore { constructor(walletId) { this.walletId = walletId; @@ -11,37 +8,14 @@ class WalletStore { identities: new Map(), }; } +} - createPathState(path) { - logger.debug(`WalletStore - Creating path state ${path}`); - if (!this.state.paths.has(path)) { - this.state.paths.set(path, { - path, - addresses: {}, - }); - } - } - - getPathState(path) { - return this.state.paths.get(path); - } - - getIndexedIdentityIds() { - return [...this.state.identities.values()]; - } - - getIdentityIdByIndex(identityIndex) { - return this.state.identities.get(identityIndex); - } - - insertIdentityIdAtIndex(identityId, identityIndex) { - const existingId = this.getIdentityIdByIndex(identityIndex); - - if (Boolean(existingId) && existingId !== identityId) { - throw new IdentityReplaceError(`Trying to replace identity at index ${identityIndex}`); - } +WalletStore.prototype.createPathState = require('./methods/createPathState'); +WalletStore.prototype.exportState = require('./methods/exportState'); +WalletStore.prototype.getIdentityIdByIndex = require('./methods/getIdentityIdByIndex'); +WalletStore.prototype.getIndexedIdentityIds = require('./methods/getIndexedIdentityIds'); +WalletStore.prototype.getPathState = require('./methods/getPathState'); +WalletStore.prototype.importState = require('./methods/importState'); +WalletStore.prototype.insertIdentityIdAtIndex = require('./methods/insertIdentityIdAtIndex'); - this.state.identities.set(identityIndex, identityId); - } -} module.exports = WalletStore; diff --git a/src/types/WalletStore/methods/createPathState.js b/src/types/WalletStore/methods/createPathState.js new file mode 100644 index 00000000..2dd21594 --- /dev/null +++ b/src/types/WalletStore/methods/createPathState.js @@ -0,0 +1,12 @@ +const logger = require('../../../logger'); + +function createPathState(path) { + logger.debug(`WalletStore - Creating path state ${path}`); + if (!this.state.paths.has(path)) { + this.state.paths.set(path, { + path, + addresses: {}, + }); + } +} +module.exports = createPathState; diff --git a/src/types/WalletStore/methods/exportState.js b/src/types/WalletStore/methods/exportState.js new file mode 100644 index 00000000..20d3ce7b --- /dev/null +++ b/src/types/WalletStore/methods/exportState.js @@ -0,0 +1,14 @@ +function exportState() { + const { walletId } = this; + const { mnemonic, paths, identities } = this.state; + + return { + walletId, + state: { + mnemonic, + paths: Object.fromEntries(paths), + identities: Object.fromEntries(identities), + }, + }; +} +module.exports = exportState; diff --git a/src/types/WalletStore/methods/getIdentityIdByIndex.js b/src/types/WalletStore/methods/getIdentityIdByIndex.js new file mode 100644 index 00000000..f90d1e57 --- /dev/null +++ b/src/types/WalletStore/methods/getIdentityIdByIndex.js @@ -0,0 +1,4 @@ +function getIdentityIdByIndex(identityIndex) { + return this.state.identities.get(identityIndex); +} +module.exports = getIdentityIdByIndex; diff --git a/src/types/WalletStore/methods/getIndexedIdentityIds.js b/src/types/WalletStore/methods/getIndexedIdentityIds.js new file mode 100644 index 00000000..e251ba48 --- /dev/null +++ b/src/types/WalletStore/methods/getIndexedIdentityIds.js @@ -0,0 +1,4 @@ +function getIndexedIdentityIds() { + return [...this.state.identities.values()]; +} +module.exports = getIndexedIdentityIds; diff --git a/src/types/WalletStore/methods/getPathState.js b/src/types/WalletStore/methods/getPathState.js new file mode 100644 index 00000000..d2143cd4 --- /dev/null +++ b/src/types/WalletStore/methods/getPathState.js @@ -0,0 +1,4 @@ +function getPathState(path) { + return this.state.paths.get(path); +} +module.exports = getPathState; diff --git a/src/types/WalletStore/methods/importState.js b/src/types/WalletStore/methods/importState.js new file mode 100644 index 00000000..2706ec30 --- /dev/null +++ b/src/types/WalletStore/methods/importState.js @@ -0,0 +1,7 @@ +function importState(state) { + this.walletId = state.walletId; + this.state.mnemonic = state.state.mnemonic; + this.state.paths = new Map(Object.entries(state.state.paths)); + this.state.identities = new Map(Object.entries(state.state.identities)); +} +module.exports = importState; diff --git a/src/types/WalletStore/methods/insertIdentityIdAtIndex.js b/src/types/WalletStore/methods/insertIdentityIdAtIndex.js new file mode 100644 index 00000000..ee8a9325 --- /dev/null +++ b/src/types/WalletStore/methods/insertIdentityIdAtIndex.js @@ -0,0 +1,12 @@ +const IdentityReplaceError = require('../../../errors/IndentityIdReplaceError'); + +function insertIdentityIdAtIndex(identityId, identityIndex) { + const existingId = this.getIdentityIdByIndex(identityIndex); + + if (Boolean(existingId) && existingId !== identityId) { + throw new IdentityReplaceError(`Trying to replace identity at index ${identityIndex}`); + } + + this.state.identities.set(identityIndex, identityId); +} +module.exports = insertIdentityIdAtIndex; From f9d1399935aa3529bb584640abf8f65932128717 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 30 Nov 2021 13:47:29 +0100 Subject: [PATCH 29/40] refact: remove old unused IdentityStore --- src/index.js | 2 -- src/types/IdentitiesStore/IdentitiesStore.js | 11 -------- .../methods/getIndexedIdentityIds.js | 14 ---------- .../IdentitiesStore/methods/importIdentity.js | 26 ------------------- tests/functional/identitiesStore.spec.js | 24 ----------------- 5 files changed, 77 deletions(-) delete mode 100644 src/types/IdentitiesStore/IdentitiesStore.js delete mode 100644 src/types/IdentitiesStore/methods/getIndexedIdentityIds.js delete mode 100644 src/types/IdentitiesStore/methods/importIdentity.js delete mode 100644 tests/functional/identitiesStore.spec.js diff --git a/src/index.js b/src/index.js index 5919e2bd..c5284a72 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,6 @@ require('setimmediate'); const Account = require('./types/Account/Account'); const ChainStore = require('./types/ChainStore/ChainStore'); const Identities = require('./types/Identities/Identities'); -const IdentitiesStore = require('./types/IdentitiesStore/IdentitiesStore'); const KeyChain = require('./types/KeyChain/KeyChain'); const KeyChainStore = require('./types/KeyChainStore/KeyChainStore'); const Storage = require('./types/Storage/Storage'); @@ -20,7 +19,6 @@ module.exports = { Account, ChainStore, Identities, - IdentitiesStore, KeyChain, KeyChainStore, Storage, diff --git a/src/types/IdentitiesStore/IdentitiesStore.js b/src/types/IdentitiesStore/IdentitiesStore.js deleted file mode 100644 index 383eb3c6..00000000 --- a/src/types/IdentitiesStore/IdentitiesStore.js +++ /dev/null @@ -1,11 +0,0 @@ -class IdentitiesStore { - constructor() { - this.state = { - wallets: {}, - }; - } - - insertIdentityAtIndex() { - - } -} diff --git a/src/types/IdentitiesStore/methods/getIndexedIdentityIds.js b/src/types/IdentitiesStore/methods/getIndexedIdentityIds.js deleted file mode 100644 index ef82fd14..00000000 --- a/src/types/IdentitiesStore/methods/getIndexedIdentityIds.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * - * @param {string} walletId - * @return {Array} - */ -function getIndexedIdentityIds(walletId) { - return [...this.state.wallets[walletId].identities] - .map((identity) => { - - }); - return [...this.state.wallets[walletId].identities]; -} - -module.exports = getIndexedIdentityIds; diff --git a/src/types/IdentitiesStore/methods/importIdentity.js b/src/types/IdentitiesStore/methods/importIdentity.js deleted file mode 100644 index 9940073e..00000000 --- a/src/types/IdentitiesStore/methods/importIdentity.js +++ /dev/null @@ -1,26 +0,0 @@ -const IdentityReplaceError = require('../../../errors/IndentityIdReplaceError'); - -/** - * - * @param {string} walletId - * @param {number} identityIndex - * @param {string} identityId - * @return void - */ -function importIdentity(walletId, identityIndex, identityId) { - console.log(walletId, identityIndex, identityId); - if (!this.store.wallets[walletId].identityIds) { - this.store.wallets[walletId].identityIds = []; - } - - const existingId = this.getIdentityIdByIndex(walletId, identityIndex); - - if (Boolean(existingId) && existingId !== identityId) { - throw new IdentityReplaceError(`Trying to replace identity at index ${identityIndex}`); - } - - this.store.wallets[walletId].identityIds[identityIndex] = identityId; - this.lastModified = Date.now(); -} - -module.exports = importIdentity; diff --git a/tests/functional/identitiesStore.spec.js b/tests/functional/identitiesStore.spec.js deleted file mode 100644 index ac8c2d83..00000000 --- a/tests/functional/identitiesStore.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -//[ -// "9Gk9T5mJY9j3dDX1D1tG5WYaV8g6zQTS2ocFFXe6NCrq", -// null, -// "HZJywfYZ87fdJFLkp7wtnTfS29zpvR63f21gqaajLYx6" -// ] - -const { expect } = require('chai'); -const { IdentitiesStore } = require('../../src'); -// const { BlockHeader,Transaction } = require("@dashevo/dashcore-lib"); -// -let identitiesStore; -// const testnetBlockHeadersFixtures = require('../../fixtures/chains/testnet/blockheaders.json'); -// const testnetTransactionsFixtures = require('../../fixtures/chains/testnet/transactions.json'); - -describe('IdentitiesStore - Functional', ()=> { - describe('simple usage', () => { - it('should create an identityStore', function () { - identitiesStore = new IdentitiesStore(); - }); - it('should import an identity', function () { - identitiesStore.importIdentity() - }); - }) -}) \ No newline at end of file From 426e919daa48bb3a0f96c403e9af815e3d88b7fe Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 30 Nov 2021 14:44:45 +0100 Subject: [PATCH 30/40] fix: ensure watched for privateKey --- src/types/Account/Account.js | 4 +++- src/types/Account/_initializeAccount.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index a3c2c117..d4519d39 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -181,7 +181,9 @@ class Account extends EventEmitter { // This forces keychainStore to set to issued key what is already its masterkey if ([WALLET_TYPES.PUBLICKEY, WALLET_TYPES.PRIVATEKEY].includes(this.walletType)) { - this.keyChainStore.getMasterKeyChain().getForPath('0'); + this.keyChainStore + .getMasterKeyChain() + .getForPath('0', { isWatched: true }); } this.cacheTx = (opts.cacheTx) ? opts.cacheTx : defaultOptions.cacheTx; diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index 67dae65e..263cf3d3 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -26,7 +26,10 @@ async function _initializeAccount(account, userUnsafePlugins) { const chainStore = account.storage.getChainStore(account.network); - const issuedPaths = account.keyChainStore.getMasterKeyChain().getIssuedPaths(); + const issuedPaths = account.keyChainStore + .getMasterKeyChain() + .getIssuedPaths(); + issuedPaths.forEach((issuedPath) => { accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); chainStore.importAddress(issuedPath.address.toString()); From 4862b71241fcccacf9fa2ca55aac89681df35bfd Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 1 Dec 2021 10:27:56 +0300 Subject: [PATCH 31/40] Removes obsolete stopWorker from the Account --- src/types/Account/Account.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/Account/Account.spec.js b/src/types/Account/Account.spec.js index 64dda6b0..63dde6b2 100644 --- a/src/types/Account/Account.spec.js +++ b/src/types/Account/Account.spec.js @@ -36,7 +36,6 @@ describe('Account - class', function suite() { storage: new Storage(), getStore: () => {}, saveState: () => {}, - stopWorker: () => {}, createAccount: () => {}, importBlockHeader: (blockheader)=>{ mockStorage.emit(EVENTS.BLOCKHEADER, {type: EVENTS.BLOCKHEADER, payload:blockheader}); From d824f5baaaf9f8a7f95a6b8f2ab4d0a887ec5022 Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 1 Dec 2021 10:28:03 +0300 Subject: [PATCH 32/40] Removes obsolete stopWorker from the Account --- src/types/Account/methods/disconnect.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/Account/methods/disconnect.js b/src/types/Account/methods/disconnect.js index c60edef6..317dc02a 100644 --- a/src/types/Account/methods/disconnect.js +++ b/src/types/Account/methods/disconnect.js @@ -20,7 +20,6 @@ module.exports = async function disconnect() { } if (this.storage) { await this.storage.saveState(); - await this.storage.stopWorker(); } if (this.removeAllListeners) this.removeAllListeners(); if (this.storage.removeAllListeners) this.storage.removeAllListeners(); From 1ba47cc58b0c857540b3d29a545872d8872464b6 Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 1 Dec 2021 12:19:49 +0300 Subject: [PATCH 33/40] Fix getTotalBalance.spec.js --- fixtures/chains/for_wallet_c922713eac.json | 1087 +++++++++++++++++ fixtures/wallets/c922713eac.json | 97 ++ .../Account/methods/getTotalBalance.spec.js | 47 +- 3 files changed, 1209 insertions(+), 22 deletions(-) create mode 100644 fixtures/chains/for_wallet_c922713eac.json create mode 100644 fixtures/wallets/c922713eac.json diff --git a/fixtures/chains/for_wallet_c922713eac.json b/fixtures/chains/for_wallet_c922713eac.json new file mode 100644 index 00000000..fba5af71 --- /dev/null +++ b/fixtures/chains/for_wallet_c922713eac.json @@ -0,0 +1,1087 @@ +{ + "network": "testnet", + "state": { + "fees": { + "minRelay": 1000 + }, + "blockHeight": 623235, + "blockHeaders": { + "000000b0e2931614db83139686c4b95b30f4351fc1c8265081da235d1ef1ed07": "0000002014b19c3f28e3ea5b8a3764cd76ad322ef1a3e7ca12cf7e72f9625f9d080000004a46458c0f1e647a4c5c0cde93c1349d846c33d143eb5e439ee7e9650da5b9ed4138a761d127011e26e70000", + "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960": "000000206c529bed8f7d56831dbfbe2c87a3c6188bbed1841002926c018993ddf60000008e4b077f4978057981ab7ced5fa3749b8559b627efcdfc7513b7892520cca9c55f4a9761d0dd011e31a50000", + "00000141c85d1b1b5a1313a139c6ebc9e748f1677d3a5a72e5c6c0635c0c07df": "0000002060b9e88760539b554be2f9f57730243c3d9207f033e04ad99eb4f30c810100004aeddaefb9b9a124e9cc154102dd189a3db7ef6bab61d78bb5013d1907798151404b9761160f021e967a0000", + "000002092c76130d20419ca56ea02cc111a121ceb0996715b37e213e00bed874": "000000206b7c87ccac5d5b836c3f3c951ef34b231d51d214ce7fbb68e1ce5474dc00000050f9e99a67a7bceca14f1c20379a7531a01d7aa8a9142ac5e400c2cf4fbddc22bf4b97618b38021eb89b0c00", + "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1": "000000200e44bcf1de3f37892c24004d1835f31689c341d411c92750a4f91626490000005581a10aa3891b2e1c163b6372c172c299b037f7443d97e2ba90c85ee8dc76f4d04c9761f08d011e55560000", + "000000d672c7929c0f570e4793670f7695c848f8bb98d910aade929b41b38ce2": "00000020cd8db43289d1574b2938b0b87bfef9c9cf1d78a7f5626159e506e079490100009cbfb07f675787ebcd3cf8329344212919ee495b471a7af6c944dd3fc07bdad0904f97617c5d011e090e0c00", + "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452": "00000020e28cb3419b92deaa10d998bbf848c895760f6793470e570f9c92c772d60000000b6ceb13eccadbdb1d4d53b4503dc17b5b02a595b7ef87c5866d25150718acb5365097613a5c011e40c40400", + "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554": "00000020d11c62bf5e2d31c9caf71dec9095aa956bddb9cdff7d8a8364c8afea3e00000079e6a9a6c22641abf7f2e2a49a4819327be5a83cf11ec44d51468ddba47f9987f2519761ed51011e981c0500" + }, + "transactions": { + "a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965": { + "transaction": "0300000001481116b70180090112d22598c4962e2034ea269c755a8445410ecdd0299a388a010000006a47304402202be55c1f32da1dd6f408e9a945d38670f64258716044da4314ffda738dbcefd2022050555557347a78209556b1399443ab4685aac8f9c579f209038f470db434c8f50121035cf01fcf7ab699115a5ace61cc818cedd6287a3ff3d2178b44bf93bdcea09c3cffffffff0199e1610d000000001976a914b0b7c8c3637ec294f583738a46d1aa07b0f6ed8c88ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4": { + "transaction": "0300000001655975b3584b18af5ab5cbc4c7f273c7638fa37ca245679222ef6fb4bc203fa4000000006b48304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c310121029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac92b9610d000000001976a9147f6f4280f91e00126d927466cd48629439b763fa88ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "ca2afbe06b0187777f7e1aa8ae90c16ea094fbf1ed1b2ae8372e34c31a2cf67d": { + "transaction": "0300000001b4ce9651f24b1839d43c8eb24d31add5d6ec3fcc984f72379cdbcee680d7f0c8010000006a47304402206c93144ccd1816503fa13e36c5cc7aa4bc834dafb7543d306b0ac122902e3b55022076c756c3ab6ce341f55674f77a21a995264028f5891c1e257691bd743260f7fb01210208ef0d91b0377fd629082192d55ac831b6c9ff0603ed6dc19cbb3d6e21913917ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac8b91610d000000001976a9144b99b817a9caa57fc141cbfa68e2dc86d54ca5ee88ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "3096b29ee16f6ec1b8e8b5f3d01beb5a231d87b51b6fa4e637e0f45b6b8b83b6": { + "transaction": "03000000017df62c1ac3342e37e82a1bedf1fb94a06ec190aea81a7e7f7787016be0fb2aca010000006a47304402200122dda1789ce203582718e77afac10cb4e33acffe728d57b54263e4c007655f0220633b18a0260c731e59bbf69e353ab824d5693d5ea7dd863093adf5916de7bbe901210381e52ecbce2dbd36931346483a2a90b48fcd6d077504ff6ef172e16b1b1104e1ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac8469610d000000001976a914cc7406fdd5c28fbea82d60e2ab1307179c474cd088ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "95fe8be7b264aa71a1a55478abce7233311b7f3a22135a6a2543988294ddbc68": { + "transaction": "0300000001b6838b6b5bf4e037e6a46f1bb5871d235aeb1bd0f3b5e8b8c16e6fe19eb29630010000006b483045022100f03d0e357ac3253f4799061814fe1fe4dad95784240b7aa4b83dadc6d06657c102205e1f81d5e9fe12fb6bd73bc4cdc23468fb9f5ea76c7a37d93281850f2f9f3bc2012103d8505ef3e6bdb5c5cfdadbce5b512979cfaac497642d8406c05bbe9d8dc1f245ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac7d41610d000000001976a914bc6604d7be005be0ed9106d923fdeb13ecd8da3288ac00000000", + "metadata": { + "blockHash": "00000141c85d1b1b5a1313a139c6ebc9e748f1677d3a5a72e5c6c0635c0c07df", + "height": 615752, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "267c4191e3b32241bf00cf15ff7e02460e4d6cc892afaaf3a28f88601b5b6a38": { + "transaction": "030000000168bcdd94829843256a5a13223a7f1b313372ceab7854a5a171aa64b2e78bfe95010000006b483045022100ce097043895c2c32f34be8010790f6c1c86b392be0368c52ece87c7233c8b1cd0220040d2ba2647d337f34ffc422b2d2edb24c9395bd0c19c680afa0fd3013493eee0121035d2cd2b5154fe849c3a9856397c26064c52b033a382b1054635ccb96290cbaa3ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac7619610d000000001976a914744e5f4f8661dcc51e88f4615fe64a1d6b26e6dd88ac00000000", + "metadata": { + "blockHash": "00000141c85d1b1b5a1313a139c6ebc9e748f1677d3a5a72e5c6c0635c0c07df", + "height": 615752, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "eb39c1c9e0e5dd8df5ec1f822a4fd96469749f56443377262d933f888568047d": { + "transaction": "0300000001386a5b1b60888fa2f3aaaf92c86c4d0e46027eff15cf00bf4122b3e391417c26010000006b483045022100cd1ff3e404263423fcea33895ee550d5fd3ae848082402748b9f31b2a3e2071902202e98c8b1c73a84a3f3b03854842aa2590d95881d5af3697b47c36f2df040e6120121031dc2909f3f80373b7b6383e61adfc8a312954b7d420ee8f9a17bddf271999d95ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac6ff1600d000000001976a914a1cd6944da97ed00333c224679234a799d7de43f88ac00000000", + "metadata": { + "blockHash": "000002092c76130d20419ca56ea02cc111a121ceb0996715b37e213e00bed874", + "height": 615754, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "d066ac08dc743bfe5a29fd541802f9269b3da17c71f6b2f64aabd6c68a671566": { + "transaction": "03000000017d046885883f932d26773344569f746964d94f2a821fecf58ddde5e0c9c139eb010000006b48304502210082037c36c8acde9120e5b5234f2b1625698b599966ab65f24498145e7076821a022057b8b2ac8056e93a5753b3c1ad95eff8b9ac0cf6135425d349f765b8db0bd501012103eae3d568169c0c7944c27623a7295ef1f3a563890123c3c33450d8ce4e60117dffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac68c9600d000000001976a914a1ecca0e966e04bc0e6d26466eedc56237eeebc188ac00000000", + "metadata": { + "blockHash": "000002092c76130d20419ca56ea02cc111a121ceb0996715b37e213e00bed874", + "height": 615754, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "a3f4b8c9c9f49434e0437ad1aa0fa9f9c621d3a8742cee174402863a5b23fe4a": { + "transaction": "03000000016615678ac6d6ab4af6b2f6717ca13d9b26f9021854fd295afe3b74dc08ac66d0010000006b483045022100db5523499661badbec84dca3ee03f701de09982497e34fd8df284532ed0e09c902202a5da05c4378f1d878558103bdabcd1801dd2aac9d782c7f7eaa31348d6d00a7012103310974f905740f426e2c21af561d442d198f474344ad79b7071fdafd92fceee4ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac61a1600d000000001976a914c6be1292e6c212259bbf760e509c7cf3e8ce15a888ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "eee88d2a03a0452dfe0abc9a84915f33d552110088c4382f0c459960494bf2ab": { + "transaction": "03000000014afe235b3a86024417ee2c74a8d321c6f9a90faad17a43e03494f4c9c9b8f4a3010000006b483045022100c222770356e4157a7926454701483d26f8ad2a1595d4fe0898eb9ce1ab2b216202202994cfb2563f53f28ab21959289a890fcd2cfdcaf7cc576be8c5930b76ade1d90121036ef5809736958928e5e369a805a7f3236369dcda8cb0ad8b41c02ec12de92b36ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac5a79600d000000001976a914526a5726b41e7f178f9c6ef94e63a321b61db0c688ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "0fd66f212549dbb84d42a745e9e0bef5e6772acfaf8b6d06579cffa15ac9fa1d": { + "transaction": "0300000001abf24b496099450c2f38c488001152d5335f91849abc0afe2d45a0032a8de8ee010000006a47304402203ff02fcd8551e4525522a37d6cc7202daea3be57b7d2a34dbb1d2cc06a38a6cc0220456f04bd6fa328e53f66167ea1c2c18f27d80838f4306e17e633410afa0aefed0121037be066d359ad86dba34a084754a9a79a63626e7ab9df7dd185505c6465969d1affffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac5351600d000000001976a914c96c81e8a992e95eade60a4e3ab967de672c4e6288ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "92be5095443f506771d6f145716ab33a12b20239b5d363c9b09d5e5cd7fb3f96": { + "transaction": "03000000011dfac95aa1ff9c57066d8bafcf2a77e6f5bee0e945a7424db8db4925216fd60f010000006a47304402210097facc4725c831d0afc70828d18c128868fddcdec91479cb3d242658bfbee887021f361e45fe35cfada131d376e290ac4ab0a9710839d49f96690b1a3a5dc160a0012102df67e08e44684da52bd8b648344a00ee6ffae73a7efdfcfb5503af799c3d069cffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac4c29600d000000001976a914c0c6b5c4f8f775acbd6de28c5aafcdb785d6d08588ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "ea704ab4f38d786116875270c141b628f182684fd39dcd57c969237cf92f5589": { + "transaction": "0300000001963ffbd75c5e9db0c963d3b53902b2123ab36a7145f1d67167503f449550be92010000006a473044022065fdcd2e579ae58af2634386d1f92da1617a0ab16bea34e5a5900b9454a528920220496e59ef90d5d79e0dcaf9b97f962e63e41a71cf336e8fdf0bdbabb72deffae801210273e7e7d1fe3d3cb5038f058bb1d5076b0781d61085f5c658ffd6246632b03f9affffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac4501600d000000001976a9145c6bb6833b27ca6252e64faa48b8a29e783df3e788ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "f86445d20beccda3749058d90a605f4179a51a04b09f1262a9aefb382fe21bfd": { + "transaction": "030000000189552ff97c2369c957cd9dd34f6882f128b641c17052871661788df3b44a70ea010000006b483045022100b671fc3bc69567298f61f5d033b7746985fe517b83e30f38dac0f1306aba0a5902201a9e509d96ab9f57c74cca218171f49b7a3d56a66afde0bf53481ebd68892e19012103108df8bbcd37ace008ec407ca8cdc95ebdb220db3df8527418a0d86c95c1af3bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac3ed95f0d000000001976a91444fcf081778ce018b167d007d910f3c8993c4c7f88ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "b2420b554dad82bffb22f10bda0124876fc6d02b4892d01833b413cad275c351": { + "transaction": "0300000001fd1be22f38fbaea962129fb0041aa579415f600ad9589074a3cdec0bd24564f8010000006b483045022100c19616212adbade24c4322520e20218a6518947cb420025b6cda0560194b3c1c0220208e1132a95674160069cd02007200acfa7568d7b4e7af5c63bbbdbe1add2f3b012102cd31f5a37c8032f34f6babb216a48d8ee5de54bc212e86a4d76bd49d10f18e91ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac37b15f0d000000001976a9148d1adb237d7f960b5d6912e21169ca9f15a2ebe988ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "174bbb78437049150cfe8c00ec53cd9f0ee038d0e9451e85eef25b5256a2e95e": { + "transaction": "030000000151c375d2ca13b43318d092482bd0c66f872401da0bf122fbbf82ad4d550b42b2010000006b483045022100c41c6a7c30c3479bccc020cf468b5c8be4188caef840b4a65f9a3bf16e8c3738022008458d62093d180bbee74e5839820189fa7fbe3160364ae69ddffe42ace22e3b01210234c8bb381c51a9de5253d77b7f5758dc73c832c2750332e62882b131c73e4955ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac30895f0d000000001976a914ea923f5fcaba8406dbbc2074262923653f2b9da588ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "fec6553adbe93a1702b43a9a871979ea0294bcd8d776ae684ff7aca0d22fe0e9": { + "transaction": "03000000015ee9a256525bf2ee851e45e9d038e00e9fcd53ec008cfe0c1549704378bb4b17010000006a47304402203abb41d61f4eaecf5ac9ffe350bd6fef1e7e6ff5733c5f3399f09d191e1b8cc602206623acc6bf4ea2219e20183fd952f42c21efc64daf53049983509dddbd633c74012103cb4ce869ba12c894772aa8300dd362af3b8eee63ef394b3d86210b4d3044a70bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac29615f0d000000001976a91493e02288f0c39d5927425ee77428696592678f5588ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "fc9863caf91f51da5ae67cd8bdada46435db0e940164bee87248026c79ccebd9": { + "transaction": "0300000001e9e02fd2a0acf74f68ae76d7d8bc9402ea7919879a3ab402173ae9db3a55c6fe010000006a4730440220273eee107fc493e599ce014f7087371e9ec28199a8a4676a588e0e7a96efaa2f02202c55628295bd3d3b1b895dc60dd7b4b98d9558393fcdb22106878721f554b5e7012102db98cb80d13a6e575589c641f22cfb75278e2b664ce1b7765fca4f16f3be8bb8ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac22395f0d000000001976a9148f8dea282edef6cdfb5c03cfe80cb139d8380d2688ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "74789930fe5d4bab8e485f1432a79704d3f111eec959aed55584c04b7f7da2dc": { + "transaction": "0300000001d9ebcc796c024872e8be6401940edb3564a4adbdd87ce65ada511ff9ca6398fc010000006a473044022009f48d0032c8190278d37cb3374edf2681cba880d4fabdeeeb041d8414c2b6dd02205b26b9ac50262c1f5d0b4369264383014a209293e5f0a26acc0322736f29d60f0121038cd3c39953611f8273ad74bbbd3e919a71c5e7481f4841f6ddc9ac0427d8297bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac1b115f0d000000001976a914299b3526d93433676cd3aaaf28839a8f83db97f988ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "4b1ec1877f9c2eaa4fbfeb034b38b21156aab4955d597fc328b6c30be1acd10e": { + "transaction": "0300000001dca27d7f4bc08455d5ae59c9ee11f1d30497a732145f488eab4b5dfe30997874010000006a473044022057fb09fa6301445cd72ec36c9bdbc1748eada3098f41227d10d258c152bc06eb02207ca0e41d447bbea34af8d3ee1ea879e03e5daf0241777e942e08a8d3450cfdb10121035a0a22c2a6d893fbdbe7c2cc7ad2d09292eb0323b78e6a64503b9f09adc7b888ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac14e95e0d000000001976a9149c7f9e354bf6e4b3e8fcff6ebeebed179464397f88ac00000000", + "metadata": { + "blockHash": "000000d672c7929c0f570e4793670f7695c848f8bb98d910aade929b41b38ce2", + "height": 615766, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "c0cbf437c0f8e980b8d64a3148a258645a821dc51631c4dfdb3dcd8324d6819e": { + "transaction": "03000000010ed1ace10bc3b628c37f595d95b4aa5611b2384b03ebbf4faa2e9c7f87c11e4b010000006b483045022100d4968053278a5e0d7d177de6aacb8d5b955b155e8a7fcf0b263c990b4058be0102201702447e79c98812aa34588d8f5f5cf955f2a92ce0c22b1eafa0abafdf95b6e0012103c921989753f9a5c4b656aa3e64da167eb4c44b20848db06f8dd0857b2468f5cbffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac0dc15e0d000000001976a914ff4c2b2c85b85a2e4ae8078587cbba81f3880c1988ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "5ec24edb2394c13db2d2b3fb1502cd5a4db8006b283eb6868cc44828df30148c": { + "transaction": "03000000019e81d62483cd3ddbdfc43116c51d825a6458a248314ad6b880e9f8c037f4cbc0010000006a4730440220624a0ca69ff3730cf2107945ffb629209fc8cefbe32abf5eecff76e8efa110f2022059f3b834178bbb360c9a473f896d7eda2ad9fff1af2c7f478ee3364824f4163d012102b997bc2dec7eebe4c504f95e0373bbd25e8c9970d4d9a84a36c701e4d1b50bcaffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac06995e0d000000001976a914315c783a94c3cb762a141cd949b631111caad00488ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "f95dbbc93352dd0cfe0350ce7407eabc5ead0cd5b3265873c5544447047eddf1": { + "transaction": "03000000018c1430df2848c48c86b63e286b00b84d5acd0215fbb3d2b23dc19423db4ec25e010000006a4730440220508d1d7cc4a3d961186913c3e17df787a8338342f8e47354c98a28b7091fec01022003aa09800685591628478d62eeee34b45d6f5dd3138f6455fd0decc83a0780a0012102bb5fbf33eed8fd6fe472584bce8551e8082ecf4d445f562b5b20b60b018e8043ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acff705e0d000000001976a9141a34cc7c74c833c38a10c390b77f7fcced77af0d88ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "5cfbb3fd2dfdd75f590278a1749fe2dae2d5395eef5e87690756adb44cb5745d": { + "transaction": "0300000001f1dd7e04474454c5735826b3d50cad5ebcea0774ce5003fe0cdd5233c9bb5df9010000006b483045022100f39957205230341508c414b17d4961f52c5dc0a426280381a407c6c46954ab09022006c8ea46b28f076211dc8d00c7fd16863285711102d2b073946323a61264a1080121023e6748994e6df1a568a0bc42fe903f7abedbe4b9161bbb4e60e0c30996df1401ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acf8485e0d000000001976a91463da38cac685abf6975de7b8d54415f9cb0f1e3088ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "34ba3e041c3026bab4f2c2c0ec1e1bb496ec72042aba9b03a324b74d1ec1d1af": { + "transaction": "03000000015d74b54cb4ad560769875eef5e39d5e2dae29f74a17802595fd7fd2dfdb3fb5c010000006a473044022000cf20af647df385691ffee7c20de78b05cd264645f784b3bd94bfa847bcd34402204ab594edbc06d4f7bc883eec99ff554a014e7bc9ae1d821e48ed75bd41ba176201210299f14e8558141cd4b58eff7da5d62bc5d12fc35ec7ca62fb577361b7b39323d5ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acf1205e0d000000001976a914cd5ff7587af9ce7762fb73ee132b392a7734691688ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "0d61420bb114d9d16b424e2822ab8412f3dec63e0b8da8df8f0fd5786782abd0": { + "transaction": "0300000001afd1c11e4db724a3039bba2a0472ec96b41b1eecc0c2f2b4ba26301c043eba34010000006a473044022046f78fed1260cc82d6c2c8be5b724d2a7c85687009c893cb464506106c40ce7c0220406ecc013482039460cd724bdabd39e6e407129b0f42367a8b8a5f6a0ac853a801210298dd86b2cef1adc0011c67fd29c314bb1b57c3f1be4d815f604d598ae22e55dcffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088aceaf85d0d000000001976a9145246323c87caa68e41f818bd2a78ffa969b8ecd288ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "0e62d7c53e01bd3a61065ca279bd77a6e1354c0c6a373502d7cc9328be3812a8": { + "transaction": "0300000001d0ab826778d50f8fdfa88d0b3ec6def31284ab22284e426bd1d914b10b42610d010000006b483045022100b7dec3519f1a1fbdd57f4cc4625a522b2cbd0980b577a79285d0e3995230129b022060af9411ce03cf9e17d30fc544ab8f31f068fd10a6b27def605e3d03c75c66d7012102c877e9a908ff2532da285c49e4f5fbb4b4fe9890167e8f53926f8f0405893ce9ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ace3d05d0d000000001976a914fd17b92a259f10b059e3b961e2245fced0ffeaf088ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "49c214eab55a17f80ec6e136861cbc56d507ea0c491864706b3cfa0a7f175634": { + "transaction": "0300000001a81238be2893ccd70235376a0c4c35e1a677bd79a25c06613abd013ec5d7620e010000006a47304402203f21c9573085b88848d22b894e5cfad783e7bb94bdd073c677b8a49495aa8f40022036cc89a2b492b1bde71d5fbc9c9a5825314669c9b4af635e34ba0d807edaf5e70121034bce811ced77c6a27f970af6ac6e5ee6a2efd713273774f3039490bc26f19f49ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acdca85d0d000000001976a914dfe1744ed3efe1c41ce02d92d6f4b3c789f3594e88ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "d17f579a4ead6a798618d3b567c2f130cac22a0830219d03404d68d9b6a5d8ce": { + "transaction": "03000000013456177f0afa3c6b706418490cea07d556bc1c8636e1c60ef8175ab5ea14c249010000006b483045022100853ad2f6f1fb290020e0c703dfea5d8ad476b671631ee3ff3b123045eca5a4d802206f81f7dc3ecd86b7ca2dd51243ec18562a9d935813b25c766839317b1b178b9b0121022a56bb1ffad84cd344cf40b5c968cb5879c1a2eff24035604a21e9acb18db570ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acd5805d0d000000001976a9145707912cce7499bd9f594536d9ef6a9442161ce388ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "9594a1c5a05fa24a78e9b93f03ee62b56d9c90e62e62298a3cf54629a355750a": { + "transaction": "0300000001ced8a5b6d9684d40039d2130082ac2ca30f1c267b5d31886796aad4e9a577fd1010000006b483045022100a321cac2bb16cbc30682700ef402c98d0c603b948d38e8d5a3b3368021bfc91e02202fb7cc27a5f701f9f15fa2a790b351e462dcd56bb3e33c959a2f78e53a5f9e400121030132d760b2488626feba05f7317a4ce255cfb5ed4bd23a78366bf0846bf666b1ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acce585d0d000000001976a914f8f4d2ba9d26ceb5b237ab220c5fa09e12495ca488ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "d522f8a9406dc497893ec3e99c69a05a1b88e0a010e3f12495628e412b85663a": { + "transaction": "03000000010a7555a32946f53c8a29622ee6909c6db562ee033fb9e9784aa25fa0c5a19495010000006a4730440220025799d9fde964000894e13249a7a856aa4f7402392e29a90a8560093f4d179402200db0ac7f4cbfeb1ec1e8ffa2eddf00cabcd53a6d51a9c7e2af90c6cd9e3d805401210372c3b4139803029186ffdaace2acc48bb3131b24b19643c7919e4c994eacd4d7ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acc7305d0d000000001976a914aefcf82febe1bd937263b53bc5f93708616665f988ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "3ee6bec9dbe57574305a25d72362434b8f3d02a79c6228d12f0a957891f5e52d": { + "transaction": "03000000013a66852b418e629524f1e310a0e0881b5aa0699ce9c33e8997c46d40a9f822d5010000006b483045022100fc74a5f633c25a152195b7ffc2e59309af554cbf26ac176f35b8607742f01854022031d4edc7ba4851e1fa98c761f2389251532943632ac6ed0fd88f23d5781535b9012102bfa11d27e6cb225d549ec6677c49c4ca84dc463648bc541f91d1f1fdd3b11a37ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acc0085d0d000000001976a914f9dfb4ac40a2d2aff83d58d78d5eeb98eaed432588ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "89181c1b627d32ea7dceda330c4c44d4992e98f7cacc392691220bba800ec6a7": { + "transaction": "03000000012de5f59178950a2fd128629ca7023d8f4b436223d7255a307475e5dbc9bee63e010000006b483045022100d9cb2fa10a5f04e17d170509732408a2026712908b296f79f545923c1c80deec022012b08498ff70108cf48be3633b539f59a572ecc121d2cdf69782f34570c71ce2012102c82f336f78045e7ba70295560bd674b5edf004a80a7ea6aef3c5fd110fc2fabbffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acb9e05c0d000000001976a914adbd1ff2c75b70c94865cb4bb0afc78c1e36d78688ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "5b02921dda2f33def0951615466578b3cac8fd19ae9a9e75ee0176bf3c8e4821": { + "transaction": "0300000001a7c60e80ba0b22912639cccaf7982e99d4444c0c33dace7dea327d621b1c1889010000006b4830450221008d15bc527302c6b330f89c34add456d702e67dfa3b633e605e434332305e308c02205a7860004ec9a7c5703febccef584b39d5a29c770b19b0bb74072514013eb1b501210351ab77c8b2b4cf0430b9d8324e46d0d672117f174bab7487e3e601e60cbc731cffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acb2b85c0d000000001976a91427d236621e716361c0253902e192b3c1ee2bd79388ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "3d2e84e85560f835084d6e6514563a16453ded77d8474244b1a5dc95c8891faf": { + "transaction": "030000000121488e3cbf7601ee759e9aae19fdc8cab3786546151695f0de332fda1d92025b010000006a47304402206b8006112d2ccf477e23673d3decfa4314af671e03fed8eda958fe6a78317b8e022014aa1dfd05e43fca8fdc233aabee6701b5bd32ee9af983497cab93bb960ca9c70121023d72e96a3287e997680e62a16120a395e3f68bd96d56291aa58eed08a5121dbfffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acab905c0d000000001976a9140c5d1982e36bd9b5f4aa08d8e4c51a5535e2a8ad88ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "2a035c18d1a51c7164beac80f374bef6b49d084681e8035071fc0adc903b606c": { + "transaction": "0300000001af1f89c895dca5b1444247d877ed3d45163a5614656e4d0835f86055e8842e3d010000006a473044022027389c256710a7b70fe3cf0b8d04c486f1f6f2e3b94662f68dae7be2e8ec741d022071d3514613d9bcef688339addd86a0103667ae997f170c82f8eceba5b7c0aae201210306fa7efecfe3e7e4bc0d71e333ad9b330ba5027f0bcbcf9209dc6dc6d1d214a6ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088aca4685c0d000000001976a914681ebc28972f2e23f88a6b8f71a30f45fe08b45f88ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "11d14e8be169305b49e5a080f0570d2be6783c4e8bbe0c32451b97e35b188ee9": { + "transaction": "03000000016c603b90dc0afc715003e88146089db4f6be74f380acbe64711ca5d1185c032a010000006a47304402203f685c0d8dc11f74c153d93d895e0bf0a0b0ec31120345a9b98baa8e28808c02022011530b45fb4a53b33fc4201fd4b2834b672931c4e5f7097f852c126146d9e6140121038977b564fcf729b468572015dcd11f2475e28fc2f86dd1d3699a6873134ceabfffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac9d405c0d000000001976a91454eee81f9d2650a8162dea7cf0f444b270a685b688ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "b8c506c24e66aabc59b409b2ab37cd97893dae1ef671c5e839dd8649a65586a0": { + "transaction": "0300000001e98e185be3971b45320cbe8b4e3c78e62b0d57f080a0e5495b3069e18b4ed111010000006b483045022100a12b68d57f6c591ffa5001987c8b2b246e396965cbb24452a2931d3837d6dd710220554d1a231a29aac6cb68b0278b1039521ab3f08ebb53ed78e4319648fcb00538012103cbb5e729a4dfbb2607e4c2ec0afae068b21b76d907b859e0980b98d5e0212b0bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac96185c0d000000001976a914f050e5615a87152533df496a5fdc7867c327e77888ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "1b33390deac7e4c58761f048ee6d04ad2770277d78f2111951347c318088f4bd": { + "transaction": "0300000001a08655a64986dd39e8c571f61eae3d8997cd37abb209b459bcaa664ec206c5b8010000006b483045022100e11f02c0571a2beac3993191d31936d9fbd4f7b4e71ebefc195763ad66c0ff680220565006889d022b790f57a6a9e8fbd404b353365e73e29b8152cf1a7f7bacf3bf012103d55102102e4a446c4f76fd26a1052ca304b1523fe8fefdc81a21bdb37a593d26ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac8ff05b0d000000001976a91404ee78127bf986f1abf07b35e30613949811410588ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "1219e136566a370d2b302059a03b77f0ad0cfd98d6767e8a877157cbf6ffdccc": { + "transaction": "0300000001bdf48880317c34511911f2787d277027ad046dee48f06187c5e4c7ea0d39331b010000006b4830450221009cf93733d4ea0de4bb5b2b7c15a146ca7798bdc9c03e305c0a30fff9d9b86c5a0220653ea274a79540674ca92d08f89543a6a0e15dc2ba53bc7e9d29a694f5088dbb0121024f2a07e71f7e37ff5bcce315b2af83b32f1730c63a1de8f3bc2a3f624e15d6e4ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac88c85b0d000000001976a91430fcee94aa13b13de63fd0b52e384cb5590c973588ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4": { + "transaction": "0300000001ccdcfff6cb5771878a7e76d698fd0cadf0773ba05920302b0d376a5636e11912010000006b483045022100e3a3d166cdb9cab262a80b8c85a3ca82f837997669ff7b3eef5be3573a1c06e202207330cfaed20b37fd63e88e1e5395383e7242c4ad91e71493ccc8a51504a8a9fd0121038d955c7bd1805614e5a7d1641433915a0833b96d96ba25375da4f217cc9fdb7affffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac81a05b0d000000001976a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + } + }, + "instantLocks": {}, + "addresses": { + "ycRqwWy2pNzwNumX79MmRyvQYVpy6w3AeX": { + "address": "ycRqwWy2pNzwNumX79MmRyvQYVpy6w3AeX", + "transactions": [ + "a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965", + "c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo": { + "address": "ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yfNFUv1vfx3EVyVGtfZNXqvFR7HLP5MGRq": { + "address": "yfNFUv1vfx3EVyVGtfZNXqvFR7HLP5MGRq", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjDWQWhNixjKLMjzmXxWqLdJUEcgyjQh1o": { + "address": "yjDWQWhNixjKLMjzmXxWqLdJUEcgyjQh1o", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygCCNzQM2Uj7Dfyi6sxPGBmRY6hqHGEFP9": { + "address": "ygCCNzQM2Uj7Dfyi6sxPGBmRY6hqHGEFP9", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yX5c33WWgLacu7Z1hnBTLKPSXbbGt4BFNt": { + "address": "yX5c33WWgLacu7Z1hnBTLKPSXbbGt4BFNt", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yYeyVQqiejQsFmkQjtbcigLLPVGJKHxQBL": { + "address": "yYeyVQqiejQsFmkQjtbcigLLPVGJKHxQBL", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTFXnvNARXGPNL1ZKFHyVah3xY6JxzQaPD": { + "address": "yTFXnvNARXGPNL1ZKFHyVah3xY6JxzQaPD", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yNGpfTzZkJNhEQUk6Wj1VdS8RUVpq6qA2g": { + "address": "yNGpfTzZkJNhEQUk6Wj1VdS8RUVpq6qA2g", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySW43fHX7uTEjJWjozu3EF3bsXywFoo1gf": { + "address": "ySW43fHX7uTEjJWjozu3EF3bsXywFoo1gf", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yM3LdfhFcMQBQoECRrh2WZwy6c8xnsA1rq": { + "address": "yM3LdfhFcMQBQoECRrh2WZwy6c8xnsA1rq", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ybDdvzUVYvqSPnvdzqgQo1ictZxTjoNTeY": { + "address": "ybDdvzUVYvqSPnvdzqgQo1ictZxTjoNTeY", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZLvsg1LuC1q9xa1FqNoKWKHZJTLb1Rs5U": { + "address": "yZLvsg1LuC1q9xa1FqNoKWKHZJTLb1Rs5U", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeGtNDLgTEfSYtYK84kXwvHXwRu3AQprku": { + "address": "yeGtNDLgTEfSYtYK84kXwvHXwRu3AQprku", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPBq6vmEXnz2FtHVztLHD9HPQf8FMbnRPr": { + "address": "yPBq6vmEXnz2FtHVztLHD9HPQf8FMbnRPr", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjcBeyf1r8EkjNFXGgX4NmNQEwk4NNH4Dp": { + "address": "yjcBeyf1r8EkjNFXGgX4NmNQEwk4NNH4Dp", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySCxSmKAfjo8fk15TCM9chiS1dAQfJ54z2": { + "address": "ySCxSmKAfjo8fk15TCM9chiS1dAQfJ54z2", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySBWPfBDnN2iWKt4uRoFaVPGDoRDQpo5i1": { + "address": "ySBWPfBDnN2iWKt4uRoFaVPGDoRDQpo5i1", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yXMhEgMKAknAUTNR31nYr4uSP7GMpmCgsm": { + "address": "yXMhEgMKAknAUTNR31nYr4uSP7GMpmCgsm", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yf9Zudcaof9c3KrdPVz3r7YPZJ1U2ECbXE": { + "address": "yf9Zudcaof9c3KrdPVz3r7YPZJ1U2ECbXE", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yXwFx2jAMxxntn421z9PsdzytmHRJGjyU4": { + "address": "yXwFx2jAMxxntn421z9PsdzytmHRJGjyU4", + "transactions": [ + "c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4", + "ca2afbe06b0187777f7e1aa8ae90c16ea094fbf1ed1b2ae8372e34c31a2cf67d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTDBgBUYbPq2XWkrnEWXTZGDcvbPxH1chY": { + "address": "yTDBgBUYbPq2XWkrnEWXTZGDcvbPxH1chY", + "transactions": [ + "ca2afbe06b0187777f7e1aa8ae90c16ea094fbf1ed1b2ae8372e34c31a2cf67d", + "3096b29ee16f6ec1b8e8b5f3d01beb5a231d87b51b6fa4e637e0f45b6b8b83b6" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yexVh4ARXbi7ZCUEEGEDde63t91N5e6kfC": { + "address": "yexVh4ARXbi7ZCUEEGEDde63t91N5e6kfC", + "transactions": [ + "3096b29ee16f6ec1b8e8b5f3d01beb5a231d87b51b6fa4e637e0f45b6b8b83b6", + "95fe8be7b264aa71a1a55478abce7233311b7f3a22135a6a2543988294ddbc68" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ydVc6fZz46csMbAea6xtd1AmW3Zohk9DX5": { + "address": "ydVc6fZz46csMbAea6xtd1AmW3Zohk9DX5", + "transactions": [ + "95fe8be7b264aa71a1a55478abce7233311b7f3a22135a6a2543988294ddbc68", + "267c4191e3b32241bf00cf15ff7e02460e4d6cc892afaaf3a28f88601b5b6a38" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yWvR7rm6s28SePpVi24AX1pNcBoTubsEcC": { + "address": "yWvR7rm6s28SePpVi24AX1pNcBoTubsEcC", + "transactions": [ + "267c4191e3b32241bf00cf15ff7e02460e4d6cc892afaaf3a28f88601b5b6a38", + "eb39c1c9e0e5dd8df5ec1f822a4fd96469749f56443377262d933f888568047d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yb4yiJrSTstkTrm4Ah9cfkyYmbC3H2TPoS": { + "address": "yb4yiJrSTstkTrm4Ah9cfkyYmbC3H2TPoS", + "transactions": [ + "eb39c1c9e0e5dd8df5ec1f822a4fd96469749f56443377262d933f888568047d", + "d066ac08dc743bfe5a29fd541802f9269b3da17c71f6b2f64aabd6c68a671566" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yb5dJVhq2pi68Qm1ZfzUsux9fCqPdw5GuM": { + "address": "yb5dJVhq2pi68Qm1ZfzUsux9fCqPdw5GuM", + "transactions": [ + "d066ac08dc743bfe5a29fd541802f9269b3da17c71f6b2f64aabd6c68a671566", + "a3f4b8c9c9f49434e0437ad1aa0fa9f9c621d3a8742cee174402863a5b23fe4a" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeSJLf7xsfenJwmwAb28suXpHLwd8bgc2K": { + "address": "yeSJLf7xsfenJwmwAb28suXpHLwd8bgc2K", + "transactions": [ + "a3f4b8c9c9f49434e0437ad1aa0fa9f9c621d3a8742cee174402863a5b23fe4a", + "eee88d2a03a0452dfe0abc9a84915f33d552110088c4382f0c459960494bf2ab" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTqDebXPzENywmQiciNjn6U5Hkv7uBYvgm": { + "address": "yTqDebXPzENywmQiciNjn6U5Hkv7uBYvgm", + "transactions": [ + "eee88d2a03a0452dfe0abc9a84915f33d552110088c4382f0c459960494bf2ab", + "0fd66f212549dbb84d42a745e9e0bef5e6772acfaf8b6d06579cffa15ac9fa1d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yegUezShAWzyZfM8WVSj5yzaKzVTRQC9n7": { + "address": "yegUezShAWzyZfM8WVSj5yzaKzVTRQC9n7", + "transactions": [ + "0fd66f212549dbb84d42a745e9e0bef5e6772acfaf8b6d06579cffa15ac9fa1d", + "92be5095443f506771d6f145716ab33a12b20239b5d363c9b09d5e5cd7fb3f96" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ydtkdeeX8NfAR7thLnzDPwrsiZ7c3a7bVL": { + "address": "ydtkdeeX8NfAR7thLnzDPwrsiZ7c3a7bVL", + "transactions": [ + "92be5095443f506771d6f145716ab33a12b20239b5d363c9b09d5e5cd7fb3f96", + "ea704ab4f38d786116875270c141b628f182684fd39dcd57c969237cf92f5589" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yUk83srvLCbpjkiYTpcPpftjGroSeYdHTF": { + "address": "yUk83srvLCbpjkiYTpcPpftjGroSeYdHTF", + "transactions": [ + "ea704ab4f38d786116875270c141b628f182684fd39dcd57c969237cf92f5589", + "f86445d20beccda3749058d90a605f4179a51a04b09f1262a9aefb382fe21bfd" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yScDono7QjZPqNJo27eCsrNwUAXFdTLK9P": { + "address": "yScDono7QjZPqNJo27eCsrNwUAXFdTLK9P", + "transactions": [ + "f86445d20beccda3749058d90a605f4179a51a04b09f1262a9aefb382fe21bfd", + "b2420b554dad82bffb22f10bda0124876fc6d02b4892d01833b413cad275c351" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZBYJEqmt8rE57unfDRseXBLkdCyfDF9VX": { + "address": "yZBYJEqmt8rE57unfDRseXBLkdCyfDF9VX", + "transactions": [ + "b2420b554dad82bffb22f10bda0124876fc6d02b4892d01833b413cad275c351", + "174bbb78437049150cfe8c00ec53cd9f0ee038d0e9451e85eef25b5256a2e95e" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yhhkAYqLNJQyu5XNAT61b2b8Qo7k5Uib1b": { + "address": "yhhkAYqLNJQyu5XNAT61b2b8Qo7k5Uib1b", + "transactions": [ + "174bbb78437049150cfe8c00ec53cd9f0ee038d0e9451e85eef25b5256a2e95e", + "fec6553adbe93a1702b43a9a871979ea0294bcd8d776ae684ff7aca0d22fe0e9" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZoLgZuUEmFANWFA8PE2zNVH1Yaz9fciGT": { + "address": "yZoLgZuUEmFANWFA8PE2zNVH1Yaz9fciGT", + "transactions": [ + "fec6553adbe93a1702b43a9a871979ea0294bcd8d776ae684ff7aca0d22fe0e9", + "fc9863caf91f51da5ae67cd8bdada46435db0e940164bee87248026c79ccebd9" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZQVV3EaLpAehKMpPiGy7w3NRmgKD7xJX1": { + "address": "yZQVV3EaLpAehKMpPiGy7w3NRmgKD7xJX1", + "transactions": [ + "fc9863caf91f51da5ae67cd8bdada46435db0e940164bee87248026c79ccebd9", + "74789930fe5d4bab8e485f1432a79704d3f111eec959aed55584c04b7f7da2dc" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yQ7SV5m46ck1fVQrJinvA982aBAUv3TCq2": { + "address": "yQ7SV5m46ck1fVQrJinvA982aBAUv3TCq2", + "transactions": [ + "74789930fe5d4bab8e485f1432a79704d3f111eec959aed55584c04b7f7da2dc", + "4b1ec1877f9c2eaa4fbfeb034b38b21156aab4955d597fc328b6c30be1acd10e" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yaaw9BZ16eoXv4RAGBA9qbSrjvTTbqbUKq": { + "address": "yaaw9BZ16eoXv4RAGBA9qbSrjvTTbqbUKq", + "transactions": [ + "4b1ec1877f9c2eaa4fbfeb034b38b21156aab4955d597fc328b6c30be1acd10e", + "c0cbf437c0f8e980b8d64a3148a258645a821dc51631c4dfdb3dcd8324d6819e" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjbLQQkP5tZ3aZ734vFXscFG14vDPnE1rx": { + "address": "yjbLQQkP5tZ3aZ734vFXscFG14vDPnE1rx", + "transactions": [ + "c0cbf437c0f8e980b8d64a3148a258645a821dc51631c4dfdb3dcd8324d6819e", + "5ec24edb2394c13db2d2b3fb1502cd5a4db8006b283eb6868cc44828df30148c" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yYqmE6MCitNpgyL7iRdNheG2ZP4bNCxoMV": { + "address": "yYqmE6MCitNpgyL7iRdNheG2ZP4bNCxoMV", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yQpSjWKF6HabXAMizRvMETDd6EbQixXQZ7": { + "address": "yQpSjWKF6HabXAMizRvMETDd6EbQixXQZ7", + "transactions": [ + "5ec24edb2394c13db2d2b3fb1502cd5a4db8006b283eb6868cc44828df30148c", + "f95dbbc93352dd0cfe0350ce7407eabc5ead0cd5b3265873c5544447047eddf1" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yNi1fiMR554w6MehxFoESXwUHk3v6AJELy": { + "address": "yNi1fiMR554w6MehxFoESXwUHk3v6AJELy", + "transactions": [ + "f95dbbc93352dd0cfe0350ce7407eabc5ead0cd5b3265873c5544447047eddf1", + "5cfbb3fd2dfdd75f590278a1749fe2dae2d5395eef5e87690756adb44cb5745d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yVRRAWki91bieh6eAYjwZZpJLcgmY14D79": { + "address": "yVRRAWki91bieh6eAYjwZZpJLcgmY14D79", + "transactions": [ + "5cfbb3fd2dfdd75f590278a1749fe2dae2d5395eef5e87690756adb44cb5745d", + "34ba3e041c3026bab4f2c2c0ec1e1bb496ec72042aba9b03a324b74d1ec1d1af" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yf3NLNnFSnZQuQEFHrAU3Kzdip3AfNXBy3": { + "address": "yf3NLNnFSnZQuQEFHrAU3Kzdip3AfNXBy3", + "transactions": [ + "34ba3e041c3026bab4f2c2c0ec1e1bb496ec72042aba9b03a324b74d1ec1d1af", + "0d61420bb114d9d16b424e2822ab8412f3dec63e0b8da8df8f0fd5786782abd0" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTpUMFdpe2WnMz9cjJp3jcDR4G6gXjW5jh": { + "address": "yTpUMFdpe2WnMz9cjJp3jcDR4G6gXjW5jh", + "transactions": [ + "0d61420bb114d9d16b424e2822ab8412f3dec63e0b8da8df8f0fd5786782abd0", + "0e62d7c53e01bd3a61065ca279bd77a6e1354c0c6a373502d7cc9328be3812a8" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjPgE4RimwUGZFJpDZjANyioWd1SbZpcaS": { + "address": "yjPgE4RimwUGZFJpDZjANyioWd1SbZpcaS", + "transactions": [ + "0e62d7c53e01bd3a61065ca279bd77a6e1354c0c6a373502d7cc9328be3812a8", + "49c214eab55a17f80ec6e136861cbc56d507ea0c491864706b3cfa0a7f175634" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygjDcpfLxzs8pweCQ6Ty8GioNDTgfQX3GQ": { + "address": "ygjDcpfLxzs8pweCQ6Ty8GioNDTgfQX3GQ", + "transactions": [ + "49c214eab55a17f80ec6e136861cbc56d507ea0c491864706b3cfa0a7f175634", + "d17f579a4ead6a798618d3b567c2f130cac22a0830219d03404d68d9b6a5d8ce" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yUFchfQMACPyoy6HtqNRv8gjEYKvjd3Wd6": { + "address": "yUFchfQMACPyoy6HtqNRv8gjEYKvjd3Wd6", + "transactions": [ + "d17f579a4ead6a798618d3b567c2f130cac22a0830219d03404d68d9b6a5d8ce", + "9594a1c5a05fa24a78e9b93f03ee62b56d9c90e62e62298a3cf54629a355750a" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yj1oiPUjyo2RLvpbyigfb7pLiX1piritz1": { + "address": "yj1oiPUjyo2RLvpbyigfb7pLiX1piritz1", + "transactions": [ + "9594a1c5a05fa24a78e9b93f03ee62b56d9c90e62e62298a3cf54629a355750a", + "d522f8a9406dc497893ec3e99c69a05a1b88e0a010e3f12495628e412b85663a" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ycGhUD69dS2V18LFy4qrfYhG1qJ8JnVvX7": { + "address": "ycGhUD69dS2V18LFy4qrfYhG1qJ8JnVvX7", + "transactions": [ + "d522f8a9406dc497893ec3e99c69a05a1b88e0a010e3f12495628e412b85663a", + "3ee6bec9dbe57574305a25d72362434b8f3d02a79c6228d12f0a957891f5e52d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yj6f6KLsQHFoVCUmfbpjSGmdqnB3j9NouZ": { + "address": "yj6f6KLsQHFoVCUmfbpjSGmdqnB3j9NouZ", + "transactions": [ + "3ee6bec9dbe57574305a25d72362434b8f3d02a79c6228d12f0a957891f5e52d", + "89181c1b627d32ea7dceda330c4c44d4992e98f7cacc392691220bba800ec6a7" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ycA6K1bW4nxYH8UBvYaXpxmdnMoXPQxp8L": { + "address": "ycA6K1bW4nxYH8UBvYaXpxmdnMoXPQxp8L", + "transactions": [ + "89181c1b627d32ea7dceda330c4c44d4992e98f7cacc392691220bba800ec6a7", + "5b02921dda2f33def0951615466578b3cac8fd19ae9a9e75ee0176bf3c8e4821" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPx12VyoeVLhMCVDiwK7Js7zDWNtNbq8JV": { + "address": "yPx12VyoeVLhMCVDiwK7Js7zDWNtNbq8JV", + "transactions": [ + "5b02921dda2f33def0951615466578b3cac8fd19ae9a9e75ee0176bf3c8e4821", + "3d2e84e85560f835084d6e6514563a16453ded77d8474244b1a5dc95c8891faf" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yMSpVBXff6XoLMwmP7N4nX4Mjo21ewkYkE": { + "address": "yMSpVBXff6XoLMwmP7N4nX4Mjo21ewkYkE", + "transactions": [ + "3d2e84e85560f835084d6e6514563a16453ded77d8474244b1a5dc95c8891faf", + "2a035c18d1a51c7164beac80f374bef6b49d084681e8035071fc0adc903b606c" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yVoywf8fK8VtPEiLyx6zknqWBjE4izUXvD": { + "address": "yVoywf8fK8VtPEiLyx6zknqWBjE4izUXvD", + "transactions": [ + "2a035c18d1a51c7164beac80f374bef6b49d084681e8035071fc0adc903b606c", + "11d14e8be169305b49e5a080f0570d2be6783c4e8bbe0c32451b97e35b188ee9" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yU4Xor7Y8XjXFNYmVebqKeaCPqDmKNxbYd": { + "address": "yU4Xor7Y8XjXFNYmVebqKeaCPqDmKNxbYd", + "transactions": [ + "11d14e8be169305b49e5a080f0570d2be6783c4e8bbe0c32451b97e35b188ee9", + "b8c506c24e66aabc59b409b2ab37cd97893dae1ef671c5e839dd8649a65586a0" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yiE7vzjGNseTtEwtXZTmjcf99hfwLziJPq": { + "address": "yiE7vzjGNseTtEwtXZTmjcf99hfwLziJPq", + "transactions": [ + "b8c506c24e66aabc59b409b2ab37cd97893dae1ef671c5e839dd8649a65586a0", + "1b33390deac7e4c58761f048ee6d04ad2770277d78f2111951347c318088f4bd" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yLmXE68Kq8UrK42QjyNWi8gFqf1ihd3UiW": { + "address": "yLmXE68Kq8UrK42QjyNWi8gFqf1ihd3UiW", + "transactions": [ + "1b33390deac7e4c58761f048ee6d04ad2770277d78f2111951347c318088f4bd", + "1219e136566a370d2b302059a03b77f0ad0cfd98d6767e8a877157cbf6ffdccc" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yQnUHRyB768tawz3iLDs5VVLYg5dGEJYQi": { + "address": "yQnUHRyB768tawz3iLDs5VVLYg5dGEJYQi", + "transactions": [ + "1219e136566a370d2b302059a03b77f0ad0cfd98d6767e8a877157cbf6ffdccc", + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX": { + "address": "yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX", + "transactions": [ + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4" + ], + "utxos": { + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4-1": { + "satoshis": 224108673, + "script": "76a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac" + } + }, + "balanceSat": 224108673, + "unconfirmedBalanceSat": 0 + }, + "ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX": { + "address": "ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeKckmFWSPq4ZgxrKtqgWLfspT5YSuinAw": { + "address": "yeKckmFWSPq4ZgxrKtqgWLfspT5YSuinAw", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yX5fXtxaF5Umi3wkCvFCCbYjQniex8YfZb": { + "address": "yX5fXtxaF5Umi3wkCvFCCbYjQniex8YfZb", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yYXYNLd3pwDnZMo8VvFZWtuTaW97yMBtvh": { + "address": "yYXYNLd3pwDnZMo8VvFZWtuTaW97yMBtvh", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPxjUsH2X9SFnpXt4ke7boWdVJb1jLRU2U": { + "address": "yPxjUsH2X9SFnpXt4ke7boWdVJb1jLRU2U", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPaJs6aVAxsSsjsaeoWEEf1KWNJHA4osTX": { + "address": "yPaJs6aVAxsSsjsaeoWEEf1KWNJHA4osTX", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yRtTcfySvuZHmnyTGnSVFJiuXQLAMtA8Zp": { + "address": "yRtTcfySvuZHmnyTGnSVFJiuXQLAMtA8Zp", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yWniGBz9g5WzC8SNw2Yz6rTiw8gXCwviHH": { + "address": "yWniGBz9g5WzC8SNw2Yz6rTiw8gXCwviHH", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTBBLUse991FY17xVQxTW6gELhDPLTbrfg": { + "address": "yTBBLUse991FY17xVQxTW6gELhDPLTbrfg", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yW8ZcyB43XEaNb1rC3vU8B787pCFd5A92G": { + "address": "yW8ZcyB43XEaNb1rC3vU8B787pCFd5A92G", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZFMjHZLcw8VLZSfBDamfaowiespUADi8x": { + "address": "yZFMjHZLcw8VLZSfBDamfaowiespUADi8x", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yS4CoPF6Bb6vZAorsTQGddhdqzUNA4NQkq": { + "address": "yS4CoPF6Bb6vZAorsTQGddhdqzUNA4NQkq", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeKFhYBcoDB5BFQVatGEY6xAWWgyAH2znL": { + "address": "yeKFhYBcoDB5BFQVatGEY6xAWWgyAH2znL", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yVtwa9msBFy3s5YW4zpKaRFEAS3CK5h1sd": { + "address": "yVtwa9msBFy3s5YW4zpKaRFEAS3CK5h1sd", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySdqTcWTqRHJBwsymifjg4j6hBTUtrhjCo": { + "address": "ySdqTcWTqRHJBwsymifjg4j6hBTUtrhjCo", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yXgzDeQ6Fa3gzthByHYeiwyVKYzDov7EtR": { + "address": "yXgzDeQ6Fa3gzthByHYeiwyVKYzDov7EtR", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZVUpv1RfnH4gTiTtuarXVpAcK1h9irJ7q": { + "address": "yZVUpv1RfnH4gTiTtuarXVpAcK1h9irJ7q", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yS4osisWi5dpSmUZFav56Uxj4Uhc3i7RaL": { + "address": "yS4osisWi5dpSmUZFav56Uxj4Uhc3i7RaL", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygAb2f6gztLoPdZrma8BRjm3MtNGZFrUbX": { + "address": "ygAb2f6gztLoPdZrma8BRjm3MtNGZFrUbX", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygukA9r4bM3CDS1gkFfTfVNozC7c5FvnGj": { + "address": "ygukA9r4bM3CDS1gkFfTfVNozC7c5FvnGj", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + } + } + } +} diff --git a/fixtures/wallets/c922713eac.json b/fixtures/wallets/c922713eac.json new file mode 100644 index 00000000..3fac8277 --- /dev/null +++ b/fixtures/wallets/c922713eac.json @@ -0,0 +1,97 @@ +{ + "walletId": "c922713eac", + "state": { + "mnemonic": null, + "paths": { + "m/44'/1'/0'": { + "path": "m/44'/1'/0'", + "addresses": { + "m/0/0": "ycRqwWy2pNzwNumX79MmRyvQYVpy6w3AeX", + "m/0/1": "ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo", + "m/0/2": "yfNFUv1vfx3EVyVGtfZNXqvFR7HLP5MGRq", + "m/0/3": "yjDWQWhNixjKLMjzmXxWqLdJUEcgyjQh1o", + "m/0/4": "ygCCNzQM2Uj7Dfyi6sxPGBmRY6hqHGEFP9", + "m/0/5": "yX5c33WWgLacu7Z1hnBTLKPSXbbGt4BFNt", + "m/0/6": "yYeyVQqiejQsFmkQjtbcigLLPVGJKHxQBL", + "m/0/7": "yTFXnvNARXGPNL1ZKFHyVah3xY6JxzQaPD", + "m/0/8": "yNGpfTzZkJNhEQUk6Wj1VdS8RUVpq6qA2g", + "m/0/9": "ySW43fHX7uTEjJWjozu3EF3bsXywFoo1gf", + "m/0/10": "yM3LdfhFcMQBQoECRrh2WZwy6c8xnsA1rq", + "m/0/11": "ybDdvzUVYvqSPnvdzqgQo1ictZxTjoNTeY", + "m/0/12": "yZLvsg1LuC1q9xa1FqNoKWKHZJTLb1Rs5U", + "m/0/13": "yeGtNDLgTEfSYtYK84kXwvHXwRu3AQprku", + "m/0/14": "yPBq6vmEXnz2FtHVztLHD9HPQf8FMbnRPr", + "m/0/15": "yjcBeyf1r8EkjNFXGgX4NmNQEwk4NNH4Dp", + "m/0/16": "ySCxSmKAfjo8fk15TCM9chiS1dAQfJ54z2", + "m/0/17": "ySBWPfBDnN2iWKt4uRoFaVPGDoRDQpo5i1", + "m/0/18": "yXMhEgMKAknAUTNR31nYr4uSP7GMpmCgsm", + "m/0/19": "yf9Zudcaof9c3KrdPVz3r7YPZJ1U2ECbXE", + "m/1/0": "yXwFx2jAMxxntn421z9PsdzytmHRJGjyU4", + "m/1/1": "yTDBgBUYbPq2XWkrnEWXTZGDcvbPxH1chY", + "m/1/2": "yexVh4ARXbi7ZCUEEGEDde63t91N5e6kfC", + "m/1/3": "ydVc6fZz46csMbAea6xtd1AmW3Zohk9DX5", + "m/1/4": "yWvR7rm6s28SePpVi24AX1pNcBoTubsEcC", + "m/1/5": "yb4yiJrSTstkTrm4Ah9cfkyYmbC3H2TPoS", + "m/1/6": "yb5dJVhq2pi68Qm1ZfzUsux9fCqPdw5GuM", + "m/1/7": "yeSJLf7xsfenJwmwAb28suXpHLwd8bgc2K", + "m/1/8": "yTqDebXPzENywmQiciNjn6U5Hkv7uBYvgm", + "m/1/9": "yegUezShAWzyZfM8WVSj5yzaKzVTRQC9n7", + "m/1/10": "ydtkdeeX8NfAR7thLnzDPwrsiZ7c3a7bVL", + "m/1/11": "yUk83srvLCbpjkiYTpcPpftjGroSeYdHTF", + "m/1/12": "yScDono7QjZPqNJo27eCsrNwUAXFdTLK9P", + "m/1/13": "yZBYJEqmt8rE57unfDRseXBLkdCyfDF9VX", + "m/1/14": "yhhkAYqLNJQyu5XNAT61b2b8Qo7k5Uib1b", + "m/1/15": "yZoLgZuUEmFANWFA8PE2zNVH1Yaz9fciGT", + "m/1/16": "yZQVV3EaLpAehKMpPiGy7w3NRmgKD7xJX1", + "m/1/17": "yQ7SV5m46ck1fVQrJinvA982aBAUv3TCq2", + "m/1/18": "yaaw9BZ16eoXv4RAGBA9qbSrjvTTbqbUKq", + "m/1/19": "yjbLQQkP5tZ3aZ734vFXscFG14vDPnE1rx", + "m/0/20": "yYqmE6MCitNpgyL7iRdNheG2ZP4bNCxoMV", + "m/1/20": "yQpSjWKF6HabXAMizRvMETDd6EbQixXQZ7", + "m/1/21": "yNi1fiMR554w6MehxFoESXwUHk3v6AJELy", + "m/1/22": "yVRRAWki91bieh6eAYjwZZpJLcgmY14D79", + "m/1/23": "yf3NLNnFSnZQuQEFHrAU3Kzdip3AfNXBy3", + "m/1/24": "yTpUMFdpe2WnMz9cjJp3jcDR4G6gXjW5jh", + "m/1/25": "yjPgE4RimwUGZFJpDZjANyioWd1SbZpcaS", + "m/1/26": "ygjDcpfLxzs8pweCQ6Ty8GioNDTgfQX3GQ", + "m/1/27": "yUFchfQMACPyoy6HtqNRv8gjEYKvjd3Wd6", + "m/1/28": "yj1oiPUjyo2RLvpbyigfb7pLiX1piritz1", + "m/1/29": "ycGhUD69dS2V18LFy4qrfYhG1qJ8JnVvX7", + "m/1/30": "yj6f6KLsQHFoVCUmfbpjSGmdqnB3j9NouZ", + "m/1/31": "ycA6K1bW4nxYH8UBvYaXpxmdnMoXPQxp8L", + "m/1/32": "yPx12VyoeVLhMCVDiwK7Js7zDWNtNbq8JV", + "m/1/33": "yMSpVBXff6XoLMwmP7N4nX4Mjo21ewkYkE", + "m/1/34": "yVoywf8fK8VtPEiLyx6zknqWBjE4izUXvD", + "m/1/35": "yU4Xor7Y8XjXFNYmVebqKeaCPqDmKNxbYd", + "m/1/36": "yiE7vzjGNseTtEwtXZTmjcf99hfwLziJPq", + "m/1/37": "yLmXE68Kq8UrK42QjyNWi8gFqf1ihd3UiW", + "m/1/38": "yQnUHRyB768tawz3iLDs5VVLYg5dGEJYQi", + "m/1/39": "yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX", + "m/1/40": "ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX", + "m/1/41": "yeKckmFWSPq4ZgxrKtqgWLfspT5YSuinAw", + "m/1/42": "yX5fXtxaF5Umi3wkCvFCCbYjQniex8YfZb", + "m/1/43": "yYXYNLd3pwDnZMo8VvFZWtuTaW97yMBtvh", + "m/1/44": "yPxjUsH2X9SFnpXt4ke7boWdVJb1jLRU2U", + "m/1/45": "yPaJs6aVAxsSsjsaeoWEEf1KWNJHA4osTX", + "m/1/46": "yRtTcfySvuZHmnyTGnSVFJiuXQLAMtA8Zp", + "m/1/47": "yWniGBz9g5WzC8SNw2Yz6rTiw8gXCwviHH", + "m/1/48": "yTBBLUse991FY17xVQxTW6gELhDPLTbrfg", + "m/1/49": "yW8ZcyB43XEaNb1rC3vU8B787pCFd5A92G", + "m/1/50": "yZFMjHZLcw8VLZSfBDamfaowiespUADi8x", + "m/1/51": "yS4CoPF6Bb6vZAorsTQGddhdqzUNA4NQkq", + "m/1/52": "yeKFhYBcoDB5BFQVatGEY6xAWWgyAH2znL", + "m/1/53": "yVtwa9msBFy3s5YW4zpKaRFEAS3CK5h1sd", + "m/1/54": "ySdqTcWTqRHJBwsymifjg4j6hBTUtrhjCo", + "m/1/55": "yXgzDeQ6Fa3gzthByHYeiwyVKYzDov7EtR", + "m/1/56": "yZVUpv1RfnH4gTiTtuarXVpAcK1h9irJ7q", + "m/1/57": "yS4osisWi5dpSmUZFav56Uxj4Uhc3i7RaL", + "m/1/58": "ygAb2f6gztLoPdZrma8BRjm3MtNGZFrUbX", + "m/1/59": "ygukA9r4bM3CDS1gkFfTfVNozC7c5FvnGj" + } + } + }, + "identities": { + "0": "D8oTFRQqiKcLEYXZBUVLsM9jBctq7YcM7MAXXT9GJesK" + } + } +} diff --git a/src/types/Account/methods/getTotalBalance.spec.js b/src/types/Account/methods/getTotalBalance.spec.js index c5db8209..78692099 100644 --- a/src/types/Account/methods/getTotalBalance.spec.js +++ b/src/types/Account/methods/getTotalBalance.spec.js @@ -1,43 +1,46 @@ const { expect } = require('chai'); -const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); +const walletStoreMock = require('../../../../fixtures/wallets/c922713eac.json'); +const chainStoreMock = require('../../../../fixtures/chains/for_wallet_c922713eac.json'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const { duffsToDash, calculateDuffBalance } = require('../../../utils'); +const Storage = require("../../Storage/Storage"); let mockedWallet; describe('Account - getTotalBalance', function suite() { this.timeout(10000); before(() => { - const storageHDW = { - store: mockedStore, - calculateDuffBalance, - getStore: () => mockedStore, - mappedAddress: {}, - }; - const walletId = Object.keys(mockedStore.wallets)[0]; + const { walletId } = walletStoreMock; + mockedWallet = { walletId, index: 0, - storage: storageHDW, + storage: new Storage(), + accountPath: "m/44'/1'/0'", + network: "testnet" }; + + mockedWallet.storage.createWalletStore(walletId) + mockedWallet.storage.createChainStore("testnet") + mockedWallet.storage.getWalletStore(walletId).importState(walletStoreMock) + mockedWallet.storage.getChainStore("testnet").importState(chainStoreMock) }); - it('should correctly get the balance', async () => { - const balance = await getTotalBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); + it('should correctly get the balance', () => { + const balance = getTotalBalance.call(mockedWallet); + expect(balance).to.equal(224108673); }); - it('should correctly get the balance confirmed only', async () => { - const balance = await getConfirmedBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); + it('should correctly get the balance confirmed only', () => { + const balance = getConfirmedBalance.call(mockedWallet); + expect(balance).to.equal(224108673); }); - it('should correctly get the balance dash value instead of duff', async () => { - const balanceTotalDash = await getTotalBalance.call(mockedWallet, false); - const balanceUnconfDash = await getUnconfirmedBalance.call(mockedWallet, false); - const balanceConfDash = await getConfirmedBalance.call(mockedWallet, false); + it('should correctly get the balance dash value instead of duff', () => { + const balanceTotalDash = getTotalBalance.call(mockedWallet, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedWallet, false); + const balanceConfDash = getConfirmedBalance.call(mockedWallet, false); - expect(balanceTotalDash).to.equal(1844.99999506); + expect(balanceTotalDash).to.equal(2.24108673); expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(1844.99999506); + expect(balanceConfDash).to.equal(2.24108673); }); }); From 6968c8fb49c2fb0f2bede04e4bdab281c181510a Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 1 Dec 2021 18:29:17 +0300 Subject: [PATCH 34/40] Prepared a mock for the wallet at getUnusedAddress.spec.js --- .../Account/methods/getUnusedAddress.spec.js | 109 +++++++++++------- 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/src/types/Account/methods/getUnusedAddress.spec.js b/src/types/Account/methods/getUnusedAddress.spec.js index bc6dc99c..0abdc3e3 100644 --- a/src/types/Account/methods/getUnusedAddress.spec.js +++ b/src/types/Account/methods/getUnusedAddress.spec.js @@ -5,53 +5,82 @@ const getAddress = require('./getAddress'); const generateAddress = require('./generateAddress'); const KeyChain = require('../../KeyChain/KeyChain'); -const mockedStore = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); +// const mockedStore = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); +const walletStoreMock = require('../../../../fixtures/wallets/c922713eac.json'); +const chainStoreMock = require('../../../../fixtures/chains/for_wallet_c922713eac.json'); +const KeyChainStore = require("../../KeyChainStore/KeyChainStore"); +const Storage = require("../../Storage/Storage"); -const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPfEan1JB7NF4STbvnjGvP9318CN7FPGZp5nsUTBqmerxtDVpsJjFufyfkTgoe6QfHcDhMqjN3ZoFKtb8SnXFeubNjQreZSq6'; +// const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPfEan1JB7NF4STbvnjGvP9318CN7FPGZp5nsUTBqmerxtDVpsJjFufyfkTgoe6QfHcDhMqjN3ZoFKtb8SnXFeubNjQreZSq6'; +const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPedfdHYgWfJFn2Nu1p7Vr7AnsXRX9pAQsj83QmEv6S27Fd66o7opsMJsc1G8xMmAWXevKPAA7FSbziZ9cHyJoGpHXyDykR8g'; describe('Account - getUnusedAddress', function suite() { this.timeout(10000); - it('should get the proper unused address', () => { - const self = { - store: mockedStore, - storage: { - getStore: () => mockedStore, - importAddresses: (_) => (_), - }, + let mockedWallet; + + before(() => { + const { walletId } = walletStoreMock; + + mockedWallet = { emit: (_) => (_), - keyChain: new KeyChain({ - type: 'HDPrivateKey', - HDPrivateKey: Dashcore.HDPrivateKey(HDRootKeyMockedStore), - }), - BIP44PATH: 'm/44\'/1\'/0\'', - walletId: '5061b8276c', + walletId, index: 0, + storage: new Storage(), + accountPath: "m/44'/1'/0'", + network: "testnet", + keyChainStore: new KeyChainStore() }; - self.getAddress = getAddress.bind(self); - self.generateAddress = generateAddress.bind(self); - - const unusedAddressExternal = getUnusedAddress.call(self); - const unusedAddressInternal = getUnusedAddress.call(self, 'internal'); - - expect(unusedAddressExternal).to.be.deep.equal({ - address: 'yaVrJ5dgELFkYwv6AydDyGPAJQ5kTJXyAN', - balanceSat: 0, - fetchedLast: 1548538385006, - path: 'm/44\'/1\'/0\'/0/5', - transactions: [], - unconfirmedBalanceSat: 0, - utxos: {}, - used: false, - }); - expect(unusedAddressInternal).to.be.deep.equal({ - address: 'yaZFt1VnAbi72mtyjDNV4AwTECqdg5Bv95', - balanceSat: 0, - fetchedLast: 1548538385164, - path: 'm/44\'/1\'/0\'/1/8', - transactions: [], - unconfirmedBalanceSat: 0, - utxos: {}, - used: false, + + mockedWallet.storage.createWalletStore(walletId) + mockedWallet.storage.createChainStore("testnet") + mockedWallet.storage.getWalletStore(walletId).importState(walletStoreMock) + mockedWallet.storage.getChainStore("testnet").importState(chainStoreMock) + + const keyChain = new KeyChain({ + type: 'HDPrivateKey', + HDPrivateKey: Dashcore.HDPrivateKey(HDRootKeyMockedStore), + lookAheadOpts: { + paths: { + 'm/0': 20, + 'm/1': 60, + }, + } }); + + mockedWallet.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); + + + mockedWallet.getAddress = getAddress.bind(mockedWallet); + mockedWallet.generateAddress = generateAddress.bind(mockedWallet); + }) + + it('should get the proper unused address', () => { + // const unusedAddressExternal = getUnusedAddress.call(mockedWallet); + const unusedAddressInternal = getUnusedAddress.call(mockedWallet, 'internal'); + + console.log(unusedAddressInternal) + + // ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX + + // expect(unusedAddressExternal).to.be.deep.equal({ + // address: 'yaVrJ5dgELFkYwv6AydDyGPAJQ5kTJXyAN', + // balanceSat: 0, + // fetchedLast: 1548538385006, + // path: 'm/44\'/1\'/0\'/0/5', + // transactions: [], + // unconfirmedBalanceSat: 0, + // utxos: {}, + // used: false, + // }); + // expect(unusedAddressInternal).to.be.deep.equal({ + // address: 'yaZFt1VnAbi72mtyjDNV4AwTECqdg5Bv95', + // balanceSat: 0, + // fetchedLast: 1548538385164, + // path: 'm/44\'/1\'/0\'/1/8', + // transactions: [], + // unconfirmedBalanceSat: 0, + // utxos: {}, + // used: false, + // }); }); }); From 09d6b9a75746cebe4c475e6d319ede9af718ddca Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Thu, 2 Dec 2021 11:00:24 +0300 Subject: [PATCH 35/40] Fixes test case for getUnusedAddress.spec.js --- .../Account/methods/getTotalBalance.spec.js | 22 +++---- .../Account/methods/getUnusedAddress.spec.js | 59 +++++++------------ 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/types/Account/methods/getTotalBalance.spec.js b/src/types/Account/methods/getTotalBalance.spec.js index 78692099..49ab43e9 100644 --- a/src/types/Account/methods/getTotalBalance.spec.js +++ b/src/types/Account/methods/getTotalBalance.spec.js @@ -7,13 +7,13 @@ const getUnconfirmedBalance = require('./getUnconfirmedBalance'); const Storage = require("../../Storage/Storage"); -let mockedWallet; +let mockedAccount; describe('Account - getTotalBalance', function suite() { this.timeout(10000); before(() => { const { walletId } = walletStoreMock; - mockedWallet = { + mockedAccount = { walletId, index: 0, storage: new Storage(), @@ -21,23 +21,23 @@ describe('Account - getTotalBalance', function suite() { network: "testnet" }; - mockedWallet.storage.createWalletStore(walletId) - mockedWallet.storage.createChainStore("testnet") - mockedWallet.storage.getWalletStore(walletId).importState(walletStoreMock) - mockedWallet.storage.getChainStore("testnet").importState(chainStoreMock) + mockedAccount.storage.createWalletStore(walletId) + mockedAccount.storage.createChainStore("testnet") + mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock) + mockedAccount.storage.getChainStore("testnet").importState(chainStoreMock) }); it('should correctly get the balance', () => { - const balance = getTotalBalance.call(mockedWallet); + const balance = getTotalBalance.call(mockedAccount); expect(balance).to.equal(224108673); }); it('should correctly get the balance confirmed only', () => { - const balance = getConfirmedBalance.call(mockedWallet); + const balance = getConfirmedBalance.call(mockedAccount); expect(balance).to.equal(224108673); }); it('should correctly get the balance dash value instead of duff', () => { - const balanceTotalDash = getTotalBalance.call(mockedWallet, false); - const balanceUnconfDash = getUnconfirmedBalance.call(mockedWallet, false); - const balanceConfDash = getConfirmedBalance.call(mockedWallet, false); + const balanceTotalDash = getTotalBalance.call(mockedAccount, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); + const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); expect(balanceTotalDash).to.equal(2.24108673); expect(balanceUnconfDash).to.equal(0); diff --git a/src/types/Account/methods/getUnusedAddress.spec.js b/src/types/Account/methods/getUnusedAddress.spec.js index 0abdc3e3..192ea3ec 100644 --- a/src/types/Account/methods/getUnusedAddress.spec.js +++ b/src/types/Account/methods/getUnusedAddress.spec.js @@ -11,17 +11,16 @@ const chainStoreMock = require('../../../../fixtures/chains/for_wallet_c922713ea const KeyChainStore = require("../../KeyChainStore/KeyChainStore"); const Storage = require("../../Storage/Storage"); -// const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPfEan1JB7NF4STbvnjGvP9318CN7FPGZp5nsUTBqmerxtDVpsJjFufyfkTgoe6QfHcDhMqjN3ZoFKtb8SnXFeubNjQreZSq6'; -const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPedfdHYgWfJFn2Nu1p7Vr7AnsXRX9pAQsj83QmEv6S27Fd66o7opsMJsc1G8xMmAWXevKPAA7FSbziZ9cHyJoGpHXyDykR8g'; +const HDRootKeyMockedStore = 'tprv8gpcZgdXPzdXKBjSzieMyfwr6KidKucLiiA9VbCLCx1spyJNd38a5KdjtVuc9bVUNpFM2LdFCrYSyUXHx1RCTdr6qQen1HTECwAZ1p8yqiB'; describe('Account - getUnusedAddress', function suite() { this.timeout(10000); - let mockedWallet; + let mockedAccount; before(() => { const { walletId } = walletStoreMock; - mockedWallet = { + mockedAccount = { emit: (_) => (_), walletId, index: 0, @@ -31,10 +30,10 @@ describe('Account - getUnusedAddress', function suite() { keyChainStore: new KeyChainStore() }; - mockedWallet.storage.createWalletStore(walletId) - mockedWallet.storage.createChainStore("testnet") - mockedWallet.storage.getWalletStore(walletId).importState(walletStoreMock) - mockedWallet.storage.getChainStore("testnet").importState(chainStoreMock) + mockedAccount.storage.createWalletStore(walletId) + mockedAccount.storage.createChainStore("testnet") + mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock) + mockedAccount.storage.getChainStore("testnet").importState(chainStoreMock) const keyChain = new KeyChain({ type: 'HDPrivateKey', @@ -47,40 +46,26 @@ describe('Account - getUnusedAddress', function suite() { } }); - mockedWallet.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); + mockedAccount.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); - - mockedWallet.getAddress = getAddress.bind(mockedWallet); - mockedWallet.generateAddress = generateAddress.bind(mockedWallet); + mockedAccount.getAddress = getAddress.bind(mockedAccount); + mockedAccount.generateAddress = generateAddress.bind(mockedAccount); }) it('should get the proper unused address', () => { - // const unusedAddressExternal = getUnusedAddress.call(mockedWallet); - const unusedAddressInternal = getUnusedAddress.call(mockedWallet, 'internal'); - - console.log(unusedAddressInternal) + const unusedAddressExternal = getUnusedAddress.call(mockedAccount); + const unusedAddressInternal = getUnusedAddress.call(mockedAccount, 'internal'); - // ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX + expect(unusedAddressExternal).to.be.deep.equal({ + address: 'ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo', + index: 1, + path: 'm/0/1' + }); - // expect(unusedAddressExternal).to.be.deep.equal({ - // address: 'yaVrJ5dgELFkYwv6AydDyGPAJQ5kTJXyAN', - // balanceSat: 0, - // fetchedLast: 1548538385006, - // path: 'm/44\'/1\'/0\'/0/5', - // transactions: [], - // unconfirmedBalanceSat: 0, - // utxos: {}, - // used: false, - // }); - // expect(unusedAddressInternal).to.be.deep.equal({ - // address: 'yaZFt1VnAbi72mtyjDNV4AwTECqdg5Bv95', - // balanceSat: 0, - // fetchedLast: 1548538385164, - // path: 'm/44\'/1\'/0\'/1/8', - // transactions: [], - // unconfirmedBalanceSat: 0, - // utxos: {}, - // used: false, - // }); + expect(unusedAddressInternal).to.be.deep.equal({ + address: 'ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX', + path: 'm/1/40', + index: 40 + }); }); }); From 35be5c300b935ecb850820478c152bb65d363a58 Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Thu, 2 Dec 2021 11:38:05 +0300 Subject: [PATCH 36/40] Fixes getUnconfirmedBalance.spec.js --- src/test/mocks/mockAccountWithStorage.js | 23 +++++++++ .../Account/methods/getTotalBalance.spec.js | 22 ++------ .../methods/getUnconfirmedBalance.spec.js | 50 ++++++++----------- .../Account/methods/getUnusedAddress.spec.js | 23 ++------- 4 files changed, 50 insertions(+), 68 deletions(-) create mode 100644 src/test/mocks/mockAccountWithStorage.js diff --git a/src/test/mocks/mockAccountWithStorage.js b/src/test/mocks/mockAccountWithStorage.js new file mode 100644 index 00000000..9821d91f --- /dev/null +++ b/src/test/mocks/mockAccountWithStorage.js @@ -0,0 +1,23 @@ +const walletStoreMock = require('../../../fixtures/wallets/c922713eac.json'); +const chainStoreMock = require('../../../fixtures/chains/for_wallet_c922713eac.json'); +const Storage = require('../../types/Storage/Storage'); + +module.exports = (opts = {}) => { + const { walletId } = walletStoreMock; + + const mockedAccount = { + walletId, + index: 0, + storage: new Storage(), + accountPath: "m/44'/1'/0'", + network: 'testnet', + ...opts, + }; + + mockedAccount.storage.createWalletStore(walletId); + mockedAccount.storage.createChainStore('testnet'); + mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock); + mockedAccount.storage.getChainStore('testnet').importState(chainStoreMock); + + return mockedAccount; +}; diff --git a/src/types/Account/methods/getTotalBalance.spec.js b/src/types/Account/methods/getTotalBalance.spec.js index 49ab43e9..0368595d 100644 --- a/src/types/Account/methods/getTotalBalance.spec.js +++ b/src/types/Account/methods/getTotalBalance.spec.js @@ -1,32 +1,16 @@ const { expect } = require('chai'); -const walletStoreMock = require('../../../../fixtures/wallets/c922713eac.json'); -const chainStoreMock = require('../../../../fixtures/chains/for_wallet_c922713eac.json'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const Storage = require("../../Storage/Storage"); - +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); let mockedAccount; describe('Account - getTotalBalance', function suite() { this.timeout(10000); before(() => { - const { walletId } = walletStoreMock; - - mockedAccount = { - walletId, - index: 0, - storage: new Storage(), - accountPath: "m/44'/1'/0'", - network: "testnet" - }; - - mockedAccount.storage.createWalletStore(walletId) - mockedAccount.storage.createChainStore("testnet") - mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock) - mockedAccount.storage.getChainStore("testnet").importState(chainStoreMock) + mockedAccount = mockAccountWithStorage() }); - it('should correctly get the balance', () => { + it('should correctly get the balance',() => { const balance = getTotalBalance.call(mockedAccount); expect(balance).to.equal(224108673); }); diff --git a/src/types/Account/methods/getUnconfirmedBalance.spec.js b/src/types/Account/methods/getUnconfirmedBalance.spec.js index c5db8209..ad562c6b 100644 --- a/src/types/Account/methods/getUnconfirmedBalance.spec.js +++ b/src/types/Account/methods/getUnconfirmedBalance.spec.js @@ -1,43 +1,35 @@ const { expect } = require('chai'); -const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const { duffsToDash, calculateDuffBalance } = require('../../../utils'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); - -let mockedWallet; -describe('Account - getTotalBalance', function suite() { +let mockedAccount; +describe('Account - getUnconfirmedBalance', function suite() { this.timeout(10000); before(() => { - const storageHDW = { - store: mockedStore, - calculateDuffBalance, - getStore: () => mockedStore, - mappedAddress: {}, - }; - const walletId = Object.keys(mockedStore.wallets)[0]; - mockedWallet = { - walletId, - index: 0, - storage: storageHDW, - }; + mockedAccount = mockAccountWithStorage() }); - it('should correctly get the balance', async () => { - const balance = await getTotalBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); + + it('should correctly get the balance', () => { + const balance = getTotalBalance.call(mockedAccount); + expect(balance).to.equal(224108673); }); - it('should correctly get the balance confirmed only', async () => { - const balance = await getConfirmedBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); + + it('should correctly get the balance confirmed only', () => { + const balance = getConfirmedBalance.call(mockedAccount); + expect(balance).to.equal(224108673); }); - it('should correctly get the balance dash value instead of duff', async () => { - const balanceTotalDash = await getTotalBalance.call(mockedWallet, false); - const balanceUnconfDash = await getUnconfirmedBalance.call(mockedWallet, false); - const balanceConfDash = await getConfirmedBalance.call(mockedWallet, false); - expect(balanceTotalDash).to.equal(1844.99999506); + // TODO: file looks like a complete duplicate of the getTotalBalance.spec.js + // Should we actually mock and test unconfirmed balance? + it('should correctly get the balance dash value instead of duff', () => { + const balanceTotalDash = getTotalBalance.call(mockedAccount, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); + const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); + + expect(balanceTotalDash).to.equal(2.24108673); expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(1844.99999506); + expect(balanceConfDash).to.equal(2.24108673); }); }); diff --git a/src/types/Account/methods/getUnusedAddress.spec.js b/src/types/Account/methods/getUnusedAddress.spec.js index 192ea3ec..109e1e8e 100644 --- a/src/types/Account/methods/getUnusedAddress.spec.js +++ b/src/types/Account/methods/getUnusedAddress.spec.js @@ -4,12 +4,8 @@ const getUnusedAddress = require('./getUnusedAddress'); const getAddress = require('./getAddress'); const generateAddress = require('./generateAddress'); const KeyChain = require('../../KeyChain/KeyChain'); - -// const mockedStore = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); -const walletStoreMock = require('../../../../fixtures/wallets/c922713eac.json'); -const chainStoreMock = require('../../../../fixtures/chains/for_wallet_c922713eac.json'); const KeyChainStore = require("../../KeyChainStore/KeyChainStore"); -const Storage = require("../../Storage/Storage"); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); const HDRootKeyMockedStore = 'tprv8gpcZgdXPzdXKBjSzieMyfwr6KidKucLiiA9VbCLCx1spyJNd38a5KdjtVuc9bVUNpFM2LdFCrYSyUXHx1RCTdr6qQen1HTECwAZ1p8yqiB'; @@ -18,22 +14,9 @@ describe('Account - getUnusedAddress', function suite() { let mockedAccount; before(() => { - const { walletId } = walletStoreMock; - - mockedAccount = { - emit: (_) => (_), - walletId, - index: 0, - storage: new Storage(), - accountPath: "m/44'/1'/0'", - network: "testnet", + mockedAccount = mockAccountWithStorage({ keyChainStore: new KeyChainStore() - }; - - mockedAccount.storage.createWalletStore(walletId) - mockedAccount.storage.createChainStore("testnet") - mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock) - mockedAccount.storage.getChainStore("testnet").importState(chainStoreMock) + }) const keyChain = new KeyChain({ type: 'HDPrivateKey', From c6abcefd44eea6b087fc04584395e97609b0c2d1 Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Thu, 2 Dec 2021 14:08:15 +0300 Subject: [PATCH 37/40] Adds cloneDeep to export and import state to prevent messing with the internal links --- src/types/WalletStore/methods/exportState.js | 6 ++++-- src/types/WalletStore/methods/importState.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/types/WalletStore/methods/exportState.js b/src/types/WalletStore/methods/exportState.js index 20d3ce7b..03b37ea6 100644 --- a/src/types/WalletStore/methods/exportState.js +++ b/src/types/WalletStore/methods/exportState.js @@ -1,3 +1,5 @@ +const { cloneDeep } = require('lodash'); + function exportState() { const { walletId } = this; const { mnemonic, paths, identities } = this.state; @@ -6,8 +8,8 @@ function exportState() { walletId, state: { mnemonic, - paths: Object.fromEntries(paths), - identities: Object.fromEntries(identities), + paths: cloneDeep(Object.fromEntries(paths)), + identities: cloneDeep(Object.fromEntries(identities)), }, }; } diff --git a/src/types/WalletStore/methods/importState.js b/src/types/WalletStore/methods/importState.js index 2706ec30..e4eb7091 100644 --- a/src/types/WalletStore/methods/importState.js +++ b/src/types/WalletStore/methods/importState.js @@ -1,7 +1,9 @@ +const { cloneDeep } = require('lodash'); + function importState(state) { this.walletId = state.walletId; this.state.mnemonic = state.state.mnemonic; - this.state.paths = new Map(Object.entries(state.state.paths)); - this.state.identities = new Map(Object.entries(state.state.identities)); + this.state.paths = new Map(cloneDeep(Object.entries(state.state.paths))); + this.state.identities = new Map(cloneDeep(Object.entries(state.state.identities))); } module.exports = importState; From ec7cc2479340c39a5425fca48432b8aa6e21ae9b Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Thu, 2 Dec 2021 14:36:19 +0300 Subject: [PATCH 38/40] Fixes getConfirmedBalance.spec.js, getTransaction.spec.js, getUTXOS.spec.js --- .../methods/getConfirmedBalance.spec.js | 47 ++--- .../Account/methods/getTransaction.spec.js | 137 ++++---------- src/types/Account/methods/getUTXOS.spec.js | 177 +++--------------- 3 files changed, 85 insertions(+), 276 deletions(-) diff --git a/src/types/Account/methods/getConfirmedBalance.spec.js b/src/types/Account/methods/getConfirmedBalance.spec.js index c5db8209..d8c6f4b6 100644 --- a/src/types/Account/methods/getConfirmedBalance.spec.js +++ b/src/types/Account/methods/getConfirmedBalance.spec.js @@ -1,43 +1,36 @@ const { expect } = require('chai'); -const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const { duffsToDash, calculateDuffBalance } = require('../../../utils'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); -let mockedWallet; +let mockedAccount; describe('Account - getTotalBalance', function suite() { this.timeout(10000); before(() => { - const storageHDW = { - store: mockedStore, - calculateDuffBalance, - getStore: () => mockedStore, - mappedAddress: {}, - }; - const walletId = Object.keys(mockedStore.wallets)[0]; - mockedWallet = { - walletId, - index: 0, - storage: storageHDW, - }; + mockedAccount = mockAccountWithStorage() }); - it('should correctly get the balance', async () => { - const balance = await getTotalBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); + + it('should correctly get the balance', () => { + const balance = getTotalBalance.call(mockedAccount); + expect(balance).to.equal(224108673); }); - it('should correctly get the balance confirmed only', async () => { - const balance = await getConfirmedBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); + + it('should correctly get the balance confirmed only', () => { + const balance = getConfirmedBalance.call(mockedAccount); + expect(balance).to.equal(224108673); }); - it('should correctly get the balance dash value instead of duff', async () => { - const balanceTotalDash = await getTotalBalance.call(mockedWallet, false); - const balanceUnconfDash = await getUnconfirmedBalance.call(mockedWallet, false); - const balanceConfDash = await getConfirmedBalance.call(mockedWallet, false); - expect(balanceTotalDash).to.equal(1844.99999506); + // TODO: file looks like a complete duplicate of the getTotalBalance.spec.js + // Should we actually mock and test confirmed balance? + it('should correctly get the balance dash value instead of duff', () => { + const balanceTotalDash = getTotalBalance.call(mockedAccount, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); + const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); + + expect(balanceTotalDash).to.equal(2.24108673); expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(1844.99999506); + expect(balanceConfDash).to.equal(2.24108673); }); }); diff --git a/src/types/Account/methods/getTransaction.spec.js b/src/types/Account/methods/getTransaction.spec.js index 139b95ae..ea65ce93 100644 --- a/src/types/Account/methods/getTransaction.spec.js +++ b/src/types/Account/methods/getTransaction.spec.js @@ -1,119 +1,64 @@ const { expect } = require('chai'); -const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getTransaction = require('./getTransaction'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); -let mockedWallet; +let mockedAccount; let fetchTransactionInfoCalledNb = 0; describe('Account - getTransaction', function suite() { this.timeout(10000); before(() => { - const storageHDW = { - store: mockedStore, - getStore: () => mockedStore, - mappedAddress: {}, - importTransactions: () => null, - }; - const walletId = Object.keys(mockedStore.wallets)[0]; - mockedWallet = { - walletId, - index: 0, - storage: storageHDW, + mockedAccount = mockAccountWithStorage({ transport: { - getTransaction: () => {fetchTransactionInfoCalledNb += 1; return null}, - }, - }; + getTransaction: () => { + fetchTransactionInfoCalledNb += 1; + return null + }, + } + }); }); it('should correctly get a existing transaction', async () => { - const tx = await getTransaction.call(mockedWallet, '92150f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5'); - expect(tx.transaction).to.deep.equal(expectedTx); + const tx = await getTransaction.call(mockedAccount, 'c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4'); + + expect(tx.transaction.toObject()).to.deep.equal(expectedTx); + expect(tx.metadata).to.deep.equal({ - hash: "92150f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5", - blockHash: '000000c5d6ca463ebbfddffe9a0a135312b6d8fc4eae2787b82b0fca9de7a554', - height: 29197, - instantLocked: false, - chainLocked: false + blockHash: '000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960', + height: 615751, + isInstantLocked: null, + isChainLocked: null }); }); + it('should correctly try to fetch un unexisting transaction', async () => { expect(fetchTransactionInfoCalledNb).to.equal(0); - const tx = await getTransaction.call(mockedWallet, '92151f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5'); + const tx = await getTransaction.call(mockedAccount, '92151f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5'); expect(fetchTransactionInfoCalledNb).to.equal(1); expect(tx).to.equal(null); }); }); const expectedTx = { - hash: '92150f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5', - blockhash: '000000c5d6ca463ebbfddffe9a0a135312b6d8fc4eae2787b82b0fca9de7a554', - blockheight: 29197, - blocktime: 1562060795, - fees: 522, - size: 521, - vout: [{ - value: '0.99990990', - n: 0, - scriptPubKey: { - hex: '76a914ba84943e63925288d2972cd5d0c2e1e06873c7c688ac', - asm: 'OP_DUP OP_HASH160 ba84943e63925288d2972cd5d0c2e1e06873c7c6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['ydKfMe2n4vWsrzvgfSieQsFFxM9XMoWBff'], - type: 'pubkeyhash', - }, - spentTxId: 'eabe39ada39b58d70c03e0e79b7d2c767ed1239dda436bbc5a58954285421acc', - spentIndex: 1, - spentHeight: 30969, - }, { - value: '1000.00000000', - n: 1, - scriptPubKey: { - hex: '76a91485ada58442067249829d52ddd6c99c97a112749188ac', - asm: 'OP_DUP OP_HASH160 85ada58442067249829d52ddd6c99c97a1127491 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['yYWGjtb7XJqbXsUPfkaTWQKzcPYfmMp1Co'], - type: 'pubkeyhash', + hash: 'c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4', + version: 3, + inputs: [ + { + prevTxId: 'a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965', + outputIndex: 0, + sequenceNumber: 4294967295, + script: '48304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c310121029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738', + scriptString: '72 0x304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c3101 33 0x029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738' + } + ], + outputs: [ + { + satoshis: 10000, + script: '76a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac' }, - spentTxId: '5a5626c59f3830d5d9e7261bed5ced2694a343100c18d4a730b26639f5832944', - spentIndex: 0, - spentHeight: 29197, - }], - vin: [{ - hash: '0c25c534aeef8a151e8ce325882f80af647621b9f0a54f995f75c0d2994966ad', - vout: 0, - sequence: 4294967294, - n: 0, - scriptSig: { - hex: '483045022100b24c95914f666ecb3ac41048110d7732b890b0d3fac9a9ff05560913e530430a022006660d72df91158f4d4710b75b9b05502a1332b5afca1ab5f98d012119f62553012103a6592040a30bf9254306a9d1086803cd450ae817ed5b4ba34e3e1b43d48bb783', - asm: '3045022100b24c95914f666ecb3ac41048110d7732b890b0d3fac9a9ff05560913e530430a022006660d72df91158f4d4710b75b9b05502a1332b5afca1ab5f98d012119f62553[ALL] 03a6592040a30bf9254306a9d1086803cd450ae817ed5b4ba34e3e1b43d48bb783', - }, - addr: 'yXzZsVfpPxjewfVd7oa2D6tBMHW7JbonBr', - valueSat: 99991512, - value: 0.99991512, - doubleSpentTxID: null, - }, { - hash: '92056b727a3e37f5946dc18aa4f497ba9c0e3a328105e743175629bf7c8f3d37', - vout: 0, - sequence: 4294967294, - n: 1, - scriptSig: { - hex: '483045022100fe69fdb70c0550b900960e9fbfd7254726a237c8b5688e5c9a7fba15947638fa02206ba0463b51922b56d0064c06b55c21328a39aa81312cf25ec982fa5c1eed9214012103353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - asm: '3045022100fe69fdb70c0550b900960e9fbfd7254726a237c8b5688e5c9a7fba15947638fa02206ba0463b51922b56d0064c06b55c21328a39aa81312cf25ec982fa5c1eed9214[ALL] 03353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - }, - addr: 'yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5', - valueSat: 50000000000, - value: 500, - doubleSpentTxID: null, - }, { - hash: 'be27a3dae2742aaca103fea0967edd9a6d0ef5cf90159af39f80ad5a7a50b7d6', - vout: 0, - sequence: 4294967294, - n: 2, - scriptSig: { - hex: '47304402205d30afd97e5efbec984faae5be922a487d7adce1518a3214966198a5423c150d02204ea0e15a5fcf5b8034294c9d2b9d6fc638f75506fcac3530c91b960eaf2e6859012103353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - asm: '304402205d30afd97e5efbec984faae5be922a487d7adce1518a3214966198a5423c150d02204ea0e15a5fcf5b8034294c9d2b9d6fc638f75506fcac3530c91b960eaf2e6859[ALL] 03353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - }, - addr: 'yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5', - valueSat: 50000000000, - value: 500, - doubleSpentTxID: null, - }], - txlock: false, - spendable: false, + { + satoshis: 224508306, + script: '76a9147f6f4280f91e00126d927466cd48629439b763fa88ac' + } + ], + nLockTime: 0 }; + diff --git a/src/types/Account/methods/getUTXOS.spec.js b/src/types/Account/methods/getUTXOS.spec.js index eafae7ef..0e2d8fbb 100644 --- a/src/types/Account/methods/getUTXOS.spec.js +++ b/src/types/Account/methods/getUTXOS.spec.js @@ -1,167 +1,38 @@ const { expect } = require('chai'); const Dashcore = require('@dashevo/dashcore-lib'); const getUTXOS = require('./getUTXOS'); -const { Transaction } = Dashcore; +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); -const mockedStoreEmpty = { - wallets: { - 123456789: { - addresses: {}, - }, - }, - chains: { - testnet: { - blockHeight: 10000 - } - } -}; +describe('Account - getUTXOS', function suite() { + this.timeout(10000); -const mockedStore2 = { - wallets: { - 123456789: { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - address: 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - balanceSat: 100000000, - fetchedLast: 0, - path: "m/44'/1'/0'/0/0", - transactions: [ - 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - '1d8f924bef2e24d945d7de2ac66e98c8625e4cefeee4e07db2ea334ce17f9c35', - '7ae825f4ecccd1e04e6c123e0c55d236c79cd04c6ab64e839aed2ae0af3003e6', - ], - index: 0, - unconfirmedBalanceSat: 0, - used: true, - utxos: { - "dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc-0": - { - address: 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - txId: 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - outputIndex: 0, - script: '76a914f8c2652847720ab6d401291e5a48e2c8fe5d3c9f88ac', - satoshis: 100000000, - }, - } - }, - "m/44'/1'/1'/0/0":{ - address: 'yQ5TfKcj3NHM4V4K5VBgoFJj9Q4LKX13gn', - balanceSat: 14419880000, - fetchedLast: 0, - path: "m/44'/1'/1'/0/0", - transactions: [ - 'b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba', - ], - index: 0, - unconfirmedBalanceSat: 0, - used: true, - utxos: { - "b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba-0": - { - address: 'yQ5TfKcj3NHM4V4K5VBgoFJj9Q4LKX13gn', - txId: 'b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba', - outputIndex: 0, - script: '76a914293b5b9a2154a0e4543027d694276cd5fdcb74cd88ac', - satoshis: 14419880000, - }, - } - } - }, - }, - }, - }, - chains: { - testnet: { - blockHeight: 10000 - } - }, - transactions: { - dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc: new Transaction({ - "hash": "f13a95fc3a9b6146590b12fbe48749738a1b3ffe30e42de5b1898f8f9d76b879", - "version": 3, - "inputs": [ - { - "prevTxId": "0000000000000000000000000000000000000000000000000000000000000000", - "outputIndex": 4294967295, - "sequenceNumber": 4294967295, - "script": "0264080101" - } - ], - "outputs": [ - { - "satoshis": 7972544484, - "script": "76a9144c1b05387342497e3c8fbe0b80754ae4b33134c488ac" - }, - { - "satoshis": 7972544480, - "script": "76a914214035c10a2d2cef9992ca715a0115366edd229e88ac" - } - ], - "nLockTime": 0, - "type": 5, - "extraPayload": "020064080000aa254fcb634bf1962b67bb64ce178a954353c71d0b6119361390a9fd1a71bd2c0000000000000000000000000000000000000000000000000000000000000000" - }), - b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba: new Transaction({ - "hash": "f13a95fc3a9b6146590b12fbe48749738a1b3ffe30e42de5b1898f8f9d76b879", - "version": 3, - "inputs": [ - { - "prevTxId": "0000000000000000000000000000000000000000000000000000000000000000", - "outputIndex": 4294967295, - "sequenceNumber": 4294967295, - "script": "0264080101" - } - ], - "outputs": [ - { - "satoshis": 7972544484, - "script": "76a9144c1b05387342497e3c8fbe0b80754ae4b33134c488ac" - }, - { - "satoshis": 7972544480, - "script": "76a914214035c10a2d2cef9992ca715a0115366edd229e88ac" - } - ], - "nLockTime": 0, - "type": 5, - "extraPayload": "020064080000aa254fcb634bf1962b67bb64ce178a954353c71d0b6119361390a9fd1a71bd2c0000000000000000000000000000000000000000000000000000000000000000" - }), - }, - transactionsMetadata:{ + it('should return empty UTXOs list for new account', () => { + const mockedAccount = mockAccountWithStorage(); + const { walletId, accountPath, network } = mockedAccount; - } -}; + // Wipe transactions and addresses from the storage to simulate empty UTXOs + mockedAccount.storage.getWalletStore(walletId).state.paths.get(accountPath).addresses = {} + const chainStore = mockedAccount.storage.getChainStore(network); + chainStore.state.blockHeaders = {}; + chainStore.state.transactions = {}; + chainStore.state.addresses = {}; + + const utxos = getUTXOS.call(mockedAccount); -describe('Account - getUTXOS', function suite() { - this.timeout(10000); - it('should get the proper UTXOS list', () => { - const utxos = getUTXOS.call({ - store: mockedStoreEmpty, - getStore: mockedStoreEmpty, - walletId: '123456789', - network: 'testnet', - walletType: 'hdwallet', - BIP44PATH: "m/44'/1'/0'" - }); expect(utxos).to.be.deep.equal([]); + }) - const utxos2 = getUTXOS.call({ - store: mockedStore2, - getStore: mockedStore2, - walletId: '123456789', - network: 'testnet', - walletType: 'hdwallet', - BIP44PATH: "m/44'/1'/0'" - }); + it('should get the proper UTXOS list', () => { + const mockedAccount = mockAccountWithStorage(); + const utxos = getUTXOS.call(mockedAccount); - expect(utxos2).to.be.deep.equal([new Dashcore.Transaction.UnspentOutput( + expect(utxos).to.be.deep.equal([new Dashcore.Transaction.UnspentOutput( { - address: new Dashcore.Address('yizmJb63ygipuJaRgYtpWCV2erQodmaZt8'), - txId: 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - outputIndex: 0, - script: new Dashcore.Script('76a914f8c2652847720ab6d401291e5a48e2c8fe5d3c9f88ac'), - satoshis: 100000000, + address: new Dashcore.Address('yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX'), + txId: '33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4', + outputIndex: 1, + script: new Dashcore.Script('76a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac'), + satoshis: 224108673, }, )]); }); From 46ac52ea3fd730f2673106042cc4abf6ceb9ab34 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Thu, 9 Dec 2021 02:51:38 +0100 Subject: [PATCH 39/40] feat: test fixes and processing of external keychain on account.importTx --- src/errors/InjectionToPluginUnallowed.js | 4 +-- src/plugins/StandardPlugin.js | 2 +- .../TransactionSyncStreamWorker.js | 5 ++-- .../methods/processChunks.js | 15 ++++++---- src/test/mocks/mockAccountWithStorage.js | 9 ++++++ src/types/Account/Account.d.ts | 2 +- .../Account/methods/createTransaction.spec.js | 13 +++++---- src/types/Account/methods/getPrivateKeys.js | 4 ++- .../Account/methods/importTransactions.js | 29 ++++++++++++++----- .../Account/methods/injectPlugin.spec.js | 2 +- src/types/Account/methods/sign.spec.js | 2 +- .../methods/getIdentityHDKeyById.js | 5 +++- .../methods/getIdentityHDKeyById.spec.js | 6 +++- src/types/KeyChain/KeyChain.js | 1 + .../KeyChain/methods/getFirstUnusedAddress.js | 14 +++++++++ .../KeyChain/methods/markAddressAsUsed.js | 9 ++++-- src/types/Storage/Storage.js | 2 ++ src/types/Storage/methods/startWorker.js | 11 +++++++ src/types/Storage/methods/stopWorker.js | 9 ++++++ .../Wallet/methods/fromHDPublicKey.spec.js | 2 +- 20 files changed, 110 insertions(+), 36 deletions(-) create mode 100644 src/types/KeyChain/methods/getFirstUnusedAddress.js create mode 100644 src/types/Storage/methods/startWorker.js create mode 100644 src/types/Storage/methods/stopWorker.js diff --git a/src/errors/InjectionToPluginUnallowed.js b/src/errors/InjectionToPluginUnallowed.js index 3d7e57f6..6d6767f0 100644 --- a/src/errors/InjectionToPluginUnallowed.js +++ b/src/errors/InjectionToPluginUnallowed.js @@ -1,8 +1,8 @@ const WalletLibError = require('./WalletLibError'); class InjectionToPluginUnallowed extends WalletLibError { - constructor(pluginName) { - super(`Injection of plugin : ${pluginName} Unallowed`); + constructor(currentPluginName, injectingPluginName) { + super(`Injection of plugin : ${injectingPluginName} into ${currentPluginName} not allowed`); } } diff --git a/src/plugins/StandardPlugin.js b/src/plugins/StandardPlugin.js index 4b2ec8fd..6a5c5a22 100644 --- a/src/plugins/StandardPlugin.js +++ b/src/plugins/StandardPlugin.js @@ -63,7 +63,7 @@ class StandardPlugin extends EventEmitter { // this.parentEvents = {on:obj.on, emit:obj.emit}; this.parentEvents = obj; } else { - this.emit('error', new InjectionToPluginUnallowed(name), { + this.emit('error', new InjectionToPluginUnallowed(this.name, name), { type: 'plugin', pluginType: 'plugin', pluginName: this.name, diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index a322c35e..db518ac9 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -50,12 +50,11 @@ class TransactionSyncStreamWorker extends Worker { * @param {string[]} addressList * @param {string} network */ - static filterWalletTransactions(transactions, addressList, network) { + static filterAddressesTransactions(transactions, addressList, network) { const spentOutputs = []; const unspentOutputs = []; const filteredTransactions = transactions.filter((tx) => { let isWalletTransaction = false; - tx.inputs.forEach((input) => { if (input.script) { const addr = input.script.toAddress(network).toString(); @@ -139,7 +138,7 @@ class TransactionSyncStreamWorker extends Worker { if (skipSynchronization) { logger.debug('TransactionSyncStreamWorker - Wallet created from a new mnemonic. Sync from the best block height.'); - const bestBlockHeight = this.storage.store.chains[this.network.toString()].blockHeight; + const bestBlockHeight = this.storage.getChainStore(this.network.toString()).blockHeight; this.setLastSyncedBlockHeight(bestBlockHeight); return; } diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js index 3229db4e..ce586c8f 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js @@ -3,6 +3,7 @@ const GrpcError = require('@dashevo/grpc-common/lib/server/error/GrpcError'); const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); const logger = require('../../../../logger'); const isBrowser = require('../../../../utils/isBrowser'); +const {chain} = require("lodash/seq"); function isAnyIntersection(arrayA, arrayB) { const intersection = arrayA.filter((e) => arrayB.indexOf(e) > -1); @@ -23,21 +24,22 @@ async function processChunks(dataChunk) { const transactionsFromResponse = this.constructor .getTransactionListFromStreamResponse(dataChunk); - const walletTransactions = this.constructor - .filterWalletTransactions(transactionsFromResponse, addresses, network); - if (walletTransactions.transactions.length) { + const addressesTransaction = this.constructor + .filterAddressesTransactions(transactionsFromResponse, addresses, network); + + if (addressesTransaction.transactions.length) { // Normalizing format of transaction for account.importTransactions - const walletTransactionWithoutMetadata = walletTransactions.transactions.map((tx) => [tx]); + const addressesTransactionsWithoutMetadata = addressesTransaction.transactions.map((tx) => [tx]); // When a transaction exist, there is multiple things we need to do : // 1) The transaction itself needs to be imported const addressesGeneratedCount = await self - .importTransactions(walletTransactionWithoutMetadata); + .importTransactions(addressesTransactionsWithoutMetadata); // 2) Transaction metadata need to be fetched and imported as well. // as such event might happen in the future // As we require height information, we fetch transaction using client - const awaitingMetadataPromises = walletTransactions.transactions + const awaitingMetadataPromises = addressesTransaction.transactions .map((transaction) => self.handleTransactionFromStream(transaction) .then(({ transactionResponse, @@ -47,6 +49,7 @@ async function processChunks(dataChunk) { Promise .all(awaitingMetadataPromises) .then(async (transactionsWithMetadata) => { + // Import into account await self.importTransactions(transactionsWithMetadata); }); diff --git a/src/test/mocks/mockAccountWithStorage.js b/src/test/mocks/mockAccountWithStorage.js index 9821d91f..549067ae 100644 --- a/src/test/mocks/mockAccountWithStorage.js +++ b/src/test/mocks/mockAccountWithStorage.js @@ -1,6 +1,7 @@ const walletStoreMock = require('../../../fixtures/wallets/c922713eac.json'); const chainStoreMock = require('../../../fixtures/chains/for_wallet_c922713eac.json'); const Storage = require('../../types/Storage/Storage'); +const { KeyChainStore, KeyChain } = require('../../index'); module.exports = (opts = {}) => { const { walletId } = walletStoreMock; @@ -14,6 +15,14 @@ module.exports = (opts = {}) => { ...opts, }; + mockedAccount.keyChainStore = new KeyChainStore(); + mockedAccount.keyChainStore.addKeyChain(new KeyChain({ + HDPrivateKey: 'tprv8gpcZgdXPzdXKBjSzieMyfwr6KidKucLiiA9VbCLCx1spyJNd38a5KdjtVuc9bVUNpFM2LdFCrYSyUXHx1RCTdr6qQen1HTECwAZ1p8yqiB', + lookAheadOpts: { + 'm/0': 40, + 'm/1': 40, + }, + }), { isMasterKeyChain: true }); mockedAccount.storage.createWalletStore(walletId); mockedAccount.storage.createChainStore('testnet'); mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock); diff --git a/src/types/Account/Account.d.ts b/src/types/Account/Account.d.ts index 5310566b..00049f84 100644 --- a/src/types/Account/Account.d.ts +++ b/src/types/Account/Account.d.ts @@ -58,7 +58,7 @@ export declare class Account { getAddresses(_type: AddressType): [AddressObj]; getBlockHeader(identifier: string|number):Promise getConfirmedBalance(displayDuffs?: boolean): number; - getPlugin(name: string): Object; + getPlugin(name: string): any; getPrivateKeys(addressList: [PublicAddress]): [PrivateKey]; getTotalBalance(displayDuffs?: boolean): number; getTransaction(txid: transactionId): Transaction; diff --git a/src/types/Account/methods/createTransaction.spec.js b/src/types/Account/methods/createTransaction.spec.js index 8b78fc27..646e7546 100644 --- a/src/types/Account/methods/createTransaction.spec.js +++ b/src/types/Account/methods/createTransaction.spec.js @@ -7,6 +7,8 @@ const { mnemonic } = require('../../../../fixtures/wallets/mnemonics/during-deve const FixtureTransport = require('../../../transport/FixtureTransport/FixtureTransport'); const getUTXOS = require('./getUTXOS'); +const getPrivateKeys = require('./getPrivateKeys'); +const getUnusedAddress = require('./getUnusedAddress'); const { simpleDescendingAccumulator } = require('../../../utils/coinSelections/strategies'); const addressesFixtures = require('../../../../fixtures/addresses.json'); @@ -14,18 +16,17 @@ const fixtureUTXOS = require('../../../transport/FixtureTransport/data/utxos/yQ1 const validStore = require('../../../../fixtures/walletStore').valid.orange.store; const craftedGenerousMinerStrategy = require('../../../../fixtures/strategies/craftedGenerousMinerStrategy'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); describe('Account - createTransaction', function suite() { this.timeout(10000); let mockWallet; it('sould warn on missing inputs', function () { - const self = { - store: validStore, - walletId: 'a3771aaf93', - getUTXOS, - network: 'testnet' - }; + const self = mockAccountWithStorage() + self.getUTXOS = getUTXOS; + self.getUnusedAddress = getUnusedAddress; + self.getPrivateKeys = getPrivateKeys; const mockOpts1 = {}; const mockOpts2 = { diff --git a/src/types/Account/methods/getPrivateKeys.js b/src/types/Account/methods/getPrivateKeys.js index db821497..f7a068f8 100644 --- a/src/types/Account/methods/getPrivateKeys.js +++ b/src/types/Account/methods/getPrivateKeys.js @@ -16,7 +16,9 @@ function getPrivateKeys(addressList) { addresses.forEach((address) => { const addressData = keyChain.getForAddress(address); - privKeys.push(addressData.key); + if (addressData) { + privKeys.push(addressData.key); + } }); return privKeys; diff --git a/src/types/Account/methods/importTransactions.js b/src/types/Account/methods/importTransactions.js index be26ef68..68e1f3de 100644 --- a/src/types/Account/methods/importTransactions.js +++ b/src/types/Account/methods/importTransactions.js @@ -1,6 +1,7 @@ const logger = require('../../../logger'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const ensureAddressesToGapLimit = require('../../../utils/bip44/ensureAddressesToGapLimit'); +const {chain} = require("lodash/seq"); /** * Import transactions and always keep a number of unused addresses up to gap @@ -23,19 +24,31 @@ module.exports = async function importTransactions(transactionsWithMayBeMetadata .getPathState(accountPath); const masterKeyChain = keyChainStore.getMasterKeyChain(); - - transactionsWithMayBeMetadata.forEach(([transaction, metadata]) => { + const keyChains = keyChainStore.getKeyChains(); + transactionsWithMayBeMetadata.forEach((transactionWithMetadata) => { + if (!Array.isArray(transactionWithMetadata)) { + throw new Error('Expecting transactions to be an array of transaction and metadata elements'); + } + const [transaction, metadata] = transactionWithMetadata; + // Affected addresses might not be from our master keychain (account) const affectedAddressesData = chainStore.importTransaction(transaction, metadata); const affectedAddresses = Object.keys(affectedAddressesData); logger.silly(`Account.importTransactions - Import ${transaction.hash} to chainStore. ${affectedAddresses.length} addresses affected.`); affectedAddresses.forEach((address) => { - const issuedPaths = masterKeyChain.markAddressAsUsed(address); - logger.silly(`Account.importTransactions - newly issued paths ${issuedPaths.length}`); - issuedPaths.forEach((issuedPath) => { - accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); - chainStore.importAddress(issuedPath.address.toString()); - }); + + keyChains.forEach((keyChain)=>{ + const issuedPaths = keyChain.markAddressAsUsed(address); + if(issuedPaths){ + issuedPaths.forEach((issuedPath)=>{ + if(keyChain.keyChainId === masterKeyChain.keyChainId){ + logger.silly(`Account.importTransactions - newly issued paths ${issuedPath.length}`); + accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); + } + chainStore.importAddress(issuedPath.address.toString()); + }) + } + }) }); }); logger.silly(`Account.importTransactions(len: ${transactionsWithMayBeMetadata.length})`); diff --git a/src/types/Account/methods/injectPlugin.spec.js b/src/types/Account/methods/injectPlugin.spec.js index 68bd7c9b..6849f691 100644 --- a/src/types/Account/methods/injectPlugin.spec.js +++ b/src/types/Account/methods/injectPlugin.spec.js @@ -22,7 +22,7 @@ describe('Account - injectPlugin', function suite() { emit: emitter.emit, } it('should prevent sensible access', async function () { - const expectedException1 = 'Injection of plugin : storage Unallowed'; + const expectedException1 = 'Injection of plugin : storage into WorkingWorker not allowed'; await expectThrowsAsync(async () => await injectPlugin.call(mockedSelf, WorkingWorker), expectedException1); }); it('should work', function (done) { diff --git a/src/types/Account/methods/sign.spec.js b/src/types/Account/methods/sign.spec.js index 6e3b6e8e..e2de5251 100644 --- a/src/types/Account/methods/sign.spec.js +++ b/src/types/Account/methods/sign.spec.js @@ -18,7 +18,7 @@ describe('Account - sign', function suite() { wallet.disconnect(); }); it('should sign a transaction with a message', function () { - account.importTransactions(transactions); + account.importTransactions([[new Dashcore.Transaction(transactions["4e2a8b05a805fcee959b8ecfd5557e196a9b8490dd280d6f599b391d650407c8"])]]); const transaction = account.createTransaction({ recipient: 'yNPbcFfabtNmmxKdGwhHomdYfVs6gikbPf', // Evonet faucet satoshis: 1000000, // 1 Dash diff --git a/src/types/Identities/methods/getIdentityHDKeyById.js b/src/types/Identities/methods/getIdentityHDKeyById.js index 374925a1..79cd6eed 100644 --- a/src/types/Identities/methods/getIdentityHDKeyById.js +++ b/src/types/Identities/methods/getIdentityHDKeyById.js @@ -5,7 +5,10 @@ * @return {HDPrivateKey} */ function getIdentityHDKeyById(identityId, keyIndex) { - const identityIndex = this.storage.getIndexedIdentityIds(this.walletId).indexOf(identityId); + const identityIndex = this.storage + .getWalletStore(this.walletId) + .getIndexedIdentityIds() + .indexOf(identityId); if (identityIndex === -1) { throw new Error(`Identity with ID ${identityId} is not associated with wallet, or it's not synced`); diff --git a/src/types/Identities/methods/getIdentityHDKeyById.spec.js b/src/types/Identities/methods/getIdentityHDKeyById.spec.js index 146d670c..2d8f3a2b 100644 --- a/src/types/Identities/methods/getIdentityHDKeyById.spec.js +++ b/src/types/Identities/methods/getIdentityHDKeyById.spec.js @@ -9,11 +9,15 @@ describe('Wallet#getIdentityHDKeyById', function suite() { this.timeout(10000); before(() => { expectedKeyMock = "123"; + const walletStoreMock = { + getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds + } + const storageMock = { store: mockedStore, getStore: () => mockedStore, mappedAddress: {}, - getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds, + getWalletStore: () => walletStoreMock, }; const walletId = Object.keys(mockedStore.wallets)[0]; walletMock = { diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 88046ea4..74931778 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -93,6 +93,7 @@ class KeyChain { KeyChain.prototype.getForPath = require('./methods/getForPath'); KeyChain.prototype.getForAddress = require('./methods/getForAddress'); KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); +KeyChain.prototype.getFirstUnusedAddress = require('./methods/getFirstUnusedAddress'); KeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); diff --git a/src/types/KeyChain/methods/getFirstUnusedAddress.js b/src/types/KeyChain/methods/getFirstUnusedAddress.js new file mode 100644 index 00000000..cb72d60c --- /dev/null +++ b/src/types/KeyChain/methods/getFirstUnusedAddress.js @@ -0,0 +1,14 @@ +function getFirstUnusedAddress() { + const allUnused = this.getIssuedPaths() + .filter((path)=>{ + return path.isUsed === false + }); + + const firstUnused = allUnused.slice(0,1)[0] + + return { + path: firstUnused.path, + address: firstUnused.address.toString() + } +} +module.exports = getFirstUnusedAddress; diff --git a/src/types/KeyChain/methods/markAddressAsUsed.js b/src/types/KeyChain/methods/markAddressAsUsed.js index 90b04f2e..e35e96a1 100644 --- a/src/types/KeyChain/methods/markAddressAsUsed.js +++ b/src/types/KeyChain/methods/markAddressAsUsed.js @@ -6,10 +6,13 @@ function markAddressAsUsed(address) { const searchResult = [...this.issuedPaths.entries()] .find(([, el]) => el.address.toString() === address.toString()); - const [, addressData] = searchResult; + if (searchResult) { + const [, addressData] = searchResult; - addressData.isUsed = true; + addressData.isUsed = true; + + return this.maybeLookAhead(); + } - return this.maybeLookAhead(); } module.exports = markAddressAsUsed; diff --git a/src/types/Storage/Storage.js b/src/types/Storage/Storage.js index 019a5670..b90346b3 100644 --- a/src/types/Storage/Storage.js +++ b/src/types/Storage/Storage.js @@ -22,5 +22,7 @@ Storage.prototype.getChainStore = require('./methods/getChainStore'); Storage.prototype.getWalletStore = require('./methods/getWalletStore'); Storage.prototype.rehydrateState = require('./methods/rehydrateState'); Storage.prototype.saveState = require('./methods/saveState'); +Storage.prototype.startWorker = require('./methods/startWorker'); +Storage.prototype.stopWorker = require('./methods/stopWorker'); module.exports = Storage; diff --git a/src/types/Storage/methods/startWorker.js b/src/types/Storage/methods/startWorker.js new file mode 100644 index 00000000..4976c0eb --- /dev/null +++ b/src/types/Storage/methods/startWorker.js @@ -0,0 +1,11 @@ +/** + * Allow to start the working interval (worker for saving state). + * @return {void} + */ +module.exports = function startWorker() { + this.interval = setInterval(() => { + if (this.lastModified > this.lastSave) { + this.saveState(); + } + }, this.autosaveIntervalTime); +}; diff --git a/src/types/Storage/methods/stopWorker.js b/src/types/Storage/methods/stopWorker.js new file mode 100644 index 00000000..f0aa6690 --- /dev/null +++ b/src/types/Storage/methods/stopWorker.js @@ -0,0 +1,9 @@ +/** + * Allow to clear the working interval (worker). + * @return {boolean} + */ +module.exports = function stopWorker() { + clearInterval(this.interval); + this.interval = null; + return true; +}; diff --git a/src/types/Wallet/methods/fromHDPublicKey.spec.js b/src/types/Wallet/methods/fromHDPublicKey.spec.js index 36177f78..fca3387d 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.spec.js +++ b/src/types/Wallet/methods/fromHDPublicKey.spec.js @@ -66,7 +66,7 @@ describe('Wallet - HDPublicKey', function suite() { .then((account)=>{ const unusedAddress = account.getUnusedAddress(); const expectedUnused = { - path: "m/44'/1'/0'/0/0", + path: "m/0/0", index: 0, address: 'yNJ3xxTXXBBf39VfMBbBuLH2k57uAwxBxj', transactions: [], From 1b5b1e44137f45b7a44e8e6e088619e63f4d8e26 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Wed, 15 Dec 2021 12:28:17 +0100 Subject: [PATCH 40/40] feat: getTransactionHistory migrate --- .../Account/methods/getTransactionHistory.js | 99 +++++++------------ src/types/Account/methods/getTransactions.js | 10 +- .../KeyChain/methods/markAddressAsUsed.js | 4 +- src/types/KeyChain/methods/maybeLookAhead.js | 4 - src/types/Storage/Storage.js | 4 +- src/types/Storage/methods/getWalletStore.js | 1 + src/utils/categorizeTransactions.js | 28 +++--- src/utils/classifyAddresses.js | 49 ++++----- 8 files changed, 91 insertions(+), 108 deletions(-) diff --git a/src/types/Account/methods/getTransactionHistory.js b/src/types/Account/methods/getTransactionHistory.js index e0d13254..5b7e3f4d 100644 --- a/src/types/Account/methods/getTransactionHistory.js +++ b/src/types/Account/methods/getTransactionHistory.js @@ -1,8 +1,6 @@ const { each } = require('lodash'); const { - filterTransactions, categorizeTransactions, - extendTransactionsWithMetadata, // calculateTransactionFees, } = require('../../../utils'); @@ -24,78 +22,49 @@ function getTransactionHistory() { network, } = this; - const transactions = this.getTransactions(); + const transactionsWithMetadata = this.getTransactions(); + const walletStore = storage.getWalletStore(walletId); const chainStore = storage.getChainStore(network); - const { blockHeaders } = chainStore; + const { blockHeaders } = chainStore.state; - // const { wallets: walletStore, transactionsMetadata } = store; + const categorizedTransactions = categorizeTransactions(transactionsWithMetadata, walletStore, accountIndex, walletType, network); + const sortedCategorizedTransactions = categorizedTransactions.sort(sortByHeightDescending); - // const accountStore = walletStore[walletId]; + each(sortedCategorizedTransactions, (categorizedTransaction) => { + const { + transaction, + from, + to, + type, + isChainLocked, + isInstantLocked, + } = categorizedTransaction; + const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash : null; + // To get time of block, let's find the blockheader. + const blockHeader = blockHeaders.get(blockHash); + // If it's unconfirmed, we won't have a blockHeader nor it's time. + const time = blockHeader ? blockHeader.time : -1; - // In store, not all transaction are specific to this account, we filter our transactions. - // const filteredTransactions = filterTransactions( - // accountStore, - // walletType, - // accountIndex, - // transactions, - // ); - // const filteredTransactionsWithMetadata = extendTransactionsWithMetadata( - // filteredTransactions, - // transactionsMetadata, - // ); + const normalizedTransactionHistory = { + // Would require knowing the vout of this vin to determinate inputAmount. + // This information could be fetched, but the necessity vs the cost is questionable. + // fees: calculateTransactionFees(categorizedTransaction.transaction), + from, + to, + type, + time, + txId: transaction.hash, + blockHash, + isChainLocked, + isInstantLocked, + }; - const transactionsWithMetadata = []; - transactions.forEach((transactionId) => { - console.log(chainStore.getTransaction(transactionId)); + transactionHistory.push(normalizedTransactionHistory); }); - // const categorizedTransactions = categorizeTransactions( - // transactionsWithMetadata, - // accountStore, - // accountIndex, - // walletType, - // network, - // ); - // - // const sortedCategorizedTransactions = categorizedTransactions.sort(sortByHeightDescending); - // - // each(sortedCategorizedTransactions, (categorizedTransaction) => { - // const { - // transaction, - // from, - // to, - // type, - // isChainLocked, - // isInstantLocked, - // } = categorizedTransaction; - // - // const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash : null; - // - // To get time of block, let's find the blockheader. - // const blockHeader = blockHeaders[blockHash]; - // - // If it's unconfirmed, we won't have a blockHeader nor it's time. - // const time = blockHeader ? blockHeader.time : -1; - - // const normalizedTransactionHistory = { - // Would require knowing the vout of this vin to determinate inputAmount. - // This information could be fetched, but the necessity vs the cost is questionable. - // fees: calculateTransactionFees(categorizedTransaction.transaction), - // from, - // to, - // type, - // time, - // txId: transaction.hash, - // blockHash, - // isChainLocked, - // isInstantLocked, - // }; - - // transactionHistory.push(normalizedTransactionHistory); - // }); // Sort by decreasing time. - // return transactionHistory.sort(sortbyTimeDescending); + return transactionHistory.sort(sortbyTimeDescending); } module.exports = getTransactionHistory; diff --git a/src/types/Account/methods/getTransactions.js b/src/types/Account/methods/getTransactions.js index 59267b00..77778716 100644 --- a/src/types/Account/methods/getTransactions.js +++ b/src/types/Account/methods/getTransactions.js @@ -4,10 +4,16 @@ */ module.exports = function getTransactions() { const chainStore = this.storage.getChainStore(this.network); + const walletStore = this.storage.getWalletStore(this.walletId); const transactions = []; - const { addresses } = this.storage.getWalletStore(this.walletId).getPathState(this.accountPath); + const { addresses } = walletStore.getPathState(this.accountPath); + Object.values(addresses).forEach((address) => { - transactions.push(...chainStore.getAddress(address).transactions); + const transactionIds = chainStore.getAddress(address).transactions; + transactionIds.forEach((transactionId) => { + const tx = chainStore.getTransaction(transactionId); + transactions.push([tx.transaction, tx.metadata]); + }); }); return transactions; }; diff --git a/src/types/KeyChain/methods/markAddressAsUsed.js b/src/types/KeyChain/methods/markAddressAsUsed.js index e35e96a1..9df149d7 100644 --- a/src/types/KeyChain/methods/markAddressAsUsed.js +++ b/src/types/KeyChain/methods/markAddressAsUsed.js @@ -1,14 +1,14 @@ const logger = require('../../../logger'); function markAddressAsUsed(address) { - logger.silly(`KeyChain - Marking ${address} as used`); const searchResult = [...this.issuedPaths.entries()] .find(([, el]) => el.address.toString() === address.toString()); if (searchResult) { - const [, addressData] = searchResult; + const [, addressData] = searchResult; + logger.silly(`KeyChain - Marking ${address} ${addressData.path} as used`); addressData.isUsed = true; return this.maybeLookAhead(); diff --git a/src/types/KeyChain/methods/maybeLookAhead.js b/src/types/KeyChain/methods/maybeLookAhead.js index 45967136..cb654dff 100644 --- a/src/types/KeyChain/methods/maybeLookAhead.js +++ b/src/types/KeyChain/methods/maybeLookAhead.js @@ -43,10 +43,6 @@ function maybeLookAhead() { const addressData = this.issuedPaths.get(path); const currentIndex = parseInt(path.split('/').splice(-1), 10); - if (prevIndex !== undefined && prevIndex !== currentIndex - 1) { - // FIXME: May be we should generate ? - throw new Error('Addresses are expected to be contiguous'); - } if (addressData.isUsed) { lastUsedIndexes[basePath] = currentIndex; diff --git a/src/types/Storage/Storage.js b/src/types/Storage/Storage.js index b90346b3..10ecd29c 100644 --- a/src/types/Storage/Storage.js +++ b/src/types/Storage/Storage.js @@ -10,7 +10,9 @@ class Storage extends EventEmitter { super(); this.wallets = new Map(); this.chains = new Map(); - this.application = {}; + this.application = { + blockHeight: 0, + }; } } diff --git a/src/types/Storage/methods/getWalletStore.js b/src/types/Storage/methods/getWalletStore.js index 23c9d085..3f43fc2b 100644 --- a/src/types/Storage/methods/getWalletStore.js +++ b/src/types/Storage/methods/getWalletStore.js @@ -1,4 +1,5 @@ function getWalletStore(walletId) { + if (!this.wallets.has(walletId)) return null; return this.wallets.get(walletId); } module.exports = getWalletStore; diff --git a/src/utils/categorizeTransactions.js b/src/utils/categorizeTransactions.js index b13bbfaf..a56c025d 100644 --- a/src/utils/categorizeTransactions.js +++ b/src/utils/categorizeTransactions.js @@ -30,14 +30,20 @@ const determineType = (inputsDetection, outputsDetection) => { return type; }; -function categorizeTransactions(transactionsWithMetadata, accountStore, accountIndex, walletType, network = 'testnet') { +function categorizeTransactions( + transactionsWithMetadata, + walletStore, + accountIndex, + walletType, + network, +) { const categorizedTransactions = []; const { - externalAddressList, - internalAddressList, - otherAccountAddressList, - } = classifyAddresses(accountStore.addresses, accountIndex, walletType); + externalAddressesList, + internalAddressesList, + otherAccountAddressesList, + } = classifyAddresses(walletStore, accountIndex, walletType, network); each(transactionsWithMetadata, (transactionWithMetadata) => { const [transaction, metadata] = transactionWithMetadata; @@ -57,9 +63,9 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI const { satoshis, script } = vout; const address = script.toAddress(network).toString(); if (address) { - if (internalAddressList.includes(address)) outputsHasChangeAddress = true; - if (externalAddressList.includes(address)) outputsHasExternalAddress = true; - if (otherAccountAddressList.includes(address)) outputsHasOtherAccountAddress = true; + if (internalAddressesList.includes(address)) outputsHasChangeAddress = true; + if (externalAddressesList.includes(address)) outputsHasExternalAddress = true; + if (otherAccountAddressesList.includes(address)) outputsHasOtherAccountAddress = true; to.push({ address, satoshis, @@ -72,9 +78,9 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI const { script } = vin; const address = script.toAddress(network).toString(); if (address) { - if (internalAddressList.includes(address)) inputsHasChangeAddress = true; - if (externalAddressList.includes(address)) inputsHasExternalAddress = true; - if (otherAccountAddressList.includes(address)) inputsHasOtherAccountAddress = true; + if (internalAddressesList.includes(address)) inputsHasChangeAddress = true; + if (externalAddressesList.includes(address)) inputsHasExternalAddress = true; + if (otherAccountAddressesList.includes(address)) inputsHasOtherAccountAddress = true; from.push({ address, }); diff --git a/src/utils/classifyAddresses.js b/src/utils/classifyAddresses.js index 52dfcff9..15869d3f 100644 --- a/src/utils/classifyAddresses.js +++ b/src/utils/classifyAddresses.js @@ -1,34 +1,37 @@ const { map, filter, difference } = require('lodash'); -const { WALLET_TYPES } = require('../CONSTANTS'); +const { WALLET_TYPES, BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../CONSTANTS'); -function classifyAddresses(addressStore, accountIndex, walletType) { - const { external, internal, misc } = addressStore; +function classifyAddresses(walletStore, accountIndex, walletType, network) { + const externalAddressesList = []; + const internalAddressesList = []; + const otherAccountAddressesList = []; + const miscAddressesList = []; - // This will filter addresses to return only the one that are directly one the account manage. - // TODO: Computational improvement can be made by having accountIndex - // member of the address info format and thus comparing only 2 numbers - const filterPathByAccount = (address) => (parseInt(address.path.split('/')[3], 10) === accountIndex); - const addressMappingPredicate = (addressInfo) => (addressInfo.address); + const rootPath = (network.toString() === 'testnet') + ? BIP44_TESTNET_ROOT_PATH + : BIP44_LIVENET_ROOT_PATH; - const externalAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? map(filter(external, filterPathByAccount), addressMappingPredicate) - : map(misc, addressMappingPredicate); + const accountsPaths = [...walletStore.state.paths.keys()]; - const internalAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? map(filter(internal, filterPathByAccount), addressMappingPredicate) - : []; + const currentAccountPath = `${rootPath}/${accountIndex}'`; - const otherAccountAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? difference( - [...map(external, addressMappingPredicate), ...map(internal, addressMappingPredicate)], - [...externalAddressList, ...internalAddressList], - ) - : []; + accountsPaths.forEach((accountPath) => { + const isCurrentAccountPath = accountPath === currentAccountPath; + const accountPaths = walletStore.getPathState(accountPath); + Object.entries(accountPaths.addresses) + .forEach(([path, address]) => { + if (isCurrentAccountPath && path.startsWith('m/0')) externalAddressesList.push(address); + else if (isCurrentAccountPath && path.startsWith('m/1')) internalAddressesList.push(address); + else if (isCurrentAccountPath) miscAddressesList.push(address); + else otherAccountAddressesList.push(address); + }); + }); return { - externalAddressList, - internalAddressList, - otherAccountAddressList, + externalAddressesList, + internalAddressesList, + otherAccountAddressesList, + miscAddressesList, }; } module.exports = classifyAddresses;