Upgrade Keys module
Keys module - Private keys of node and sinks (shares) are managed by keys module - sink_chest is moved here - Seperate sinkIDs for each service Sink Private key security improvements - Shares are stored as index in SQL - Indexes are mapped with sinkIDs in file system - File is shuffled on interval - Every file data is stored in encrypted format Other changes - changes required for the above - RefundTransact is now dedicated to Convert service only (as every service has diff sinkID) - moved periodicProcess to background.js
This commit is contained in:
parent
178fcf4ce2
commit
6c0237b0b5
@ -285,7 +285,7 @@ CREATE TABLE DirectConvert(
|
|||||||
PRIMARY KEY(id)
|
PRIMARY KEY(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE RefundTransact(
|
CREATE TABLE RefundConvert(
|
||||||
id INT NOT NULL AUTO_INCREMENT,
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
floID CHAR(34) NOT NULL,
|
floID CHAR(34) NOT NULL,
|
||||||
asset_type TINYINT NOT NULL,
|
asset_type TINYINT NOT NULL,
|
||||||
@ -317,12 +317,27 @@ CREATE table _backupCache(
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE sinkShares(
|
CREATE TABLE sinkShares(
|
||||||
floID CHAR(34) NOT NULL,
|
num INT NOT NULL AUTO_INCREMENT,
|
||||||
share TEXT,
|
share TEXT,
|
||||||
time_stored TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
time_stored TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(num)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE discardedSinks(
|
||||||
|
id INT AUTO_INCREMENT,
|
||||||
|
floID CHAR(34) NOT NULL,
|
||||||
|
discard_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
KEY(id),
|
||||||
PRIMARY KEY(floID)
|
PRIMARY KEY(floID)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER discardedSinks_I AFTER INSERT ON discardedSinks
|
||||||
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('discardedSinks', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||||
|
CREATE TRIGGER discardedSinks_U AFTER UPDATE ON discardedSinks
|
||||||
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('discardedSinks', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||||
|
CREATE TRIGGER discardedSinks_D AFTER DELETE ON discardedSinks
|
||||||
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('discardedSinks', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||||
|
|
||||||
CREATE TRIGGER RequestLog_I AFTER INSERT ON RequestLog
|
CREATE TRIGGER RequestLog_I AFTER INSERT ON RequestLog
|
||||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||||
CREATE TRIGGER RequestLog_U AFTER UPDATE ON RequestLog
|
CREATE TRIGGER RequestLog_U AFTER UPDATE ON RequestLog
|
||||||
@ -400,12 +415,12 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('DirectConvert', NEW.id) O
|
|||||||
CREATE TRIGGER DirectConvert_D AFTER DELETE ON DirectConvert
|
CREATE TRIGGER DirectConvert_D AFTER DELETE ON DirectConvert
|
||||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('DirectConvert', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('DirectConvert', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||||
|
|
||||||
CREATE TRIGGER RefundTransact_I AFTER INSERT ON RefundTransact
|
CREATE TRIGGER RefundConvert_I AFTER INSERT ON RefundConvert
|
||||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundTransact', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundConvert', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||||
CREATE TRIGGER RefundTransact_U AFTER UPDATE ON RefundTransact
|
CREATE TRIGGER RefundConvert_U AFTER UPDATE ON RefundConvert
|
||||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundTransact', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundConvert', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||||
CREATE TRIGGER RefundTransact_D AFTER DELETE ON RefundTransact
|
CREATE TRIGGER RefundConvert_D AFTER DELETE ON RefundConvert
|
||||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundTransact', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundConvert', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||||
|
|
||||||
CREATE TRIGGER UserTag_I AFTER INSERT ON UserTag
|
CREATE TRIGGER UserTag_I AFTER INSERT ON UserTag
|
||||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||||
|
|||||||
@ -18,7 +18,7 @@ TRUNCATE CloseBondTransact;
|
|||||||
TRUNCATE CloseFundTransact;
|
TRUNCATE CloseFundTransact;
|
||||||
TRUNCATE ConvertFund;
|
TRUNCATE ConvertFund;
|
||||||
TRUNCATE DirectConvert;
|
TRUNCATE DirectConvert;
|
||||||
TRUNCATE RefundTransact;
|
TRUNCATE RefundConvert;
|
||||||
|
|
||||||
/* Blockchain data */
|
/* Blockchain data */
|
||||||
TRUNCATE LastTx;
|
TRUNCATE LastTx;
|
||||||
|
|||||||
@ -6,10 +6,17 @@ module.exports = {
|
|||||||
SIGN_EXPIRE_TIME: 5 * 60 * 1000, //5 mins
|
SIGN_EXPIRE_TIME: 5 * 60 * 1000, //5 mins
|
||||||
MAX_SESSION_TIMEOUT: 30 * 24 * 60 * 60 * 1000, //30 days
|
MAX_SESSION_TIMEOUT: 30 * 24 * 60 * 60 * 1000, //30 days
|
||||||
},
|
},
|
||||||
market: {
|
background: {
|
||||||
PERIOD_INTERVAL: 5 * 60 * 1000, //5 min,
|
PERIOD_INTERVAL: 5 * 60 * 1000, //5 min,
|
||||||
WAIT_TIME: 2 * 60 * 1000, //2 mins,
|
WAIT_TIME: 2 * 60 * 1000, //2 mins,
|
||||||
REQUEST_TIMEOUT: 24 * 60 * 60 * 1000, //1 day
|
REQUEST_TIMEOUT: 24 * 60 * 60 * 1000, //1 day
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
SHARES_PER_NODE: 8,
|
||||||
|
SHARE_THRESHOLD: 50 / 100, //50%
|
||||||
|
DISCARD_COOLDOWN: 24 * 60 * 60 * 1000, //1 day
|
||||||
|
},
|
||||||
|
market: {
|
||||||
LAUNCH_SELLER_TAG: "launch-seller",
|
LAUNCH_SELLER_TAG: "launch-seller",
|
||||||
MAXIMUM_LAUNCH_SELL_CHIPS: 100000,
|
MAXIMUM_LAUNCH_SELL_CHIPS: 100000,
|
||||||
TRADE_HASH_PREFIX: "z1",
|
TRADE_HASH_PREFIX: "z1",
|
||||||
@ -29,7 +36,6 @@ module.exports = {
|
|||||||
MIN_FUND: 0.3 // 30%
|
MIN_FUND: 0.3 // 30%
|
||||||
},
|
},
|
||||||
backup: {
|
backup: {
|
||||||
SHARE_THRESHOLD: 50 / 100, //50%
|
|
||||||
HASH_N_ROW: 100,
|
HASH_N_ROW: 100,
|
||||||
BACKUP_INTERVAL: 5 * 60 * 1000, //5 min
|
BACKUP_INTERVAL: 5 * 60 * 1000, //5 min
|
||||||
BACKUP_SYNC_TIMEOUT: 10 * 60 * 1000, //10 mins
|
BACKUP_SYNC_TIMEOUT: 10 * 60 * 1000, //10 mins
|
||||||
|
|||||||
@ -1,15 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const keys = require('./keys');
|
||||||
const blockchain = require('./blockchain');
|
const blockchain = require('./blockchain');
|
||||||
const conversion_rates = require('./services/conversion').getRate;
|
const conversion_rates = require('./services/conversion').getRate;
|
||||||
const bond_util = require('./services/bonds').util;
|
const bond_util = require('./services/bonds').util;
|
||||||
const fund_util = require('./services/bobs-fund').util;
|
const fund_util = require('./services/bobs-fund').util;
|
||||||
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
||||||
const DB = require("./database");
|
const DB = require("./database");
|
||||||
|
const coupling = require('./coupling');
|
||||||
|
const price = require('./price');
|
||||||
|
|
||||||
|
const {
|
||||||
|
PERIOD_INTERVAL,
|
||||||
|
REQUEST_TIMEOUT
|
||||||
|
} = require('./_constants')['background'];
|
||||||
const {
|
const {
|
||||||
LAUNCH_SELLER_TAG,
|
LAUNCH_SELLER_TAG,
|
||||||
MAXIMUM_LAUNCH_SELL_CHIPS,
|
MAXIMUM_LAUNCH_SELL_CHIPS
|
||||||
REQUEST_TIMEOUT,
|
|
||||||
} = require('./_constants')["market"];
|
} = require('./_constants')["market"];
|
||||||
|
|
||||||
var assetList; //container and allowed assets
|
var assetList; //container and allowed assets
|
||||||
@ -20,7 +27,7 @@ const verifyTx = {};
|
|||||||
function confirmDepositFLO() {
|
function confirmDepositFLO() {
|
||||||
DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, pCode.STATUS_PENDING]).then(results => {
|
DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, pCode.STATUS_PENDING]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
verifyTx.FLO(r.floID, r.txid).then(amount => {
|
verifyTx.FLO(r.floID, r.txid, keys.sink_groups.EXCHANGE).then(amount => {
|
||||||
addSellChipsIfLaunchSeller(r.floID, amount).then(txQueries => {
|
addSellChipsIfLaunchSeller(r.floID, amount).then(txQueries => {
|
||||||
txQueries.push(updateBalance.add(r.floID, "FLO", amount));
|
txQueries.push(updateBalance.add(r.floID, "FLO", amount));
|
||||||
txQueries.push(["UPDATE VaultTransactions SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id]]);
|
txQueries.push(["UPDATE VaultTransactions SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id]]);
|
||||||
@ -38,7 +45,7 @@ function confirmDepositFLO() {
|
|||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyTx.FLO = function (sender, txid) {
|
verifyTx.FLO = function (sender, txid, group) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
floBlockchainAPI.getTx(txid).then(tx => {
|
floBlockchainAPI.getTx(txid).then(tx => {
|
||||||
let vin_sender = tx.vin.filter(v => v.addr === sender)
|
let vin_sender = tx.vin.filter(v => v.addr === sender)
|
||||||
@ -50,7 +57,7 @@ verifyTx.FLO = function (sender, txid) {
|
|||||||
return reject([false, "Transaction not included in any block yet"]);
|
return reject([false, "Transaction not included in any block yet"]);
|
||||||
if (!tx.confirmations)
|
if (!tx.confirmations)
|
||||||
return reject([false, "Transaction not confirmed yet"]);
|
return reject([false, "Transaction not confirmed yet"]);
|
||||||
let amount = tx.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
|
let amount = tx.vout.reduce((a, v) => keys.sink_chest.includes(group, v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
|
||||||
if (amount == 0)
|
if (amount == 0)
|
||||||
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
||||||
else
|
else
|
||||||
@ -95,7 +102,7 @@ function addSellChipsIfLaunchSeller(floID, quantity) {
|
|||||||
function confirmDepositToken() {
|
function confirmDepositToken() {
|
||||||
DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_TOKEN, pCode.STATUS_PENDING]).then(results => {
|
DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_TOKEN, pCode.STATUS_PENDING]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
verifyTx.token(r.floID, r.txid).then(({ token, amount, flo_amount }) => {
|
verifyTx.token(r.floID, r.txid, keys.sink_groups.EXCHANGE).then(({ token, amount, flo_amount }) => {
|
||||||
DB.query("SELECT id FROM VaultTransactions where floID=? AND mode=? AND asset=? AND asset_type=? AND txid=?", [r.floID, pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, r.txid]).then(result => {
|
DB.query("SELECT id FROM VaultTransactions where floID=? AND mode=? AND asset=? AND asset_type=? AND txid=?", [r.floID, pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, r.txid]).then(result => {
|
||||||
let txQueries = [];
|
let txQueries = [];
|
||||||
//Add the FLO balance if necessary
|
//Add the FLO balance if necessary
|
||||||
@ -119,7 +126,7 @@ function confirmDepositToken() {
|
|||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyTx.token = function (sender, txid, currencyOnly = false) {
|
verifyTx.token = function (sender, txid, group, currencyOnly = false) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
floTokenAPI.getTx(txid).then(tx => {
|
floTokenAPI.getTx(txid).then(tx => {
|
||||||
if (tx.parsedFloData.type !== "transfer")
|
if (tx.parsedFloData.type !== "transfer")
|
||||||
@ -135,7 +142,7 @@ verifyTx.token = function (sender, txid, currencyOnly = false) {
|
|||||||
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender)
|
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender)
|
||||||
if (!vin_sender.length)
|
if (!vin_sender.length)
|
||||||
return reject([true, "Transaction not sent by the sender"]);
|
return reject([true, "Transaction not sent by the sender"]);
|
||||||
let flo_amount = tx.transactionDetails.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
|
let flo_amount = tx.transactionDetails.vout.reduce((a, v) => keys.sink_chest.includes(group, v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
|
||||||
if (flo_amount == 0)
|
if (flo_amount == 0)
|
||||||
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
||||||
else
|
else
|
||||||
@ -188,7 +195,7 @@ function confirmVaultWithdraw() {
|
|||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyTx.BTC = function (sender, txid) {
|
verifyTx.BTC = function (sender, txid, group) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
btcOperator.getTx(txid).then(tx => {
|
btcOperator.getTx(txid).then(tx => {
|
||||||
let vin_sender = tx.inputs.filter(v => floCrypto.isSameAddr(v.address, sender))
|
let vin_sender = tx.inputs.filter(v => floCrypto.isSameAddr(v.address, sender))
|
||||||
@ -201,7 +208,7 @@ verifyTx.BTC = function (sender, txid) {
|
|||||||
if (!tx.confirmations)
|
if (!tx.confirmations)
|
||||||
return reject([false, "Transaction not confirmed yet"]);
|
return reject([false, "Transaction not confirmed yet"]);
|
||||||
let amount = tx.outputs.reduce((a, v) =>
|
let amount = tx.outputs.reduce((a, v) =>
|
||||||
blockchain.chests.includes(floCrypto.toFloID(v.address, { bech: [coinjs.bech32.version] })) ? a + parseFloat(v.value) : a, 0);
|
keys.sink_chest.includes(group, floCrypto.toFloID(v.address, { bech: [coinjs.bech32.version] })) ? a + parseFloat(v.value) : a, 0);
|
||||||
if (amount == 0)
|
if (amount == 0)
|
||||||
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
||||||
else
|
else
|
||||||
@ -213,7 +220,7 @@ verifyTx.BTC = function (sender, txid) {
|
|||||||
function verifyConvert() {
|
function verifyConvert() {
|
||||||
//Set all timeout convert request to refund mode (thus, asset will be refund if tx gets confirmed later)
|
//Set all timeout convert request to refund mode (thus, asset will be refund if tx gets confirmed later)
|
||||||
let req_timeout = new Date(Date.now() - REQUEST_TIMEOUT),
|
let req_timeout = new Date(Date.now() - REQUEST_TIMEOUT),
|
||||||
to_refund_sql = "INSERT INTO RefundTransact (floID, in_txid, asset_type, asset, r_status)" +
|
to_refund_sql = "INSERT INTO RefundConvert (floID, in_txid, asset_type, asset, r_status)" +
|
||||||
" SELECT floID, in_txid, ? AS asset_type, ? AS asset, r_status" +
|
" SELECT floID, in_txid, ? AS asset_type, ? AS asset, r_status" +
|
||||||
" WHERE r_status=? AND locktime<? AND mode=?";
|
" WHERE r_status=? AND locktime<? AND mode=?";
|
||||||
let txQueries = [];
|
let txQueries = [];
|
||||||
@ -224,7 +231,7 @@ function verifyConvert() {
|
|||||||
DB.query("SELECT id, floID, mode, in_txid, amount, quantity FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_PENDING, "BTC"]).then(results => {
|
DB.query("SELECT id, floID, mode, in_txid, amount, quantity FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_PENDING, "BTC"]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
if (r.mode == pCode.CONVERT_MODE_GET) {
|
if (r.mode == pCode.CONVERT_MODE_GET) {
|
||||||
verifyTx.token(r.floID, r.in_txid, true).then(({ amount }) => {
|
verifyTx.token(r.floID, r.in_txid, keys.sink_groups.CONVERT, true).then(({ amount }) => {
|
||||||
if (r.amount !== amount)
|
if (r.amount !== amount)
|
||||||
throw ([true, "Transaction amount mismatched in blockchain"]);
|
throw ([true, "Transaction amount mismatched in blockchain"]);
|
||||||
conversion_rates.BTC_INR().then(rate => {
|
conversion_rates.BTC_INR().then(rate => {
|
||||||
@ -237,7 +244,7 @@ function verifyConvert() {
|
|||||||
.then(_ => null).catch(error => console.error(error));
|
.then(_ => null).catch(error => console.error(error));
|
||||||
});
|
});
|
||||||
} else if (r.mode == pCode.CONVERT_MODE_PUT) {
|
} else if (r.mode == pCode.CONVERT_MODE_PUT) {
|
||||||
verifyTx.BTC(r.floID, r.in_txid).then(quantity => {
|
verifyTx.BTC(r.floID, r.in_txid, keys.sink_groups.CONVERT).then(quantity => {
|
||||||
if (r.quantity !== quantity)
|
if (r.quantity !== quantity)
|
||||||
throw ([true, "Transaction quantity mismatched in blockchain"]);
|
throw ([true, "Transaction quantity mismatched in blockchain"]);
|
||||||
conversion_rates.BTC_INR().then(rate => {
|
conversion_rates.BTC_INR().then(rate => {
|
||||||
@ -293,7 +300,7 @@ function verifyConvertFundDeposit() {
|
|||||||
DB.query("SELECT id, mode, txid, coin FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_PROCESSING, "BTC"]).then(results => {
|
DB.query("SELECT id, mode, txid, coin FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_PROCESSING, "BTC"]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
if (r.mode == pCode.CONVERT_MODE_GET) { //deposit currency
|
if (r.mode == pCode.CONVERT_MODE_GET) { //deposit currency
|
||||||
verifyTx.token(floGlobals.adminID, r.txid, true).then(({ amount }) => {
|
verifyTx.token(floGlobals.adminID, r.txid, keys.sink_groups.CONVERT, true).then(({ amount }) => {
|
||||||
DB.query("UPDATE ConvertFund SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id])
|
DB.query("UPDATE ConvertFund SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id])
|
||||||
.then(result => console.info(`Deposit-fund ${amount} ${floGlobals.currency} successful`))
|
.then(result => console.info(`Deposit-fund ${amount} ${floGlobals.currency} successful`))
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
@ -304,7 +311,7 @@ function verifyConvertFundDeposit() {
|
|||||||
.then(_ => null).catch(error => console.error(error));
|
.then(_ => null).catch(error => console.error(error));
|
||||||
});
|
});
|
||||||
} else if (r.mode == pCode.CONVERT_MODE_PUT) {//deposit coin
|
} else if (r.mode == pCode.CONVERT_MODE_PUT) {//deposit coin
|
||||||
verifyTx.BTC(floGlobals.adminID, r.txid).then(quantity => {
|
verifyTx.BTC(floGlobals.adminID, r.txid, keys.sink_groups.CONVERT).then(quantity => {
|
||||||
DB.query("UPDATE ConvertFund SET r_status=?, quantity=? WHERE id=?", [pCode.STATUS_SUCCESS, quantity, r.id])
|
DB.query("UPDATE ConvertFund SET r_status=?, quantity=? WHERE id=?", [pCode.STATUS_SUCCESS, quantity, r.id])
|
||||||
.then(result => console.info(`Deposit-fund ${quantity} ${r.coin} successful`))
|
.then(result => console.info(`Deposit-fund ${quantity} ${r.coin} successful`))
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
@ -354,67 +361,47 @@ function confirmConvertFundWithdraw() {
|
|||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyRefund() {
|
function verifyConvertRefund() {
|
||||||
DB.query("SELECT id, floID, asset_type, asset, in_txid FROM RefundTransact WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => {
|
DB.query("SELECT id, floID, asset_type, asset, in_txid FROM RefundConvert WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
if (r.ASSET_TYPE_COIN) {
|
if (r.ASSET_TYPE_COIN) {
|
||||||
if (r.asset == "FLO")
|
if (r.asset == "BTC") //Convert is only for BTC right now
|
||||||
verifyTx.FLO(r.floID, r.in_txid)
|
verifyTx.BTC(r.floID, r.in_txid, keys.sink_groups.CONVERT)
|
||||||
.then(amount => blockchain.refundTransact.init(r.floID, r.asset, amount, r.id))
|
.then(amount => blockchain.refundConvert.init(r.floID, r.asset, amount, r.id))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error[0])
|
if (error[0])
|
||||||
DB.query("UPDATE RefundTransact SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||||
.then(_ => null).catch(error => console.error(error));
|
|
||||||
});
|
|
||||||
else if (r.asset == "BTC")
|
|
||||||
verifyTx.BTC(r.floID, r.in_txid)
|
|
||||||
.then(amount => blockchain.refundTransact.init(r.floID, r.asset, amount, r.id))
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
if (error[0])
|
|
||||||
DB.query("UPDATE RefundTransact SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
|
||||||
.then(_ => null).catch(error => console.error(error));
|
.then(_ => null).catch(error => console.error(error));
|
||||||
});
|
});
|
||||||
} else if (r.ASSET_TYPE_TOKEN)
|
} else if (r.ASSET_TYPE_TOKEN)
|
||||||
verifyTx.token(r.floID, r.in_txid).then(({ token, amount }) => {
|
verifyTx.token(r.floID, r.in_txid, keys.sink_groups.CONVERT, true).then(({ amount }) => {
|
||||||
if (token !== r.asset)
|
blockchain.refundConvert.init(r.floID, floGlobals.currency, amount, r.id);
|
||||||
throw ([true, "Transaction token mismatched"]);
|
|
||||||
else
|
|
||||||
blockchain.refundTransact.init(r.floID, token, amount, r.id);
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error[0])
|
if (error[0])
|
||||||
DB.query("UPDATE RefundTransact SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||||
.then(_ => null).catch(error => console.error(error));
|
.then(_ => null).catch(error => console.error(error));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
function retryRefund() {
|
function retryConvertRefund() {
|
||||||
DB.query("SELECT id, floID, asset, amount FROM RefundTransact WHERE r_status=?", [pCode.STATUS_PROCESSING]).then(results => {
|
DB.query("SELECT id, floID, asset, amount FROM RefundConvert WHERE r_status=?", [pCode.STATUS_PROCESSING]).then(results => {
|
||||||
results.forEach(r => blockchain.refundTransact.retry(r.floID, r.asset, r.amount, r.id))
|
results.forEach(r => blockchain.refundConvert.retry(r.floID, r.asset, r.amount, r.id))
|
||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmRefund() {
|
function confirmConvertRefund() {
|
||||||
DB.query("SELECT * FROM RefundTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
DB.query("SELECT * FROM RefundConvert WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
||||||
results.forEach(r => { //TODO
|
results.forEach(r => {
|
||||||
if (r.ASSET_TYPE_COIN) {
|
if (r.ASSET_TYPE_COIN) {
|
||||||
if (r.asset == "FLO")
|
if (r.asset == "BTC") //Convert is only for BTC right now
|
||||||
floBlockchainAPI.getTx(r.out_txid).then(tx => {
|
|
||||||
if (!tx.blockheight || !tx.confirmations) //Still not confirmed
|
|
||||||
return;
|
|
||||||
DB.query("UPDATE RefundTransact SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
|
||||||
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
|
||||||
.catch(error => console.error(error))
|
|
||||||
}).catch(error => console.error(error));
|
|
||||||
else if (r.asset == "BTC")
|
|
||||||
btcOperator.getTx(r.out_txid).then(tx => {
|
btcOperator.getTx(r.out_txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
DB.query("UPDATE RefundTransact SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||||
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
||||||
.catch(error => console.error(error))
|
.catch(error => console.error(error))
|
||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
@ -422,7 +409,7 @@ function confirmRefund() {
|
|||||||
floTokenAPI.getTx(r.out_txid).then(tx => {
|
floTokenAPI.getTx(r.out_txid).then(tx => {
|
||||||
if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed
|
if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
DB.query("UPDATE RefundTransact SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||||
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
@ -443,7 +430,7 @@ function confirmBondClosing() {
|
|||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
||||||
floBlockchainAPI.writeData(global.myFloID, closeBondString, global.myPrivKey, bond_util.config.adminID).then(txid => {
|
floBlockchainAPI.writeData(keys.node_id, closeBondString, keys.node_priv, bond_util.config.adminID).then(txid => {
|
||||||
DB.query("UPDATE CloseBondTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id])
|
DB.query("UPDATE CloseBondTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id])
|
||||||
.then(result => console.info("Bond closed:", r.bond_id))
|
.then(result => console.info("Bond closed:", r.bond_id))
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
@ -466,7 +453,7 @@ function confirmFundClosing() {
|
|||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
||||||
floBlockchainAPI.writeData(global.myFloID, closeFundString, global.myPrivKey, fund_util.config.adminID).then(txid => {
|
floBlockchainAPI.writeData(keys.node_id, closeFundString, keys.node_priv, fund_util.config.adminID).then(txid => {
|
||||||
DB.query("UPDATE CloseFundTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id])
|
DB.query("UPDATE CloseFundTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id])
|
||||||
.then(result => console.info("Fund investment closed:", r.fund_id, r.floID))
|
.then(result => console.info("Fund investment closed:", r.fund_id, r.floID))
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
@ -476,34 +463,75 @@ function confirmFundClosing() {
|
|||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Periodic Process
|
||||||
|
|
||||||
function processAll() {
|
function processAll() {
|
||||||
//deposit-withdraw asset balance
|
//deposit-withdraw asset balance
|
||||||
confirmDepositFLO();
|
if (keys.sink_chest.list(keys.sink_groups.EXCHANGE).length) {
|
||||||
confirmDepositToken();
|
confirmDepositFLO();
|
||||||
retryVaultWithdrawal();
|
confirmDepositToken();
|
||||||
confirmVaultWithdraw();
|
retryVaultWithdrawal();
|
||||||
|
confirmVaultWithdraw();
|
||||||
|
}
|
||||||
//convert service
|
//convert service
|
||||||
verifyConvert();
|
if (keys.sink_chest.list(keys.sink_groups.CONVERT).length) {
|
||||||
retryConvert();
|
verifyConvert();
|
||||||
confirmConvert();
|
retryConvert();
|
||||||
verifyConvertFundDeposit();
|
confirmConvert();
|
||||||
retryConvertFundWithdraw();
|
verifyConvertFundDeposit();
|
||||||
confirmConvertFundWithdraw();
|
retryConvertFundWithdraw();
|
||||||
|
confirmConvertFundWithdraw();
|
||||||
|
verifyConvertRefund();
|
||||||
|
retryConvertRefund();
|
||||||
|
confirmConvertRefund();
|
||||||
|
}
|
||||||
//blockchain-bond service
|
//blockchain-bond service
|
||||||
retryBondClosing();
|
if (keys.sink_chest.list(keys.sink_groups.BLOCKCHAIN_BONDS).length) {
|
||||||
confirmBondClosing();
|
retryBondClosing();
|
||||||
|
confirmBondClosing();
|
||||||
|
}
|
||||||
//bob's fund service
|
//bob's fund service
|
||||||
retryFundClosing();
|
if (keys.sink_chest.list(keys.sink_groups.EXCHANGE).length) {
|
||||||
confirmFundClosing();
|
retryFundClosing();
|
||||||
//refund transactions
|
confirmFundClosing();
|
||||||
verifyRefund();
|
}
|
||||||
retryRefund();
|
}
|
||||||
confirmRefund();
|
|
||||||
|
var lastSyncBlockHeight = 0;
|
||||||
|
|
||||||
|
function periodicProcess() {
|
||||||
|
floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => {
|
||||||
|
if (lastSyncBlockHeight < result.blocks[0].height) {
|
||||||
|
lastSyncBlockHeight = result.blocks[0].height;
|
||||||
|
processAll();
|
||||||
|
console.log("Last Block :", lastSyncBlockHeight);
|
||||||
|
}
|
||||||
|
}).catch(error => console.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function periodicProcess_start() {
|
||||||
|
periodicProcess_stop();
|
||||||
|
periodicProcess();
|
||||||
|
assetList.forEach(asset => coupling.initiate(asset, true));
|
||||||
|
price.storeHistory.start();
|
||||||
|
periodicProcess.instance = setInterval(periodicProcess, PERIOD_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function periodicProcess_stop() {
|
||||||
|
if (periodicProcess.instance !== undefined) {
|
||||||
|
clearInterval(periodicProcess.instance);
|
||||||
|
delete periodicProcess.instance;
|
||||||
|
}
|
||||||
|
coupling.stopAll();
|
||||||
|
price.storeHistory.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
blockchain,
|
blockchain,
|
||||||
process: processAll,
|
periodicProcess: {
|
||||||
|
start: periodicProcess_start,
|
||||||
|
stop: periodicProcess_stop
|
||||||
|
},
|
||||||
set assetList(assets) {
|
set assetList(assets) {
|
||||||
assetList = assets;
|
assetList = assets;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,93 +1,74 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const keys = require('../keys');
|
||||||
const K_Bucket = require('../../docs/scripts/floExchangeAPI').K_Bucket;
|
const K_Bucket = require('../../docs/scripts/floExchangeAPI').K_Bucket;
|
||||||
const slave = require('./slave');
|
const slave = require('./slave');
|
||||||
const sync = require('./sync');
|
const sync = require('./sync');
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const DB = require("../database");
|
|
||||||
|
|
||||||
const {
|
const { BACKUP_INTERVAL } = require("../_constants")["backup"];
|
||||||
SHARE_THRESHOLD
|
const { DISCARD_COOLDOWN } = require("../_constants")["keys"];
|
||||||
} = require("../_constants")["backup"];
|
|
||||||
|
|
||||||
var app, wss, tokenList; //Container for app and wss
|
var _app, _wss, tokenList; //Container for app and wss
|
||||||
var nodeList, nodeURL, nodeKBucket; //Container for (backup) node list
|
var nodeList, nodeURL, nodeKBucket; //Container for (backup) node list
|
||||||
const connectedSlaves = {},
|
const connectedSlaves = {},
|
||||||
shares_collected = {},
|
shares_collected = {},
|
||||||
shares_pending = {};
|
shares_pending = {},
|
||||||
|
discarded_sinks = [];
|
||||||
|
|
||||||
var _mode = null;
|
var _mode = null;
|
||||||
const SLAVE_MODE = 0,
|
const SLAVE_MODE = 0,
|
||||||
MASTER_MODE = 1;
|
MASTER_MODE = 1;
|
||||||
|
|
||||||
const sinkList = {};
|
|
||||||
|
|
||||||
const chests = {
|
|
||||||
get list() {
|
|
||||||
return Object.keys(sinkList);
|
|
||||||
},
|
|
||||||
get activeList() {
|
|
||||||
let result = [];
|
|
||||||
for (let id in sinkList)
|
|
||||||
if (sinkList[id])
|
|
||||||
result.push(id);
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
includes(id) {
|
|
||||||
return (id in sinkList);
|
|
||||||
},
|
|
||||||
isActive(id) {
|
|
||||||
return (id in sinkList && sinkList[id] !== null);
|
|
||||||
},
|
|
||||||
get pick() {
|
|
||||||
let sinks = Object.keys(sinkList),
|
|
||||||
i = floCrypto.randInt(0, sinks.length);
|
|
||||||
return sinks[i];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Shares
|
//Shares
|
||||||
function generateShares(sinkKey) {
|
function generateShares(sinkKey) {
|
||||||
let nextNodes = nodeKBucket.nextNode(global.myFloID, null),
|
let nextNodes = nodeKBucket.nextNode(keys.node_id, null),
|
||||||
aliveNodes = Object.keys(connectedSlaves);
|
aliveNodes = Object.keys(connectedSlaves);
|
||||||
nextNodes.unshift(global.myFloID);
|
nextNodes.unshift(keys.node_id);
|
||||||
aliveNodes.unshift(global.myFloID);
|
aliveNodes.unshift(keys.node_id);
|
||||||
let N = nextNodes.length + 1,
|
let shares, mappedShares = {};
|
||||||
th = Math.ceil(aliveNodes.length * SHARE_THRESHOLD) + 1,
|
shares = keys.generateShares(sinkKey, nextNodes.length, aliveNodes.length);
|
||||||
shares, refShare, mappedShares = {};
|
|
||||||
shares = floCrypto.createShamirsSecretShares(sinkKey, N, th);
|
|
||||||
refShare = shares.pop();
|
|
||||||
for (let i in nextNodes)
|
for (let i in nextNodes)
|
||||||
mappedShares[nextNodes[i]] = [refShare, shares[i]].join("|");
|
mappedShares[nextNodes[i]] = shares[i];
|
||||||
return mappedShares;
|
return mappedShares;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendShare(ws, sinkID, keyShare) {
|
function sendShares(ws, sinkID) {
|
||||||
if (shares_pending[sinkID][ws.floID] === keyShare) //this should always be true unless there is an error
|
if (!(sinkID in shares_pending) || !(ws.floID in shares_pending[sinkID].shares))
|
||||||
delete shares_pending[sinkID][ws.floID]; //delete the share after sending it to respective slave
|
return;
|
||||||
ws.send(JSON.stringify({
|
let { ref, group } = shares_pending[sinkID],
|
||||||
|
shares = shares_pending[sinkID].shares[ws.floID];
|
||||||
|
delete shares_pending[sinkID].shares[ws.floID]; //delete the share after sending it to respective slave
|
||||||
|
shares.forEach(s => ws.send(JSON.stringify({
|
||||||
command: "SINK_SHARE",
|
command: "SINK_SHARE",
|
||||||
sinkID,
|
sinkID, ref, group,
|
||||||
keyShare: floCrypto.encryptData(keyShare, ws.pubKey)
|
share: floCrypto.encryptData(s, ws.pubKey)
|
||||||
}));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendSharesToNodes(sinkID, shares) {
|
function sendSharesToNodes(sinkID, group, shares) {
|
||||||
shares_pending[sinkID] = shares;
|
if (discarded_sinks.includes(sinkID)) { //sinkID is discarded, abort the new shares
|
||||||
|
let i = discarded_sinks.findIndex(sinkID);
|
||||||
|
discarded_sinks.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ref = Date.now();
|
||||||
|
shares_pending[sinkID] = { shares, group, ref };
|
||||||
|
if (keys.node_id in shares) {
|
||||||
|
shares.forEach(s => keys.addShare(group, sinkID, ref, s))
|
||||||
|
delete shares_pending[sinkID].shares[keys.node_id];
|
||||||
|
}
|
||||||
for (let node in shares)
|
for (let node in shares)
|
||||||
if (node in connectedSlaves)
|
if (node in connectedSlaves)
|
||||||
sendShare(connectedSlaves[node], sinkID, shares[node]);
|
sendShares(connectedSlaves[node], sinkID);
|
||||||
if (global.myFloID in shares) {
|
keys.sink_chest.set_id(group, sinkID, ref);
|
||||||
slave.storeShare(sinkID, shares[global.myFloID], false);
|
|
||||||
delete shares_pending[sinkID][global.myFloID];
|
|
||||||
}
|
|
||||||
sinkList[sinkID] = Date.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestShare(ws, sinkID) {
|
function requestShare(ws, group, sinkID) {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
command: "SEND_SHARE",
|
command: "SEND_SHARE",
|
||||||
sinkID: sinkID,
|
group, sinkID,
|
||||||
pubKey: global.myPubKey
|
pubKey: keys.node_pub
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,20 +107,16 @@ function transferMoneyToNewSink(oldSinkID, oldSinkKey, newSink) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function collectAndCall(sinkID, callback, timeout = null) {
|
function collectAndCall(group, sinkID, callback, timeout = null) {
|
||||||
if (!(callback instanceof Function))
|
if (!(callback instanceof Function))
|
||||||
throw Error("callback should be a function");
|
throw Error("callback should be a function");
|
||||||
if (!(sinkID in shares_collected)) { //if not already collecting shares for sinkID, then initiate collection
|
if (!(sinkID in shares_collected)) { //if not already collecting shares for sinkID, then initiate collection
|
||||||
shares_collected[sinkID] = {
|
shares_collected[sinkID] = { group, ref: 0, callbacks: [], shares: {} };
|
||||||
callbacks: [],
|
|
||||||
shares: {}
|
|
||||||
};
|
|
||||||
for (let floID in connectedSlaves)
|
for (let floID in connectedSlaves)
|
||||||
requestShare(connectedSlaves[floID], sinkID);
|
requestShare(connectedSlaves[floID], group, sinkID);
|
||||||
DB.query("SELECT share FROM sinkShares WHERE floID=?", [sinkID]).then(result => {
|
keys.getShares(group, sinkID)
|
||||||
if (result.length)
|
.then(({ ref, shares }) => shares.forEach(s => collectShares(sinkID, ref, s)))
|
||||||
collectShares(myFloID, sinkID, Crypto.AES.decrypt(result[0].share, global.myPrivKey))
|
.catch(error => console.error(error))
|
||||||
}).catch(error => console.error(error))
|
|
||||||
}
|
}
|
||||||
shares_collected[sinkID].callbacks.push(callback);
|
shares_collected[sinkID].callbacks.push(callback);
|
||||||
if (timeout)
|
if (timeout)
|
||||||
@ -153,14 +130,22 @@ function collectAndCall(sinkID, callback, timeout = null) {
|
|||||||
|
|
||||||
collectAndCall.isAlive = (sinkID, callbackRef) => (sinkID in shares_collected && shares_collected[sinkID].callbacks.indexOf(callbackRef) != -1);
|
collectAndCall.isAlive = (sinkID, callbackRef) => (sinkID in shares_collected && shares_collected[sinkID].callbacks.indexOf(callbackRef) != -1);
|
||||||
|
|
||||||
function collectShares(floID, sinkID, share) {
|
function collectShares(sinkID, ref, share) {
|
||||||
if (_mode !== MASTER_MODE)
|
if (_mode !== MASTER_MODE)
|
||||||
return console.warn("Not serving as master");
|
return console.warn("Not serving as master");
|
||||||
if (!(sinkID in shares_collected))
|
if (!(sinkID in shares_collected))
|
||||||
return console.error("Something is wrong! Slaves are sending sinkID thats not been collected");
|
return console.error("Something is wrong! Slaves are sending sinkID thats not been collected");
|
||||||
shares_collected[sinkID].shares[floID] = share.split("|");
|
if (shares_collected[sinkID].ref > ref)
|
||||||
|
return console.debug("Received expired share");
|
||||||
|
else if (shares_collected[sinkID].ref < ref) {
|
||||||
|
shares_collected[sinkID].ref = ref;
|
||||||
|
shares_collected[sinkID].shares = [];
|
||||||
|
}
|
||||||
|
if (shares_collected[sinkID].shares.includes(share))
|
||||||
|
return console.debug("Received duplicate share");
|
||||||
|
shares_collected[sinkID].shares.push(share);
|
||||||
try {
|
try {
|
||||||
let sinkKey = floCrypto.retrieveShamirSecret([].concat(...Object.values(shares_collected[sinkID].shares)));
|
let sinkKey = floCrypto.retrieveShamirSecret(shares_collected[sinkID].shares);
|
||||||
if (floCrypto.verifyPrivKey(sinkKey, sinkID)) {
|
if (floCrypto.verifyPrivKey(sinkKey, sinkID)) {
|
||||||
console.debug("Shares collected successfully for", sinkID);
|
console.debug("Shares collected successfully for", sinkID);
|
||||||
shares_collected[sinkID].callbacks.forEach(fn => fn instanceof Function ? fn(sinkKey) : null);
|
shares_collected[sinkID].callbacks.forEach(fn => fn instanceof Function ? fn(sinkKey) : null);
|
||||||
@ -182,11 +167,11 @@ function connectWS(floID) {
|
|||||||
|
|
||||||
function connectToMaster(i = 0, init = false) {
|
function connectToMaster(i = 0, init = false) {
|
||||||
if (i >= nodeList.length) {
|
if (i >= nodeList.length) {
|
||||||
console.error("No master is found, and myFloID is not in list. This should not happen!");
|
console.error("No master is found and Node not in list. This should not happen!");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
let floID = nodeList[i];
|
let floID = nodeList[i];
|
||||||
if (floID === myFloID)
|
if (floID === keys.node_id)
|
||||||
serveAsMaster(init);
|
serveAsMaster(init);
|
||||||
else
|
else
|
||||||
connectWS(floID).then(ws => {
|
connectWS(floID).then(ws => {
|
||||||
@ -199,32 +184,16 @@ function connectToMaster(i = 0, init = false) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Node becomes master
|
|
||||||
function serveAsMaster(init) {
|
|
||||||
console.info('Starting master process');
|
|
||||||
slave.stop();
|
|
||||||
_mode = MASTER_MODE;
|
|
||||||
informLiveNodes(init);
|
|
||||||
app.resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
function serveAsSlave(ws, init) {
|
|
||||||
console.info('Starting slave process');
|
|
||||||
app.pause();
|
|
||||||
slave.start(ws, init);
|
|
||||||
_mode = SLAVE_MODE;
|
|
||||||
}
|
|
||||||
|
|
||||||
function informLiveNodes(init) {
|
function informLiveNodes(init) {
|
||||||
let message = {
|
let message = {
|
||||||
floID: global.myFloID,
|
floID: keys.node_id,
|
||||||
type: "UPDATE_MASTER",
|
type: "UPDATE_MASTER",
|
||||||
pubKey: global.myPubKey,
|
pubKey: keys.node_pub,
|
||||||
req_time: Date.now()
|
req_time: Date.now()
|
||||||
};
|
};
|
||||||
message.sign = floCrypto.signData(message.type + "|" + message.req_time, global.myPrivKey);
|
message.sign = floCrypto.signData(message.type + "|" + message.req_time, keys.node_priv);
|
||||||
message = JSON.stringify(message);
|
message = JSON.stringify(message);
|
||||||
let nodes = nodeList.filter(n => n !== global.myFloID);
|
let nodes = nodeList.filter(n => n !== keys.node_id);
|
||||||
Promise.allSettled(nodes.map(n => connectWS(n))).then(result => {
|
Promise.allSettled(nodes.map(n => connectWS(n))).then(result => {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
for (let i in result)
|
for (let i in result)
|
||||||
@ -237,20 +206,29 @@ function informLiveNodes(init) {
|
|||||||
console.warn(`Node(${nodes[i]}) is offline`);
|
console.warn(`Node(${nodes[i]}) is offline`);
|
||||||
if (init && flag)
|
if (init && flag)
|
||||||
syncRequest();
|
syncRequest();
|
||||||
DB.query("SELECT floID, share FROM sinkShares ORDER BY time_stored DESC").then(result => {
|
keys.getStoredList().then(result => {
|
||||||
if (result.length)
|
if (Object.keys(result).length) {
|
||||||
result.forEach(r => reconstructShares(r.floID));
|
keys.getDiscardedList().then(discarded_list => {
|
||||||
else if (!flag) {
|
for (let group in result)
|
||||||
|
result.group.forEach(id => {
|
||||||
|
if (!(id in discarded_list))
|
||||||
|
reconstructShares(group, id);
|
||||||
|
});
|
||||||
|
}).catch(error => console.error(error))
|
||||||
|
} else if (!flag) {
|
||||||
console.log("Starting the exchange...");
|
console.log("Starting the exchange...");
|
||||||
let newSink = floCrypto.generateNewID();
|
//generate a sinkID for each group in starting list
|
||||||
console.debug("Generated sink:", newSink.floID, newSink.privKey);
|
keys.sink_groups.generate_list.forEach(group => {
|
||||||
sendSharesToNodes(newSink.floID, generateShares(newSink.privKey));
|
let newSink = floCrypto.generateNewID();
|
||||||
|
console.debug("Generated sink:", group, newSink.floID);
|
||||||
|
sendSharesToNodes(newSink.floID, group, generateShares(newSink.privKey));
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncRequest(cur = global.myFloID) {
|
function syncRequest(cur = keys.node_id) {
|
||||||
//Sync data from next available node
|
//Sync data from next available node
|
||||||
let nextNode = nodeKBucket.nextNode(cur);
|
let nextNode = nodeKBucket.nextNode(cur);
|
||||||
if (!nextNode)
|
if (!nextNode)
|
||||||
@ -261,16 +239,16 @@ function syncRequest(cur = global.myFloID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateMaster(floID) {
|
function updateMaster(floID) {
|
||||||
let currentMaster = _mode === MASTER_MODE ? global.myFloID : slave.masterWS.floID;
|
let currentMaster = _mode === MASTER_MODE ? keys.node_id : slave.masterWS.floID;
|
||||||
if (nodeList.indexOf(floID) < nodeList.indexOf(currentMaster))
|
if (nodeList.indexOf(floID) < nodeList.indexOf(currentMaster))
|
||||||
connectToMaster();
|
connectToMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reconstructShares(sinkID) {
|
function reconstructShares(group, sinkID) {
|
||||||
if (_mode !== MASTER_MODE)
|
if (_mode !== MASTER_MODE)
|
||||||
return console.warn("Not serving as master");
|
return console.warn("Not serving as master");
|
||||||
sinkList[sinkID] = null;
|
keys.sink_chest.set_id(group, sinkID, null);
|
||||||
collectAndCall(sinkID, sinkKey => sendSharesToNodes(sinkID, generateShares(sinkKey)));
|
collectAndCall(group, sinkID, sinkKey => sendSharesToNodes(sinkID, group, generateShares(sinkKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function slaveConnect(floID, pubKey, ws, sinks) {
|
function slaveConnect(floID, pubKey, ws, sinks) {
|
||||||
@ -282,29 +260,82 @@ function slaveConnect(floID, pubKey, ws, sinks) {
|
|||||||
|
|
||||||
//Send shares if need to be delivered
|
//Send shares if need to be delivered
|
||||||
for (let sinkID in shares_pending)
|
for (let sinkID in shares_pending)
|
||||||
if (floID in shares_pending[sinkID])
|
if (floID in shares_pending[sinkID].shares)
|
||||||
sendShare(ws, sinkID, shares_pending[floID]);
|
sendShares(ws, sinkID);
|
||||||
//Request shares if any
|
//Request shares if any
|
||||||
for (let sinkID in shares_collected)
|
for (let sinkID in shares_collected)
|
||||||
requestShare(ws, sinkID); //if (!(floID in shares_collected[sinkID].shares))
|
requestShare(ws, shares_collected[sinkID].group, sinkID);
|
||||||
//check if sinks in slaves are present
|
//check if sinks in slaves are present
|
||||||
if (Array.isArray(sinks))
|
if (Array.isArray(sinks)) {
|
||||||
for (let sinkID of sinks)
|
for (let sinkID of sinks) {
|
||||||
if (!(sinkID in sinkList))
|
if (!keys.sink_chest.includes(shares_collected[sinkID].group, sinkID))
|
||||||
reconstructShares(sinkID);
|
keys.checkIfDiscarded(sinkID)
|
||||||
/*
|
.then(result => result === false ? reconstructShares(shares_collected[sinkID].group, sinkID) : null)
|
||||||
if (shares_pending === null || //The 1st backup is connected
|
.catch(error => console.error(error))
|
||||||
Object.keys(connectedSlaves).length < Math.pow(SHARE_THRESHOLD, 2) * Object.keys(shares_pending).length) //re-calib shares for better
|
}
|
||||||
sendSharesToNodes(sinkID, generateShares(sinkPrivKey))
|
}
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
function checkForDiscardedSinks() {
|
||||||
|
let cur_time = Date.now(),
|
||||||
|
all_sinks = keys.sink_chest.get_all();
|
||||||
|
for (let group in all_sinks)
|
||||||
|
all_sinks[group].forEach(id => keys.checkIfDiscarded(id).then(result => {
|
||||||
|
console.debug(group, id); //Check if group is correctly mapped, or if its changed by loop
|
||||||
|
if (result != false) {
|
||||||
|
if (cur_time - result > DISCARD_COOLDOWN)
|
||||||
|
keys.sink_chest.rm_id(group, id);
|
||||||
|
else
|
||||||
|
keys.sink_chest.set_id(group, id, null);
|
||||||
|
if (sinkID in shares_collected && !discarded_sinks.includes(sinkID))
|
||||||
|
discarded_sinks.push(sinkID);
|
||||||
|
}
|
||||||
|
}).catch(error => console.debug(error)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Master interval process
|
||||||
|
function intervalProcess() {
|
||||||
|
checkForDiscardedSinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
intervalProcess.start = () => {
|
||||||
|
intervalProcess.stop();
|
||||||
|
intervalProcess.instance = setInterval(intervalProcess, BACKUP_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
intervalProcess.stop = () => {
|
||||||
|
if (intervalProcess.instance !== undefined) {
|
||||||
|
clearInterval(intervalProcess.instance);
|
||||||
|
delete intervalProcess.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Node becomes master
|
||||||
|
function serveAsMaster(init) {
|
||||||
|
console.info('Starting master process');
|
||||||
|
slave.stop();
|
||||||
|
_mode = MASTER_MODE;
|
||||||
|
keys.sink_chest.reset();
|
||||||
|
intervalProcess.start();
|
||||||
|
informLiveNodes(init);
|
||||||
|
_app.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Node becomes slave
|
||||||
|
function serveAsSlave(ws, init) {
|
||||||
|
console.info('Starting slave process');
|
||||||
|
intervalProcess.stop();
|
||||||
|
_app.pause();
|
||||||
|
slave.start(ws, init);
|
||||||
|
_mode = SLAVE_MODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Transmistter
|
//Transmistter
|
||||||
function startBackupTransmitter(server) {
|
function startBackupTransmitter(server) {
|
||||||
wss = new WebSocket.Server({
|
_wss = new WebSocket.Server({
|
||||||
server
|
server
|
||||||
});
|
});
|
||||||
wss.on('connection', ws => {
|
_wss.on('connection', ws => {
|
||||||
ws.on('message', message => {
|
ws.on('message', message => {
|
||||||
//verify if from a backup node
|
//verify if from a backup node
|
||||||
try {
|
try {
|
||||||
@ -335,7 +366,7 @@ function startBackupTransmitter(server) {
|
|||||||
slaveConnect(request.floID, request.pubKey, ws, request.sinks);
|
slaveConnect(request.floID, request.pubKey, ws, request.sinks);
|
||||||
break;
|
break;
|
||||||
case "SINK_SHARE":
|
case "SINK_SHARE":
|
||||||
collectShares(request.floID, request.sinkID, floCrypto.decryptData(request.share, global.myPrivKey))
|
collectShares(request.sinkID, request.ref, floCrypto.decryptData(request.share, keys.node_priv))
|
||||||
default:
|
default:
|
||||||
invalid = "Invalid Request Type";
|
invalid = "Invalid Request Type";
|
||||||
}
|
}
|
||||||
@ -361,16 +392,15 @@ function startBackupTransmitter(server) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initProcess(a) {
|
function initProcess(app) {
|
||||||
app = a;
|
_app = app;
|
||||||
app.chests = chests;
|
startBackupTransmitter(_app.server);
|
||||||
app.collectAndCall = collectAndCall;
|
|
||||||
startBackupTransmitter(app.server);
|
|
||||||
connectToMaster(0, true);
|
connectToMaster(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: initProcess,
|
init: initProcess,
|
||||||
|
collectAndCall,
|
||||||
set nodeList(list) {
|
set nodeList(list) {
|
||||||
nodeURL = list;
|
nodeURL = list;
|
||||||
nodeKBucket = new K_Bucket(floGlobals.adminID, Object.keys(nodeURL));
|
nodeKBucket = new K_Bucket(floGlobals.adminID, Object.keys(nodeURL));
|
||||||
@ -383,6 +413,6 @@ module.exports = {
|
|||||||
tokenList = assets.filter(a => a.toUpperCase() !== "FLO");
|
tokenList = assets.filter(a => a.toUpperCase() !== "FLO");
|
||||||
},
|
},
|
||||||
get wss() {
|
get wss() {
|
||||||
return wss;
|
return _wss;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const keys = require("../keys");
|
||||||
const DB = require("../database");
|
const DB = require("../database");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -21,18 +22,23 @@ function startSlaveProcess(ws, init) {
|
|||||||
ws.on('message', processDataFromMaster);
|
ws.on('message', processDataFromMaster);
|
||||||
masterWS = ws;
|
masterWS = ws;
|
||||||
let sinks_stored = [];
|
let sinks_stored = [];
|
||||||
DB.query("SELECT floID FROM sinkShares").then(result => {
|
Promise.all([keys.getStoredList(), keys.getDiscardedList()]).then(result => {
|
||||||
sinks_stored = result.map(r => r.floID);
|
let stored_list = result[0],
|
||||||
|
discarded_list = result[1];
|
||||||
|
for (let g in stored_list)
|
||||||
|
for (let id in stored_list[g])
|
||||||
|
if (!(stored_list[g][id] in discarded_list))
|
||||||
|
sinks_stored.push(id);
|
||||||
}).catch(error => console.error(error)).finally(_ => {
|
}).catch(error => console.error(error)).finally(_ => {
|
||||||
//inform master
|
//inform master
|
||||||
let message = {
|
let message = {
|
||||||
floID: global.myFloID,
|
floID: keys.node_id,
|
||||||
pubKey: global.myPubKey,
|
pubKey: keys.node_pub,
|
||||||
sinks: sinks_stored,
|
sinks: sinks_stored,
|
||||||
req_time: Date.now(),
|
req_time: Date.now(),
|
||||||
type: "SLAVE_CONNECT"
|
type: "SLAVE_CONNECT"
|
||||||
}
|
}
|
||||||
message.sign = floCrypto.signData(message.type + "|" + message.req_time, global.myPrivKey);
|
message.sign = floCrypto.signData(message.type + "|" + message.req_time, keys.node_priv);
|
||||||
ws.send(JSON.stringify(message));
|
ws.send(JSON.stringify(message));
|
||||||
//start sync
|
//start sync
|
||||||
if (init)
|
if (init)
|
||||||
@ -58,14 +64,14 @@ function requestBackupSync(checksum_trigger, ws) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query('SELECT MAX(u_time) as last_time FROM _backup').then(result => {
|
DB.query('SELECT MAX(u_time) as last_time FROM _backup').then(result => {
|
||||||
let request = {
|
let request = {
|
||||||
floID: global.myFloID,
|
floID: keys.node_id,
|
||||||
pubKey: global.myPubKey,
|
pubKey: keys.node_pub,
|
||||||
type: "BACKUP_SYNC",
|
type: "BACKUP_SYNC",
|
||||||
last_time: result[0].last_time,
|
last_time: result[0].last_time,
|
||||||
checksum: checksum_trigger,
|
checksum: checksum_trigger,
|
||||||
req_time: Date.now()
|
req_time: Date.now()
|
||||||
};
|
};
|
||||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, global.myPrivKey);
|
request.sign = floCrypto.signData(request.type + "|" + request.req_time, keys.node_priv);
|
||||||
ws.send(JSON.stringify(request));
|
ws.send(JSON.stringify(request));
|
||||||
resolve(request);
|
resolve(request);
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
@ -134,10 +140,10 @@ function processDataFromMaster(message) {
|
|||||||
processBackupData(message);
|
processBackupData(message);
|
||||||
else switch (message.command) {
|
else switch (message.command) {
|
||||||
case "SINK_SHARE":
|
case "SINK_SHARE":
|
||||||
storeSinkShare(message.sinkID, message.keyShare);
|
storeSinkShare(message.group, message.sinkID, message.share, message.ref);
|
||||||
break;
|
break;
|
||||||
case "SEND_SHARE":
|
case "SEND_SHARE":
|
||||||
sendSinkShare(message.sinkID, message.pubKey);
|
sendSinkShare(message.group, message.sinkID, message.pubKey);
|
||||||
break;
|
break;
|
||||||
case "REQUEST_ERROR":
|
case "REQUEST_ERROR":
|
||||||
console.log(message.error);
|
console.log(message.error);
|
||||||
@ -150,30 +156,26 @@ function processDataFromMaster(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeSinkShare(sinkID, keyShare, decrypt = true) {
|
function storeSinkShare(group, sinkID, share, ref) {
|
||||||
if (decrypt)
|
share = floCrypto.decryptData(share, keys.node_priv);
|
||||||
keyShare = floCrypto.decryptData(keyShare, global.myPrivKey)
|
keys.addShare(group, sinkID, ref, share)
|
||||||
let encryptedShare = Crypto.AES.encrypt(keyShare, global.myPrivKey);
|
|
||||||
console.debug(Date.now(), '|sinkID:', sinkID, '|EnShare:', encryptedShare);
|
|
||||||
DB.query("INSERT INTO sinkShares (floID, share) VALUE (?) ON DUPLICATE KEY UPDATE share=?", [[sinkID, encryptedShare], encryptedShare])
|
|
||||||
.then(_ => null).catch(error => console.error(error));
|
.then(_ => null).catch(error => console.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendSinkShare(sinkID, pubKey) {
|
function sendSinkShare(group, sinkID, pubKey) {
|
||||||
DB.query("SELECT share FROM sinkShares WHERE floID=?", [sinkID]).then(result => {
|
keys.getShares(group, sinkID).then(({ ref, shares }) => {
|
||||||
if (!result.length)
|
shares.forEach(s => {
|
||||||
return console.warn(`key-shares for ${sinkID} not found in database!`);
|
let response = {
|
||||||
let share = Crypto.AES.decrypt(result[0].share, global.myPrivKey);
|
type: "SINK_SHARE",
|
||||||
let response = {
|
sinkID, ref,
|
||||||
type: "SINK_SHARE",
|
share: floCrypto.encryptData(s, pubKey),
|
||||||
sinkID: sinkID,
|
floID: keys.node_id,
|
||||||
share: floCrypto.encryptData(share, pubKey),
|
pubKey: keys.node_pub,
|
||||||
floID: global.myFloID,
|
req_time: Date.now()
|
||||||
pubKey: global.myPubKey,
|
}
|
||||||
req_time: Date.now()
|
response.sign = floCrypto.signData(response.type + "|" + response.req_time, keys.node_priv); //TODO: strengthen signature
|
||||||
}
|
masterWS.send(JSON.stringify(response));
|
||||||
response.sign = floCrypto.signData(response.type + "|" + response.req_time, global.myPrivKey); //TODO: strengthen signature
|
})
|
||||||
masterWS.send(JSON.stringify(response));
|
|
||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,13 +358,13 @@ function requestHash(tables) {
|
|||||||
//TODO: resync only necessary data (instead of entire table)
|
//TODO: resync only necessary data (instead of entire table)
|
||||||
let self = requestInstance;
|
let self = requestInstance;
|
||||||
let request = {
|
let request = {
|
||||||
floID: global.myFloID,
|
floID: keys.node_id,
|
||||||
pubKey: global.myPubKey,
|
pubKey: keys.node_pub,
|
||||||
type: "HASH_SYNC",
|
type: "HASH_SYNC",
|
||||||
tables: tables,
|
tables: tables,
|
||||||
req_time: Date.now()
|
req_time: Date.now()
|
||||||
};
|
};
|
||||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, global.myPrivKey);
|
request.sign = floCrypto.signData(request.type + "|" + request.req_time, keys.node_priv);
|
||||||
self.ws.send(JSON.stringify(request));
|
self.ws.send(JSON.stringify(request));
|
||||||
self.request = request;
|
self.request = request;
|
||||||
self.checksum = null;
|
self.checksum = null;
|
||||||
@ -411,13 +413,13 @@ function verifyHash(hashes) {
|
|||||||
|
|
||||||
function requestTableChunks(tables, ws) {
|
function requestTableChunks(tables, ws) {
|
||||||
let request = {
|
let request = {
|
||||||
floID: global.myFloID,
|
floID: keys.node_id,
|
||||||
pubKey: global.myPubKey,
|
pubKey: keys.node_pub,
|
||||||
type: "RE_SYNC",
|
type: "RE_SYNC",
|
||||||
tables: tables,
|
tables: tables,
|
||||||
req_time: Date.now()
|
req_time: Date.now()
|
||||||
};
|
};
|
||||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, global.myPrivKey);
|
request.sign = floCrypto.signData(request.type + "|" + request.req_time, keys.node_priv);
|
||||||
ws.send(JSON.stringify(request));
|
ws.send(JSON.stringify(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +429,5 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
start: startSlaveProcess,
|
start: startSlaveProcess,
|
||||||
stop: stopSlaveProcess,
|
stop: stopSlaveProcess,
|
||||||
storeShare: storeSinkShare,
|
|
||||||
syncRequest: ws => requestInstance.open(ws)
|
syncRequest: ws => requestInstance.open(ws)
|
||||||
}
|
}
|
||||||
@ -1,17 +1,25 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
||||||
|
const { collectAndCall } = require('./backup/head');
|
||||||
|
const keys = require('./keys');
|
||||||
const DB = require("./database");
|
const DB = require("./database");
|
||||||
|
|
||||||
var collectAndCall; //container for collectAndCall function from backup module
|
|
||||||
var chests; //container for blockchain ids (where assets are stored)
|
|
||||||
|
|
||||||
const TYPE_VAULT = "VAULT",
|
const TYPE_VAULT = "VAULT",
|
||||||
TYPE_CONVERT = "CONVERT",
|
TYPE_CONVERT = "CONVERT",
|
||||||
TYPE_CONVERT_POOL = "CONVERT_POOL",
|
TYPE_CONVERT_POOL = "CONVERT_POOL",
|
||||||
TYPE_REFUND = "REFUND",
|
TYPE_CONVERT_REFUND = "REFUND",
|
||||||
TYPE_BOND = "BOND",
|
TYPE_BLOCKCHAIN_BOND = "BOND",
|
||||||
TYPE_FUND = "BOB-FUND";
|
TYPE_BOBS_FUND = "BOB-FUND";
|
||||||
|
|
||||||
|
const SINK_GROUP = {
|
||||||
|
[TYPE_VAULT]: keys.sink_groups.EXCHANGE,
|
||||||
|
[TYPE_CONVERT]: keys.sink_groups.CONVERT,
|
||||||
|
[TYPE_CONVERT_POOL]: keys.sink_groups.CONVERT,
|
||||||
|
[TYPE_CONVERT_REFUND]: keys.sink_groups.CONVERT,
|
||||||
|
[TYPE_BLOCKCHAIN_BOND]: keys.sink_groups.BLOCKCHAIN_BONDS,
|
||||||
|
[TYPE_BOBS_FUND]: keys.sink_groups.BOBS_FUND
|
||||||
|
}
|
||||||
|
|
||||||
const balance_locked = {},
|
const balance_locked = {},
|
||||||
balance_cache = {},
|
balance_cache = {},
|
||||||
@ -19,9 +27,9 @@ const balance_locked = {},
|
|||||||
[TYPE_VAULT]: {},
|
[TYPE_VAULT]: {},
|
||||||
[TYPE_CONVERT]: {},
|
[TYPE_CONVERT]: {},
|
||||||
[TYPE_CONVERT_POOL]: {},
|
[TYPE_CONVERT_POOL]: {},
|
||||||
[TYPE_REFUND]: {},
|
[TYPE_CONVERT_REFUND]: {},
|
||||||
[TYPE_BOND]: {},
|
[TYPE_BLOCKCHAIN_BOND]: {},
|
||||||
[TYPE_FUND]: {}
|
[TYPE_BOBS_FUND]: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getBalance(sinkID, asset) {
|
function getBalance(sinkID, asset) {
|
||||||
@ -36,10 +44,10 @@ function getBalance(sinkID, asset) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSinkID(quantity, asset, sinkList = null) {
|
function getSinkID(type, quantity, asset, sinkList = null) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!sinkList)
|
if (!sinkList)
|
||||||
sinkList = chests.list.map(s => [s, s in balance_cache ? balance_cache[s][asset] || 0 : 0]) //TODO: improve sorting
|
sinkList = keys.sink_chest.list(SINK_GROUP[type]).map(s => [s, s in balance_cache ? balance_cache[s][asset] || 0 : 0]) //TODO: improve sorting
|
||||||
.sort((a, b) => b[1] - a[1]).map(x => x[0]);
|
.sort((a, b) => b[1] - a[1]).map(x => x[0]);
|
||||||
if (!sinkList.length)
|
if (!sinkList.length)
|
||||||
return reject(`Insufficient balance in chests for asset(${asset})`);
|
return reject(`Insufficient balance in chests for asset(${asset})`);
|
||||||
@ -51,12 +59,12 @@ function getSinkID(quantity, asset, sinkList = null) {
|
|||||||
if (balance > (quantity + (sinkID in balance_locked ? balance_locked[sinkID][asset] || 0 : 0)))
|
if (balance > (quantity + (sinkID in balance_locked ? balance_locked[sinkID][asset] || 0 : 0)))
|
||||||
return resolve(sinkID);
|
return resolve(sinkID);
|
||||||
else
|
else
|
||||||
getSinkID(quantity, asset, sinkList)
|
getSinkID(type, quantity, asset, sinkList)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
getSinkID(quantity, asset, sinkList)
|
getSinkID(type, quantity, asset, sinkList)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
@ -67,9 +75,9 @@ const WITHDRAWAL_MESSAGE = {
|
|||||||
[TYPE_VAULT]: "(withdrawal from market)",
|
[TYPE_VAULT]: "(withdrawal from market)",
|
||||||
[TYPE_CONVERT]: "(convert coin)",
|
[TYPE_CONVERT]: "(convert coin)",
|
||||||
[TYPE_CONVERT_POOL]: "(convert fund)",
|
[TYPE_CONVERT_POOL]: "(convert fund)",
|
||||||
[TYPE_REFUND]: "(refund from market)",
|
[TYPE_CONVERT_REFUND]: "(refund from market)",
|
||||||
[TYPE_BOND]: "(bond closing)",
|
[TYPE_BLOCKCHAIN_BOND]: "(bond closing)",
|
||||||
[TYPE_FUND]: "(fund investment closing)"
|
[TYPE_BOBS_FUND]: "(fund investment closing)"
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendTx(floID, asset, quantity, sinkID, sinkKey, message) {
|
function sendTx(floID, asset, quantity, sinkID, sinkKey, message) {
|
||||||
@ -89,14 +97,14 @@ const updateSyntax = {
|
|||||||
[TYPE_VAULT]: "UPDATE VaultTransactions SET r_status=?, txid=? WHERE id=?",
|
[TYPE_VAULT]: "UPDATE VaultTransactions SET r_status=?, txid=? WHERE id=?",
|
||||||
[TYPE_CONVERT]: "UPDATE DirectConvert SET r_status=?, out_txid=? WHERE id=?",
|
[TYPE_CONVERT]: "UPDATE DirectConvert SET r_status=?, out_txid=? WHERE id=?",
|
||||||
[TYPE_CONVERT_POOL]: "UPDATE ConvertFund SET r_status=?, txid=? WHERE id=?",
|
[TYPE_CONVERT_POOL]: "UPDATE ConvertFund SET r_status=?, txid=? WHERE id=?",
|
||||||
[TYPE_REFUND]: "UPDATE RefundTransact SET r_status=?, out_txid=? WHERE id=?",
|
[TYPE_CONVERT_REFUND]: "UPDATE RefundConvert SET r_status=?, out_txid=? WHERE id=?",
|
||||||
[TYPE_BOND]: "UPDATE CloseBondTransact SET r_status=?, txid=? WHERE id=?",
|
[TYPE_BLOCKCHAIN_BOND]: "UPDATE CloseBondTransact SET r_status=?, txid=? WHERE id=?",
|
||||||
[TYPE_FUND]: "UPDATE CloseFundTransact SET r_status=?, txid=? WHERE id=?"
|
[TYPE_BOBS_FUND]: "UPDATE CloseFundTransact SET r_status=?, txid=? WHERE id=?"
|
||||||
};
|
};
|
||||||
|
|
||||||
function sendAsset(floID, asset, quantity, type, id) {
|
function sendAsset(floID, asset, quantity, type, id) {
|
||||||
quantity = global.toStandardDecimal(quantity);
|
quantity = global.toStandardDecimal(quantity);
|
||||||
getSinkID(quantity, asset).then(sinkID => {
|
getSinkID(type, quantity, asset).then(sinkID => {
|
||||||
let callback = (sinkKey) => {
|
let callback = (sinkKey) => {
|
||||||
//Send asset to user via API
|
//Send asset to user via API
|
||||||
sendTx(floID, asset, quantity, sinkID, sinkKey, WITHDRAWAL_MESSAGE[type]).then(txid => {
|
sendTx(floID, asset, quantity, sinkID, sinkKey, WITHDRAWAL_MESSAGE[type]).then(txid => {
|
||||||
@ -110,7 +118,7 @@ function sendAsset(floID, asset, quantity, type, id) {
|
|||||||
balance_locked[sinkID][asset] -= quantity;
|
balance_locked[sinkID][asset] -= quantity;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
collectAndCall(sinkID, callback);
|
collectAndCall(sinkID, callback); //TODO: add timeout to prevent infinite wait
|
||||||
callbackCollection[type][id] = callback;
|
callbackCollection[type][id] = callback;
|
||||||
if (!(sinkID in balance_locked))
|
if (!(sinkID in balance_locked))
|
||||||
balance_locked[sinkID] = {};
|
balance_locked[sinkID] = {};
|
||||||
@ -165,39 +173,30 @@ function convertFundWithdraw_retry(asset, amount, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bondTransact_retry(floID, amount, btc_rate, usd_rate, id) {
|
function bondTransact_retry(floID, amount, btc_rate, usd_rate, id) {
|
||||||
if (id in callbackCollection[TYPE_BOND])
|
if (id in callbackCollection[TYPE_BLOCKCHAIN_BOND])
|
||||||
console.debug("A callback is already pending for this Bond closing");
|
console.debug("A callback is already pending for this Bond closing");
|
||||||
else sendAsset(floID, "BTC", amount / (btc_rate * usd_rate), TYPE_BOND, id);
|
else sendAsset(floID, "BTC", amount / (btc_rate * usd_rate), TYPE_BLOCKCHAIN_BOND, id);
|
||||||
}
|
}
|
||||||
function fundTransact_retry(floID, amount, btc_rate, usd_rate, id) {
|
function fundTransact_retry(floID, amount, btc_rate, usd_rate, id) {
|
||||||
if (id in callbackCollection[TYPE_FUND])
|
if (id in callbackCollection[TYPE_BOBS_FUND])
|
||||||
console.debug("A callback is already pending for this Fund investment closing");
|
console.debug("A callback is already pending for this Fund investment closing");
|
||||||
else sendAsset(floID, "BTC", amount / (btc_rate * usd_rate), TYPE_FUND, id);
|
else sendAsset(floID, "BTC", amount / (btc_rate * usd_rate), TYPE_BOBS_FUND, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function refundTransact_init(floID, asset, amount, id) {
|
function refundConvert_init(floID, asset, amount, id) {
|
||||||
amount = global.toStandardDecimal(amount);
|
amount = global.toStandardDecimal(amount);
|
||||||
DB.query("UPDATE RefundTransact SET amount=?, r_status=?, locktime=DEFAULT WHERE id=?", [amount, pCode.STATUS_PROCESSING, id])
|
DB.query("UPDATE RefundConvert SET amount=?, r_status=?, locktime=DEFAULT WHERE id=?", [amount, pCode.STATUS_PROCESSING, id])
|
||||||
.then(result => sendAsset(floID, asset, amount, TYPE_REFUND, id))
|
.then(result => sendAsset(floID, asset, amount, TYPE_CONVERT_REFUND, id))
|
||||||
.catch(error => console.error(error))
|
.catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
function refundTransact_retry(floID, asset, amount, id) {
|
function refundConvert_retry(floID, asset, amount, id) {
|
||||||
if (id in callbackCollection[TYPE_REFUND])
|
if (id in callbackCollection[TYPE_CONVERT_REFUND])
|
||||||
console.debug("A callback is already pending for this Refund");
|
console.debug("A callback is already pending for this Refund");
|
||||||
else sendAsset(floID, asset, amount, TYPE_REFUND, id);
|
else sendAsset(floID, asset, amount, TYPE_CONVERT_REFUND, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
set collectAndCall(fn) {
|
|
||||||
collectAndCall = fn;
|
|
||||||
},
|
|
||||||
get chests() {
|
|
||||||
return chests;
|
|
||||||
},
|
|
||||||
set chests(c) {
|
|
||||||
chests = c;
|
|
||||||
},
|
|
||||||
withdrawAsset: {
|
withdrawAsset: {
|
||||||
init: withdrawAsset_init,
|
init: withdrawAsset_init,
|
||||||
retry: withdrawAsset_retry
|
retry: withdrawAsset_retry
|
||||||
@ -219,8 +218,8 @@ module.exports = {
|
|||||||
fundTransact: {
|
fundTransact: {
|
||||||
retry: fundTransact_retry
|
retry: fundTransact_retry
|
||||||
},
|
},
|
||||||
refundTransact: {
|
refundConvert: {
|
||||||
init: refundTransact_init,
|
init: refundConvert_init,
|
||||||
retry: refundTransact_retry
|
retry: refundConvert_retry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
380
src/keys.js
Normal file
380
src/keys.js
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const DB = require("./database");
|
||||||
|
|
||||||
|
const { SHARES_PER_NODE, SHARE_THRESHOLD } = require("./_constants")["keys"];
|
||||||
|
|
||||||
|
const PRIV_EKEY_MIN = 32,
|
||||||
|
PRIV_EKEY_MAX = 48,
|
||||||
|
PRIME_FILE_TYPE = 'binary',
|
||||||
|
INDEX_FILE_TYPE = 'utf-8',
|
||||||
|
INT_MIN = -2147483648,
|
||||||
|
INT_MAX = 2147483647,
|
||||||
|
INDEX_FILE_NAME_LENGTH = 16,
|
||||||
|
INDEX_FILE_EXT = '.txt',
|
||||||
|
MIN_DUMMY_FILES = 16,
|
||||||
|
MAX_DUMMY_FILES = 24,
|
||||||
|
MIN_DUMMY_SIZE_MUL = 0.5,
|
||||||
|
MAX_DUMMY_SIZE_MUL = 1.5,
|
||||||
|
SIZE_FACTOR = 100;
|
||||||
|
|
||||||
|
var node_priv, e_key, node_id, node_pub; //containers for node-key wrapper
|
||||||
|
const _ = {
|
||||||
|
get node_priv() {
|
||||||
|
if (!node_priv || !e_key)
|
||||||
|
throw Error("keys not set");
|
||||||
|
return Crypto.AES.decrypt(node_priv, e_key);
|
||||||
|
},
|
||||||
|
set node_priv(key) {
|
||||||
|
node_pub = floCrypto.getPubKeyHex(key);
|
||||||
|
node_id = floCrypto.getFloID(node_pub);
|
||||||
|
if (!key || !node_pub || !node_id)
|
||||||
|
throw Error("Invalid Keys");
|
||||||
|
let n = floCrypto.randInt(PRIV_EKEY_MIN, PRIV_EKEY_MAX)
|
||||||
|
e_key = floCrypto.randString(n);
|
||||||
|
node_priv = Crypto.AES.encrypt(key, e_key);
|
||||||
|
},
|
||||||
|
args_dir: path.resolve(__dirname, '..', '..', 'args'),
|
||||||
|
index_dir: path.join(this.args_dir, 'indexes'),
|
||||||
|
prime_file: path.join(this.args_dir, 'prime_index.b'),
|
||||||
|
get index_file() {
|
||||||
|
try {
|
||||||
|
let data = fs.readFileSync(this.prime_file, PRIME_FILE_TYPE),
|
||||||
|
fname = Crypto.AES.decrypt(data, this.node_priv);
|
||||||
|
return path.join(this.index_dir, fname + INDEX_FILE_EXT);
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error);
|
||||||
|
throw Error("Prime-Index Missing/Corrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(_.prime_file, PRIME_FILE_TYPE, (err, res) => {
|
||||||
|
var data, cur_filename, new_filename, priv_key;
|
||||||
|
try {
|
||||||
|
priv_key = _.node_priv;
|
||||||
|
} catch (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
if (res.length) { //prime file not empty
|
||||||
|
try {
|
||||||
|
cur_filename = Crypto.AES.decrypt(res, priv_key);
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error);
|
||||||
|
return reject("Prime file corrupted");
|
||||||
|
} try { //read data from index file
|
||||||
|
let tmp = fs.readFileSync(path.join(_.index_dir, cur_filename + INDEX_FILE_EXT), INDEX_FILE_TYPE);
|
||||||
|
tmp = Crypto.AES.decrypt(tmp, priv_key);
|
||||||
|
JSON.parse(tmp); //check if data is JSON parse-able
|
||||||
|
data = tmp;
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error);
|
||||||
|
return reject("Index file corrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try { //delete all old dummy files
|
||||||
|
let files = fs.readdirSync(_.index_dir);
|
||||||
|
for (const file of files)
|
||||||
|
if (!cur_filename || file !== cur_filename + INDEX_FILE_EXT) //check if file is current file
|
||||||
|
fs.unlinkSync(path.join(_.index_dir, file));
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error);
|
||||||
|
return reject("Clear index directory failed");
|
||||||
|
} try { //create files (dummy and new index file)
|
||||||
|
let N = floCrypto.randInt(MIN_DUMMY_FILES, MAX_DUMMY_FILES),
|
||||||
|
k = floCrypto.randInt(0, N);
|
||||||
|
if (typeof data === 'undefined' || data.length == 0) //no existing data, initialize
|
||||||
|
data = JSON.stringify({});
|
||||||
|
let data_size = data.length;
|
||||||
|
for (let i = 0; i <= N; i++) {
|
||||||
|
let f_data, f_name = floCrypto.randString(INDEX_FILE_NAME_LENGTH);
|
||||||
|
if (i == k) {
|
||||||
|
new_filename = f_name;
|
||||||
|
f_data = data;
|
||||||
|
} else {
|
||||||
|
let d_size = data_size * (floCrypto.randInt(MIN_DUMMY_SIZE_MUL * SIZE_FACTOR, MAX_DUMMY_SIZE_MUL * SIZE_FACTOR) / SIZE_FACTOR);
|
||||||
|
f_data = floCrypto.randString(d_size, false);
|
||||||
|
}
|
||||||
|
f_data = Crypto.AES.encrypt(f_data, priv_key);
|
||||||
|
fs.writeFileSync(path.join(_.index_dir, f_name + INDEX_FILE_EXT), f_data, INDEX_FILE_TYPE);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error);
|
||||||
|
return reject("Index file creation failed");
|
||||||
|
} try { //update prime file
|
||||||
|
let en_filename = Crypto.AES.encrypt(new_filename, priv_key);
|
||||||
|
fs.writeFileSync(_.prime_file, en_filename, PRIME_FILE_TYPE);
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error);
|
||||||
|
return reject("Update prime file failed");
|
||||||
|
}
|
||||||
|
if (cur_filename)
|
||||||
|
fs.unlink(path.join(_.index_dir, file), err => err ? console.debug(err) : null);
|
||||||
|
resolve("Key management initiated");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffle() {
|
||||||
|
readIndexFile().then(data => {
|
||||||
|
let new_filename, cur_filename = Crypto.AES.decrypt(fs.readFileSync(_.prime_file, PRIME_FILE_TYPE), _.node_priv);
|
||||||
|
fs.readdir(_.index_dir, (err, files) => {
|
||||||
|
if (err)
|
||||||
|
return console.error(err);
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
let data_size = data.length;
|
||||||
|
for (let file of files) {
|
||||||
|
let f_data, f_name = floCrypto.randString(INDEX_FILE_NAME_LENGTH);
|
||||||
|
if (file === cur_filename) {
|
||||||
|
new_filename = f_name;
|
||||||
|
f_data = data;
|
||||||
|
} else {
|
||||||
|
let d_size = data_size * (floCrypto.randInt(MIN_DUMMY_SIZE_MUL * SIZE_FACTOR, MAX_DUMMY_SIZE_MUL * SIZE_FACTOR) / SIZE_FACTOR);
|
||||||
|
f_data = floCrypto.randString(d_size, false);
|
||||||
|
}
|
||||||
|
f_data = Crypto.AES.encrypt(f_data, _.node_priv);
|
||||||
|
//rename and rewrite the file
|
||||||
|
fs.renameSync(path.join(_.args_dir, file + INDEX_FILE_EXT), path.join(_.index_dir, f_name + INDEX_FILE_EXT));
|
||||||
|
fs.writeFileSync(path.join(_.index_dir, f_name + INDEX_FILE_EXT), f_data, INDEX_FILE_TYPE);
|
||||||
|
}
|
||||||
|
//update prime file
|
||||||
|
if (!new_filename)
|
||||||
|
throw Error("Index file has not been renamed");
|
||||||
|
let en_filename = Crypto.AES.encrypt(new_filename, _.node_priv);
|
||||||
|
fs.writeFileSync(_.prime_file, en_filename, PRIME_FILE_TYPE);
|
||||||
|
})
|
||||||
|
}).catch(error => console.error(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
function readIndexFile() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(_.index_file, INDEX_FILE_TYPE, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.debug(err);
|
||||||
|
return reject('Unable to read Index file');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
data = JSON.parse(Crypto.AES.decrypt(data, _.node_priv));
|
||||||
|
resolve(data);
|
||||||
|
} catch {
|
||||||
|
reject("Index file corrupted");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeIndexFile(data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let en_data = Crypto.AES.encrypt(JSON.stringify(data), _.node_priv);
|
||||||
|
fs.writeFile(_.index_file, en_data, INDEX_FILE_TYPE, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.debug(err);
|
||||||
|
return reject('Unable to write Index file');
|
||||||
|
} else resolve("Updated Index file");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getShares(group, id, ignoreDiscarded = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
checkIfDiscarded(id).then(result => {
|
||||||
|
if (ignoreDiscarded && result != false)
|
||||||
|
return reject("Trying to get share for discarded ID");
|
||||||
|
readIndexFile().then(data => {
|
||||||
|
if (!(group in data))
|
||||||
|
reject("Group not found in Index file");
|
||||||
|
else if (!(id in data[group]))
|
||||||
|
reject("ID not found in Index file");
|
||||||
|
else {
|
||||||
|
let ref = data[group][id].shift();
|
||||||
|
DB.query("SELECT share FROM sinkShares WHERE num IN (?)", [data[group][id]])
|
||||||
|
.then(result => resolve({ ref, shares: result.map(r => Crypto.AES.decrypt(r.share, _.node_priv)) }))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeShareAtRandom(share) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let rand = floCrypto.randInt(INT_MIN, INT_MAX);
|
||||||
|
DB.query("INSERT INTO sinkShares(num, share) VALUE (?)", [[rand, share]])
|
||||||
|
.then(result => resolve(result.insertId)).catch(error => {
|
||||||
|
if (error.code === "ER_DUP_ENTRY")
|
||||||
|
storeShareAtRandom(share) //try again (with diff rand_num)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
else
|
||||||
|
reject(error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addShare(group, id, ref, share) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
checkIfDiscarded(id).then(result => {
|
||||||
|
if (result != false)
|
||||||
|
return reject("Trying to store share for discarded ID");
|
||||||
|
readIndexFile().then(data => {
|
||||||
|
if (!(group in data))
|
||||||
|
data[group] = {};
|
||||||
|
if (!(id in data[group]))
|
||||||
|
data[group][id] = [ref];
|
||||||
|
else if (ref < data[group][id][0])
|
||||||
|
return reject("reference is lower than current");
|
||||||
|
else if (ref > data[group][id][0]) {
|
||||||
|
let old_shares = data[group][id];
|
||||||
|
data[group][id] = [ref];
|
||||||
|
old_shares.shift();
|
||||||
|
DB.query("DELETE FROM sinkShares WHERE num in (?", [old_shares])//delete old shares
|
||||||
|
.then(_ => null).catch(error => console.error(error));
|
||||||
|
}
|
||||||
|
let encrypted_share = Crypto.AES.encrypt(share, _.node_priv);
|
||||||
|
console.debug(ref, '|sinkID:', sinkID, '|EnShare:', encrypted_share);
|
||||||
|
storeShareAtRandom(encrypted_share).then(i => {
|
||||||
|
data[group][id].push(i);
|
||||||
|
writeIndexFile(data).then(_ => resolve(i)).catch(error => reject(error));
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateShares(sinkKey, total_n, min_n) {
|
||||||
|
let shares = floCrypto.createShamirsSecretShares(sinkKey, total_n * SHARES_PER_NODE, min_n * SHARES_PER_NODE * SHARE_THRESHOLD);
|
||||||
|
let node_shares = Array(total_n);
|
||||||
|
for (let i = 0; i < total_n; i++)
|
||||||
|
node_shares[i] = shares.splice(0, SHARES_PER_NODE);
|
||||||
|
return node_shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStoredList(group = null) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
readIndexFile().then(data => {
|
||||||
|
if (group !== null) {
|
||||||
|
if (group in data)
|
||||||
|
resolve(Object.keys(data.group));
|
||||||
|
else
|
||||||
|
reject("Group not found in Index file");
|
||||||
|
} else {
|
||||||
|
let ids = {};
|
||||||
|
for (let group in data)
|
||||||
|
ids[group] = Object.keys(data.group);
|
||||||
|
resolve(ids);
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiscardedList() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DB.query("SELECT floID, discard_time FROM discardedSinks")
|
||||||
|
.then(result => resolve(Object.fromEntries(result.map(r => [r.floID, r.discard_time]))))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfDiscarded(id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DB.query("SELECT discard_time FROM discardedSinks WHERE floID=?", [id])
|
||||||
|
.then(result => resolve(result.length ? result[0].discard_time : false))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sink groups and chest
|
||||||
|
const sink_groups = {
|
||||||
|
get EXCHANGE() { return "exchange" },
|
||||||
|
get CONVERT() { return "convert" },
|
||||||
|
get BLOCKCHAIN_BONDS() { return "blockchain_bonds" },
|
||||||
|
get BOBS_FUND() { return "bobs_fund" },
|
||||||
|
get generate_list() { //list to generate when starting exchange
|
||||||
|
return [this.EXCHANGE, this.CONVERT]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sink_ids = {}, sink_chest = {
|
||||||
|
reset() {
|
||||||
|
for (let i in sink_ids)
|
||||||
|
delete sink_ids[i];
|
||||||
|
},
|
||||||
|
set_id(group, id, value) {
|
||||||
|
if (!(group in sink_ids))
|
||||||
|
sink_ids[group] = {};
|
||||||
|
sink_ids[group][id] = value;
|
||||||
|
},
|
||||||
|
rm_id(group, id) {
|
||||||
|
return delete sink_ids[group][id];
|
||||||
|
},
|
||||||
|
get_id(group, id) {
|
||||||
|
return sink_ids[group][id];
|
||||||
|
},
|
||||||
|
list(group) {
|
||||||
|
return Object.keys(sink_ids[group] || {});
|
||||||
|
},
|
||||||
|
active_list(group) {
|
||||||
|
let ids = [];
|
||||||
|
if (group in sink_ids)
|
||||||
|
for (let id in sink_ids[group])
|
||||||
|
if (sink_ids[group][id])
|
||||||
|
ids.push(id);
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
includes(group, id) {
|
||||||
|
return group in sink_ids ? (id in sink_ids[group]) : null;
|
||||||
|
},
|
||||||
|
isActive(group, id) {
|
||||||
|
return group in sink_ids ? (id in sink_ids && sink_ids[id]) : null;
|
||||||
|
},
|
||||||
|
pick(group) {
|
||||||
|
let ids = this.list(group),
|
||||||
|
i = floCrypto.randInt(0, ids.length);
|
||||||
|
return ids[i];
|
||||||
|
},
|
||||||
|
active_pick(group) {
|
||||||
|
let ids = this.active_list(group),
|
||||||
|
i = floCrypto.randInt(0, ids.length);
|
||||||
|
return ids[i];
|
||||||
|
},
|
||||||
|
get_all() {
|
||||||
|
let ids = {};
|
||||||
|
for (let g in sink_ids)
|
||||||
|
ids[g] = Object.keys(sink_ids[g])
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: initialize,
|
||||||
|
getShares,
|
||||||
|
addShare,
|
||||||
|
generateShares,
|
||||||
|
getStoredList,
|
||||||
|
getDiscardedList,
|
||||||
|
checkIfDiscarded,
|
||||||
|
set node_priv(key) {
|
||||||
|
_.node_priv = key;
|
||||||
|
},
|
||||||
|
get node_priv() {
|
||||||
|
return _.node_priv;
|
||||||
|
},
|
||||||
|
get node_id() {
|
||||||
|
return node_id;
|
||||||
|
},
|
||||||
|
get node_id() {
|
||||||
|
return node_pub;
|
||||||
|
},
|
||||||
|
get sink_groups() {
|
||||||
|
return sink_groups;
|
||||||
|
},
|
||||||
|
get sink_chest() {
|
||||||
|
return sink_chest;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ global.btcOperator = require('../docs/scripts/btcOperator');
|
|||||||
floGlobals.application = application;
|
floGlobals.application = application;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const keys = require('./keys');
|
||||||
const DB = require("./database");
|
const DB = require("./database");
|
||||||
const App = require('./app');
|
const App = require('./app');
|
||||||
|
|
||||||
@ -178,17 +179,13 @@ module.exports = function startServer() {
|
|||||||
console.error('Password not entered!');
|
console.error('Password not entered!');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
global.myPrivKey = Crypto.AES.decrypt(_tmp, _pass);
|
keys.node_priv = Crypto.AES.decrypt(_tmp, _pass);
|
||||||
global.myPubKey = floCrypto.getPubKeyHex(global.myPrivKey);
|
|
||||||
global.myFloID = floCrypto.getFloID(global.myPubKey);
|
|
||||||
if (!global.myFloID || !global.myPubKey || !global.myPrivKey)
|
|
||||||
throw "Invalid Keys";
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Unable to load private key!');
|
console.error('Unable to load private key!');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Logged in as", global.myFloID);
|
console.log("Logged in as", keys.node_id);
|
||||||
|
|
||||||
DB.connect(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(result => {
|
DB.connect(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(result => {
|
||||||
app = new App(config['secret']);
|
app = new App(config['secret']);
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const coupling = require('./coupling');
|
const coupling = require('./coupling');
|
||||||
|
const price = require("./price");
|
||||||
const background = require('./background');
|
const background = require('./background');
|
||||||
const DB = require("./database");
|
const DB = require("./database");
|
||||||
|
const blockchain = require('./blockchain');
|
||||||
const blockchain = background.blockchain;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
PERIOD_INTERVAL,
|
|
||||||
WAIT_TIME,
|
|
||||||
TRADE_HASH_PREFIX,
|
TRADE_HASH_PREFIX,
|
||||||
TRANSFER_HASH_PREFIX
|
TRANSFER_HASH_PREFIX
|
||||||
} = require('./_constants')["market"];
|
} = require('./_constants')["market"];
|
||||||
@ -41,7 +39,7 @@ function getRateHistory(asset, duration) {
|
|||||||
if (!asset || !assetList.includes(asset))
|
if (!asset || !assetList.includes(asset))
|
||||||
reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid asset(${asset})`));
|
reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid asset(${asset})`));
|
||||||
else
|
else
|
||||||
coupling.price.getHistory(asset, duration)
|
price.getHistory(asset, duration)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
})
|
})
|
||||||
@ -469,58 +467,14 @@ function checkDistributor(floID, asset) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Periodic Process
|
|
||||||
|
|
||||||
function periodicProcess_start() {
|
|
||||||
periodicProcess_stop();
|
|
||||||
periodicProcess();
|
|
||||||
assetList.forEach(asset => coupling.initiate(asset, true));
|
|
||||||
coupling.price.storeHistory.start();
|
|
||||||
periodicProcess.instance = setInterval(periodicProcess, PERIOD_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function periodicProcess_stop() {
|
|
||||||
if (periodicProcess.instance !== undefined) {
|
|
||||||
clearInterval(periodicProcess.instance);
|
|
||||||
delete periodicProcess.instance;
|
|
||||||
}
|
|
||||||
coupling.stopAll();
|
|
||||||
coupling.price.storeHistory.stop();
|
|
||||||
};
|
|
||||||
|
|
||||||
var lastSyncBlockHeight = 0;
|
|
||||||
|
|
||||||
function periodicProcess() {
|
|
||||||
if (periodicProcess.timeout !== undefined) {
|
|
||||||
clearTimeout(periodicProcess.timeout);
|
|
||||||
delete periodicProcess.timeout;
|
|
||||||
}
|
|
||||||
if (!blockchain.chests.list.length)
|
|
||||||
return periodicProcess.timeout = setTimeout(periodicProcess, WAIT_TIME);
|
|
||||||
|
|
||||||
floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => {
|
|
||||||
if (lastSyncBlockHeight < result.blocks[0].height) {
|
|
||||||
lastSyncBlockHeight = result.blocks[0].height;
|
|
||||||
background.process();
|
|
||||||
console.log("Last Block :", lastSyncBlockHeight);
|
|
||||||
}
|
|
||||||
}).catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
get rates() {
|
get rates() {
|
||||||
return coupling.price.currentRates;
|
return price.currentRates;
|
||||||
},
|
},
|
||||||
get priceCountDown() {
|
get priceCountDown() {
|
||||||
return coupling.price.lastTimes;
|
return price.lastTimes;
|
||||||
},
|
|
||||||
get chests() {
|
|
||||||
return blockchain.chests;
|
|
||||||
},
|
|
||||||
set chests(c) {
|
|
||||||
blockchain.chests = c;
|
|
||||||
},
|
},
|
||||||
addBuyOrder,
|
addBuyOrder,
|
||||||
addSellOrder,
|
addSellOrder,
|
||||||
@ -539,18 +493,11 @@ module.exports = {
|
|||||||
removeTag,
|
removeTag,
|
||||||
addDistributor,
|
addDistributor,
|
||||||
removeDistributor,
|
removeDistributor,
|
||||||
periodicProcess: {
|
|
||||||
start: periodicProcess_start,
|
|
||||||
stop: periodicProcess_stop
|
|
||||||
},
|
|
||||||
set assetList(assets) {
|
set assetList(assets) {
|
||||||
assetList = assets;
|
assetList = assets;
|
||||||
background.assetList = assets;
|
background.assetList = assets;
|
||||||
},
|
},
|
||||||
get assetList() {
|
get assetList() {
|
||||||
return assetList
|
return assetList
|
||||||
},
|
}
|
||||||
set collectAndCall(fn) {
|
|
||||||
blockchain.collectAndCall = fn;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue
Block a user