From c7d377d44264b5429adfd8a47706a62b153d4368 Mon Sep 17 00:00:00 2001 From: mbidenaio Date: Wed, 3 Mar 2021 23:52:54 +0500 Subject: [PATCH] Improve logging --- bsc/index.js | 35 +++++++--- idena/index.js | 30 +++++--- index.js | 173 +++++++++++++++++++++++++++++----------------- logger/index.js | 19 +++++ package-lock.json | 52 ++++++++++++++ package.json | 1 + routes/swaps.js | 84 ++++++++++++++++++---- 7 files changed, 300 insertions(+), 94 deletions(-) create mode 100644 logger/index.js diff --git a/bsc/index.js b/bsc/index.js index 4e06bfc..82d5901 100644 --- a/bsc/index.js +++ b/bsc/index.js @@ -5,6 +5,7 @@ const ethers = require('ethers'); const abi = require('./abi.js'); const InputDataDecoder = require('ethereum-input-data-decoder'); require('dotenv').config(); +const logger = require('../logger').child({component: "bsc"}) exports.mint = async function (address, amount) { @@ -27,7 +28,7 @@ exports.mint = async function (address, amount) { fees: parseFloat(fees / 10 ** 18) } } catch (error) { - console.log(error); + logger.error(`Failed to mint: ${error}`); return null } @@ -46,6 +47,7 @@ exports.isValidBurnTx = async function (txHash, address, amount, date) { } return result.inputs[1] } catch (error) { + logger.error(`Failed to extract dest address: ${error}`); return false } } @@ -60,39 +62,52 @@ exports.isValidBurnTx = async function (txHash, address, amount, date) { let txReceipt = await provider.getTransactionReceipt(txHash); if (txReceipt.status !== 1) { + logger.info(`Wrong status, actual: ${txReceipt.status}, expected: 1`); return false } if (txReceipt.logs.length === 0) { + logger.info(`No logs`); return false } if (txReceipt.to.toLowerCase() !== process.env.BSC_CONTRACT.toLowerCase()) { + logger.info(`Wrong recipient, actual: ${txReceipt.to}, expected: ${process.env.BSC_CONTRACT}`); return false } let tx = await provider.getTransaction(txHash) let destAddress = tx && extractDestAddress(tx.data) if (destAddress.toLowerCase() !== address.toLowerCase().slice(2)) { + logger.info(`Wrong dest address, actual: ${destAddress}, expected: ${address}`); return false } - if (contract.interface.parseLog(txReceipt.logs[0]).name !== "Transfer") { + const method = contract.interface.parseLog(txReceipt.logs[0]).name + if (method !== "Transfer") { + logger.info(`Wrong method, actual: ${method}, expected: Transfer`); return false } - if (!(contract.interface.parseLog(txReceipt.logs[0]).args.value >= ethers.utils.parseEther(amount.toString()))) { + const value = contract.interface.parseLog(txReceipt.logs[0]).args.value + if (!(value >= ethers.utils.parseEther(amount.toString()))) { + logger.info(`Wrong value, actual: ${value}, expected: at least ${amount}`); return false } - if (contract.interface.parseLog(txReceipt.logs[0]).args.from.toLowerCase() !== tx.from.toLowerCase()) { + const from = contract.interface.parseLog(txReceipt.logs[0]).args.from + if (from.toLowerCase() !== tx.from.toLowerCase()) { + logger.info(`Wrong sender, actual: ${from}, expected: ${tx.from}`); return false } - if (contract.interface.parseLog(txReceipt.logs[0]).args.to.toLowerCase() !== "0x0000000000000000000000000000000000000000") { + const to = contract.interface.parseLog(txReceipt.logs[0]).args.to + if (to.toLowerCase() !== "0x0000000000000000000000000000000000000000") { + logger.info(`Wrong recipient, actual: ${to}, expected: 0x0000000000000000000000000000000000000000`); return false } const block = await provider.getBlock(tx.blockHash) const blockDate = new Date(block.timestamp * 1000); if (blockDate.getTime() < date.getTime()) { + logger.info("Tx is not actual"); return false } return true } catch (error) { - console.log("Failed to check if burn tx is valid", error); + logger.error(`Failed to check if burn tx is valid: ${error}`); return false } } @@ -107,7 +122,7 @@ exports.isTxExist = async function (txHash) { return false } } catch (error) { - console.log(error); + logger.error(`Failed to check if tx exists: ${error}`); return false } } @@ -121,7 +136,7 @@ exports.isTxConfirmed = async function (txHash) { return false } } catch (error) { - console.log(error); + logger.error(`Failed to check if tx is confirmed: ${error}`); return false } @@ -137,7 +152,7 @@ exports.getContractAddress = function () { async function getIdenaPrice() { let resp = await axios.get("https://api.coingecko.com/api/v3/simple/price?ids=idena&vs_currencies=bnb"); - if (resp.status == 200 && resp.data.idena.bnb) { + if (resp.status === 200 && resp.data.idena.bnb) { return ethers.utils.parseEther(resp.data.idena.bnb.toString()); } else { return 0 @@ -153,7 +168,7 @@ exports.isNewTx = async function (tx) { return true } } catch (error) { - console.log(error); + logger.error(`Failed to check if tx is new: ${error}`); return false } } \ No newline at end of file diff --git a/idena/index.js b/idena/index.js index 3114afd..6572413 100644 --- a/idena/index.js +++ b/idena/index.js @@ -5,12 +5,14 @@ const { axios = require("axios"), fs = require('fs'); require('dotenv').config(); +const logger = require('../logger').child({component: "idena"}) exports.send = async function (address, amount) { try { let epoch = await getEpoch(); let nonce = await getNonce(epoch); if (nonce !== null && epoch !== null) { + logger.info(`Sending idena tx, address: ${address}, amount: ${amount}, epoch: ${epoch}, nonce: ${nonce}`) amount = parseFloat(amount) - parseFloat(process.env.IDENA_FIXED_FEES) const tx = await new Transaction( nonce, @@ -38,7 +40,7 @@ exports.send = async function (address, amount) { } } catch (error) { - console.log(error); + logger.error(`Failed to send tx: ${error}`); return null } @@ -54,7 +56,7 @@ async function getTransaction(tx) { }); return transaction.data.result || null } catch (error) { - console.error("Failed to get idena transaction:", error); + logger.error(`Failed to get tx: ${error}`); return null } } @@ -79,7 +81,7 @@ exports.isTxConfirmed = async function (tx) { }); return bcn_syncing.data.result.highestBlock > bcn_block.data.result.height + parseInt(process.env.IDENA_CONFIRMATIONS_BLOCKS) || false } catch (error) { - console.log(error); + logger.error(`Failed to check if tx is confirmed: ${error}`); return false } } @@ -89,6 +91,7 @@ exports.isTxActual = async function (txHash, date) { const transaction = await getTransaction(txHash); return await isTxActual(transaction, date) } catch (error) { + logger.error(`Failed to check if tx is actual: ${error}`); return false } } @@ -97,6 +100,7 @@ async function isTxActual(tx, date) { try { return new Date(tx.timestamp * 1000).getTime() >= date.getTime() } catch (error) { + logger.error(`Failed to check if tx is actual: ${error}`); return false } } @@ -111,6 +115,7 @@ async function getEpoch() { }) return apiResp.data.result.epoch; } catch (error) { + logger.error(`Failed to get epoch: ${error}`); return null } } @@ -134,7 +139,7 @@ async function getNonce(epoch) { return null } } catch (error) { - console.log(error); + logger.error(`Failed to get nonce: ${error}`); return null } } @@ -149,6 +154,7 @@ exports.isValidSendTx = async function (txHash, address, amount, date) { } return comment.substring(prefix.length) } catch (error) { + logger.error(`Failed to extract dest address: ${error}`); return false } } @@ -156,27 +162,34 @@ exports.isValidSendTx = async function (txHash, address, amount, date) { try { let transaction = await getTransaction(txHash); if (!transaction) { + logger.info("No tx"); return false } const destAddress = extractDestAddress(transaction.payload) if (!destAddress || destAddress.toLowerCase() !== address.toLowerCase()) { + logger.info(`Wrong dest address, actual: ${destAddress}, expected: ${address}`); return false } - if (transaction.to !== privateKeyToAddress(process.env.IDENA_PRIVATE_KEY)) { + const recipient = privateKeyToAddress(process.env.IDENA_PRIVATE_KEY) + if (transaction.to !== recipient) { + logger.info(`Wrong tx recipient, actual: ${transaction.to}, expected: ${recipient}`); return false } if (!(parseFloat(transaction.amount) >= parseFloat(amount))) { + logger.info(`Wrong tx amount, actual: ${transaction.amount}, expected: at least ${amount}`); return false } if (transaction.type !== "send") { + logger.info(`Wrong tx type, actual: ${transaction.type}, expected: send`); return false } if (transaction.timestamp && !await isTxActual(transaction, date)) { + logger.info("Tx is not actual"); return false } return true } catch (error) { - console.log(error); + logger.error(`Failed to check if idena tx is valid: ${error}`); return false } } @@ -190,7 +203,7 @@ exports.isTxExist = async function (txHash) { return false } } catch (error) { - console.log(error); + logger.error(`Failed to check if tx exists: ${error}`); return false } } @@ -198,6 +211,7 @@ exports.isTxExist = async function (txHash) { exports.getWalletAddress = function () { return privateKeyToAddress(process.env.IDENA_PRIVATE_KEY); } + exports.isNewTx = async function (tx) { try { const [data] = await db.promise().execute("SELECT `id` FROM `used_txs` WHERE `tx_hash` = ? AND `blockchain` = 'idena';", [tx]); @@ -207,7 +221,7 @@ exports.isNewTx = async function (tx) { return true } } catch (error) { + logger.error(`Failed to check if tx is new: ${error}`); return false } - } \ No newline at end of file diff --git a/index.js b/index.js index 2966424..df6822a 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,10 @@ const express = require('express'), idena = require('./idena'), bsc = require('./bsc'); +const logger = require('./logger').child({component: "processing"}) +logger.info('Idena bridge started') +console.log('Idena bridge started') + db = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USERNAME, @@ -14,98 +18,138 @@ db = mysql.createPool({ timezone: 'UTC' }); -async function checkSwap(swap, conP) { - if (swap.type === 0 && swap.idena_tx) { - if (await idena.isTxExist(swap.idena_tx)) { - if (await idena.isValidSendTx(swap.idena_tx, swap.address, swap.amount, swap.time) && await idena.isNewTx(swap.idena_tx)) { - if (await idena.isTxConfirmed(swap.idena_tx)) { - if (!await idena.isTxActual(swap.idena_tx, swap.time)) { - // not valid (not actual) - await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) - return - } - // confirmed - const [data2] = await conP.execute("INSERT INTO `used_txs`(`blockchain`,`tx_hash`) VALUES ('idena',?);", [swap.idena_tx]); - if (!data2.insertId) { - return - } - let { - hash, - fees - } = await bsc.mint(swap.address, swap.amount); - if (!hash) { - await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`mined` = '1' ,`fail_reason` = 'Unknown' WHERE `uuid` = ?", [swap.uuid]) - return - } - await conP.execute("UPDATE `swaps` SET `status` = 'Success' ,`mined` = '1' ,`bsc_tx` = ? ,`fees` = ? WHERE `uuid` = ?", [hash, fees, swap.uuid]) - return - } - // waiting to be confirmed - await conP.execute("UPDATE `swaps` SET `mined` = '0' WHERE `uuid` = ?", [swap.uuid]) - return - } - // not new or not valid - await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) - return - } +async function handleIdenaToBscSwap(swap, conP, logger) { + if (!await idena.isTxExist(swap.idena_tx)) { let date = Date.parse(swap.time); date.setDate(date.getDate() + 1); if (date < Date.now()) { + logger.info("Swap is outdated") await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`fail_reason` = 'Time' WHERE `uuid` = ?", [swap.uuid]) } + logger.trace("Swap skipped") return } - if (swap.type === 1 && swap.bsc_tx) { - if (await bsc.isValidBurnTx(swap.bsc_tx, swap.address, swap.amount, swap.time) && await bsc.isNewTx(swap.bsc_tx)) { - if (await bsc.isTxConfirmed(swap.bsc_tx)) { - // confirmed - const [data2] = await conP.execute("INSERT INTO `used_txs`(`blockchain`,`tx_hash`) VALUES ('bsc',?);", [swap.bsc_tx]); - if (!data2.insertId) { - return - } - let { - hash, - fees, - errorMessage - } = await idena.send(swap.address, swap.amount); - if (!hash) { - const reason = errorMessage ? errorMessage : 'Unknown' - await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`mined` = '1' ,`fail_reason` = ? WHERE `uuid` = ?", [reason, swap.uuid]) - return - } - await conP.execute("UPDATE `swaps` SET `status` = 'Success' ,`mined` = '1' ,`idena_tx` = ? , `fees` = ? WHERE `uuid` = ?", [hash, fees, swap.uuid]) - return - } - // waiting to be confirmed - await conP.execute("UPDATE `swaps` SET `mined` = '0' WHERE `uuid` = ?", [swap.uuid]) - return - } - // not new or not valid + if (!await idena.isValidSendTx(swap.idena_tx, swap.address, swap.amount, swap.time)) { + // not valid + logger.info("Idena tx is invalid") + await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) + return + } + if (!await idena.isNewTx(swap.idena_tx)) { + // not new + logger.info("Idena tx already used") + await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) + return + } + if (!await idena.isTxConfirmed(swap.idena_tx)) { + // waiting to be confirmed + logger.debug("Idena tx is not confirmed") + await conP.execute("UPDATE `swaps` SET `mined` = '0' WHERE `uuid` = ?", [swap.uuid]) + return + } + if (!await idena.isTxActual(swap.idena_tx, swap.time)) { + // not valid (not actual) + logger.info("Idena tx is not actual") + await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) + return + } + // confirmed + const [data] = await conP.execute("INSERT INTO `used_txs`(`blockchain`,`tx_hash`) VALUES ('idena',?);", [swap.idena_tx]); + if (!data.insertId) { + logger.error("Unable to insert used idena tx") + return + } + let { + hash, + fees + } = await bsc.mint(swap.address, swap.amount); + if (!hash) { + logger.error("Unable to mint bsc coins") + await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`mined` = '1' ,`fail_reason` = 'Unknown' WHERE `uuid` = ?", [swap.uuid]) + return + } + logger.info(`Swap completed, bsc tx hash: ${hash}, fees: ${fees}`) + await conP.execute("UPDATE `swaps` SET `status` = 'Success' ,`mined` = '1' ,`bsc_tx` = ? ,`fees` = ? WHERE `uuid` = ?", [hash, fees, swap.uuid]) +} + +async function handleBscToIdenaSwap(swap, conP, logger) { + if (!await bsc.isValidBurnTx(swap.bsc_tx, swap.address, swap.amount, swap.time)) { + // not valid + logger.info("BSC tx is invalid") await conP.execute("UPDATE `swaps` SET `status` = 'Fail' , `mined` = '2' , `fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) + } + if (!await bsc.isNewTx(swap.bsc_tx)) { + // not new + logger.info("BSC tx already used") + await conP.execute("UPDATE `swaps` SET `status` = 'Fail' , `mined` = '2' , `fail_reason` = 'Not Valid' WHERE `uuid` = ?", [swap.uuid]) + } + if (!await bsc.isTxConfirmed(swap.bsc_tx)) { + // waiting to be confirmed + logger.debug("BSC tx is not confirmed") + await conP.execute("UPDATE `swaps` SET `mined` = '0' WHERE `uuid` = ?", [swap.uuid]) + } + // confirmed + const [data2] = await conP.execute("INSERT INTO `used_txs`(`blockchain`,`tx_hash`) VALUES ('bsc',?);", [swap.bsc_tx]); + if (!data2.insertId) { + logger.error("Unable to insert used BSC tx") + return + } + let { + hash, + fees, + errorMessage + } = await idena.send(swap.address, swap.amount); + if (!hash) { + const reason = errorMessage ? errorMessage : 'Unknown' + logger.error(`Unable to send idena tx: ${reason}`) + await conP.execute("UPDATE `swaps` SET `status` = 'Fail' ,`mined` = '1' ,`fail_reason` = ? WHERE `uuid` = ?", [reason, swap.uuid]) return } + logger.info(`Swap completed, idena tx hash: ${hash}`) + await conP.execute("UPDATE `swaps` SET `status` = 'Success' ,`mined` = '1' ,`idena_tx` = ? , `fees` = ? WHERE `uuid` = ?", [hash, fees, swap.uuid]) +} + +async function handleSwap(swap, conP, logger) { + if (swap.type === 0 && swap.idena_tx) { + await handleIdenaToBscSwap(swap, conP, logger) + return + } + + if (swap.type === 1 && swap.bsc_tx) { + await handleBscToIdenaSwap(swap, conP, logger) + return + } + let date = new Date(swap.time); date.setDate(date.getDate() + 1); if (date < Date.now()) { + logger.info("Swap is outdated") await conP.execute("UPDATE `swaps` SET `status` = 'Fail' , `mined` = '2' ,`fail_reason` = 'Time' WHERE `uuid` = ?", [swap.uuid]) } + logger.trace("Swap skipped") } async function checkSwaps() { + logger.trace("Starting to check swaps") let conP = db.promise(); let sql = "SELECT * FROM `swaps` WHERE `status` = 'Pending';"; let data try { [data] = await conP.execute(sql); } catch (error) { - console.error(`Failed to load pending swaps: ${error}`); + logger.error(`Failed to load pending swaps: ${error}`); + return + } + if (!data.length) { return } + logger.trace(`Starting to handle pending swaps, cnt: ${data.length}`) for (swap of data) { + const swapLogger = logger.child({swapId: swap.uuid}) try { - await checkSwap(swap, conP) + await handleSwap(swap, conP, swapLogger) } catch (error) { - console.error(`Failed to handle swap ${swap.uuid}: ${error}`); + swapLogger.error(`Failed to handle swap: ${error}`); } } } @@ -116,6 +160,7 @@ async function loopCheckSwaps() { } loopCheckSwaps(); + const swaps = require('./routes/swaps'); app.use(cors()) app.use(bodyParser.json()); @@ -123,4 +168,4 @@ app.use('/swaps', swaps); var port = 8000; -app.listen(port, () => console.log(`Server started, listening on port: ${port}`)); \ No newline at end of file +app.listen(port, () => logger.info(`Server started, listening on port: ${port}`)); \ No newline at end of file diff --git a/logger/index.js b/logger/index.js new file mode 100644 index 0000000..7055a4d --- /dev/null +++ b/logger/index.js @@ -0,0 +1,19 @@ +const path = require('path') +const pino = require('pino') +const fs = require('fs'); + +const logDir = process.env.LOG_DIR +if (logDir && !fs.existsSync(logDir)){ + fs.mkdirSync(logDir, { recursive: true }); +} + +const logger = pino( + { + level: process.env.LOG_LEVEL || 'info', + base: {pid: process.pid}, + timestamp: () => `,"time":"${new Date().toISOString()}"`, + }, + path.join(logDir || '', 'idena-bridge.log') +) + +module.exports = logger; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 77c634c..0348ea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1449,6 +1449,11 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, + "atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, "axios": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", @@ -2160,6 +2165,16 @@ "vary": "~1.1.2" } }, + "fast-redact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.0.tgz", + "integrity": "sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w==" + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -2182,6 +2197,11 @@ "locate-path": "^2.0.0" } }, + "flatstr": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", + "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" + }, "follow-redirects": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", @@ -2709,6 +2729,24 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, + "pino": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.11.1.tgz", + "integrity": "sha512-PoDR/4jCyaP1k2zhuQ4N0NuhaMtei+C9mUHBRRJQujexl/bq3JkeL2OC23ada6Np3zeUMHbO4TGzY2D/rwZX3w==", + "requires": { + "fast-redact": "^3.0.0", + "fast-safe-stringify": "^2.0.7", + "flatstr": "^1.0.12", + "pino-std-serializers": "^3.1.0", + "quick-format-unescaped": "^4.0.1", + "sonic-boom": "^1.0.2" + } + }, + "pino-std-serializers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", + "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -2728,6 +2766,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "quick-format-unescaped": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", + "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==" + }, "quick-lru": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", @@ -2928,6 +2971,15 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "sonic-boom": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.3.2.tgz", + "integrity": "sha512-/B4tAuK2+hIlR94GhhWU1mJHWk5lt0CEuBvG0kvk1qIAzQc4iB1TieMio8DCZxY+Y7tsuzOxSUDOGmaUm3vXMg==", + "requires": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", diff --git a/package.json b/package.json index 48690b4..0d1e9ae 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "js-sha3": "^0.8.0", "mysql2": "^2.2.5", "nconf": "^0.11.1", + "pino": "^6.11.1", "secp256k1": "^4.0.2", "uuid": "^8.3.2" }, diff --git a/routes/swaps.js b/routes/swaps.js index dcfe62b..63a9524 100644 --- a/routes/swaps.js +++ b/routes/swaps.js @@ -9,23 +9,48 @@ const { const { ethers } = require('ethers'); +const logger = require('../logger').child({component: "api"}) -router.get('/latest', function (req, res) { +router.get('/latest', async function (req, res) { + try { + await latest(req, res) + } catch (error) { + logger.error(`Failed ${req.path}: ${error}`) + res.sendStatus(500) + } +}); + +async function latest(req, res) { + const reqInfo = req.path + logger.debug(`Got ${reqInfo}`) let sql = "SELECT `address`,`type`,`amount`,`status`,`time` FROM `swaps` ORDER BY time DESC LIMIT 50;"; - db.query(sql, function (err, result, fields) { - if (err) { - console.error(`Failed to handle request '/latest': ${err}`) + db.query(sql, function (error, result, fields) { + if (error) { + logger.error(`Failed ${reqInfo}: ${error}`) res.sendStatus(500) return } + logger.debug(`Completed ${reqInfo}`) res.status(200).json({ result: result }) }) -}); +} router.get('/info/:uuid', async function (req, res) { + try { + await info(req, res) + } catch (error) { + logger.error(`Failed ${req.path}: ${error}`) + res.sendStatus(500) + } +}); + +async function info(req, res) { + const reqInfo = req.path + logger.debug(`Got ${reqInfo}`) if (!uuid.validate(req.params.uuid)) { + logger.debug(`Bad request ${reqInfo}`) res.sendStatus(400); return } @@ -33,29 +58,44 @@ router.get('/info/:uuid', async function (req, res) { db.promise().execute(sql, [req.params.uuid]) .then(([data, fields]) => { if (!data[0]) { + logger.debug(`Not found ${reqInfo}`) res.sendStatus(404); return } + logger.debug(`Completed ${reqInfo}`) res.status(200).json({ result: data[0] }) }) .catch(err => { - console.error(`Failed to handle request '/info/${req.params.uuid}': ${err}`) + logger.error(`Failed ${reqInfo}: ${err}`) res.sendStatus(500); }); -}); +} router.post('/assign', async function (req, res) { + try { + await assign(req, res) + } catch (error) { + logger.error(`Failed ${req.path} (uuid=${req.body.uuid}): ${error}`) + res.sendStatus(500) + } +}); + +async function assign(req, res) { + const reqInfo = `${req.path} (uuid=${req.body.uuid}, tx=${req.body.tx})` + logger.debug(`Got ${reqInfo}`) if (!uuid.validate(req.body.uuid)) { + logger.debug(`Bad request ${reqInfo}`) res.sendStatus(400); return } + function reject(err) { - const errorMessagePrefix = `Failed to handle request '/assign', uuid=${req.body.uuid}:` - console.error(`${errorMessagePrefix} ${err}`) + logger.error(`Failed ${reqInfo}: ${err}`) res.sendStatus(500); } + let conP = db.promise(); let sql = "SELECT `uuid`,`amount`,`address`,`type`,`idena_tx`,`bsc_tx`, `time` FROM `swaps` WHERE `uuid` = ? LIMIT 1;"; let data @@ -71,15 +111,18 @@ router.post('/assign', async function (req, res) { if (await idena.isValidSendTx(req.body.tx, data[0].address, data[0].amount, data[0].time) && await idena.isNewTx(req.body.tx)) { sql = "UPDATE `swaps` SET `idena_tx` = ? WHERE `uuid` = ? ;"; conP.execute(sql, [req.body.tx, req.body.uuid]).then(() => { + logger.debug(`Completed ${reqInfo}`) res.sendStatus(200); }).catch(reject) return } + logger.debug(`Bad request ${reqInfo}`) res.sendStatus(400); return } sql = "UPDATE `swaps` SET `idena_tx` = ? WHERE `uuid` = ?;"; conP.query(sql, [req.body.tx, req.body.uuid]).then(() => { + logger.debug(`Completed ${reqInfo}`) res.sendStatus(200); }).catch(reject) return @@ -89,26 +132,42 @@ router.post('/assign', async function (req, res) { if (await bsc.isValidBurnTx(req.body.tx, data[0].address, data[0].amount, data[0].time) && await bsc.isNewTx(req.body.tx)) { sql = "UPDATE `swaps` SET `bsc_tx` = ? WHERE `uuid` = ?;"; conP.query(sql, [req.body.tx, req.body.uuid]).then(() => { + logger.debug(`Completed ${reqInfo}`) res.sendStatus(200); }).catch(reject) return } + logger.debug(`Bad request ${reqInfo}`) res.sendStatus(400); return } sql = "UPDATE `swaps` SET `bsc_tx` = ? WHERE `uuid` = ?;"; conP.query(sql, [req.body.tx, req.body.uuid]).then(() => { + logger.debug(`Completed ${reqInfo}`) res.sendStatus(200); }).catch(reject) return } + logger.debug(`Bad request ${reqInfo}`) res.sendStatus(400); +} + +router.post('/create', async function (req, res) { + try { + await create(req, res) + } catch (error) { + logger.error(`Failed ${req.path} (type=${req.body.type}, amount=${req.body.amount}, address=${req.body.address}): ${error}`) + res.sendStatus(500) + } }); -router.post('/create', function (req, res) { +async function create(req, res) { + const reqInfo = `${req.path} (type=${req.body.type}, amount=${req.body.amount}, address=${req.body.address})` + logger.debug(`Got ${reqInfo}`) let type = parseInt(req.body.type); let amount = parseFloat(req.body.amount); if (!utils.isAddress(req.body.address) || (type !== 0 && type !== 1) || !(amount >= process.env.MIN_SWAP)) { + logger.debug(`Bad request ${reqInfo}`) res.sendStatus(400); return } @@ -122,16 +181,17 @@ router.post('/create', function (req, res) { ]; db.execute(sql, values, function (err, data, fields) { if (err) { - console.error(`Failed to handle request '/create': ${err}`) + logger.error(`Failed to handle request '/create': ${err}`) res.sendStatus(500) return } + logger.debug(`Completed ${reqInfo}: ${newUUID}`) res.status(200).json({ result: { "uuid": newUUID } }) }) -}); +} module.exports = router; \ No newline at end of file