UNPKG

dagcoin-core

Version:
596 lines (478 loc) 22.8 kB
'use strict'; var instance = null; // My module function AccountManager() { var self = this; self.conf = require('byteballcore/conf.js'); self.crypto = require('crypto'); self.device = require('byteballcore/device.js'); self.network = require('byteballcore/network'); self.consolidation = require('./consolidation'); self.composer = require('byteballcore/composer.js'); var DatabaseManager = require('./databaseManager'); self.dbManager = DatabaseManager.getInstance(); self.confManager = require('./confManager').getInstance(); var KeyManager = require('./keyManager'); self.keyManager = new KeyManager(); var WalletManager = require('./walletManager'); self.walletManager = new WalletManager(); self.Signer = require('./signer'); self.Mnemonic = require('bitcore-mnemonic'); this.timedPromises = require('./promiseManager'); self.paymentQueue = self.timedPromises.PromiseEnqueuer('payments', function (toAddress, amount) { return self.walletManager.readSingleAddress().then(function (fromAddress) { return self.checkThereAreStableBytes(fromAddress); }).then(function () { return self.sendPayment(toAddress, amount); }); }, self.conf.MIN_PAYMENT_DELAY, true); console.log('MINIMUM PAYMENT DELAY SET TO ' + self.conf.MIN_PAYMENT_DELAY + ' ms'); } AccountManager.prototype.createAccount = function () { console.log('COULD NOT READ THE KEY FILE, IT NEEDS TO BE GENERATED'); var suggestedDeviceName = require('os').hostname() || 'Headless'; var deviceName = this.conf.deviceName; if (!deviceName) { deviceName = suggestedDeviceName; } var self = this; var mnemonic = new self.Mnemonic(); // generates new mnemonic while (!self.Mnemonic.isValid(mnemonic.toString())) { mnemonic = new self.Mnemonic(); } return this.confManager.write({ deviceName: deviceName }).then(function () { self.deviceTempPrivKey = self.crypto.randomBytes(32); self.devicePrevTempPrivKey = self.crypto.randomBytes(32); self.mnemonicPhrase = mnemonic.phrase; self.keys = { mnemonic_phrase: self.mnemonicPhrase, temp_priv_key: self.deviceTempPrivKey.toString('base64'), prev_temp_priv_key: self.devicePrevTempPrivKey.toString('base64') }; return self.keyManager.write(self.keys); }).then(function (keys) { return self.getPassPhrase(); }).then(function (passPhrase) { console.log('KEYS CREATED'); self.xPrivKey = mnemonic.toHDPrivateKey(passPhrase); self.signer = new self.Signer(self.xPrivKey); console.log('SIGNER CREATED'); return self.walletManager.create(self.xPrivKey); }).then(function () { console.log('RETURNING THE ACCOUNT'); return Promise.resolve(self); }); }; AccountManager.prototype.getSigner = function () { return this.signer; }; AccountManager.prototype.getKeys = function () { return this.keys; }; AccountManager.prototype.getPassPhrase = function () { var _this = this; if (this.passPhrase != null) { return Promise.resolve(this.passPhrase); } return this.confManager.get('PASS_PHRASE').then(function (passPhrase) { if (passPhrase == null) { return Promise.reject('THE ENVIRONMENT VARIABLE PASS_PHRASE IS NOT SET'); } _this.passPhrase = passPhrase; return Promise.resolve(passPhrase); }); }; AccountManager.prototype.getPrivateKey = function () { return this.xPrivKey; }; AccountManager.prototype.getPairingCode = function () { return this.pairingCode; }; AccountManager.prototype.readAccount = function () { var self = this; console.log('-----------------------'); if (self.conf.control_addresses) { console.log('REMOTE ACCESS ALLOWED FROM DEVICES: ' + self.conf.control_addresses.join(', ')); } if (self.conf.payout_address) { console.log('PAYOUTS ALLOWED TO DEVICED: ' + self.conf.payout_address); } console.log('-----------------------'); return self.confManager.get('PASS_PHRASE').then(function (passPhrase) { if (passPhrase == null) { return Promise.reject(new Error('COULD NOT FIND PASS_PHRASE. IT SHOULD BE SET AS AN ENVIRONMENT VARIABLE')); } self.passPhrase = passPhrase; return self.keyManager.read(); }).then(function (data) { self.keys = JSON.parse(data); var mnemonic = new self.Mnemonic(self.keys.mnemonic_phrase); self.xPrivKey = mnemonic.toHDPrivateKey(self.passPhrase); self.signer = new self.Signer(self.xPrivKey); return self.walletManager.exists().then(function (doesExist) { if (doesExist) { return Promise.resolve(self); } else { return self.walletManager.create(self.xPrivKey).then(function () { return Promise.resolve(self); }); } }); }, function () { //TODO: handle the error. Right now it is assumed the problem is the wallet not existing. return self.createAccount(); }).then(function (account) { if (!account) { return Promise.reject('THE ACCOUNT COULD NOT BE RETRIEVED OR CREATED'); } return self.walletManager.getSingle(); }).then(function (walletId) { console.log('PRIVATE KEY: ' + JSON.stringify(self.getPrivateKey())); console.log('OTHER KEYS: ' + JSON.stringify(self.getKeys())); self.walletId = walletId; var devicePrivKey = self.getPrivateKey().derive("m/1'").privateKey.bn.toBuffer({ size: 32 }); self.device.setDevicePrivateKey(devicePrivKey); self.myDeviceAddress = self.device.getMyDeviceAddress(); self.dbManager.query("SELECT 1 FROM extended_pubkeys WHERE device_address=?", [self.myDeviceAddress]).then(function (rows) { if (rows.length > 1) { return Promise.reject("MORE THAN ONE extended_pubkey?!?"); } else if (rows.length === 0) { setTimeout(function () { return Promise.reject('THE PASSPHRASE IS INCORRECT'); }, 1000); } else { return Promise.resolve(); } }); }).then(function () { require('byteballcore/wallet.js'); // we don't need any of its functions but it listens for hub messages var keys = self.getKeys(); var mnemonic_phrase = keys.mnemonic_phrase; var deviceTempPrivKey = Buffer(keys.temp_priv_key, 'base64'); var devicePrevTempPrivKey = Buffer(keys.prev_temp_priv_key, 'base64'); var saveTempKeys = function saveTempKeys(new_temp_key, new_prev_temp_key, onDone) { var processedKeys = { mnemonic_phrase: mnemonic_phrase, temp_priv_key: deviceTempPrivKey.toString('base64'), prev_temp_priv_key: devicePrevTempPrivKey.toString('base64') }; self.keyManager.write(processedKeys).then(function () { return onDone; }); }; self.device.setTempKeys(deviceTempPrivKey, devicePrevTempPrivKey, saveTempKeys); self.device.setDeviceName(self.conf.deviceName); self.device.setDeviceHub(self.conf.hub); self.myDevicePubkey = self.device.getMyDevicePubKey(); console.log('====== my device address: ' + self.myDeviceAddress); console.log('====== my device pubkey: ' + self.myDevicePubkey); return self.confManager.get('PERMANENT_PAIRING_SECRET'); }).then(function (permanent_pairing_secret) { if (permanent_pairing_secret) { self.pairingCode = self.myDevicePubkey + '@' + self.conf.hub + '#' + permanent_pairing_secret; console.log('====== my pairing code: ' + self.pairingCode); } if (self.conf.bLight) { var light_wallet = require('byteballcore/light_wallet.js'); light_wallet.setLightVendorHost(self.conf.hub); } if (self.conf.MAX_UNSPENT_OUTPUTS && self.conf.CONSOLIDATION_INTERVAL) { var consolidate = function consolidate() { if (!self.network.isCatchingUp()) { self.consolidation.consolidate(self.walletId, self.getSigner()); } }; setInterval(consolidate, self.conf.CONSOLIDATION_INTERVAL); setTimeout(consolidate, 300 * 1000); } return new Promise(function (resolve) { //LISTENS FOR UPDATES FOR 2 SECONDS BEFORE DOING ANYTHING setTimeout(function () { console.log('ACCOUNT READY'); resolve(); }, 2000); }); }); }; AccountManager.prototype.sendPayment = function (toAddress, amount) { var self = this; var signer = self.getSigner(); if (!signer) { return Promise.reject('THE SIGNER IS NOT DEFINED. USE THIS METHOD ONLY AFTER LOADING THE ACCOUNT WITH readAccount'); } return self.walletManager.readSingleAddress().then(function (fromAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { reject('COULD NOT DELIVER ' + amount + ' BYTES TO ' + toAddress + ' BECAUSE: ' + err); }; var callbacks = self.composer.getSavingCallbacks({ ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }); // i.e.: "LS3PUAGJ2CEYBKWPODVV72D3IWWBXNXO" var payee_address = toAddress; var arrOutputs = [{ address: fromAddress, amount: 0 }, // the change { address: payee_address, amount: amount // the receiver }]; self.composer.composePaymentJoint([fromAddress], arrOutputs, signer, callbacks); }); }); }; AccountManager.prototype.sendText = function (text) { var self = this; var signer = self.getSigner(); if (!signer) { return Promise.reject('THE SIGNER IS NOT DEFINED. USE THIS METHOD ONLY AFTER LOADING THE ACCOUNT WITH readAccount'); } return self.walletManager.readSingleAddress().then(function (fromAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { if (typeof err === 'string') { reject(new Error('COULD NOT DELIVER ' + amount + ' BYTES TO ' + toAddress + ' BECAUSE: ' + err)); } else { console.log('COULD NOT DELIVER ' + amount + ' BYTES TO ' + toAddress); reject(err); } }; var callbacks = self.composer.getSavingCallbacks({ ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }); self.composer.composeTextJoint([fromAddress], [fromAddress], text, signer, callbacks); }); }); }; AccountManager.prototype.sendPaymentSequentially = function (toAddress, amount) { console.log('ENQUEUEING A NEW PAYMENT TO ' + toAddress + ' OF ' + amount + ' BYTES'); return this.paymentQueue.enqueue(toAddress, amount); }; AccountManager.prototype.checkBytesForAddress = function (address) { var self = this; return self.dbManager.query('SELECT asset, address, is_stable, SUM(amount) AS balance\n FROM outputs CROSS JOIN units USING(unit)\n WHERE is_spent=0 AND sequence=\'good\' AND address = ?\n GROUP BY asset, address, is_stable\n UNION ALL\n SELECT NULL AS asset, address, 1 AS is_stable, SUM(amount) AS balance FROM witnessing_outputs\n WHERE is_spent=0 AND address = ? GROUP BY address\n UNION ALL\n SELECT NULL AS asset, address, 1 AS is_stable, SUM(amount) AS balance FROM headers_commission_outputs\n WHERE is_spent=0 AND address = ? GROUP BY address', [address, address, address]).then(function (rows) { var assocBalances = {}; assocBalances["base"] = { stable: 0, pending: 0, total: 0 }; for (var i = 0; i < rows.length; i++) { var row = rows[i]; var asset = row.asset || "base"; if (!assocBalances[asset]) { assocBalances[asset] = { stable: 0, pending: 0, total: 0 }; console.log('CREATED THE BALANCES ARRAY OF ADDRESS ' + address + ' FOR ASSET ' + asset); } console.log('UPDATING BALANCE OF ' + address + ' FOR ASSET ' + asset + ': ' + (row.is_stable ? 'stable' : 'pending') + ' ' + row.balance); assocBalances[asset][row.is_stable ? 'stable' : 'pending'] += row.balance; assocBalances[asset]['total'] += row.balance; } return Promise.resolve(assocBalances['base'].total); }); }; AccountManager.prototype.checkThereAreStableBytes = function (fromAddress) { var self = this; console.log('CHECKING HOW MANY BYTES ARE STABLE ON THE MAIN FUNDING ADDRESS (' + fromAddress + ') BEFORE MAKING A PAYMENT'); return self.dbManager.query('SELECT asset, address, is_stable, SUM(amount) AS balance\n FROM outputs CROSS JOIN units USING(unit)\n WHERE is_spent=0 AND sequence=\'good\' AND address = ?\n GROUP BY asset, address, is_stable\n UNION ALL\n SELECT NULL AS asset, address, 1 AS is_stable, SUM(amount) AS balance FROM witnessing_outputs\n WHERE is_spent=0 AND address = ? GROUP BY address\n UNION ALL\n SELECT NULL AS asset, address, 1 AS is_stable, SUM(amount) AS balance FROM headers_commission_outputs\n WHERE is_spent=0 AND address = ? GROUP BY address', [fromAddress, fromAddress, fromAddress]).then(function (rows) { var totalStableAmount = 0; for (var i = 0; i < rows.length; i++) { var row = rows[i]; if (row.asset || !row.is_stable) { continue; } totalStableAmount += row.balance; } if (totalStableAmount >= self.conf.MIN_STABLE_BYTES_ON_MAIN_BEFORE_FUNDING) { console.log('ENOUGH STABLE BYTES ON THE MAIN FUNDING NODE ADDRESS (' + fromAddress + ') FOR A PAYMENT'); return Promise.resolve(true); } else { return new Promise(function (resolve) { console.log('NOT ENOUGH STABLE BYTES ON ' + fromAddress + '. ' + ('WAITING ' + self.conf.MAIN_ADDRESS_FUNDS_INSPECTION_PERIOD + ' ms BEFORE CHECKING AGAIN ...')); setTimeout(resolve, self.conf.MAIN_ADDRESS_FUNDS_INSPECTION_PERIOD); }).then(function () { return self.checkThereAreStableBytes(fromAddress); }); } }); }; /** * Takes all bytes from a shared address and sends them to the main address. It works only if the definition of the shared address * allows full controls to the funding main address (shared address defined since October, 10th). * * The following definition allows BCS55XCV5RIJWXJWVGFLDRMWKKXRUVCS to control the shared address. * ["or",[["address","BCS55XCV5RIJWXJWVGFLDRMWKKXRUVCS"],["and",[["address","BCS55XCV5RIJWXJWVGFLDRMWKKXRUVCS"],["address","4SQ5PP7Z7LYIDLOCXRPXXTR5M3K6L66I"]]]]] * @param toBeEmptiedAddress A shared address to be emptied. * @returns {*} A promise containing the generated payment joint or the rejection reason. */ AccountManager.prototype.emptySharedAddress = function (toBeEmptiedAddress) { var self = this; var signer = self.getSigner(); if (!signer) { return Promise.reject('THE SIGNER IS NOT DEFINED. USE THIS METHOD ONLY AFTER LOADING THE ACCOUNT WITH readAccount'); } return self.walletManager.readSingleAddress().then(function (toAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { reject('COULD NOT DELIVER BYTES TO ' + toAddress + ' BECAUSE: ' + err); }; var callbacks = self.composer.getSavingCallbacks({ ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }); var arrOutputs = [{ address: toAddress, amount: 0 // the receiver }]; self.composer.composeJoint({ send_all: true, paying_addresses: [toBeEmptiedAddress], shared_addresses: [toBeEmptiedAddress], outputs: arrOutputs, signer: signer, callbacks: callbacks }); }); }); }; AccountManager.prototype.transferFees = function (destination) { var self = this; var signer = self.getSigner(); if (!signer) { return Promise.reject('THE SIGNER IS NOT DEFINED. USE THIS METHOD ONLY AFTER LOADING THE ACCOUNT WITH readAccount'); } return self.walletManager.readSingleAddress().then(function (fromAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { reject('COULD NOT DELIVER DAGCOINS TO ' + destination + ' BECAUSE: ' + err); }; var callbacks = self.composer.getSavingCallbacks({ ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }); var divisibleAsset = require('byteballcore/divisible_asset.js'); divisibleAsset.composeAndSaveDivisibleAssetPaymentJoint({ asset: self.conf.dagcoinAsset, paying_addresses: [fromAddress], fee_paying_addresses: [fromAddress], change_address: fromAddress, to_address: destination, send_all: true, signer: signer, callbacks: callbacks }); }); }); }; AccountManager.prototype.payDagcoins = function (destination, amount) { var self = this; var signer = self.getSigner(); if (!signer) { return Promise.reject('THE SIGNER IS NOT DEFINED. USE THIS METHOD ONLY AFTER LOADING THE ACCOUNT WITH readAccount'); } return self.walletManager.readSingleAddress().then(function (fromAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { reject('COULD NOT DELIVER DAGCOINS TO ' + destination + ' BECAUSE: ' + err); }; var callbacks = self.composer.getSavingCallbacks({ ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }); var divisibleAsset = require('byteballcore/divisible_asset.js'); divisibleAsset.composeAndSaveDivisibleAssetPaymentJoint({ asset: self.conf.dagcoinAsset, paying_addresses: [fromAddress], fee_paying_addresses: [fromAddress], change_address: fromAddress, to_address: destination, amount: amount, signer: signer, callbacks: callbacks }); }); }); }; AccountManager.prototype.sendMultiPayment = function (outputs) { if (outputs == null || outputs.length === 0) { return Promise.reject('NO OUTPUTS SPECIFIED'); } var self = this; var signer = self.getSigner(); return self.walletManager.readSingleAddress().then(function (fromAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { reject('COULD NOT DELIVER ' + JSON.stringify(outputs) + ' BECAUSE: ' + err); }; var callbacks = self.composer.getSavingCallbacks({ ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }); // i.e.: "LS3PUAGJ2CEYBKWPODVV72D3IWWBXNXO" // const payee_address = toAddress; // Example of output array /*const arrOutputs = [ {address: fromAddress, amount: 0}, // the change {address: payee_address, amount: amount} // the receiver ];*/ //Change address outputs.push({ address: fromAddress, amount: 0 }); self.composer.composePaymentJoint([fromAddress], outputs, signer, callbacks); }); }); }; AccountManager.prototype.sendMultiAssetPayment = function (outputs) { if (outputs == null || outputs.length === 0) { return Promise.reject('NO OUTPUTS SPECIFIED'); } var self = this; var signer = self.getSigner(); return self.walletManager.readSingleAddress().then(function (fromAddress) { return new Promise(function (resolve, reject) { var onError = function onError(err) { reject('COULD NOT DELIVER ASSET ' + JSON.stringify(outputs) + ' BECAUSE: ' + err); }; var callbacks = { ifNotEnoughFunds: onError, ifError: onError, ifOk: function ifOk(objJoint) { self.network.broadcastJoint(objJoint); resolve(objJoint); } }; var divisibleAsset = require('byteballcore/divisible_asset.js'); divisibleAsset.composeAndSaveDivisibleAssetPaymentJoint({ asset: self.conf.dagcoinAsset, paying_addresses: [fromAddress], fee_paying_addresses: [fromAddress], change_address: fromAddress, asset_outputs: outputs, signer: signer, callbacks: callbacks }); }); }); }; module.exports = AccountManager; module.exports.getInstance = function () { if (!instance) { instance = new AccountManager(); } return instance; };