diff --git a/args/schema.sql b/args/schema.sql index 9945813..74bb07d 100644 --- a/args/schema.sql +++ b/args/schema.sql @@ -1,201 +1,226 @@ -/* Main Tables */ +/* Blockchain Data */ -CREATE TABLE Users ( -floID CHAR(34) NOT NULL, -pubKey CHAR(66) NOT NULL, -created DATETIME DEFAULT CURRENT_TIMESTAMP, -PRIMARY KEY(floID) +CREATE TABLE LastTx( + floID CHAR(34) NOT NULL, + num INT, + PRIMARY KEY(floID) ); -CREATE TABLE Sessions ( -id INT NOT NULL AUTO_INCREMENT, -floID CHAR(34) NOT NULL, -proxyKey CHAR(66) NOT NULL, -session_time DATETIME DEFAULT CURRENT_TIMESTAMP, -KEY (id), -PRIMARY KEY(floID), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE Request_Log( -floID CHAR(34) NOT NULL, -request TEXT NOT NULL, -sign TEXT NOT NULL, -request_time DATETIME DEFAULT CURRENT_TIMESTAMP, -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE Cash ( -id INT NOT NULL AUTO_INCREMENT, -floID CHAR(34) NOT NULL UNIQUE, -rupeeBalance DECIMAL(12, 2) DEFAULT 0.00, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE Vault ( -id INT NOT NULL AUTO_INCREMENT, -floID CHAR(34) NOT NULL, -locktime DATETIME DEFAULT CURRENT_TIMESTAMP, -base DECIMAL(10, 2), -quantity FLOAT NOT NULL, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE SellOrder ( -id INT NOT NULL AUTO_INCREMENT, -floID CHAR(34) NOT NULL, -quantity FLOAT NOT NULL, -minPrice DECIMAL(10, 2), -time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE BuyOrder ( -id INT NOT NULL AUTO_INCREMENT, -floID CHAR(34) NOT NULL, -quantity FLOAT NOT NULL, -maxPrice DECIMAL(10, 2) NOT NULL, -time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE Transactions ( -seller CHAR(34) NOT NULL, -buyer CHAR(34) NOT NULL, -quantity FLOAT NOT NULL, -unitValue DECIMAL(10, 2) NOT NULL, -tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, -FOREIGN KEY (buyer) REFERENCES Users(floID), -FOREIGN KEY (seller) REFERENCES Users(floID) -); - -CREATE TABLE inputFLO ( -id INT NOT NULL AUTO_INCREMENT, -txid VARCHAR(128) NOT NULL, -floID CHAR(34) NOT NULL, -amount FLOAT, -status VARCHAR(50) NOT NULL, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE outputFLO ( -id INT NOT NULL AUTO_INCREMENT, -txid VARCHAR(128), -floID CHAR(34) NOT NULL, -amount FLOAT NOT NULL, -status VARCHAR(50) NOT NULL, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE inputRupee ( -id INT NOT NULL AUTO_INCREMENT, -txid VARCHAR(128) NOT NULL, -floID CHAR(34) NOT NULL, -amount FLOAT, -status VARCHAR(50) NOT NULL, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE outputRupee ( -id INT NOT NULL AUTO_INCREMENT, -txid VARCHAR(128), -floID CHAR(34) NOT NULL, -amount FLOAT NOT NULL, -status VARCHAR(50) NOT NULL, -PRIMARY KEY(id), -FOREIGN KEY (floID) REFERENCES Users(floID) -); - -CREATE TABLE lastTx( -floID CHAR(34) NOT NULL, -num INT, -PRIMARY KEY(floID) -); - -CREATE TABLE nodeList( -floID CHAR(34) NOT NULL, -uri TINYTEXT, -PRIMARY KEY(floID) -); - -CREATE TABLE trustedList( -floID CHAR(34) NOT NULL, -PRIMARY KEY(floID), -FOREIGN KEY (floID) REFERENCES Users(floID) +CREATE TABLE NodeList( + floID CHAR(34) NOT NULL, + uri TINYTEXT, + PRIMARY KEY(floID) ); CREATE TABLE TagList ( -tag VARCHAR(50) NOT NULL, -sellPriority INT, -buyPriority INT, -api TINYTEXT, -PRIMARY KEY(tag) + tag VARCHAR(50) NOT NULL, + sellPriority INT, + buyPriority INT, + api TINYTEXT, + PRIMARY KEY(tag) ); -CREATE TABLE Tags ( -id INT NOT NULL AUTO_INCREMENT, -floID CHAR(34) NOT NULL, -tag VARCHAR(50) NOT NULL, -PRIMARY KEY(floID, tag), -KEY (id), -FOREIGN KEY (floID) REFERENCES Users(floID), -FOREIGN KEY (tag) REFERENCES TagList(tag) +CREATE TABLE AssetList ( + asset VARCHAR(64) NOT NULL, + initialPrice FLOAT, + PRIMARY KEY(asset) ); -CREATE TABLE priceHistory ( -rate FLOAT NOT NULL, -rec_time DATETIME DEFAULT CURRENT_TIMESTAMP +CREATE TABLE TrustedList( + floID CHAR(34) NOT NULL, + PRIMARY KEY(floID) ); -CREATE TABLE auditTransaction( -rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, -unit_price FLOAT NOT NULL, -quantity FLOAT NOT NULL, -total_cost FLOAT NOT NULL, -sellerID CHAR(34) NOT NULL, -FLO_seller_old FLOAT NOT NULL, -FLO_seller_new FLOAT NOT NULL, -Rupee_seller_old FLOAT NOT NULL, -Rupee_seller_new FLOAT NOT NULL, -buyerID CHAR(34) NOT NULL, -FLO_buyer_old FLOAT NOT NULL, -FLO_buyer_new FLOAT NOT NULL, -Rupee_buyer_old FLOAT NOT NULL, -Rupee_buyer_new FLOAT NOT NULL, -FOREIGN KEY (sellerID) REFERENCES Users(floID), -FOREIGN KEY (buyerID) REFERENCES Users(floID) +/* User Data */ + +CREATE TABLE Users ( + floID CHAR(34) NOT NULL, + pubKey CHAR(66) NOT NULL, + created DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(floID) +); + +CREATE TABLE UserSession ( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + proxyKey CHAR(66) NOT NULL, + session_time DATETIME DEFAULT CURRENT_TIMESTAMP, + KEY (id), + PRIMARY KEY(floID), + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +CREATE TABLE Cash ( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL UNIQUE, + balance DECIMAL(12, 2) DEFAULT 0.00, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +CREATE TABLE Vault ( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + locktime DATETIME DEFAULT CURRENT_TIMESTAMP, + asset VARCHAR(64) NOT NULL, + base DECIMAL(10, 2), + quantity FLOAT NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID), + FOREIGN KEY (asset) REFERENCES AssetList(asset) +); + +CREATE TABLE UserTag ( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + tag VARCHAR(50) NOT NULL, + PRIMARY KEY(floID, tag), + KEY (id), + FOREIGN KEY (floID) REFERENCES Users(floID), + FOREIGN KEY (tag) REFERENCES TagList(tag) +); + +/* User Requests */ + +CREATE TABLE Request_Log( + floID CHAR(34) NOT NULL, + request TEXT NOT NULL, + sign TEXT NOT NULL, + request_time DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +CREATE TABLE SellOrder ( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + asset VARCHAR(64) NOT NULL, + quantity FLOAT NOT NULL, + minPrice DECIMAL(10, 2), + time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID), + FOREIGN KEY (asset) REFERENCES AssetList(asset) +); + +CREATE TABLE BuyOrder ( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + asset VARCHAR(64) NOT NULL, + quantity FLOAT NOT NULL, + maxPrice DECIMAL(10, 2) NOT NULL, + time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID), + FOREIGN KEY (asset) REFERENCES AssetList(asset) +); + +CREATE TABLE InputFLO ( + id INT NOT NULL AUTO_INCREMENT, + txid VARCHAR(128) NOT NULL, + floID CHAR(34) NOT NULL, + amount FLOAT, + status VARCHAR(50) NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +CREATE TABLE OutputFLO ( + id INT NOT NULL AUTO_INCREMENT, + txid VARCHAR(128), + floID CHAR(34) NOT NULL, + amount FLOAT NOT NULL, + status VARCHAR(50) NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +CREATE TABLE InputToken ( + id INT NOT NULL AUTO_INCREMENT, + txid VARCHAR(128) NOT NULL, + floID CHAR(34) NOT NULL, + token VARCHAR(64), + amount FLOAT, + status VARCHAR(50) NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +CREATE TABLE OutputToken ( + id INT NOT NULL AUTO_INCREMENT, + txid VARCHAR(128), + floID CHAR(34) NOT NULL, + token VARCHAR(64), + amount FLOAT NOT NULL, + status VARCHAR(50) NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY (floID) REFERENCES Users(floID) +); + +/* Transaction Data */ + +CREATE TABLE PriceHistory ( + asset VARCHAR(64) NOT NULL, + rate FLOAT NOT NULL, + rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (asset) REFERENCES AssetList(asset) +); + +CREATE TABLE TransactionHistory ( + seller CHAR(34) NOT NULL, + buyer CHAR(34) NOT NULL, + asset VARCHAR(64) NOT NULL, + quantity FLOAT NOT NULL, + unitValue DECIMAL(10, 2) NOT NULL, + tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (buyer) REFERENCES Users(floID), + FOREIGN KEY (seller) REFERENCES Users(floID), + FOREIGN KEY (asset) REFERENCES AssetList(asset) +); + +CREATE TABLE AuditTransaction( + rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, + unit_price FLOAT NOT NULL, + quantity FLOAT NOT NULL, + total_cost FLOAT NOT NULL, + asset VARCHAR(64) NOT NULL, + sellerID CHAR(34) NOT NULL, + seller_old_asset FLOAT NOT NULL, + seller_new_asset FLOAT NOT NULL, + seller_old_cash FLOAT NOT NULL, + seller_new_cash FLOAT NOT NULL, + buyerID CHAR(34) NOT NULL, + buyer_old_asset FLOAT NOT NULL, + buyer_new_asset FLOAT NOT NULL, + buyer_old_cash FLOAT NOT NULL, + buyer_new_cash FLOAT NOT NULL, + FOREIGN KEY (sellerID) REFERENCES Users(floID), + FOREIGN KEY (buyerID) REFERENCES Users(floID), + FOREIGN KEY (asset) REFERENCES AssetList(asset) +); + +/* Backup Feature (Tables & Triggers) */ + +CREATE TABLE _backup ( + t_name VARCHAR(20), + id INT, + mode BOOLEAN DEFAULT TRUE, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(t_name, id) ); CREATE TABLE sinkShares( -floID CHAR(34) NOT NULL, -share TEXT, -time_ DATETIME DEFAULT CURRENT_TIMESTAMP, -PRIMARY KEY(floID) + floID CHAR(34) NOT NULL, + share TEXT, + time_ DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(floID) ); -/* Backup feature (Table and Triggers) */ - -CREATE TABLE _backup ( -t_name VARCHAR(20), -id INT, -mode BOOLEAN DEFAULT TRUE, -timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, -PRIMARY KEY(t_name, id) -); - -CREATE TRIGGER Sessions_I AFTER INSERT ON Sessions -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Sessions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Sessions_U AFTER UPDATE ON Sessions -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Sessions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Sessions_D AFTER DELETE ON Sessions -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Sessions', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER UserSession_I AFTER INSERT ON UserSession +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER UserSession_U AFTER UPDATE ON UserSession +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER UserSession_D AFTER DELETE ON UserSession +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; CREATE TRIGGER Cash_I AFTER INSERT ON Cash FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; @@ -225,37 +250,37 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUP CREATE TRIGGER BuyOrder_D AFTER DELETE ON BuyOrder FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER inputFLO_I AFTER INSERT ON inputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER inputFLO_U AFTER UPDATE ON inputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER inputFLO_D AFTER DELETE ON inputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER InputFLO_I AFTER INSERT ON InputFLO +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER InputFLO_U AFTER UPDATE ON InputFLO +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER InputFLO_D AFTER DELETE ON InputFLO +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER outputFLO_I AFTER INSERT ON outputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER outputFLO_U AFTER UPDATE ON outputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER outputFLO_D AFTER DELETE ON outputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER OutputFLO_I AFTER INSERT ON OutputFLO +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER OutputFLO_U AFTER UPDATE ON OutputFLO +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER OutputFLO_D AFTER DELETE ON OutputFLO +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER inputRupee_I AFTER INSERT ON inputRupee -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER inputRupee_U AFTER UPDATE ON inputRupee -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER inputRupee_D AFTER DELETE ON inputRupee -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER InputToken_I AFTER INSERT ON InputToken +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER InputToken_U AFTER UPDATE ON InputToken +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER InputToken_D AFTER DELETE ON InputToken +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER outputRupee_I AFTER INSERT ON outputRupee -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER outputRupee_U AFTER UPDATE ON outputRupee -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER outputRupee_D AFTER DELETE ON outputRupee -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER OutputToken_I AFTER INSERT ON OutputToken +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER OutputToken_U AFTER UPDATE ON OutputToken +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER OutputToken_D AFTER DELETE ON OutputToken +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputToken', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER Tags_I AFTER INSERT ON Tags -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Tags', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Tags_U AFTER UPDATE ON Tags -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Tags', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Tags_D AFTER DELETE ON Tags -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Tags', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; \ No newline at end of file +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, timestamp=DEFAULT; +CREATE TRIGGER UserTag_U AFTER UPDATE ON UserTag +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER UserTag_D AFTER DELETE ON UserTag +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; \ No newline at end of file diff --git a/args/truncateAll.sql b/args/truncateAll.sql index da0d4a6..1d3dfed 100644 --- a/args/truncateAll.sql +++ b/args/truncateAll.sql @@ -1,23 +1,24 @@ /* Node data */ TRUNCATE _backup; -TRUNCATE auditTransaction; +TRUNCATE AuditTransaction; TRUNCATE BuyOrder; TRUNCATE Cash; -TRUNCATE inputFLO; -TRUNCATE inputRupee; -TRUNCATE outputFLO; -TRUNCATE outputRupee; -TRUNCATE priceHistory; +TRUNCATE InputFLO; +TRUNCATE InputToken; +TRUNCATE OutputFLO; +TRUNCATE OutputToken; +TRUNCATE PriceHistory; TRUNCATE Request_Log; TRUNCATE SellOrder; -TRUNCATE Sessions; -TRUNCATE Tags; -TRUNCATE Transactions; +TRUNCATE UserSession; +TRUNCATE UserTag; +TRUNCATE TransactionHistory; TRUNCATE Vault; TRUNCATE Users; /* Blockchain data */ -TRUNCATE lastTx; -TRUNCATE nodeList; -TRUNCATE trustedList; -TRUNCATE TagList; \ No newline at end of file +TRUNCATE LastTx; +TRUNCATE NodeList; +TRUNCATE TrustedList; +TRUNCATE TagList; +TRUNCATE AssetList; \ No newline at end of file diff --git a/public/floGlobals.js b/public/floGlobals.js index 1153a3c..5e04811 100644 --- a/public/floGlobals.js +++ b/public/floGlobals.js @@ -13,7 +13,7 @@ const floGlobals = { sendAmt: 0.001, fee: 0.0005, tokenURL: "https://ranchimallflo.duckdns.org/", - token: "rupee" + currency: "rupee" }; (typeof global !== "undefined" ? global : window).cryptocoin = floGlobals.blockchain; diff --git a/public/fn.js b/public/fn.js index f225ab0..4bdbe97 100644 --- a/public/fn.js +++ b/public/fn.js @@ -1,4 +1,5 @@ //console.log(document.cookie.toString()); +const INVALID_SERVER_MSG = "INCORRECT_SERVER_ERROR"; var nodeList, nodeURL, nodeKBucket; //Container for (backup) node list function exchangeAPI(api, options) { @@ -52,7 +53,7 @@ const tokenAPI = { }).catch(error => reject(error)) }) }, - sendToken: function(privKey, amount, receiverID, message = "", token = 'rupee') { + sendToken: function(privKey, amount, receiverID, message = "", token = floGlobals.currency) { return new Promise((resolve, reject) => { let senderID = floCrypto.getFloID(privKey); if (typeof amount !== "number" || amount <= 0) @@ -69,7 +70,9 @@ const tokenAPI = { } function ResponseError(status, data) { - if (this instanceof ResponseError) { + if (data === INVALID_SERVER_MSG) + location.reload(); + else if (this instanceof ResponseError) { this.data = data; this.status = status; } else @@ -148,10 +151,10 @@ function getTransactionList() { }); } -function getRate() { +function getRates() { return new Promise((resolve, reject) => { - exchangeAPI('/get-rate') - .then(result => responseParse(result, false) + exchangeAPI('/get-rates') + .then(result => responseParse(result) .then(result => resolve(result)) .catch(error => reject(error))) .catch(error => reject(error)); @@ -265,7 +268,7 @@ function logout(floID, proxySecret) { }) } -function buy(quantity, max_price, floID, proxySecret) { +function buy(asset, quantity, max_price, floID, proxySecret) { return new Promise((resolve, reject) => { if (typeof quantity !== "number" || quantity <= 0) return reject(`Invalid quantity (${quantity})`); @@ -273,12 +276,14 @@ function buy(quantity, max_price, floID, proxySecret) { return reject(`Invalid max_price (${max_price})`); let request = { floID: floID, + asset: asset, quantity: quantity, max_price: max_price, timestamp: Date.now() }; request.sign = signRequest({ type: "buy_order", + asset: asset, quantity: quantity, max_price: max_price, timestamp: request.timestamp @@ -299,7 +304,7 @@ function buy(quantity, max_price, floID, proxySecret) { } -function sell(quantity, min_price, floID, proxySecret) { +function sell(asset, quantity, min_price, floID, proxySecret) { return new Promise((resolve, reject) => { if (typeof quantity !== "number" || quantity <= 0) return reject(`Invalid quantity (${quantity})`); @@ -307,6 +312,7 @@ function sell(quantity, min_price, floID, proxySecret) { return reject(`Invalid min_price (${min_price})`); let request = { floID: floID, + asset: asset, quantity: quantity, min_price: min_price, timestamp: Date.now() @@ -314,6 +320,7 @@ function sell(quantity, min_price, floID, proxySecret) { request.sign = signRequest({ type: "sell_order", quantity: quantity, + asset: asset, min_price: min_price, timestamp: request.timestamp }, proxySecret); @@ -376,7 +383,7 @@ function depositFLO(quantity, floID, sinkID, privKey, proxySecret) { }; request.sign = signRequest({ type: "deposit_FLO", - txid: request.txid, + txid: txid, timestamp: request.timestamp }, proxySecret); console.debug(request); @@ -404,7 +411,7 @@ function withdrawFLO(quantity, floID, proxySecret) { }; request.sign = signRequest({ type: "withdraw_FLO", - amount: request.amount, + amount: quantity, timestamp: request.timestamp }, proxySecret); console.debug(request); @@ -422,24 +429,24 @@ function withdrawFLO(quantity, floID, proxySecret) { }) } -function depositRupee(quantity, floID, sinkID, privKey, proxySecret) { +function depositToken(token, quantity, floID, sinkID, privKey, proxySecret) { return new Promise((resolve, reject) => { if (!floCrypto.verifyPrivKey(privKey, floID)) return reject("Invalid Private Key"); - tokenAPI.sendToken(privKey, quantity, sinkID, 'Deposit Rupee in market').then(txid => { + tokenAPI.sendToken(privKey, quantity, sinkID, 'Deposit Rupee in market', token).then(txid => { let request = { floID: floID, txid: txid, timestamp: Date.now() }; request.sign = signRequest({ - type: "deposit_Rupee", - txid: request.txid, + type: "deposit_Token", + txid: txid, timestamp: request.timestamp }, proxySecret); console.debug(request); - exchangeAPI('/deposit-rupee', { + exchangeAPI('/deposit-token', { method: "POST", headers: { 'Content-Type': 'application/json' @@ -453,21 +460,23 @@ function depositRupee(quantity, floID, sinkID, privKey, proxySecret) { }) } -function withdrawRupee(quantity, floID, proxySecret) { +function withdrawToken(token, quantity, floID, proxySecret) { return new Promise((resolve, reject) => { let request = { floID: floID, + token: token, amount: quantity, timestamp: Date.now() }; request.sign = signRequest({ - type: "withdraw_Rupee", - amount: request.amount, + type: "withdraw_Token", + token: token, + amount: quantity, timestamp: request.timestamp }, proxySecret); console.debug(request); - exchangeAPI('/withdraw-rupee', { + exchangeAPI('/withdraw-token', { method: "POST", headers: { 'Content-Type': 'application/json' @@ -490,8 +499,8 @@ function addUserTag(tag_user, tag, floID, proxySecret) { }; request.sign = signRequest({ command: "add_Tag", - user: request.user, - tag: request.tag, + user: tag_user, + tag: tag, timestamp: request.timestamp }, proxySecret); console.debug(request); @@ -519,8 +528,8 @@ function removeUserTag(tag_user, tag, floID, proxySecret) { }; request.sign = signRequest({ command: "remove_Tag", - user: request.user, - tag: request.tag, + user: tag_user, + tag: tag, timestamp: request.timestamp }, proxySecret); console.debug(request); diff --git a/public/home.html b/public/home.html index c02e190..81855d7 100644 --- a/public/home.html +++ b/public/home.html @@ -102,6 +102,7 @@ Sell + @@ -976,23 +977,23 @@ } const render = { orderCard(orderDetails = {}) { - const { id, quantity, price, time, type } = orderDetails + const { id, asset, quantity, price, time, type } = orderDetails const card = getRef('order_template').content.cloneNode(true).firstElementChild card.dataset.id = id card.dataset.type = type card.querySelector('.order-card__type').textContent = type - card.querySelector('.order-card__quantity').textContent = `${quantity} FLO` + card.querySelector('.order-card__quantity').textContent = `${quantity} ${asset}` card.querySelector('.order-card__price-type').textContent = type === 'buy' ? 'Max price' : 'Min price' card.querySelector('.order-card__price').textContent = formatAmount(price) card.querySelector('.order-card__time').textContent = getFormattedTime(time, true) return card }, transactionCard(transactionDetails = {}) { - const { buyer, seller, type, other, quantity, unitValue, time } = transactionDetails + const { buyer, seller, type, other, asset, quantity, unitValue, time } = transactionDetails const card = getRef('transaction_template').content.cloneNode(true).firstElementChild card.dataset.type = type card.querySelector('.transaction-card__type').textContent = type - card.querySelector('.transaction-card__quantity').textContent = `${quantity} FLO` + card.querySelector('.transaction-card__quantity').textContent = `${quantity} ${asset}` card.querySelector('.transaction-card__price').textContent = formatAmount(unitValue) card.querySelector('.transaction-card__total').textContent = formatAmount(parseFloat((unitValue * quantity).toFixed(2))) card.querySelector('.more-info').dataset.buyer = buyer @@ -1002,12 +1003,12 @@ return card }, marketOrderCard(orderDetails = {}) { - const { floID, quantity, unitValue, time, type } = orderDetails + const { floID, asset, quantity, unitValue, time, type } = orderDetails const card = getRef('market_order_template').content.cloneNode(true).firstElementChild card.dataset.type = type card.classList.add(`transaction-card--${type}`) card.querySelector('.transaction-card__type').textContent = type - card.querySelector('.transaction-card__quantity').textContent = `${quantity} FLO` + card.querySelector('.transaction-card__quantity').textContent = `${quantity} ${asset}` card.querySelector('.transaction-card__price').textContent = formatAmount(unitValue) card.querySelector('.transaction-card__total').textContent = formatAmount(parseFloat((unitValue * quantity).toFixed(2))) card.querySelector('.more-info').dataset.time = time @@ -1017,9 +1018,9 @@ return card }, marketTransactionCard(transactionDetails = {}) { - const { buyer, seller, quantity, unitValue, time } = transactionDetails + const { buyer, seller, asset, quantity, unitValue, time } = transactionDetails const card = getRef('market_transaction_template').content.cloneNode(true).firstElementChild - card.querySelector('.transaction-card__quantity').textContent = `${quantity} FLO` + card.querySelector('.transaction-card__quantity').textContent = `${quantity} ${asset}` card.querySelector('.transaction-card__price').textContent = `₹${unitValue}` card.querySelector('.transaction-card__total').textContent = formatAmount(parseFloat((unitValue * quantity).toFixed(2))) card.querySelector('.more-info').dataset.time = time @@ -1047,14 +1048,15 @@ getRef('quantity_type').textContent = tradeType === 'buy' ? `Rupee` : `FLO` }) async function tradeFlo() { + const asset = getRef('get_asset').value; const quantity = parseFloat(getRef('get_quantity').value) const price = parseFloat(getRef('get_price').value) showProcess('trade_button_wrapper') try { if (tradeType === 'buy') { - await buy(quantity, price, proxy.userID, await proxy.secret) + await buy(asset, quantity, price, proxy.userID, await proxy.secret) //TODO: asset_name } else { - await sell(quantity, price, proxy.userID, await proxy.secret) + await sell(asset, quantity, price, proxy.userID, await proxy.secret) //TODO: asset_name } getRef('trade_button_wrapper').append(getRef('success_template').content.cloneNode(true)) notify(`Placed ${tradeType} order`, 'success') @@ -1197,14 +1199,14 @@ if (asset === 'FLO') { await depositFLO(quantity, proxy.userID, proxy.sinkID, privKey, proxySecret) } else { - await depositRupee(quantity, proxy.userID, proxy.sinkID, privKey, proxySecret) + await depositToken(quantity, proxy.userID, proxy.sinkID, privKey, proxySecret) //TODO: token_name } showWalletResult('success', `Sent ${asset} deposit request`, 'This may take upto 30 mins to reflect in your wallet.') } else { if (asset === 'FLO') { await withdrawFLO(quantity, proxy.userID, proxySecret) } else { - await withdrawRupee(quantity, proxy.userID, proxySecret) + await withdrawToken(quantity, proxy.userID, proxySecret) //TODO: token_name } showWalletResult('success', `Sent ${asset} withdraw request`, 'This may take upto 30 mins to reflect in your wallet.') } @@ -1422,9 +1424,10 @@ if (ordersType === 'open') { const allOpenOrders = [...buyOrders, ...sellOrders].sort((a, b) => new Date(b.time_placed).getTime() - new Date(a.time_placed).getTime()) allOpenOrders.forEach(order => { - const { id, quantity, minPrice = undefined, maxPrice = undefined, time_placed } = order + const { id, asset, quantity, minPrice = undefined, maxPrice = undefined, time_placed } = order const orderDetails = { id, + asset, quantity, type: minPrice ? 'sell' : 'buy', price: minPrice || maxPrice, @@ -1434,7 +1437,7 @@ }) } else { transactions.forEach(transaction => { - const { quantity, unitValue, tx_time, buyer, seller } = transaction + const {asset, quantity, unitValue, tx_time, buyer, seller } = transaction let type, other; if (seller === proxy.userID) { type = 'Sold'; @@ -1449,6 +1452,7 @@ seller, type, other, + asset, quantity, unitValue, time: tx_time @@ -1470,9 +1474,10 @@ const [buyOrders, sellOrders] = await Promise.all([getBuyList(), getSellList()]) const allOpenOrders = [...buyOrders, ...sellOrders].sort((a, b) => new Date(b.time_placed).getTime() - new Date(a.time_placed).getTime()) allOpenOrders.forEach(order => { - const { floID, quantity, minPrice = undefined, maxPrice = undefined, time_placed } = order + const { floID, asset, quantity, minPrice = undefined, maxPrice = undefined, time_placed } = order const orderDetails = { floID, + asset, quantity, type: minPrice ? 'sell' : 'buy', unitValue: minPrice || maxPrice, @@ -1488,10 +1493,11 @@ try { const marketTransactions = await getTransactionList() marketTransactions.forEach(transaction => { - const { seller, buyer, quantity, unitValue, tx_time } = transaction + const { seller, buyer, asset, quantity, unitValue, tx_time } = transaction const transactionDetails = { buyer, seller, + asset, quantity, unitValue, time: tx_time @@ -1554,14 +1560,14 @@ notify("Password minimum length is 4", 'error'); else { let tmp = Crypto.AES.encrypt(this.private, pwd); - localStorage.setItem("proxy_secret", "?" + tmp); + localStorage.setItem("exchange-proxy_secret", "?" + tmp); notify("Successfully locked with Password", 'success'); } }).catch(_ => null); }, clear() { - localStorage.removeItem("proxy_secret"); - localStorage.removeItem("user_ID"); + localStorage.removeItem("exchange-proxy_secret"); + localStorage.removeItem("exchange-user_ID"); this.user = null; this.private = null; this.public = null; @@ -1570,19 +1576,19 @@ return getRef("sink_id").value; }, set userID(id){ - localStorage.setItem("user_ID", id); + localStorage.setItem("exchange-user_ID", id); this.user = id; }, get userID(){ if(this.user) return this.user; else{ - let id = localStorage.getItem('user_ID'); + let id = localStorage.getItem('exchange-user_ID'); return id ? this.user = id : undefined; } }, set secret(key) { - localStorage.setItem("proxy_secret", key); + localStorage.setItem("exchange-proxy_secret", key); this.private = key; this.public = floCrypto.getPubKeyHex(key); }, @@ -1605,7 +1611,7 @@ Reject("Unable to fetch Proxy secret"); } }; - let tmp = localStorage.getItem("proxy_secret"); + let tmp = localStorage.getItem("exchange-proxy_secret"); if (typeof tmp !== "string") Reject("Unable to fetch Proxy secret"); else if (tmp.startsWith("?")) { @@ -1629,11 +1635,21 @@ } let floExchangeRate = 0 - function updateRate() { - getRate().then(rate => { - floExchangeRate = parseFloat(rate) - getRef('flo_rate').textContent = formatAmount(parseFloat(rate)) - getRef('get_price').value = parseFloat(parseFloat(rate).toFixed(2)) + function updateRate(init = false) { + getRates().then(rates => { + console.debug(rates); + if(init){ + let assetList = getRef('get_asset'); + for(let asset in rates){ + let e = document.createElement('option'); + e.innerText = asset; + assetList.appendChild(e); + } + } + let flo_rate = rates["FLO"]; + floExchangeRate = parseFloat(flo_rate) + getRef('flo_rate').textContent = formatAmount(parseFloat(flo_rate)) + getRef('get_price').value = parseFloat(parseFloat(flo_rate).toFixed(2)) }).catch(error => console.error(error)) } @@ -1652,7 +1668,7 @@ } } else console.info("refresh"); - updateRate(); + updateRate(init); renderMarketOrders(); if(proxy.userID) account(); @@ -1686,7 +1702,8 @@ getRef("user_id").value = acc.floID; getRef("sink_id").value = acc.sinkID; //FLO Balance - let flo_total = acc.coins.reduce((a, x) => a + x.quantity, 0); + console.debug(acc.vault); + let flo_total = acc.vault.reduce((a, x) => a + (x.asset === "FLO" ? x.quantity : 0), 0); let flo_locked = acc.sellOrders.reduce((a, x) => a + x.quantity, 0); let flo_net = flo_total - flo_locked; console.debug("FLO", flo_total, flo_locked, flo_net); @@ -1694,7 +1711,7 @@ showBalance("flo", flo_net, flo_locked,) //Rupee Balance - let rupee_total = acc.rupee_total; + let rupee_total = acc.cash; let rupee_locked = acc.buyOrders.reduce((a, x) => a + x.quantity * x.maxPrice, 0); let rupee_net = rupee_total - rupee_locked; console.debug("RUPEE", rupee_total, rupee_locked, rupee_net); diff --git a/src/app.js b/src/app.js index cc05e06..ce01d54 100644 --- a/src/app.js +++ b/src/app.js @@ -4,7 +4,7 @@ const express = require('express'); //const sessions = require('express-session'); const Request = require('./request'); -const REFRESH_INTERVAL = 5 * 1000; //10 * 60 * 1000; +const REFRESH_INTERVAL = 1 * 60 * 1000; module.exports = function App(secret, DB) { @@ -72,7 +72,7 @@ module.exports = function App(secret, DB) { //list all process transactions and rate app.get('/list-transactions', Request.ListTransactions); - app.get('/get-rate', Request.getRate) + app.get('/get-rates', Request.getRates) //get account details app.post('/account', Request.Account); @@ -80,8 +80,8 @@ module.exports = function App(secret, DB) { //withdraw and deposit request app.post('/deposit-flo', Request.DepositFLO); app.post('/withdraw-flo', Request.WithdrawFLO); - app.post('/deposit-rupee', Request.DepositRupee); - app.post('/withdraw-rupee', Request.WithdrawRupee); + app.post('/deposit-token', Request.DepositToken); + app.post('/withdraw-token', Request.WithdrawToken); //Manage user tags (Access to trusted IDs only) @@ -108,6 +108,10 @@ module.exports = function App(secret, DB) { set: (ids) => Request.trustedIDs = ids }); + Object.defineProperty(self, "assetList", { + set: (assets) => Request.assetList = assets + }); + //Start (or) Stop servers self.start = (port) => new Promise(resolve => { server = app.listen(port, () => { diff --git a/src/backup/head.js b/src/backup/head.js index e0f3afb..d924c25 100644 --- a/src/backup/head.js +++ b/src/backup/head.js @@ -5,7 +5,7 @@ const slave = require('./slave'); const WebSocket = require('ws'); const shareThreshold = 50 / 100; -var DB, app, wss; //Container for database and app +var DB, app, wss, tokenList; //Container for database and app var nodeList, nodeURL, nodeKBucket; //Container for (backup) node list var nodeShares = null, nodeSinkID = null, @@ -104,8 +104,8 @@ function send_dataImmutable(timestamp, ws) { const immutable_tables = { Users: "created", Request_Log: "request_time", - Transactions: "tx_time", - priceHistory: "rec_time" + TransactionHistory: "tx_time", + PriceHistory: "rec_time" }; const sendTable = (table, timeCol) => new Promise((res, rej) => { DB.query(`SELECT * FROM ${table} WHERE ${timeCol} > ?`, [timestamp]) @@ -175,16 +175,36 @@ function storeSink(sinkID, sinkPrivKey) { .catch(error => console.error(error)); } -function transferMoneyToNewSink(oldSinkID, oldSinkKey) { +function transferMoneyToNewSink(oldSinkID, oldSinkKey, newSink) { + const transferToken = token => new Promise((resolve, reject) => { + tokenAPI.getBalance(oldSinkID, token).then(tokenBalance => { + floBlockchainAPI.writeData(oldSinkID, `send ${tokenBalance} ${token}# |Exchange-market New sink`, oldSinkKey, newSink.floID, false) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }) + }); return new Promise((resolve, reject) => { - let newSink = generateNewSink(); - floBlockchainAPI.getBalance(oldSinkID).then(balFLO => { - tokenAPI.getBalance(oldSinkID).then(balRupee => { - floBlockchainAPI.sendTx(oldSinkID, newSink.floID, balFLO - floGlobals.fee, oldSinkKey, `send ${balRupee} ${floGlobals.token}# |Exchange-market New sink`) - .then(result => resolve(newSink)) - .catch(error => reject(error)) - }).catch(error => reject(error)); - }).catch(error => reject(error)) + console.debug("Transferring tokens to new Sink:", newSink.floID) + Promise.allSettled(tokenList.map(token => transferToken(token))).then(result => { + let failedFlag = false; + tokenList.forEach((token, i) => { + if (result[i].status === "fulfilled") + console.log(token, result[i].value); + else { + failedFlag = true; + console.error(token, result[i].reason); + } + }); + if (failedFlag) + return reject("Some token transfer has failed"); + floBlockchainAPI.getBalance(oldSinkID).then(floBalance => { + tokenAPI.getBalance(oldSinkID).then(cashBalance => { + floBlockchainAPI.sendTx(oldSinkID, newSink.floID, floBalance - floGlobals.fee, oldSinkKey, `send ${cashBalance} ${floGlobals.currency}# |Exchange-market New sink`) + .then(result => resolve(result)) + .catch(error => reject(error)) + }).catch(error => reject(error)); + }).catch(error => reject(error)) + }); }) } @@ -196,15 +216,17 @@ function collectShares(floID, sinkID, share) { return console.error("Something is wrong! Slaves are sending different sinkID"); collectShares.shares[floID] = share; try { - let privKey = floCrypto.retrieveShamirSecret(Object.values(collectShares.shares)); - if (floCrypto.verifyPrivKey(privKey, collectShares.sinkID)) { - transferMoneyToNewSink(collectShares.sinkID, privKey).then(newSink => { - delete collectShares.sinkID; - delete collectShares.shares; - collectShares.active = false; - storeSink(newSink.floID, newSink.privKey); - sendSharesToNodes(newSink.floID, newSink.shares); - }).catch(error => console.error(error)); + let oldSinkKey = floCrypto.retrieveShamirSecret(Object.values(collectShares.shares)); + if (floCrypto.verifyPrivKey(oldSinkKey, collectShares.sinkID)) { + let newSink = generateNewSink(); + transferMoneyToNewSink(collectShares.sinkID, oldSinkKey, newSink).then(result => { + console.log("Money transfer successful", result); + delete collectShares.sinkID; + delete collectShares.shares; + collectShares.active = false; + sendSharesToNodes(newSink.floID, newSink.shares); + }).catch(error => console.error(error)) + .finally(_ => storeSink(newSink.floID, newSink.privKey)); } } catch (error) { //Unable to retrive sink private key. Waiting for more shares! Do nothing for now @@ -391,6 +413,9 @@ module.exports = { nodeList = nodeKBucket.order; console.debug(nodeList); }, + set assetList(assets) { + tokenList = assets.filter(a => a.toUpperCase() !== "FLO"); + }, set DB(db) { DB = db; slave.DB = db; diff --git a/src/backup/slave.js b/src/backup/slave.js index ed670c0..51d9818 100644 --- a/src/backup/slave.js +++ b/src/backup/slave.js @@ -32,6 +32,7 @@ function stopSlaveProcess() { if (masterWS !== null) { masterWS.onclose = () => null; masterWS.close(); + requestInstance.close(); masterWS = null; } if (intervalID !== null) { @@ -45,8 +46,8 @@ function requestBackupSync(ws) { const tables = { Users: "created", Request_Log: "request_time", - Transactions: "tx_time", - //priceHistory: "rec_time", + TransactionHistory: "tx_time", + //PriceHistory: "rec_time", _backup: "timestamp" }; let subs = []; @@ -161,6 +162,9 @@ function sendSinkShare() { } function processBackupData(response) { + //TODO: Sync improvements needed. (2 types) + //1. Either sync has to be completed or rollback all + //2. Each table/data should be treated as independent chunks const self = requestInstance; self.last_response_time = Date.now(); switch (response.command) { diff --git a/src/coupling.js b/src/coupling.js index 72099bd..67c2f53 100644 --- a/src/coupling.js +++ b/src/coupling.js @@ -5,9 +5,9 @@ const price = require("./price"); var DB; //container for database -function initiate() { - price.getRates().then(cur_rate => { - group.getBestPairs(cur_rate) +function startCouplingForAsset(asset) { + price.getRates(asset).then(cur_rate => { + group.getBestPairs(asset, cur_rate) .then(bestPairQueue => processCoupling(bestPairQueue)) .catch(error => console.error("initiateCoupling", error)) }).catch(error => console.error(error)); @@ -19,17 +19,21 @@ function processCoupling(bestPairQueue) { seller_best = pair_result.sellOrder; console.debug("Sell:", seller_best); console.debug("Buy:", buyer_best); - spendFLO(buyer_best, seller_best, pair_result.null_base).then(spend_result => { - let tx_quantity = spend_result.quantity, - txQueries = spend_result.txQueries, - clear_sell = spend_result.incomplete && !spend_result.flag_baseNull; //clear_sell can be true only if an order is placed without enough FLO - processOrders(seller_best, buyer_best, txQueries, tx_quantity, clear_sell); - updateBalance(seller_best, buyer_best, txQueries, bestPairQueue.cur_rate, tx_quantity); + spendAsset(bestPairQueue.asset, buyer_best, seller_best, pair_result.null_base).then(spent => { + if (!spent.quantity) { + //Happens when there are only Null-base assets + bestPairQueue.next(spent.quantity, spent.incomplete); + processCoupling(bestPairQueue); + return; + } + let txQueries = spent.txQueries; + processOrders(seller_best, buyer_best, txQueries, spent.quantity, spent.incomplete && pair_result.null_base); + updateBalance(seller_best, buyer_best, txQueries, bestPairQueue.asset, bestPairQueue.cur_rate, spent.quantity); //begin audit - beginAudit(seller_best.floID, buyer_best.floID, bestPairQueue.cur_rate, tx_quantity).then(audit => { + beginAudit(seller_best.floID, buyer_best.floID, bestPairQueue.asset, bestPairQueue.cur_rate, spent.quantity).then(audit => { //process txn query in SQL DB.transaction(txQueries).then(_ => { - bestPairQueue.next(tx_quantity, spend_result.incomplete, spend_result.flag_baseNull); + bestPairQueue.next(spent.quantity, spent.incomplete); console.log(`Transaction was successful! BuyOrder:${buyer_best.id}| SellOrder:${seller_best.id}`); audit.end(); price.updateLastTime(); @@ -46,7 +50,7 @@ function processCoupling(bestPairQueue) { console.error(error.buy); noBuy = null; } else { - console.log("No valid buyOrders."); + console.log("No valid buyOrders for Asset:", bestPairQueue.asset); noBuy = true; } if (error.sell === undefined) @@ -55,35 +59,31 @@ function processCoupling(bestPairQueue) { console.error(error.sell); noSell = null; } else { - console.log("No valid sellOrders."); + console.log("No valid sellOrders for Asset:", bestPairQueue.asset); noSell = true; } - price.noOrder(noBuy, noSell); + price.noOrder(bestPairQueue.asset, noBuy, noSell); }); } -function spendFLO(buyOrder, sellOrder, null_base) { +function spendAsset(asset, buyOrder, sellOrder, null_base) { return new Promise((resolve, reject) => { - DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY base", [sellOrder.floID]).then(result => { + DB.query('SELECT id, quantity FROM Vault WHERE floID=? AND asset=? AND base IS ' + + (null_base ? "NULL ORDER BY locktime" : "NOT NULL ORDER BY base"), [sellOrder.floID, asset]).then(result => { let rem = Math.min(buyOrder.quantity, sellOrder.quantity), - txQueries = [], - flag_baseNull = false; + txQueries = []; for (let i = 0; i < result.length && rem > 0; i++) - if (result[i].base || null_base) { - if (rem < result[i].quantity) { - txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, result[i].id]]); - rem = 0; - } else { - txQueries.push(["DELETE FROM Vault WHERE id=?", [result[i].id]]); - rem -= result[i].quantity; - } - } else - flag_baseNull = true; + if (rem < result[i].quantity) { + txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, result[i].id]]); + rem = 0; + } else { + txQueries.push(["DELETE FROM Vault WHERE id=?", [result[i].id]]); + rem -= result[i].quantity; + } resolve({ quantity: Math.min(buyOrder.quantity, sellOrder.quantity) - rem, txQueries, - incomplete: rem > 0, - flag_baseNull + incomplete: rem > 0 }); }).catch(error => reject(error)); }) @@ -98,55 +98,56 @@ function processOrders(seller_best, buyer_best, txQueries, quantity, clear_sell) else txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]); //Process Sell Order - if (quantity == seller_best.quantity || clear_sell) + if (quantity == seller_best.quantity || clear_sell) //clear_sell must be true iff an order is placed without enough Asset txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]); else txQueries.push(["UPDATE SellOrder SET quantity=quantity-? WHERE id=?", [quantity, seller_best.id]]); } -function updateBalance(seller_best, buyer_best, txQueries, cur_price, quantity) { - //Update rupee balance for seller and buyer +function updateBalance(seller_best, buyer_best, txQueries, asset, cur_price, quantity) { + //Update cash balance for seller and buyer let totalAmount = cur_price * quantity; - txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance+? WHERE floID=?", [totalAmount, seller_best.floID]]); - txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance-? WHERE floID=?", [totalAmount, buyer_best.floID]]); + txQueries.push(["UPDATE Cash SET balance=balance+? WHERE floID=?", [totalAmount, seller_best.floID]]); + txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [totalAmount, buyer_best.floID]]); //Add coins to Buyer - txQueries.push(["INSERT INTO Vault(floID, base, quantity) VALUES (?, ?, ?)", [buyer_best.floID, cur_price, quantity]]) + txQueries.push(["INSERT INTO Vault(floID, asset, base, quantity) VALUES (?, ?, ?, ?)", [buyer_best.floID, asset, cur_price, quantity]]) //Record transaction - txQueries.push(["INSERT INTO Transactions (seller, buyer, quantity, unitValue) VALUES (?, ?, ?, ?)", [seller_best.floID, buyer_best.floID, quantity, cur_price]]); + txQueries.push(["INSERT INTO TransactionHistory (seller, buyer, asset, quantity, unitValue) VALUES (?, ?, ?, ?, ?)", [seller_best.floID, buyer_best.floID, asset, quantity, cur_price]]); } -function beginAudit(sellerID, buyerID, unit_price, quantity) { +function beginAudit(sellerID, buyerID, asset, unit_price, quantity) { return new Promise((resolve, reject) => { - auditBalance(sellerID, buyerID).then(old_bal => resolve({ - end: () => endAudit(sellerID, buyerID, old_bal, unit_price, quantity) + auditBalance(sellerID, buyerID, asset).then(old_bal => resolve({ + end: () => endAudit(sellerID, buyerID, asset, old_bal, unit_price, quantity) })).catch(error => reject(error)) }) } -function endAudit(sellerID, buyerID, old_bal, unit_price, quantity) { - auditBalance(sellerID, buyerID).then(new_bal => { - DB.query("INSERT INTO auditTransaction (sellerID, buyerID, quantity, unit_price, total_cost, " + - " Rupee_seller_old, Rupee_seller_new, Rupee_buyer_old, Rupee_buyer_new," + - " FLO_seller_old, FLO_seller_new, FLO_buyer_old, FLO_buyer_new) " + - " Value (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [sellerID, buyerID, quantity, unit_price, quantity * unit_price, - old_bal[sellerID].Rupee, new_bal[sellerID].Rupee, old_bal[buyerID].Rupee, new_bal[buyerID].Rupee, - old_bal[sellerID].FLO, new_bal[sellerID].FLO, old_bal[buyerID].FLO, new_bal[buyerID].FLO, +function endAudit(sellerID, buyerID, asset, old_bal, unit_price, quantity) { + auditBalance(sellerID, buyerID, asset).then(new_bal => { + DB.query("INSERT INTO AuditTransaction (asset, quantity, unit_price, total_cost," + + " sellerID, seller_old_cash, seller_old_asset, seller_new_cash, seller_new_asset," + + " buyerID, buyer_old_cash, buyer_old_asset, buyer_new_cash, buyer_new_asset)" + + " Value (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + asset, quantity, unit_price, quantity * unit_price, + sellerID, old_bal[sellerID].cash, old_bal[sellerID].asset, new_bal[sellerID].cash, new_bal[sellerID].asset, + buyerID, old_bal[buyerID].cash, old_bal[buyerID].asset, new_bal[buyerID].cash, new_bal[buyerID].asset, ]).then(_ => null).catch(error => console.error(error)) }).catch(error => console.error(error)); } -function auditBalance(sellerID, buyerID) { +function auditBalance(sellerID, buyerID, asset) { return new Promise((resolve, reject) => { let balance = { [sellerID]: {}, [buyerID]: {} }; - DB.query("SELECT floID, rupeeBalance FROM Cash WHERE floID IN (?, ?)", [sellerID, buyerID]).then(result => { + DB.query("SELECT floID, balance FROM Cash WHERE floID IN (?, ?)", [sellerID, buyerID]).then(result => { for (let i in result) - balance[result[i].floID].Rupee = result[i].rupeeBalance; - DB.query("SELECT floID, SUM(quantity) as floBal FROM Vault WHERE floID IN (?, ?) GROUP BY floID", [sellerID, buyerID]).then(result => { + balance[result[i].floID].cash = result[i].balance; + DB.query("SELECT floID, SUM(quantity) as asset_balance FROM Vault WHERE asset=? AND floID IN (?, ?) GROUP BY floID", [asset, sellerID, buyerID]).then(result => { for (let i in result) - balance[result[i].floID].FLO = result[i].floBal; + balance[result[i].floID].asset = result[i].asset_balance; resolve(balance); }).catch(error => reject(error)) }).catch(error => reject(error)) @@ -154,8 +155,11 @@ function auditBalance(sellerID, buyerID) { } module.exports = { - initiate, - group, + initiate: startCouplingForAsset, + group: { + addTag: group.addTag, + removeTag: group.removeTag + }, price, set DB(db) { DB = db; diff --git a/src/group.js b/src/group.js index 03d402b..9888866 100644 --- a/src/group.js +++ b/src/group.js @@ -4,7 +4,7 @@ var DB; //container for database function addTag(floID, tag) { return new Promise((resolve, reject) => { - DB.query("INSERT INTO Tags (floID, tag) VALUE (?,?)", [floID, tag]) + DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag]) .then(result => resolve(`Added ${floID} to ${tag}`)) .catch(error => { if (error.code === "ER_DUP_ENTRY") @@ -19,28 +19,31 @@ function addTag(floID, tag) { function removeTag(floID, tag) { return new Promise((resolve, reject) => { - DB.query("DELETE FROM Tags WHERE floID=? AND tag=?", [floID, tag]) + DB.query("DELETE FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) .then(result => resolve(`Removed ${floID} from ${tag}`)) .catch(error => reject(error)); }) } -function getBestPairs(currentRate) { +function getBestPairs(asset, cur_rate) { return new Promise((resolve, reject) => { DB.query("SELECT tag, sellPriority, buyPriority FROM TagList").then(result => { //Sorted in Ascending (ie, stack; pop for highest) let tags_buy = result.sort((a, b) => a.buyPriority > b.buyPriority ? 1 : -1).map(r => r.tag); let tags_sell = result.sort((a, b) => a.sellPriority > b.sellPriority ? 1 : -1).map(r => r.tag); - resolve(new bestPair(currentRate, tags_buy, tags_sell)); + resolve(new bestPair(asset, cur_rate, tags_buy, tags_sell)); }).catch(error => reject(error)) }) } -const bestPair = function(cur_rate, tags_buy, tags_sell) { - const currentRate = cur_rate; +const bestPair = function(asset, cur_rate, tags_buy, tags_sell) { + + Object.defineProperty(this, 'asset', { + get: () => asset + }); Object.defineProperty(this, 'cur_rate', { - get: () => currentRate + get: () => cur_rate, }); this.get = () => new Promise((resolve, reject) => { @@ -59,7 +62,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { }).catch(error => reject(error)) }); - this.next = (tx_quantity, incomplete_sell, flag_sell) => { + this.next = (tx_quantity, incomplete_sell) => { let buy = getBuyOrder.cache, sell = getSellOrder.cache; if (buy.cur_order && sell.cur_order) { @@ -74,7 +77,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { if (tx_quantity < sell.cur_order.quantity) { sell.cur_order.quantity -= tx_quantity; if (incomplete_sell) { - if (!sell.mode_null && flag_sell) + if (!sell.mode_null) sell.null_queue.push(sell.cur_order); sell.cur_order = null; } @@ -89,7 +92,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { const getSellOrder = () => new Promise((resolve, reject) => { let cache = getSellOrder.cache; if (cache.cur_order) { //If cache already has a pending order - verifySellOrder(cache.cur_order, currentRate, cache.mode_null).then(result => { + verifySellOrder(cache.cur_order, asset, cur_rate, cache.mode_null).then(result => { cache.cur_order = result; resolve(result); }).catch(error => { @@ -102,7 +105,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { .catch(error => reject(error)) }) } else if (cache.orders && cache.orders.length) { //If cache already has orders in priority - getTopValidSellOrder(cache.orders, currentRate, cache.mode_null).then(result => { + getTopValidSellOrder(cache.orders, asset, cur_rate, cache.mode_null).then(result => { cache.cur_order = result; resolve(result); }).catch(error => { @@ -116,14 +119,14 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { }) } else if (cache.tags.length) { //If cache has remaining tags cache.cur_tag = cache.tags.pop(); - getSellOrdersInTag(cache.cur_tag, currentRate).then(orders => { + getSellOrdersInTag(cache.cur_tag, asset, cur_rate).then(orders => { cache.orders = orders; getSellOrder() .then(result => resolve(result)) .catch(error => reject(error)) }).catch(error => reject(error)); } else if (!cache.end) { //Un-tagged floID's orders (do only once) - getUntaggedSellOrders(currentRate).then(orders => { + getUntaggedSellOrders(asset, cur_rate).then(orders => { cache.orders = orders; cache.cur_tag = null; cache.end = true; @@ -131,7 +134,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { .then(result => resolve(result)) .catch(error => reject(error)) }).catch(error => reject(error)); - } else if (!cache.mode_null) { //Lowest priority Coins (FLO Brought from other sources) + } else if (!cache.mode_null) { //Lowest priority Assets (Brought from other sources) cache.orders = cache.null_queue.reverse(); //Reverse it so that we can pop the highest priority cache.mode_null = true; cache.null_queue = null; @@ -150,7 +153,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { const getBuyOrder = () => new Promise((resolve, reject) => { let cache = getBuyOrder.cache; if (cache.cur_order) { //If cache already has a pending order - verifyBuyOrder(cache.cur_order, currentRate).then(result => { + verifyBuyOrder(cache.cur_order, cur_rate).then(result => { cache.cur_order = result; resolve(result); }).catch(error => { @@ -163,7 +166,7 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { .catch(error => reject(error)) }) } else if (cache.orders && cache.orders.length) { //If cache already has orders in priority - getTopValidBuyOrder(cache.orders, currentRate).then(result => { + getTopValidBuyOrder(cache.orders, cur_rate).then(result => { cache.cur_order = result; resolve(result); }).catch(error => { @@ -177,14 +180,14 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { }) } else if (cache.tags.length) { //If cache has remaining tags cache.cur_tag = cache.tags.pop(); - getBuyOrdersInTag(cache.cur_tag, currentRate).then(orders => { + getBuyOrdersInTag(cache.cur_tag, asset, cur_rate).then(orders => { cache.orders = orders; getBuyOrder() .then(result => resolve(result)) .catch(error => reject(error)) }).catch(error => reject(error)); } else if (!cache.end) { //Un-tagged floID's orders (do only once) - getUntaggedBuyOrders(currentRate).then(orders => { + getUntaggedBuyOrders(asset, cur_rate).then(orders => { cache.orders = orders; cache.cur_tag = null; cache.end = true; @@ -200,31 +203,34 @@ const bestPair = function(cur_rate, tags_buy, tags_sell) { }; } -function getUntaggedSellOrders(cur_price) { +function getUntaggedSellOrders(asset, cur_price) { return new Promise((resolve, reject) => { DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" + - " LEFT JOIN Tags ON Tags.floID = SellOrder.floID" + - " WHERE Tags.floID IS NULL AND SellOrder.minPrice <=? ORDER BY SellOrder.time_placed DESC", [cur_price]) + " LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" + + " WHERE UserTag.floID IS NULL AND SellOrder.asset = ? AND SellOrder.minPrice <=?" + + " ORDER BY SellOrder.time_placed DESC", [asset, cur_price]) .then(orders => resolve(orders)) .catch(error => reject(error)) }) } -function getUntaggedBuyOrders(cur_price) { +function getUntaggedBuyOrders(asset, cur_price) { return new Promise((resolve, reject) => { DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" + - " LEFT JOIN Tags ON Tags.floID = BuyOrder.floID" + - " WHERE Tags.floID IS NULL AND BuyOrder.maxPrice >=? ORDER BY BuyOrder.time_placed DESC", [cur_price]) + " LEFT JOIN UserTag ON UserTag.floID = BuyOrder.floID" + + " WHERE UserTag.floID IS NULL AND BuyOrder.asset = ? AND BuyOrder.maxPrice >=? " + + " ORDER BY BuyOrder.time_placed DESC", [asset, cur_price]) .then(orders => resolve(orders)) .catch(error => reject(error)) }) } -function getSellOrdersInTag(tag, cur_price) { +function getSellOrdersInTag(tag, asset, cur_price) { return new Promise((resolve, reject) => { DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" + - " INNER JOIN Tags ON Tags.floID = SellOrder.floID" + - " WHERE Tags.tag = ? AND SellOrder.minPrice <=? ORDER BY SellOrder.time_placed DESC", [tag, cur_price]).then(orders => { + " INNER JOIN UserTag ON UserTag.floID = SellOrder.floID" + + " WHERE UserTag.tag = ? AND SellOrder.asset = ? AND SellOrder.minPrice <=?" + + " ORDER BY SellOrder.time_placed DESC", [tag, asset, cur_price]).then(orders => { if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required. resolve(orders); else @@ -238,11 +244,12 @@ function getSellOrdersInTag(tag, cur_price) { }); } -function getBuyOrdersInTag(tag, cur_price) { +function getBuyOrdersInTag(tag, asset, cur_price) { return new Promise((resolve, reject) => { DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" + - " INNER JOIN Tags ON Tags.floID = BuyOrder.floID" + - " WHERE Tags.tag = ? AND BuyOrder.maxPrice >=? ORDER BY BuyOrder.time_placed DESC", [tag, cur_price]).then(orders => { + " INNER JOIN UserTag ON UserTag.floID = BuyOrder.floID" + + " WHERE UserTag.tag = ? AND BuyOrder.asset = ? AND BuyOrder.maxPrice >=?" + + " ORDER BY BuyOrder.time_placed DESC", [tag, asset, cur_price]).then(orders => { if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required. resolve(orders); else @@ -287,26 +294,26 @@ function fetch_api(api, id) { }) } -function getTopValidSellOrder(orders, cur_price, mode_null) { +function getTopValidSellOrder(orders, asset, cur_price, mode_null) { return new Promise((resolve, reject) => { if (!orders.length) return reject(false) - verifySellOrder(orders.pop(), cur_price, mode_null) //pop: as the orders are sorted in ascending (highest point should be checked 1st) + verifySellOrder(orders.pop(), asset, cur_price, mode_null) //pop: as the orders are sorted in ascending (highest point should be checked 1st) .then(result => resolve(result)) .catch(error => { if (error !== false) return reject(error); - getTopValidSellOrder(orders, cur_price, mode_null) + getTopValidSellOrder(orders, asset, cur_price, mode_null) .then(result => resolve(result)) .catch(error => reject(error)); }); }); } -function verifySellOrder(sellOrder, cur_price, mode_null) { +function verifySellOrder(sellOrder, asset, cur_price, mode_null) { return new Promise((resolve, reject) => { if (!mode_null) - DB.query("SELECT quantity, base FROM Vault WHERE floID=? AND base IS NOT NULL ORDER BY base", [sellOrder.floID]).then(result => { + DB.query("SELECT quantity, base FROM Vault WHERE floID=? AND asset=? AND base IS NOT NULL ORDER BY base", [sellOrder.floID, asset]).then(result => { let rem = sellOrder.quantity, sell_base = 0, base_quantity = 0; @@ -329,10 +336,10 @@ function verifySellOrder(sellOrder, cur_price, mode_null) { resolve(sellOrder); }).catch(error => reject(error)); else if (mode_null) - DB.query("SELECT SUM(quantity) as total FROM Vault WHERE floID=?", [sellOrder.floID]).then(result => { - if (result.total < sellOrder.quantity) - console.warn(`Sell Order ${sellOrder.id} was made without enough FLO. This should not happen`); - if (result.total > 0) + DB.query("SELECT SUM(quantity) as total FROM Vault WHERE floID=? AND asset=?", [sellOrder.floID, asset]).then(result => { + if (result[0].total < sellOrder.quantity) + console.warn(`Sell Order ${sellOrder.id} was made without enough Assets. This should not happen`); + if (result[0].total > 0) resolve(sellOrder); else reject(false); @@ -358,10 +365,10 @@ function getTopValidBuyOrder(orders, cur_price) { function verifyBuyOrder(buyOrder, cur_price) { return new Promise((resolve, reject) => { - DB.query("SELECT rupeeBalance AS bal FROM Cash WHERE floID=?", [buyOrder.floID]).then(result => { + DB.query("SELECT balance AS bal FROM Cash WHERE floID=?", [buyOrder.floID]).then(result => { if (result[0].bal < cur_price * buyOrder.quantity) { - //This should not happen unless a buy order is placed when user doesnt have enough rupee balance - console.warn(`Buy order ${buyOrder.id} is active, but rupee# is insufficient`); + //This should not happen unless a buy order is placed when user doesnt have enough cash balance + console.warn(`Buy order ${buyOrder.id} is active, but Cash is insufficient`); reject(false); } else resolve(buyOrder); diff --git a/src/main.js b/src/main.js index 42a2b73..fbd3758 100644 --- a/src/main.js +++ b/src/main.js @@ -25,7 +25,7 @@ function refreshData(startup = false) { function refreshDataFromBlockchain() { return new Promise((resolve, reject) => { - DB.query("SELECT num FROM lastTx WHERE floID=?", [floGlobals.adminID]).then(result => { + DB.query("SELECT num FROM LastTx WHERE floID=?", [floGlobals.adminID]).then(result => { let lastTx = result.length ? result[0].num : 0; floBlockchainAPI.readData(floGlobals.adminID, { ignoreOld: lastTx, @@ -34,6 +34,7 @@ function refreshDataFromBlockchain() { }).then(result => { let promises = [], nodes_change = false, + assets_change = false, trusted_change = false; result.data.reverse().forEach(data => { var content = JSON.parse(data)[floGlobals.application]; @@ -42,20 +43,26 @@ function refreshDataFromBlockchain() { nodes_change = true; if (content.Nodes.remove) for (let n of content.Nodes.remove) - promises.push(DB.query("DELETE FROM nodeList WHERE floID=?", [n])); + promises.push(DB.query("DELETE FROM NodeList WHERE floID=?", [n])); if (content.Nodes.add) for (let n in content.Nodes.add) - promises.push(DB.query("INSERT INTO nodeList (floID, uri) VALUE (?,?) AS new ON DUPLICATE KEY UPDATE uri=new.uri", [n, content.Nodes.add[n]])); + promises.push(DB.query("INSERT INTO NodeList (floID, uri) VALUE (?,?) AS new ON DUPLICATE KEY UPDATE uri=new.uri", [n, content.Nodes.add[n]])); + } + //Asset List + if (content.Assets) { + assets_change = true; + for (let a in content.Assets) + promises.push(DB.query("INSERT INTO AssetList (asset, initialPrice) VALUE (?,?) AS new ON DUPLICATE KEY UPDATE initialPrice=new.initialPrice", [a, content.Assets[a]])); } //Trusted List if (content.Trusted) { trusted_change = true; if (content.Trusted.remove) for (let id of content.Trusted.remove) - promises.push(DB.query("DELETE FROM trustedList WHERE floID=?", [id])); + promises.push(DB.query("DELETE FROM TrustedList WHERE floID=?", [id])); if (content.Trusted.add) for (let id of content.Trusted.add) - promises.push(DB.query("INSERT INTO trustedList (floID) VALUE (?) AS new ON DUPLICATE KEY UPDATE floID=new.floID", [id])); + promises.push(DB.query("INSERT INTO TrustedList (floID) VALUE (?) AS new ON DUPLICATE KEY UPDATE floID=new.floID", [id])); } //Tag List with priority and API if (content.Tag) { @@ -71,7 +78,7 @@ function refreshDataFromBlockchain() { promises.push(`UPDATE TagList WHERE tag=? SET ${a}=?`, [t, content.Tag.update[t][a]]); } }); - promises.push(DB.query("INSERT INTO lastTx (floID, num) VALUE (?, ?) AS new ON DUPLICATE KEY UPDATE num=new.num", [floGlobals.adminID, result.totalTxs])); + promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?, ?) AS new ON DUPLICATE KEY UPDATE num=new.num", [floGlobals.adminID, result.totalTxs])); //Check if all save process were successful Promise.allSettled(promises).then(results => { console.debug(results.filter(r => r.status === "rejected")); @@ -80,6 +87,7 @@ function refreshDataFromBlockchain() { }); resolve({ nodes: nodes_change, + assets: assets_change, trusted: trusted_change }); }).catch(error => reject(error)); @@ -92,6 +100,8 @@ function loadDataFromDB(changes, startup) { let promises = []; if (startup || changes.nodes) promises.push(loadDataFromDB.nodeList()); + if (startup || changes.assets) + promises.push(loadDataFromDB.assetList()); if (startup || changes.trusted) promises.push(loadDataFromDB.trustedIDs()); Promise.all(promises) @@ -102,7 +112,7 @@ function loadDataFromDB(changes, startup) { loadDataFromDB.nodeList = function() { return new Promise((resolve, reject) => { - DB.query("SELECT * FROM nodeList").then(result => { + DB.query("SELECT floID, uri FROM NodeList").then(result => { let nodes = {} for (let i in result) nodes[result[i].floID] = result[i].uri; @@ -113,9 +123,23 @@ loadDataFromDB.nodeList = function() { }) } +loadDataFromDB.assetList = function() { + return new Promise((resolve, reject) => { + DB.query("SELECT asset FROM AssetList").then(result => { + let assets = []; + for (let i in result) + assets.push(result[i].asset); + //update dependents + backup.assetList = assets; + app.assetList = assets; + resolve(assets); + }).catch(error => reject(error)) + }) +} + loadDataFromDB.trustedIDs = function() { return new Promise((resolve, reject) => { - DB.query("SELECT * FROM trustedList").then(result => { + DB.query("SELECT * FROM TrustedList").then(result => { let trustedIDs = []; for (let i in result) trustedIDs.push(result[i].floID); diff --git a/src/market.js b/src/market.js index 1458959..8758940 100644 --- a/src/market.js +++ b/src/market.js @@ -3,9 +3,59 @@ const coupling = require('./coupling'); const MINIMUM_BUY_REQUIREMENT = 0.1; -var DB; //container for database +var DB, assetList; //container for database and allowed assets -function addSellOrder(floID, quantity, min_price) { +const getAssetBalance = (floID, asset) => new Promise((resolve, reject) => { + let promises = (asset === floGlobals.currency) ? [ + DB.query("SELECT balance FROM Cash WHERE floID=?", [floID]), + DB.query("SELECT SUM(quantity*maxPrice) AS locked FROM BuyOrder WHERE floID=?", [floID]) + ] : [ + DB.query("SELECT SUM(quantity) AS balance FROM Vault WHERE floID=? AND asset=?", [floID, asset]), + DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset]) + ]; + Promise.all(promises).then(result => resolve({ + total: result[0][0].balance, + locked: result[1][0].locked, + net: result[0][0].balance - result[1][0].locked + })).catch(error => reject(error)) +}); + +getAssetBalance.check = (floID, asset, amount) => new Promise((resolve, reject) => { + getAssetBalance(floID, asset).then(balance => { + if (balance.total < amount) + reject(INVALID(`Insufficient ${asset}`)); + else if (balance.net < amount) + reject(INVALID(`Insufficient ${asset} (Some are locked in orders)`)); + else + resolve(true); + }).catch(error => reject(error)) +}) + +const consumeAsset = (floID, asset, amount, txQueries = []) => new Promise((resolve, reject) => { + //If asset/token is currency update Cash else consume from Vault + if (asset === floGlobals.currency) { + txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [amount, floID]]); + return resolve(txQueries); + } else + DB.query("SELECT id, quantity FROM Vault WHERE floID=? AND asset=? ORDER BY locktime", [floID, asset]).then(coins => { + let rem = amount; + for (let i = 0; i < coins.length && rem > 0; i++) { + if (rem < coins[i].quantity) { + txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]); + rem = 0; + } else { + txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]); + rem -= result[i].quantity; + } + } + if (rem > 0) //should not happen AS the total and net is checked already + reject(INVALID("Insufficient Asset")); + else + resolve(txQueries); + }).catch(error => reject(error)); +}); + +function addSellOrder(floID, asset, quantity, min_price) { return new Promise((resolve, reject) => { if (!floID || !floCrypto.validateAddr(floID)) return reject(INVALID("Invalid FLO ID")); @@ -13,41 +63,33 @@ function addSellOrder(floID, quantity, min_price) { return reject(INVALID(`Invalid quantity (${quantity})`)); else if (typeof min_price !== "number" || min_price <= 0) return reject(INVALID(`Invalid min_price (${min_price})`)); - checkSellRequirement(floID).then(_ => { - DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => { - let total = result.pop()["total"] || 0; - if (total < quantity) - return reject(INVALID("Insufficient FLO")); - DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => { - let locked = result.pop()["locked"] || 0; - let available = total - locked; - if (available < quantity) - return reject(INVALID("Insufficient FLO (Some FLO are locked in another sell order)")); - DB.query("INSERT INTO SellOrder(floID, quantity, minPrice) VALUES (?, ?, ?)", [floID, quantity, min_price]) - .then(result => resolve("Added SellOrder to DB")) - .catch(error => reject(error)); - }).catch(error => reject(error)); - }).catch(error => reject(error)); + else if (!assetList.includes(asset)) + return reject(INVALID(`Invalid asset (${asset})`)); + getAssetBalance.check(floID, asset, quantity).then(_ => { + checkSellRequirement(floID).then(_ => { + DB.query("INSERT INTO SellOrder(floID, asset, quantity, minPrice) VALUES (?, ?, ?, ?)", [floID, asset, quantity, min_price]) + .then(result => resolve("Added SellOrder to DB")) + .catch(error => reject(error)); + }).catch(error => reject(error)) }).catch(error => reject(error)); }); } -function checkSellRequirement(floID) { - return new Promise((resolve, reject) => { - DB.query("SELECT * FROM Tags WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => { - if (result.length) - return resolve(true); - DB.query("SELECT SUM(quantity) AS brought FROM Transactions WHERE buyer=?", [floID]).then(result => { - if (result[0].brought >= MINIMUM_BUY_REQUIREMENT) - resolve(true); - else - reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} FLO before placing a sell order unless they are a Miner`)); - }).catch(error => reject(error)) +const checkSellRequirement = floID => new Promise((resolve, reject) => { + DB.query("SELECT * FROM UserTag WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => { + if (result.length) + return resolve(true); + //TODO: Should seller need to buy same type of asset before selling? + DB.query("SELECT SUM(quantity) AS brought FROM TransactionHistory WHERE buyer=?", [floID]).then(result => { + if (result[0].brought >= MINIMUM_BUY_REQUIREMENT) + resolve(true); + else + reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} FLO before placing a sell order unless they are a Miner`)); }).catch(error => reject(error)) - }) -} + }).catch(error => reject(error)) +}); -function addBuyOrder(floID, quantity, max_price) { +function addBuyOrder(floID, asset, quantity, max_price) { return new Promise((resolve, reject) => { if (!floID || !floCrypto.validateAddr(floID)) return reject(INVALID("Invalid FLO ID")); @@ -55,21 +97,12 @@ function addBuyOrder(floID, quantity, max_price) { return reject(INVALID(`Invalid quantity (${quantity})`)); else if (typeof max_price !== "number" || max_price <= 0) return reject(INVALID(`Invalid max_price (${max_price})`)); - DB.query("SELECT rupeeBalance FROM Cash WHERE floID=?", [floID]).then(result => { - if (result.length < 1) - return reject(INVALID("FLO ID not registered")); - let total = result.pop()["rupeeBalance"]; - if (total < quantity * max_price) - return reject(INVALID("Insufficient Rupee balance")); - DB.query("SELECT SUM(maxPrice * quantity) AS locked FROM BuyOrder WHERE floID=?", [floID]).then(result => { - let locked = result.pop()["locked"] || 0; - let available = total - locked; - if (available < quantity * max_price) - return reject(INVALID("Insufficient Rupee balance (Some rupee tokens are locked in another buy order)")); - DB.query("INSERT INTO BuyOrder(floID, quantity, maxPrice) VALUES (?, ?, ?)", [floID, quantity, max_price]) - .then(result => resolve("Added BuyOrder to DB")) - .catch(error => reject(error)); - }).catch(error => reject(error)); + else if (!assetList.includes(asset)) + return reject(INVALID(`Invalid asset (${asset})`)); + getAssetBalance.check(floID, asset, quantity).then(_ => { + DB.query("INSERT INTO BuyOrder(floID, asset, quantity, maxPrice) VALUES (?, ?, ?, ?)", [floID, asset, quantity, max_price]) + .then(result => resolve("Added BuyOrder to DB")) + .catch(error => reject(error)); }).catch(error => reject(error)); }); } @@ -101,11 +134,11 @@ function cancelOrder(type, id, floID) { function getAccountDetails(floID) { return new Promise((resolve, reject) => { let select = []; - select.push(["rupeeBalance", "Cash"]); - select.push(["base, quantity", "Vault"]); - select.push(["id, quantity, minPrice, time_placed", "SellOrder"]); - select.push(["id, quantity, maxPrice, time_placed", "BuyOrder"]); - let promises = select.map(a => DB.query("SELECT " + a[0] + " FROM " + a[1] + " WHERE floID=?", [floID])); + select.push(["balance", "Cash"]); + select.push(["asset, AVG(base) AS avg_base, SUM(quantity) AS quantity", "Vault", "GROUP BY asset"]); + select.push(["id, asset, quantity, minPrice, time_placed", "SellOrder"]); + select.push(["id, asset, quantity, maxPrice, time_placed", "BuyOrder"]); + let promises = select.map(a => DB.query(`SELECT ${a[0]} FROM ${a[1]} WHERE floID=? ${a[2] || ""}`, [floID])); Promise.allSettled(promises).then(results => { let response = { floID: floID, @@ -117,10 +150,10 @@ function getAccountDetails(floID) { else switch (i) { case 0: - response.rupee_total = a.value[0].rupeeBalance; + response.cash = a.value[0].balance; break; case 1: - response.coins = a.value; + response.vault = a.value; break; case 2: response.sellOrders = a.value; @@ -130,7 +163,7 @@ function getAccountDetails(floID) { break; } }); - DB.query("SELECT * FROM Transactions WHERE seller=? OR buyer=?", [floID, floID]) + DB.query("SELECT * FROM TransactionHistory WHERE seller=? OR buyer=?", [floID, floID]) .then(result => response.transactions = result) .catch(error => console.error(error)) .finally(_ => resolve(response)); @@ -140,7 +173,7 @@ function getAccountDetails(floID) { function depositFLO(floID, txid) { return new Promise((resolve, reject) => { - DB.query("SELECT status FROM inputFLO WHERE txid=? AND floID=?", [txid, floID]).then(result => { + DB.query("SELECT status FROM InputFLO WHERE txid=? AND floID=?", [txid, floID]).then(result => { if (result.length) { switch (result[0].status) { case "PENDING": @@ -151,7 +184,7 @@ function depositFLO(floID, txid) { return reject(INVALID("Transaction already used to add coins")); } } else - DB.query("INSERT INTO inputFLO(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) + DB.query("INSERT INTO InputFLO(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) .then(result => resolve("Deposit request in process")) .catch(error => reject(error)); }).catch(error => reject(error)) @@ -159,19 +192,19 @@ function depositFLO(floID, txid) { } function confirmDepositFLO() { - DB.query("SELECT id, floID, txid FROM inputFLO WHERE status=?", ["PENDING"]).then(results => { + DB.query("SELECT id, floID, txid FROM InputFLO WHERE status=?", ["PENDING"]).then(results => { results.forEach(req => { confirmDepositFLO.checkTx(req.floID, req.txid).then(amount => { let txQueries = []; txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, amount]]); - txQueries.push(["UPDATE inputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]); + txQueries.push(["UPDATE InputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]); DB.transaction(txQueries) - .then(result => console.debug("FLO deposited for ", req.floID)) + .then(result => console.debug("FLO deposited:", req.floID, amount)) .catch(error => console.error(error)) }).catch(error => { console.error(error); if (error[0]) - DB.query("UPDATE inputFLO SET status=? WHERE id=?", ["REJECTED", req.id]) + DB.query("UPDATE InputFLO SET status=? WHERE id=?", ["REJECTED", req.id]) .then(_ => null).catch(error => console.error(error)); }); }) @@ -206,45 +239,23 @@ function withdrawFLO(floID, amount) { return reject(INVALID("Invalid FLO ID")); else if (typeof amount !== "number" || amount <= 0) return reject(INVALID(`Invalid amount (${amount})`)); - DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => { - let total = result.pop()["total"] || 0; - if (total < amount) - return reject(INVALID("Insufficient FLO")); - DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => { - let locked = result.pop()["locked"] || 0; - let available = total - locked; - if (available < amount) - return reject(INVALID("Insufficient FLO (Some FLO are locked in sell orders)")); - DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY locktime", [floID]).then(coins => { - let rem = amount, - txQueries = []; - for (let i = 0; i < coins.length && rem > 0; i++) { - if (rem < coins[i].quantity) { - txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]); - rem = 0; - } else { - txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]); - rem -= coins[i].quantity; - } - } - if (rem > 0) //should not happen AS the total and net is checked already - return reject(INVALID("Insufficient FLO")); - DB.transaction(txQueries).then(result => { - //Send FLO to user via blockchain API - floBlockchainAPI.sendTx(global.myFloID, floID, amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(txid => { - if (!txid) - throw Error("Transaction not successful"); - //Transaction was successful, Add in DB - DB.query("INSERT INTO outputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"]) - .then(_ => null).catch(error => console.error(error)) - .finally(_ => resolve("Withdrawal was successful")); - }).catch(error => { - console.debug(error); - DB.query("INSERT INTO outputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) - .then(_ => null).catch(error => console.error(error)) - .finally(_ => resolve("Withdrawal request is in process")); - }); - }).catch(error => reject(error)); + getAssetBalance.check(floID, "FLO", amount).then(_ => { + consumeAsset(floID, "FLO", amount).then(txQueries => { + DB.transaction(txQueries).then(result => { + //Send FLO to user via blockchain API + floBlockchainAPI.sendTx(global.myFloID, floID, amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(txid => { + if (!txid) + throw Error("Transaction not successful"); + //Transaction was successful, Add in DB + DB.query("INSERT INTO OutputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"]) + .then(_ => null).catch(error => console.error(error)) + .finally(_ => resolve("Withdrawal was successful")); + }).catch(error => { + console.debug(error); + DB.query("INSERT INTO OutputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) + .then(_ => null).catch(error => console.error(error)) + .finally(_ => resolve("Withdrawal request is in process")); + }); }).catch(error => reject(error)); }).catch(error => reject(error)); }).catch(error => reject(error)); @@ -252,13 +263,13 @@ function withdrawFLO(floID, amount) { } function retryWithdrawalFLO() { - DB.query("SELECT id, floID, amount FROM outputFLO WHERE status=?", ["PENDING"]).then(results => { + DB.query("SELECT id, floID, amount FROM OutputFLO WHERE status=?", ["PENDING"]).then(results => { results.forEach(req => { floBlockchainAPI.sendTx(global.myFloID, req.floID, req.amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(txid => { if (!txid) throw Error("Transaction not successful"); //Transaction was successful, Add in DB - DB.query("UPDATE outputFLO SET status=? WHERE id=?", ["WAITING_CONFIRMATION", req.id]) + DB.query("UPDATE OutputFLO SET status=? WHERE id=?", ["WAITING_CONFIRMATION", req.id]) .then(_ => null).catch(error => console.error(error)); }).catch(error => console.error(error)); }) @@ -266,22 +277,22 @@ function retryWithdrawalFLO() { } function confirmWithdrawalFLO() { - DB.query("SELECT id, floID, txid FROM outputFLO WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { + DB.query("SELECT id, floID, amount, txid FROM OutputFLO WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { results.forEach(req => { floBlockchainAPI.getTx(req.txid).then(tx => { if (!tx.blockheight || !tx.confirmations) //Still not confirmed return; - DB.query("UPDATE outputFLO SET status=? WHERE id=?", ["SUCCESS", req.id]) - .then(result => console.debug("FLO withdrawed for ", req.floID)) + DB.query("UPDATE OutputFLO SET status=? WHERE id=?", ["SUCCESS", req.id]) + .then(result => console.debug("FLO withdrawed:", req.floID, req.amount)) .catch(error => console.error(error)) }).catch(error => console.error(error)); }) }).catch(error => console.error(error)); } -function depositRupee(floID, txid) { +function depositToken(floID, txid) { return new Promise((resolve, reject) => { - DB.query("SELECT status FROM inputRupee WHERE txid=? AND floID=?", [txid, floID]).then(result => { + DB.query("SELECT status FROM InputToken WHERE txid=? AND floID=?", [txid, floID]).then(result => { if (result.length) { switch (result[0].status) { case "PENDING": @@ -292,53 +303,58 @@ function depositRupee(floID, txid) { return reject(INVALID("Transaction already used to add tokens")); } } else - DB.query("INSERT INTO inputRupee(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) + DB.query("INSERT INTO InputToken(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) .then(result => resolve("Deposit request in process")) .catch(error => reject(error)); }).catch(error => reject(error)) }); } -function confirmDepositRupee() { - DB.query("SELECT id, floID, txid FROM inputRupee WHERE status=?", ["PENDING"]).then(results => { +function confirmDepositToken() { + DB.query("SELECT id, floID, txid FROM InputToken WHERE status=?", ["PENDING"]).then(results => { results.forEach(req => { - confirmDepositRupee.checkTx(req.floID, req.txid).then(amounts => { - DB.query("SELECT id FROM inputFLO where floID=? AND txid=?", [req.floID, req.txid]).then(result => { + confirmDepositToken.checkTx(req.floID, req.txid).then(amounts => { + DB.query("SELECT id FROM InputFLO where floID=? AND txid=?", [req.floID, req.txid]).then(result => { let txQueries = [], - amount_rupee = amounts[0]; + token_name = amounts[0], + amount_token = amounts[1]; //Add the FLO balance if necessary if (!result.length) { - let amount_flo = amounts[1]; - txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, amount_flo]]); - txQueries.push(["INSERT INTO inputFLO(txid, floID, amount, status) VALUES (?, ?, ?, ?)", [req.txid, req.floID, amount_flo, "SUCCESS"]]); + let amount_flo = amounts[2]; + txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [req.floID, "FLO", amount_flo]]); + txQueries.push(["INSERT INTO InputFLO(txid, floID, amount, status) VALUES (?, ?, ?, ?)", [req.txid, req.floID, amount_flo, "SUCCESS"]]); } - txQueries.push(["UPDATE inputRupee SET status=? WHERE id=?", ["SUCCESS", req.id]]); - txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance+? WHERE floID=?", [amount_rupee, req.floID]]); + txQueries.push(["UPDATE InputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]); + if (token_name === floGlobals.currency) + txQueries.push(["UPDATE Cash SET balance=balance+? WHERE floID=?", [amount_token, req.floID]]); + else + txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [req.floID, token_name, amount_token]]); DB.transaction(txQueries) - .then(result => console.debug("Rupee deposited for ", req.floID)) + .then(result => console.debug("Token deposited:", req.floID, token_name, amount_token)) .catch(error => console.error(error)); }).catch(error => console.error(error)); }).catch(error => { console.error(error); if (error[0]) - DB.query("UPDATE inputRupee SET status=? WHERE id=?", ["REJECTED", req.id]) + DB.query("UPDATE InputToken SET status=? WHERE id=?", ["REJECTED", req.id]) .then(_ => null).catch(error => console.error(error)); }); }) }).catch(error => console.error(error)) } -confirmDepositRupee.checkTx = function(sender, txid) { +confirmDepositToken.checkTx = function(sender, txid) { return new Promise((resolve, reject) => { - const receiver = global.myFloID; //receiver should be market's floID (ie, adminID) + const receiver = global.sinkID; //receiver should be market's floID (ie, sinkID) tokenAPI.getTx(txid).then(tx => { if (tx.parsedFloData.type !== "transfer") return reject([true, "Transaction type not 'transfer'"]); else if (tx.parsedFloData.transferType !== "token") return reject([true, "Transaction transfer is not 'token'"]); - else if (tx.parsedFloData.tokenIdentification !== "rupee") - return reject([true, "Transaction token is not 'rupee'"]); - var amount_rupee = tx.parsedFloData.tokenAmount; + var token_name = tx.parsedFloData.tokenIdentification, + amount_token = tx.parsedFloData.tokenAmount; + if (!assetList.includes(token_name) || token_name === "FLO") + return reject([true, "Token not authorised"]); let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender) if (!vin_sender.length) return reject([true, "Transaction not sent by the sender"]); @@ -346,60 +362,36 @@ confirmDepositRupee.checkTx = function(sender, txid) { if (amount_flo == 0) return reject([true, "Transaction receiver is not market ID"]); else - resolve([amount_rupee, amount_flo]); + resolve([token_name, amount_token, amount_flo]); }).catch(error => reject([false, error])) }) } -function withdrawRupee(floID, amount) { +function withdrawToken(floID, token, amount) { return new Promise((resolve, reject) => { if (!floID || !floCrypto.validateAddr(floID)) return reject(INVALID("Invalid FLO ID")); else if (typeof amount !== "number" || amount <= 0) return reject(INVALID(`Invalid amount (${amount})`)); - DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => { - let required_flo = floGlobals.sendAmt + floGlobals.fee, - total = result.pop()["total"] || 0; - if (total < required_flo) - return reject(INVALID(`Insufficient FLO! Required ${required_flo} FLO to withdraw tokens`)); - DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => { - let locked = result.pop()["locked"] || 0; - let available = total - locked; - if (available < required_flo) - return reject(INVALID(`Insufficient FLO (Some FLO are locked in sell orders)! Required ${required_flo} FLO to withdraw tokens`)); - DB.query("SELECT rupeeBalance FROM Cash WHERE floID=?", [floID]).then(result => { - if (result.length < 1) - return reject(INVALID(`FLO_ID: ${floID} not registered`)); - if (result[0].rupeeBalance < amount) - return reject(INVALID('Insufficient Rupee balance')); - DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY locktime", [floID]).then(coins => { - let rem = required_flo, - txQueries = []; - for (let i = 0; i < coins.length && rem > 0; i++) { - if (rem < coins[i].quantity) { - txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]); - rem = 0; - } else { - txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]); - rem -= result[i].quantity; - } - } - if (rem > 0) //should not happen AS the total and net is checked already - return reject(INVALID("Insufficient FLO")); - txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance-? WHERE floID=?", [amount, floID]]); - + else if (!assetList.includes(token) || token === "FLO") + return reject(INVALID("Invalid Token")); + //Check for FLO balance (transaction fee) + const required_flo = floGlobals.sendAmt + floGlobals.fee; + getAssetBalance.check(floID, "FLO", required_flo).then(_ => { + getAssetBalance.check(floID, token, amount).then(_ => { + consumeAsset(floID, "FLO", required_flo).then(txQueries => { + consumeAsset(floID, token, amount, txQueries).then(txQueries => { DB.transaction(txQueries).then(result => { //Send FLO to user via blockchain API - tokenAPI.sendToken(global.myPrivKey, amount, '(withdrawal from market)', floID).then(txid => { - if (!txid) - throw Error("Transaction not successful"); + tokenAPI.sendToken(global.myPrivKey, amount, floID, '(withdrawal from market)', token).then(txid => { + if (!txid) throw Error("Transaction not successful"); //Transaction was successful, Add in DB - DB.query("INSERT INTO outputRupee (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"]) + DB.query("INSERT INTO OutputToken (floID, token, amount, txid, status) VALUES (?, ?, ?, ?, ?)", [floID, token, amount, txid, "WAITING_CONFIRMATION"]) .then(_ => null).catch(error => console.error(error)) .finally(_ => resolve("Withdrawal was successful")); }).catch(error => { console.debug(error); - DB.query("INSERT INTO outputRupee (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) + DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"]) .then(_ => null).catch(error => console.error(error)) .finally(_ => resolve("Withdrawal request is in process")); }); @@ -411,26 +403,26 @@ function withdrawRupee(floID, amount) { }); } -function retryWithdrawalRupee() { - DB.query("SELECT id, floID, amount FROM outputRupee WHERE status=?", ["PENDING"]).then(results => { +function retryWithdrawalToken() { + DB.query("SELECT id, floID, token, amount FROM OutputToken WHERE status=?", ["PENDING"]).then(results => { results.forEach(req => { - tokenAPI.sendToken(global.myPrivKey, req.amount, '(withdrawal from market)', req.floID).then(txid => { + tokenAPI.sendToken(global.myPrivKey, req.amount, req.floID, '(withdrawal from market)', req.token).then(txid => { if (!txid) throw Error("Transaction not successful"); //Transaction was successful, Add in DB - DB.query("UPDATE outputRupee SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, req.id]) + DB.query("UPDATE OutputToken SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, req.id]) .then(_ => null).catch(error => console.error(error)); }).catch(error => console.error(error)); }); }).catch(error => reject(error)); } -function confirmWithdrawalRupee() { - DB.query("SELECT id, floID, amount, txid FROM outputRupee WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { +function confirmWithdrawalToken() { + DB.query("SELECT id, floID, token, amount, txid FROM OutputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { results.forEach(req => { tokenAPI.getTx(req.txid).then(tx => { - DB.query("UPDATE outputRupee SET status=? WHERE id=?", ["SUCCESS", req.id]) - .then(result => console.debug("Rupee withdrawed for ", req.floID)) + DB.query("UPDATE OutputToken SET status=? WHERE id=?", ["SUCCESS", req.id]) + .then(result => console.debug("Token withdrawed:", req.floID, req.token, req.amount)) .catch(error => console.error(error)); }).catch(error => console.error(error)); }) @@ -439,21 +431,21 @@ function confirmWithdrawalRupee() { function periodicProcess() { blockchainReCheck(); - coupling.initiate(); + assetList.forEach(asset => coupling.initiate(asset)); } function blockchainReCheck() { confirmDepositFLO(); - confirmDepositRupee(); + confirmDepositToken(); retryWithdrawalFLO(); - retryWithdrawalRupee(); + retryWithdrawalToken(); confirmWithdrawalFLO(); - confirmWithdrawalRupee(); + confirmWithdrawalToken(); } module.exports = { - get rate() { - return coupling.price.currentRate; + get rates() { + return coupling.price.currentRates; }, addBuyOrder, addSellOrder, @@ -461,12 +453,15 @@ module.exports = { getAccountDetails, depositFLO, withdrawFLO, - depositRupee, - withdrawRupee, + depositToken, + withdrawToken, periodicProcess, group: coupling.group, set DB(db) { DB = db; coupling.DB = db; + }, + set assetList(assets) { + assetList = assets; } }; \ No newline at end of file diff --git a/src/price.js b/src/price.js index eb833ee..c738075 100644 --- a/src/price.js +++ b/src/price.js @@ -10,41 +10,48 @@ const MIN_TIME = 10 * 1000, // 1 * 60 * 60 * 1000, var DB; //container for database -var cur_rate, //container for FLO price (from API or by model) - lastTime = Date.now(), //container for timestamp of the last tx - noBuyOrder, - noSellOrder; +var currentRate = {}, //container for FLO price (from API or by model) + lastTime = {}, //container for timestamp of the last tx + noBuyOrder = {}, + noSellOrder = {}; -const updateLastTime = () => lastTime = Date.now(); +const updateLastTime = asset => lastTime[asset] = Date.now(); //store FLO price in DB every 1 hr -function storeRate(rate = cur_rate) { - DB.query("INSERT INTO priceHistory (rate) VALUE (?)", rate) +function storeRate(asset, rate) { + DB.query("INSERT INTO PriceHistory (asset, rate) VALUE (?, ?)", [asset, rate]) .then(_ => null).catch(error => console.error(error)) } -setInterval(storeRate, REC_HISTORY_INTERVAL) +setInterval(() => { + for (let asset in currentRate) + storeRate(asset, currentRate[asset]); +}, REC_HISTORY_INTERVAL) -function getPastRate(hrs = 24) { +function getPastRate(asset, hrs = 24) { return new Promise((resolve, reject) => { - DB.query("SELECT rate FROM priceHistory WHERE rec_time >= NOW() - INTERVAL ? hour ORDER BY rec_time LIMIT 1", [hrs]) + DB.query("SELECT rate FROM PriceHistory WHERE asset=? AND rec_time >= NOW() - INTERVAL ? hour ORDER BY rec_time LIMIT 1", [asset, hrs]) .then(result => result.length ? resolve(result[0].rate) : reject('No records found in past 24hrs')) .catch(error => reject(error)) }); } -function loadRate() { +function loadRate(asset) { return new Promise((resolve, reject) => { - if (typeof cur_rate !== "undefined") - return resolve(cur_rate); - DB.query("SELECT rate FROM priceHistory ORDER BY rec_time DESC LIMIT 1").then(result => { + if (typeof currentRate[asset] !== "undefined") + return resolve(currentRate[asset]); + updateLastTime(asset); + DB.query("SELECT rate FROM PriceHistory WHERE asset=? ORDER BY rec_time DESC LIMIT 1", [asset]).then(result => { if (result.length) - resolve(cur_rate = result[0].rate); + resolve(currentRate[asset] = result[0].rate); else - fetchRates().then(rate => resolve(cur_rate = rate)).catch(error => reject(error)); + DB.query("SELECT initialPrice FROM AssetList WHERE asset=?", [asset]) + .then(result => resolve(currentRate[asset] = result[0].initialPrice)) + .catch(error => reject(error)) }).catch(error => reject(error)); }) } +/* function fetchRates() { return new Promise((resolve, reject) => { fetchRates.FLO_USD().then(FLO_rate => { @@ -83,57 +90,58 @@ fetchRates.USD_INR = function() { }).catch(error => reject(error)); }); } +*/ -function getRates() { +function getRates(asset) { return new Promise((resolve, reject) => { - loadRate().then(_ => { - console.debug(cur_rate); + loadRate(asset).then(_ => { + console.debug(asset, currentRate[asset]); let cur_time = Date.now(); - if (cur_time - lastTime < MIN_TIME) //Minimum time to update not crossed: No update required - resolve(cur_rate); - else if (noBuyOrder && noSellOrder) //Both are not available: No update required - resolve(cur_rate); - else if (noBuyOrder === null || noSellOrder === null) //An error has occured during last process: No update (might cause price to crash/jump) - resolve(cur_rate); + if (cur_time - lastTime[asset] < MIN_TIME) //Minimum time to update not crossed: No update required + resolve(currentRate[asset]); + else if (noBuyOrder[asset] && noSellOrder[asset]) //Both are not available: No update required + resolve(currentRate[asset]); + else if (noBuyOrder[asset] === null || noSellOrder[asset] === null) //An error has occured during last process: No update (might cause price to crash/jump) + resolve(currentRate[asset]); else - getPastRate().then(ratePast24hr => { - if (noBuyOrder) { + getPastRate(asset).then(ratePast24hr => { + if (noBuyOrder[asset]) { //No Buy, But Sell available: Decrease the price - let tmp_val = cur_rate * (1 - DOWN_RATE); + let tmp_val = currentRate[asset] * (1 - DOWN_RATE); if (tmp_val >= ratePast24hr * (1 - MAX_DOWN_PER_DAY)) { - cur_rate = tmp_val; - updateLastTime(); + currentRate[asset] = tmp_val; + updateLastTime(asset); } else console.debug("Max Price down for the day has reached"); - resolve(cur_rate); - } else if (noSellOrder) { + resolve(currentRate[asset]); + } else if (noSellOrder[asset]) { //No Sell, But Buy available: Increase the price checkForRatedSellers().then(result => { if (result) { - let tmp_val = cur_rate * (1 + UP_RATE); + let tmp_val = currentRate[asset] * (1 + UP_RATE); if (tmp_val <= ratePast24hr * (1 + MAX_UP_PER_DAY)) { - cur_rate = tmp_val; - updateLastTime(); + currentRate[asset] = tmp_val; + updateLastTime(asset); } else console.debug("Max Price up for the day has reached"); } - }).catch(error => console.error(error)).finally(_ => resolve(cur_rate)); + }).catch(error => console.error(error)).finally(_ => resolve(currentRate[asset])); } }).catch(error => { console.error(error); - resolve(cur_rate); + resolve(currentRate[asset]); }); }).catch(error => reject(error)); }) } -function checkForRatedSellers() { +function checkForRatedSellers(asset) { //Check if there are best rated sellers? return new Promise((resolve, reject) => { DB.query("SELECT MAX(sellPriority) as max_p FROM TagList").then(result => { let ratedMin = result[0].max_p * (1 - TOP_RANGE); DB.query("SELECT COUNT(*) as value FROM SellOrder WHERE floID IN (" + - " SELECT Tags.floID FROM Tags INNER JOIN TagList ON Tags.tag = TagList.tag" + + " SELECT UserTag.floID FROM UserTag INNER JOIN TagList ON UserTag.tag = TagList.tag" + " WHERE TagList.sellPriority > ?)", [ratedMin]).then(result => { resolve(result[0].value > 0); }).catch(error => reject(error)) @@ -144,14 +152,14 @@ function checkForRatedSellers() { module.exports = { getRates, updateLastTime, - noOrder(buy, sell) { - noBuyOrder = buy; - noSellOrder = sell; + noOrder(asset, buy, sell) { + noBuyOrder[asset] = buy; + noSellOrder[asset] = sell; }, set DB(db) { DB = db; }, - get currentRate() { - return cur_rate + get currentRates() { + return Object.assign({}, currentRate); } } \ No newline at end of file diff --git a/src/request.js b/src/request.js index 8f4055e..dc04956 100644 --- a/src/request.js +++ b/src/request.js @@ -22,19 +22,19 @@ const oneDay = 1000 * 60 * 60 * 24; const maxSessionTimeout = 60 * oneDay; var serving; -const INVALID_SERVER_MSG = "Incorrect Server. Please connect to main server."; +const INVALID_SERVER_MSG = "INCORRECT_SERVER_ERROR"; function validateRequestFromFloID(request, sign, floID, proxy = true) { return new Promise((resolve, reject) => { if (!serving) return reject(INVALID(INVALID_SERVER_MSG)); else if (!floCrypto.validateAddr(floID)) - return reject(INVALID.e_code).send("Invalid floID"); - DB.query("SELECT " + (proxy ? "session_time, proxyKey AS pubKey FROM Sessions" : "pubKey FROM Users") + " WHERE floID=?", [floID]).then(result => { + return reject(INVALID("Invalid floID")); + DB.query("SELECT " + (proxy ? "session_time, proxyKey AS pubKey FROM UserSession" : "pubKey FROM Users") + " WHERE floID=?", [floID]).then(result => { if (result.length < 1) return reject(INVALID(proxy ? "Session not active" : "User not registered")); if (proxy && result[0].session_time + maxSessionTimeout < Date.now()) - return reject(INVALID.e_code).send("Session Expired! Re-login required"); + return reject(INVALID("Session Expired! Re-login required")); let req_str = validateRequest(request, sign, result[0].pubKey); req_str instanceof INVALID ? reject(req_str) : resolve(req_str); }).catch(error => reject(error)); @@ -62,6 +62,8 @@ function storeRequest(floID, req_str, sign) { } function getLoginCode(req, res) { + if (!serving) + return res.status(INVALID.e_code).send(INVALID_SERVER_MSG); let randID = floCrypto.randString(8, true) + Math.round(Date.now() / 1000); let hash = Crypto.SHA1(randID + secret); res.send({ @@ -111,7 +113,7 @@ function Login(req, res) { proxyKey: data.proxyKey, timestamp: data.timestamp }, data.sign, data.floID, false).then(req_str => { - DB.query("INSERT INTO Sessions (floID, proxyKey) VALUE (?, ?) AS new " + + DB.query("INSERT INTO UserSession (floID, proxyKey) VALUE (?, ?) AS new " + "ON DUPLICATE KEY UPDATE session_time=DEFAULT, proxyKey=new.proxyKey", [data.floID, data.proxyKey]).then(_ => { storeRequest(data.floID, req_str, data.sign); @@ -136,7 +138,7 @@ function Logout(req, res) { type: "logout", timestamp: data.timestamp }, data.sign, data.floID).then(req_str => { - DB.query("DELETE FROM Sessions WHERE floID=?", [data.floID]).then(_ => { + DB.query("DELETE FROM UserSession WHERE floID=?", [data.floID]).then(_ => { storeRequest(data.floID, req_str, data.sign); res.send('Logout successful'); }).catch(error => { @@ -157,11 +159,12 @@ function PlaceSellOrder(req, res) { let data = req.body; validateRequestFromFloID({ type: "sell_order", + asset: data.asset, quantity: data.quantity, min_price: data.min_price, timestamp: data.timestamp }, data.sign, data.floID).then(req_str => { - market.addSellOrder(data.floID, data.quantity, data.min_price) + market.addSellOrder(data.floID, data.asset, data.quantity, data.min_price) .then(result => { storeRequest(data.floID, req_str, data.sign); res.send('Sell Order placed successfully'); @@ -187,11 +190,12 @@ function PlaceBuyOrder(req, res) { let data = req.body; validateRequestFromFloID({ type: "buy_order", + asset: data.asset, quantity: data.quantity, max_price: data.max_price, timestamp: data.timestamp }, data.sign, data.floID).then(req_str => { - market.addBuyOrder(data.floID, data.quantity, data.max_price) + market.addBuyOrder(data.floID, data.asset, data.quantity, data.max_price) .then(result => { storeRequest(data.floID, req_str, data.sign); res.send('Buy Order placed successfully'); @@ -259,16 +263,16 @@ function ListBuyOrders(req, res) { function ListTransactions(req, res) { //TODO: Limit size (recent) - DB.query("SELECT * FROM Transactions ORDER BY tx_time DESC") + DB.query("SELECT * FROM TransactionHistory ORDER BY tx_time DESC") .then(result => res.send(result)) .catch(error => res.status(INTERNAL.e_code).send("Try again later!")); } -function getRate(req, res) { +function getRates(req, res) { if (!serving) res.status(INVALID.e_code).send(INVALID_SERVER_MSG); else - res.send(`${market.rate}`); + res.send(market.rates); } function Account(req, res) { @@ -349,14 +353,14 @@ function WithdrawFLO(req, res) { }); } -function DepositRupee(req, res) { +function DepositToken(req, res) { let data = req.body; validateRequestFromFloID({ - type: "deposit_Rupee", + type: "deposit_Token", txid: data.txid, timestamp: data.timestamp }, data.sign, data.floID).then(req_str => { - market.depositRupee(data.floID, data.txid).then(result => { + market.depositToken(data.floID, data.txid).then(result => { storeRequest(data.floID, req_str, data.sign); res.send(result); }).catch(error => { @@ -377,14 +381,15 @@ function DepositRupee(req, res) { }); } -function WithdrawRupee(req, res) { +function WithdrawToken(req, res) { let data = req.body; validateRequestFromFloID({ - type: "withdraw_Rupee", + type: "withdraw_Token", + token: data.token, amount: data.amount, timestamp: data.timestamp }, data.sign, data.floID).then(req_str => { - market.withdrawRupee(data.floID, data.amount).then(result => { + market.withdrawToken(data.floID, data.token, data.amount).then(result => { storeRequest(data.floID, req_str, data.sign); res.send(result); }).catch(error => { @@ -479,18 +484,21 @@ module.exports = { ListSellOrders, ListBuyOrders, ListTransactions, - getRate, + getRates, Account, DepositFLO, WithdrawFLO, - DepositRupee, - WithdrawRupee, + DepositToken, + WithdrawToken, periodicProcess: market.periodicProcess, addUserTag, removeUserTag, set trustedIDs(ids) { trustedIDs = ids; }, + set assetList(assets){ + market.assetList = assets; + }, set DB(db) { DB = db; market.DB = db; diff --git a/src/set_globals.js b/src/set_globals.js index b743c93..f402c37 100644 --- a/src/set_globals.js +++ b/src/set_globals.js @@ -11,4 +11,13 @@ try { } finally { for (let p in param) global[p] = param[p]; -} \ No newline at end of file +} + +/* +//Trace the debug logs in node js +var debug = console.debug; +console.debug = function() { + debug.apply(console, arguments); + console.trace(); +}; +*/ \ No newline at end of file diff --git a/src/tokenAPI.js b/src/tokenAPI.js index be96954..4a3ccb0 100644 --- a/src/tokenAPI.js +++ b/src/tokenAPI.js @@ -14,7 +14,7 @@ }).catch(error => reject(error)) }) }, - getBalance: function(floID, token = floGlobals.token) { + getBalance: function(floID, token = floGlobals.currency) { return new Promise((resolve, reject) => { this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`) .then(result => resolve(result.balance || 0)) @@ -35,7 +35,7 @@ }).catch(error => reject(error)) }) }, - sendToken: function(privKey, amount, message = "", receiverID = floGlobals.adminID, token = floGlobals.token) { + sendToken: function(privKey, amount, receiverID, message = "", token = floGlobals.currency) { return new Promise((resolve, reject) => { let senderID = floCrypto.getFloID(privKey); if (typeof amount !== "number" || amount <= 0)