diff --git a/app.js b/app.js index 0461959..64e350f 100644 --- a/app.js +++ b/app.js @@ -16,7 +16,7 @@ var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'jade'); +app.set('view engine', 'pug'); // uncomment after placing your favicon in /public app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); diff --git a/bin/createUserAndDb.js b/bin/createUserAndDb.js new file mode 100644 index 0000000..8807390 --- /dev/null +++ b/bin/createUserAndDb.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +var exec = require('child_process').exec; +var models = require('../models'); +var env = require('../config/config.json')['env'] || 'development'; +var config = require(__dirname + '/../config/config.json')['database'][env]; + +if (process.argv.length < 4) { + console.log('Provide root user name and password for mysql'); + process.exit(0); +} + +const dropUserDB = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "drop database ${config.database};drop user ${config.username}"` +const createdb = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "create database ${config.database}"`; +const createUser = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "create user ${config.username} identified by '${config.password}'"`; +const grantAccess = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "grant all on ${config.database}.* to ${config.username}"`; + +exec(dropUserDB, function(err,stdout,stderr) { + console.log(stdout); + exec(createdb, function(err,stdout,stderr) { + if (err) { + console.log(err); + process.exit(0); + } else { + console.log(stdout); + exec(createUser, function(err, stdout, stderr) { + if (err) { + console.log(err); + process.exit(0); + } else { + console.log(stdout); + exec(grantAccess, function(err, stdout, stderr) { + if (err) { + console.log(err); + } else { + console.log(`\nUSER (${config.username}) AND DATABASE (${config.database}) CREATED SUCCESSFULLY`); + console.log(stdout); + } + }); + } + }); + } + }); +}); diff --git a/bin/initdb.js b/bin/initdb.js index 9500b56..ea66b58 100644 --- a/bin/initdb.js +++ b/bin/initdb.js @@ -1,51 +1,12 @@ #!/usr/bin/env node -var exec = require('child_process').exec; var models = require('../models'); -var env = require('../config/config.json')['env'] || 'development'; -var config = require(__dirname + '/../config/config.json')['database'][env]; -if (process.argv.length < 4) { - console.log('Provide root user name and password for mysql'); +models.sequelize.sync({force: true}) +.then(() => { + console.log('Tables created'); + process.exit(0); +}) +.catch((err) => { + console.log(err); process.exit(0); -} - -const dropUserDB = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "drop database ${config.database};drop user ${config.username}"` -const createdb = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "create database ${config.database}"`; -const createUser = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "create user ${config.username} identified by '${config.password}'"`; -const grantAccess = `mysql -u${process.argv[2]} -p${process.argv[3]} -e "grant all on ${config.database}.* to ${config.username}"`; - -exec(dropUserDB, function(err,stdout,stderr) { - console.log(stdout); - exec(createdb, function(err,stdout,stderr) { - if (err) { - console.log(err); - process.exit(0); - } else { - console.log(stdout); - exec(createUser, function(err, stdout, stderr) { - if (err) { - console.log(err); - process.exit(0); - } else { - console.log(stdout); - exec(grantAccess, function(err, stdout, stderr) { - if (err) { - console.log(err); - } else { - console.log(stdout); - models.sequelize.sync({force: true}) - .then(() => { - console.log(`\nUSER (${config.username}) AND DATABASE (${config.database}) CREATED SUCCESSFULLY`); - process.exit(0); - }) - .catch((err) => { - console.log(err); - process.exit(0); - }); - } - }); - } - }); - } - }); }); diff --git a/bin/syncBlockchain.js b/bin/syncBlockchain.js index 99dab88..20b16ed 100644 --- a/bin/syncBlockchain.js +++ b/bin/syncBlockchain.js @@ -39,7 +39,6 @@ async function saveTransaction(txid, blockHeight) { id: 1 })); const tx = JSON.parse(res_tx)['result']; - // console.log('tx:', tx); if (tx === null) { await models.Failure.create({ msg: `${txid} fetching failed`, @@ -50,11 +49,17 @@ async function saveTransaction(txid, blockHeight) { txid: tx.txid, BlockHeight: blockHeight, }); + + // Loop over vouts for (var i = 0; i < tx.vout.length; i++) { const vout = tx.vout[i]; + const m_vout = await models.Vout.create({ + n: vout.n, value: vout.value, }); + + // Loop over addresses in vout for (var y = 0; y < vout.scriptPubKey.addresses.length; y++) { const address = vout.scriptPubKey.addresses[y]; let m_address = await models.Address.findOne({ @@ -69,18 +74,26 @@ async function saveTransaction(txid, blockHeight) { } await m_vout.addAddresses(m_address); } - await transaction.addVouts(m_vout); + await transaction.addVouts(m_vout, {through: {direction: 1}}); } for (var i = 0; i < tx.vin.length; i++) { const vin = tx.vin[i]; if (vin.txid) { - const trans = await models.Transaction.findOne({ + const vout = await models.Vout.findAll({ + include: { + model: models.Transaction, + where: { + txid: vin.txid, + }, + }, where: { - txid: vin.txid, + n: vin.vout, }, }); - if (trans) { - await transaction.addTxtx(trans); + if (vout) { + await transaction.addVouts(vout, { through: { direction: 0, }, }); + } else { + throw('Couldnt find vout for VIN'); } } } diff --git a/bin/www b/bin/www index 176e1b5..813b522 100755 --- a/bin/www +++ b/bin/www @@ -8,12 +8,13 @@ var app = require('../app'); var debug = require('debug')('gostexplr:server'); var http = require('http'); var models = require('../models'); +var config = require('../config/config.json'); /** * Get port from environment and store in Express. */ -var port = normalizePort(process.env.PORT || '3000'); +var port = config['port'] || '3000'; app.set('port', port); /** diff --git a/config/config.json.example b/config/config.json.example index ae6b1fe..8c7e5fa 100644 --- a/config/config.json.example +++ b/config/config.json.example @@ -30,5 +30,6 @@ "hostname": "127.0.0.1", "port": 9376 }, - "env": "production" + "env": "production", + "port": 3000 } diff --git a/models/transaction.js b/models/transaction.js index dc52897..e54ae64 100644 --- a/models/transaction.js +++ b/models/transaction.js @@ -11,21 +11,21 @@ module.exports = (sequelize, DataTypes) => { }], }); - const TxToTx = sequelize.define('TxToTx', {}, { + const TransactionVouts = sequelize.define('TransactionVouts', { + 'direction': DataTypes.TINYINT(1), + }, { timestamps: false, }); - Transaction.belongsToMany(Transaction, { through: TxToTx, as: 'txtx' }); - Transaction.associate = function (models) { models.Transaction.belongsTo(models.Block, { onDelete: "CASCADE", foreignKey: { allowNull: false - } + }, }); - models.Transaction.hasMany(models.Vout); + models.Transaction.belongsToMany(models.Vout, { through: 'TransactionVouts' }); }; return Transaction; diff --git a/models/vout.js b/models/vout.js index 8fc46b5..2125497 100644 --- a/models/vout.js +++ b/models/vout.js @@ -2,19 +2,15 @@ module.exports = (sequelize, DataTypes) => { const Vout = sequelize.define('Vout', { + n: DataTypes.MEDIUMINT.UNSIGNED, value: DataTypes.DECIMAL(16, 8), }, { timestamps: false, }); Vout.associate = function (models) { - models.Vout.belongsTo(models.Transaction, { - onDelete: "CASCADE", - foreignKey: { - allowNull: false - } - }); models.Vout.belongsToMany(models.Address, { through: 'AddressVout' }); + models.Vout.belongsToMany(models.Transaction, { through: 'TransactionVouts' }); }; return Vout; diff --git a/package.json b/package.json index 64c6b5e..ebbcdba 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "start": "node ./bin/www", "initdb": "node ./bin/initdb.js", + "createUserAndDb": "node ./bin/createUserAndDb.js", "syncBlockchain": "node ./bin/syncBlockchain.js", "addN": "node ./bin/addNToVouts.js" }, @@ -22,10 +23,10 @@ "debug": "~2.6.9", "express": "~4.15.5", "forever": "^0.15.3", - "jade": "~1.11.0", "morgan": "~1.9.0", "mysql": "^2.15.0", "mysql2": "^1.5.1", + "pug": "^2.0.0-rc.4", "sequelize": "^4.32.2", "sequelize-cli": "^3.2.0", "serve-favicon": "~2.4.5" diff --git a/routes/address.js b/routes/address.js index 8ad24d0..9922326 100644 --- a/routes/address.js +++ b/routes/address.js @@ -5,36 +5,38 @@ var router = express.Router(); /* GET home page. */ router.get('/:address/:offset*?', async function(req, res, next) { - const addrss = encodeURI(req.params.address); - const limit = 30; - const paramPage = parseInt(req.params.offset); - const page = isNaN(paramPage) || paramPage < 1 ? 1 : paramPage; - const offset = 30 * (page - 1); - const txes = await models.sequelize.query(` - SELECT txid - FROM Transactions as t - LEFT JOIN Vouts as v - ON v.TransactionId=t.id - LEFT JOIN AddressVouts as av - ON v.id=av.VoutId - LEFT JOIN Addresses as a - ON a.id=av.AddressId - WHERE a.address='${addrss}' - LIMIT ${limit} - OFFSET ${offset}; - `); + const safe_address = encodeURI(req.params.address); + + const address = await models.Address.findOne({ + where: { + address: safe_address, + }, + include: { + model: models.Vout, + include: { + model: models.Transaction, + include: { + model: models.Vout, + }, + }, + }, + }); - if (txes === null) { + if (address === null) { res.status(404).render('404'); return; } + const limit = 30; + const paramPage = parseInt(req.params.offset); + const page = isNaN(paramPage) || paramPage < 1 ? 1 : paramPage; + const offset = 30 * (page - 1); - const nextpage = txes[0].length === 30 ? page + 1 : null; + const nextpage = address.Vouts.length === 30 ? page + 1 : null; const prevpage = page > 1 ? page - 1 : null; - + console.log(address.toJSON()); + res.render('address', { - address: addrss, - txes: txes[0], + address: address.toJSON(), nextpage, prevpage, }); diff --git a/routes/block.js b/routes/block.js index 9708ae5..ca34c24 100644 --- a/routes/block.js +++ b/routes/block.js @@ -13,10 +13,12 @@ router.get('/:hash', async function(req, res, next) { model: models.Transaction, }, }); + if (blockInstance === null) { res.status(404).render('404'); return; } + const lastBlock = await models.Block.findOne({ attributes: [ [models.sequelize.fn('MAX', models.sequelize.col('height')), 'maxheight'] diff --git a/routes/transaction.js b/routes/transaction.js index 813d8b6..5ba24a6 100644 --- a/routes/transaction.js +++ b/routes/transaction.js @@ -15,27 +15,34 @@ router.get('/:txid', async function(req, res, next) { model: models.Block, },{ model: models.Vout, - include: { - model: models.Address, - } - }, { - model: models.Transaction, - as: 'txtx', - }], + include: [ + { + model: models.Address, + }, { + model: models.Transaction, + include: { + model: models.Vout, + }, + } + ], + },], }); + if (transaction === null) { res.status(404).render('404'); return; } - const vouts = []; - transaction.Vouts.forEach((vout) => { - vout.Addresses.forEach((address) => { - vouts.push({ - address: address.address, - value: vout.value, - }); - }); - }); + + // const vouts = []; + // transaction.Vouts.forEach((vout) => { + // vout.Addresses.forEach((address) => { + // vouts.push({ + // address: address.address, + // value: vout.value, + // }); + // }); + // }); + const lastBlock = await models.Block.findOne({ attributes: [ [models.sequelize.fn('MAX', models.sequelize.col('height')), 'maxheight'] @@ -43,10 +50,20 @@ router.get('/:txid', async function(req, res, next) { raw: true, }); const confirmations = lastBlock.maxheight - transaction.Block.height + 1; - transaction.blockTime = transaction.Block.time.toUTCString(); + + const txJson = transaction.toJSON(); + // txJson.vins = txJson.Vouts.filter({TransactionVouts: {direction: 0}}); + // txJson.vouts txJson.Vouts.filter({TransactionVouts: {direction: 1}}); + const txTemplate = Object.assign(txJson, { + vins: txJson.Vouts.filter((vout) => vout.TransactionVouts.direction === 0), + vouts: txJson.Vouts.filter((vout) => vout.TransactionVouts.direction === 1), + }); + + txTemplate.blockTime = transaction.Block.time.toUTCString(); + res.render('transaction', { - transaction, - vouts, + transaction: txTemplate, + // vouts, confirmations, }); }); diff --git a/views/404.jade b/views/404.pug similarity index 100% rename from views/404.jade rename to views/404.pug diff --git a/views/address.jade b/views/address.jade deleted file mode 100644 index 6f5d9a9..0000000 --- a/views/address.jade +++ /dev/null @@ -1,15 +0,0 @@ -extends layout - -block content - h3 Address - div= address - - h3 Transactions - each val in txes - div - a(href='/transaction/#{val.txid}/') #{val.txid} - div.pagination - if prevpage - a(href='/address/#{address}/#{prevpage}/', style='float:left') Back - if nextpage - a(href='/address/#{address}/#{nextpage}/', style='float:right') Next diff --git a/views/address.pug b/views/address.pug new file mode 100644 index 0000000..e9c6920 --- /dev/null +++ b/views/address.pug @@ -0,0 +1,21 @@ +extends layout + +block content + h3 Address + div= address.address + + h3 Transactions + table + each vout in address.Vouts + tr + if vout.Transactions[0].TransactionVouts.direction == 1 + td OUT + else + td IN + td + a(href=`/transaction/${vout.Transactions[0].txid}/`) #{vout.Transactions[0].txid} + div.pagination + if prevpage + a(href=`/address/#{address}/${prevpage}/`, style='float:left') Back + if nextpage + a(href=`/address/${address}/${nextpage}/`, style='float:right') Next diff --git a/views/block.jade b/views/block.pug similarity index 73% rename from views/block.jade rename to views/block.pug index d01c003..dd3bbb1 100644 --- a/views/block.jade +++ b/views/block.pug @@ -9,13 +9,13 @@ block content tr td.capitalize #{key} td - a(href='/block/#{block.nextblockhash}/') #{block.nextblockhash} + a(href=`/block/${block.nextblockhash}/`) #{block.nextblockhash} -continue if (key === 'previousblockhash') tr td.capitalize #{key} td - a(href='/block/#{block.previousblockhash}/') #{block.previousblockhash} + a(href=`/block/${block.previousblockhash}/`) #{block.previousblockhash} -continue if (key === 'Transactions') -continue @@ -26,5 +26,5 @@ block content h3 Transactions each tx in block.Transactions div - a(href='/transaction/#{tx.txid}/') #{tx.txid} + a(href=`/transaction/${tx.txid}/`) #{tx.txid} diff --git a/views/error.jade b/views/error.pug similarity index 100% rename from views/error.jade rename to views/error.pug diff --git a/views/index.jade b/views/index.pug similarity index 68% rename from views/index.jade rename to views/index.pug index 2d8c05b..43c04cc 100644 --- a/views/index.jade +++ b/views/index.pug @@ -8,4 +8,4 @@ block content tr td #{block.height} td - a(href='/block/#{block.hash}/') #{block.hash} + a(href='/block/' + block.hash + '/') #{block.hash} diff --git a/views/layout.jade b/views/layout.pug similarity index 100% rename from views/layout.jade rename to views/layout.pug diff --git a/views/transaction.jade b/views/transaction.jade deleted file mode 100644 index d45bb78..0000000 --- a/views/transaction.jade +++ /dev/null @@ -1,37 +0,0 @@ -extends layout - -block content - h3 Transaction - - table - tr - td Hash - td #{transaction.txid} - tr - td Block - td - a(href='/block/#{transaction.Block.hash}/') #{transaction.Block.hash} - tr - td Block time - td #{transaction.blockTime} - tr - td Confirmations - td #{confirmations} - - h3 In - if transaction.txtx.length - table - each val in transaction.txtx - tr - td - a(href='/transaction/#{val.txid}/') #{val.txid} - else - div Mined - - h3 Out - table - each val in vouts - tr - td - a(href='/address/#{val.address}/') #{val.address} - td #{val.value} \ No newline at end of file diff --git a/views/transaction.pug b/views/transaction.pug new file mode 100644 index 0000000..f213a32 --- /dev/null +++ b/views/transaction.pug @@ -0,0 +1,54 @@ +extends layout + +block content + h3 Transaction + + table + tr + td Hash + td #{transaction.txid} + tr + td Block + td + a(href=`/block/${transaction.Block.hash}/`) #{transaction.Block.hash} + tr + td Block time + td #{transaction.blockTime} + tr + td Confirmations + td #{confirmations} + + h3 In + if transaction.vins.length + table + tr + th Address + th Value + th Transaction + each vin in transaction.vins + tr + td + a(href=`/address/${vin.Addresses[0].address}/`) #{vin.Addresses[0].address} + td #{vin.value} + td + a(href=`/transaction/${vin.Transactions[0].txid}/`) #{vin.Transactions[0].txid} + else + div Mined + + h3 Out + table + tr + th Address + th Value + th Transaction + each vout in transaction.vouts + each address in vout.Addresses + tr + td + a(href=`/address/${address.address}/`) #{address.address} + td #{vout.value} + td + if vout.Transactions[0].TransactionVouts.direction == 0 + a(href=`/transaction/${vout.Transactions[0].txid}/`) #{vout.Transactions[0].txid} + else + span No yet