mirror of https://github.com/GOSTSec/gostexplr
xcps
7 years ago
commit
5686c01015
31 changed files with 926 additions and 0 deletions
@ -0,0 +1,22 @@ |
|||||||
|
|
||||||
|
cd gostexplr |
||||||
|
npm i |
||||||
|
npm run initdb db_root_username db_root_password |
||||||
|
npm run syncBlockchain |
||||||
|
npm run |
||||||
|
|
||||||
|
|
||||||
|
TODO: |
||||||
|
- cut possible -000 from transaction |
||||||
|
- datetime zone |
||||||
|
- search NOT FOUND message |
||||||
|
- pagination on index page |
||||||
|
- sync blockchain by db transaction |
||||||
|
- block page by index |
||||||
|
- move database to server |
||||||
|
- setup cron |
||||||
|
- ? transaction connect vout to transaction |
||||||
|
- address balance info |
||||||
|
- lint |
||||||
|
- tests |
||||||
|
- ES7 |
@ -0,0 +1,52 @@ |
|||||||
|
var express = require('express'); |
||||||
|
var path = require('path'); |
||||||
|
var favicon = require('serve-favicon'); |
||||||
|
var logger = require('morgan'); |
||||||
|
var cookieParser = require('cookie-parser'); |
||||||
|
var bodyParser = require('body-parser'); |
||||||
|
|
||||||
|
var index = require('./routes/index'); |
||||||
|
var address = require('./routes/address'); |
||||||
|
var transaction = require('./routes/transaction'); |
||||||
|
var block = require('./routes/block'); |
||||||
|
var search = require('./routes/search'); |
||||||
|
|
||||||
|
var app = express(); |
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, 'views')); |
||||||
|
app.set('view engine', 'jade'); |
||||||
|
|
||||||
|
// uncomment after placing your favicon in /public
|
||||||
|
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); |
||||||
|
app.use(logger('dev')); |
||||||
|
app.use(bodyParser.json()); |
||||||
|
app.use(bodyParser.urlencoded({ extended: false })); |
||||||
|
app.use(cookieParser()); |
||||||
|
app.use(express.static(path.join(__dirname, 'public'))); |
||||||
|
|
||||||
|
app.use('/', index); |
||||||
|
app.use('/address', address); |
||||||
|
app.use('/transaction', transaction); |
||||||
|
app.use('/block', block); |
||||||
|
app.use('/search', search); |
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) { |
||||||
|
var err = new Error('Not Found'); |
||||||
|
err.status = 404; |
||||||
|
next(err); |
||||||
|
}); |
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) { |
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message; |
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {}; |
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500); |
||||||
|
res.render('error'); |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = app; |
@ -0,0 +1,51 @@ |
|||||||
|
#!/usr/bin/env node
|
||||||
|
var exec = require('child_process').exec; |
||||||
|
var models = require('../models'); |
||||||
|
var env = process.env.NODE_ENV || 'development'; |
||||||
|
var config = require(__dirname + '/../config/config.json')[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(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); |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,14 @@ |
|||||||
|
#!/usr/bin/env node
|
||||||
|
var models = require('../models'); |
||||||
|
|
||||||
|
models.sequelize.sync({force: true}) |
||||||
|
.then(() => { |
||||||
|
console.log('REINIT SUCCESS') |
||||||
|
process.exit(0); |
||||||
|
}) |
||||||
|
.catch((err) => { |
||||||
|
console.log(err); |
||||||
|
process.exit(0); |
||||||
|
}); |
||||||
|
|
||||||
|
|
@ -0,0 +1,154 @@ |
|||||||
|
var http = require('http'); |
||||||
|
var models = require('../models'); |
||||||
|
|
||||||
|
const username = 'gostcoinrpc'; |
||||||
|
const password = 'CEQLt9zrNnmyzosSV7pjb3EksAkuY9qeqoUCwDQc2wPc'; |
||||||
|
const hostname = '127.0.0.1'; |
||||||
|
const port = 9376; |
||||||
|
|
||||||
|
function MakeRPCRequest(postData) { |
||||||
|
return new Promise(function(resolve, reject) { |
||||||
|
var post_options = { |
||||||
|
host: hostname, |
||||||
|
port: port, |
||||||
|
auth: `${username}:${password}`, |
||||||
|
path: '/', |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/x-www-form-urlencoded', |
||||||
|
'Content-Length': Buffer.byteLength(postData) |
||||||
|
} |
||||||
|
}; |
||||||
|
var post_req = http.request(post_options, function(res) { |
||||||
|
res.setEncoding("utf8"); |
||||||
|
let body = ""; |
||||||
|
res.on("data", data => { |
||||||
|
body += data; |
||||||
|
}); |
||||||
|
res.on("end", () => { |
||||||
|
resolve(body); |
||||||
|
}); |
||||||
|
}); |
||||||
|
post_req.write(postData); |
||||||
|
post_req.end(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async function saveTransaction(txid, blockHeight) { |
||||||
|
const res_tx = await MakeRPCRequest(JSON.stringify({ |
||||||
|
method: 'getrawtransaction', |
||||||
|
params: [txid, 1], |
||||||
|
id: 1 |
||||||
|
})); |
||||||
|
const tx = JSON.parse(res_tx)['result']; |
||||||
|
// console.log('tx:', tx);
|
||||||
|
if (tx === null) { |
||||||
|
await models.Failure.create({ |
||||||
|
msg: `${txid} fetching failed`, |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
const transaction = await models.Transaction.create({ |
||||||
|
txid: tx.txid, |
||||||
|
BlockHeight: blockHeight, |
||||||
|
}); |
||||||
|
for (var i = 0; i < tx.vout.length; i++) { |
||||||
|
const vout = tx.vout[i]; |
||||||
|
const m_vout = await models.Vout.create({ |
||||||
|
value: vout.value, |
||||||
|
}); |
||||||
|
for (var y = 0; y < vout.scriptPubKey.addresses.length; y++) { |
||||||
|
const address = vout.scriptPubKey.addresses[y]; |
||||||
|
let m_address = await models.Address.findOne({ |
||||||
|
where: { |
||||||
|
address, |
||||||
|
}, |
||||||
|
}); |
||||||
|
if (m_address === null) { |
||||||
|
m_address = await models.Address.create({ |
||||||
|
address, |
||||||
|
}); |
||||||
|
} |
||||||
|
await m_vout.addAddresses(m_address); |
||||||
|
} |
||||||
|
await transaction.addVouts(m_vout); |
||||||
|
} |
||||||
|
for (var i = 0; i < tx.vin.length; i++) { |
||||||
|
const vin = tx.vin[i]; |
||||||
|
if (vin.txid) { |
||||||
|
const trans = await models.Transaction.findOne({ |
||||||
|
where: { |
||||||
|
txid: vin.txid, |
||||||
|
}, |
||||||
|
}); |
||||||
|
if (trans) { |
||||||
|
await transaction.addTxtx(trans); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function syncNextBlock(syncedHeight) { |
||||||
|
const height = syncedHeight + 1; |
||||||
|
const res_hash = await MakeRPCRequest(JSON.stringify({ |
||||||
|
method: 'getblockhash', |
||||||
|
params: [height], |
||||||
|
id: 1 |
||||||
|
})); |
||||||
|
const blockHash = JSON.parse(res_hash)['result']; |
||||||
|
const res_block = await MakeRPCRequest(JSON.stringify({ |
||||||
|
method: 'getblock', |
||||||
|
params: [blockHash], |
||||||
|
id: 1 |
||||||
|
})); |
||||||
|
const block = JSON.parse(res_block)['result']; |
||||||
|
block.time = new Date(block.time * 1000); |
||||||
|
await models.Block.create(block); |
||||||
|
// console.log('block:', block);
|
||||||
|
for (var i = 0; i < block.tx.length; i++) { |
||||||
|
await saveTransaction(block.tx[i], block.height); |
||||||
|
} |
||||||
|
|
||||||
|
return height; |
||||||
|
} |
||||||
|
|
||||||
|
async function getCurrentHeight() { |
||||||
|
const result = await MakeRPCRequest(JSON.stringify({ |
||||||
|
method: 'getblockcount', |
||||||
|
params: [], |
||||||
|
id: 1 |
||||||
|
})); |
||||||
|
|
||||||
|
return JSON.parse(result)['result']; |
||||||
|
} |
||||||
|
|
||||||
|
async function getSyncedHeight() { |
||||||
|
const result = await models.Block.findOne({ |
||||||
|
attributes: ['height'], |
||||||
|
order: [['height', 'DESC']], |
||||||
|
limit: 1 |
||||||
|
}); |
||||||
|
const height = result ? result.height : -1; |
||||||
|
return height; |
||||||
|
} |
||||||
|
|
||||||
|
async function syncBlockchain() { |
||||||
|
let syncedHeight = await getSyncedHeight(); |
||||||
|
console.log('\x1b[36m%s\x1b[0m', 'syncedHeight is', syncedHeight); |
||||||
|
|
||||||
|
let currentHeight = await getCurrentHeight(); |
||||||
|
console.log('\x1b[36m%s\x1b[0m', 'currentHeight is', currentHeight); |
||||||
|
while (syncedHeight < currentHeight) { |
||||||
|
syncedHeight = await syncNextBlock(syncedHeight); |
||||||
|
console.log('\x1b[36m%s\x1b[0m', 'syncedHeight: ', syncedHeight) |
||||||
|
} |
||||||
|
process.exit(0); |
||||||
|
} |
||||||
|
|
||||||
|
var postData = JSON.stringify({ |
||||||
|
'method': 'getinfo', |
||||||
|
'params': [], |
||||||
|
'id': 1 |
||||||
|
}); |
||||||
|
|
||||||
|
syncBlockchain(); |
@ -0,0 +1,2 @@ |
|||||||
|
require('babel-register'); |
||||||
|
require('./syncBlockchain.babel.js'); |
@ -0,0 +1,92 @@ |
|||||||
|
#!/usr/bin/env node |
||||||
|
|
||||||
|
/** |
||||||
|
* Module dependencies. |
||||||
|
*/ |
||||||
|
|
||||||
|
var app = require('../app'); |
||||||
|
var debug = require('debug')('gostexplr:server'); |
||||||
|
var http = require('http'); |
||||||
|
var models = require('../models'); |
||||||
|
|
||||||
|
/** |
||||||
|
* Get port from environment and store in Express. |
||||||
|
*/ |
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3000'); |
||||||
|
app.set('port', port); |
||||||
|
|
||||||
|
/** |
||||||
|
* Create HTTP server. |
||||||
|
*/ |
||||||
|
|
||||||
|
var server = http.createServer(app); |
||||||
|
|
||||||
|
models.sequelize.sync().then(function() { |
||||||
|
/** |
||||||
|
* Listen on provided port, on all network interfaces. |
||||||
|
*/ |
||||||
|
|
||||||
|
server.listen(port); |
||||||
|
server.on('error', onError); |
||||||
|
server.on('listening', onListening); |
||||||
|
}); |
||||||
|
/** |
||||||
|
* Normalize a port into a number, string, or false. |
||||||
|
*/ |
||||||
|
|
||||||
|
function normalizePort(val) { |
||||||
|
var port = parseInt(val, 10); |
||||||
|
|
||||||
|
if (isNaN(port)) { |
||||||
|
// named pipe |
||||||
|
return val; |
||||||
|
} |
||||||
|
|
||||||
|
if (port >= 0) { |
||||||
|
// port number |
||||||
|
return port; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Event listener for HTTP server "error" event. |
||||||
|
*/ |
||||||
|
|
||||||
|
function onError(error) { |
||||||
|
if (error.syscall !== 'listen') { |
||||||
|
throw error; |
||||||
|
} |
||||||
|
|
||||||
|
var bind = typeof port === 'string' |
||||||
|
? 'Pipe ' + port |
||||||
|
: 'Port ' + port; |
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages |
||||||
|
switch (error.code) { |
||||||
|
case 'EACCES': |
||||||
|
console.error(bind + ' requires elevated privileges'); |
||||||
|
process.exit(1); |
||||||
|
break; |
||||||
|
case 'EADDRINUSE': |
||||||
|
console.error(bind + ' is already in use'); |
||||||
|
process.exit(1); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Event listener for HTTP server "listening" event. |
||||||
|
*/ |
||||||
|
|
||||||
|
function onListening() { |
||||||
|
var addr = server.address(); |
||||||
|
var bind = typeof addr === 'string' |
||||||
|
? 'pipe ' + addr |
||||||
|
: 'port ' + addr.port; |
||||||
|
debug('Listening on ' + bind); |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
{ |
||||||
|
"development": { |
||||||
|
"username": "geuser", |
||||||
|
"password": "geuser", |
||||||
|
"database": "gedb", |
||||||
|
"host": "127.0.0.1", |
||||||
|
"dialect": "mysql", |
||||||
|
"logging": false |
||||||
|
}, |
||||||
|
"test": { |
||||||
|
"username": "root", |
||||||
|
"password": null, |
||||||
|
"database": "database_test", |
||||||
|
"host": "127.0.0.1", |
||||||
|
"dialect": "mysql" |
||||||
|
}, |
||||||
|
"production": { |
||||||
|
"username": "geuser", |
||||||
|
"password": "geuser", |
||||||
|
"database": "gedb", |
||||||
|
"host": "127.0.0.1", |
||||||
|
"dialect": "mysql", |
||||||
|
"logging": false |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => { |
||||||
|
const Address = sequelize.define('Address', { |
||||||
|
address: DataTypes.STRING(34), |
||||||
|
}, { |
||||||
|
timestamps: false, |
||||||
|
indexes: [{ |
||||||
|
unique: true, |
||||||
|
fields: ['address'] |
||||||
|
}], |
||||||
|
}); |
||||||
|
|
||||||
|
const AddressVout = sequelize.define('AddressVout', {}, { timestamps: false }); |
||||||
|
|
||||||
|
Address.associate = function (models) { |
||||||
|
models.Address.belongsToMany(models.Vout, { through: 'AddressVout' }); |
||||||
|
}; |
||||||
|
|
||||||
|
return Address; |
||||||
|
}; |
@ -0,0 +1,33 @@ |
|||||||
|
'use strict'; |
||||||
|
module.exports = (sequelize, DataTypes) => { |
||||||
|
const Block = sequelize.define('Block', { |
||||||
|
height: { |
||||||
|
type: DataTypes.INTEGER.UNSIGNED, |
||||||
|
primaryKey: true, |
||||||
|
}, |
||||||
|
hash: DataTypes.STRING(64), |
||||||
|
confirmations: DataTypes.MEDIUMINT.UNSIGNED, |
||||||
|
size: DataTypes.MEDIUMINT.UNSIGNED, |
||||||
|
version: DataTypes.TINYINT.UNSIGNED, |
||||||
|
merkleroot: DataTypes.STRING(64), |
||||||
|
time: DataTypes.DATE, |
||||||
|
nonce: DataTypes.BIGINT, |
||||||
|
bits: DataTypes.STRING(8), |
||||||
|
difficulty: DataTypes.DECIMAL(16, 8), |
||||||
|
previousblockhash: DataTypes.STRING(64), |
||||||
|
nextblockhash: DataTypes.STRING(64), |
||||||
|
}, { |
||||||
|
timestamps: false, |
||||||
|
indexes: [{ |
||||||
|
unique: true, |
||||||
|
fields: ['hash', 'height'] |
||||||
|
}], |
||||||
|
freezeTableName: true, |
||||||
|
}); |
||||||
|
|
||||||
|
Block.associate = function(models) { |
||||||
|
models.Block.hasMany(models.Transaction); |
||||||
|
}; |
||||||
|
|
||||||
|
return Block; |
||||||
|
}; |
@ -0,0 +1,12 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => { |
||||||
|
const Failure = sequelize.define('Failure', { |
||||||
|
msg: DataTypes.STRING, |
||||||
|
}, { |
||||||
|
timestamps: true, |
||||||
|
updatedAt: false, |
||||||
|
}); |
||||||
|
|
||||||
|
return Failure; |
||||||
|
}; |
@ -0,0 +1,36 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
var fs = require('fs'); |
||||||
|
var path = require('path'); |
||||||
|
var Sequelize = require('sequelize'); |
||||||
|
var basename = path.basename(__filename); |
||||||
|
var env = process.env.NODE_ENV || 'development'; |
||||||
|
var config = require(__dirname + '/../config/config.json')[env]; |
||||||
|
var db = {}; |
||||||
|
|
||||||
|
if (config.use_env_variable) { |
||||||
|
var sequelize = new Sequelize(process.env[config.use_env_variable], config); |
||||||
|
} else { |
||||||
|
var sequelize = new Sequelize(config.database, config.username, config.password, config); |
||||||
|
} |
||||||
|
|
||||||
|
fs |
||||||
|
.readdirSync(__dirname) |
||||||
|
.filter(file => { |
||||||
|
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); |
||||||
|
}) |
||||||
|
.forEach(file => { |
||||||
|
var model = sequelize['import'](path.join(__dirname, file)); |
||||||
|
db[model.name] = model; |
||||||
|
}); |
||||||
|
|
||||||
|
Object.keys(db).forEach(modelName => { |
||||||
|
if (db[modelName].associate) { |
||||||
|
db[modelName].associate(db); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
db.sequelize = sequelize; |
||||||
|
db.Sequelize = Sequelize; |
||||||
|
|
||||||
|
module.exports = db; |
@ -0,0 +1,32 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => { |
||||||
|
const Transaction = sequelize.define('Transaction', { |
||||||
|
txid: DataTypes.STRING(64), |
||||||
|
}, { |
||||||
|
timestamps: false, |
||||||
|
indexes: [{ |
||||||
|
unique: true, |
||||||
|
fields: ['txid'] |
||||||
|
}], |
||||||
|
}); |
||||||
|
|
||||||
|
const TxToTx = sequelize.define('TxToTx', {}, { |
||||||
|
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); |
||||||
|
}; |
||||||
|
|
||||||
|
return Transaction; |
||||||
|
}; |
@ -0,0 +1,21 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => { |
||||||
|
const Vout = sequelize.define('Vout', { |
||||||
|
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' }); |
||||||
|
}; |
||||||
|
|
||||||
|
return Vout; |
||||||
|
}; |
@ -0,0 +1,25 @@ |
|||||||
|
{ |
||||||
|
"name": "gostexplr", |
||||||
|
"version": "0.0.0", |
||||||
|
"private": true, |
||||||
|
"scripts": { |
||||||
|
"start": "node ./bin/www", |
||||||
|
"initdb": "node ./bin/initdb.js", |
||||||
|
"reinitTables": "node ./bin/reinitTables.js", |
||||||
|
"syncBlockchain": "node ./bin/syncBlockchain.js" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"babel-register": "^6.26.0", |
||||||
|
"body-parser": "~1.18.2", |
||||||
|
"cookie-parser": "~1.4.3", |
||||||
|
"debug": "~2.6.9", |
||||||
|
"express": "~4.15.5", |
||||||
|
"jade": "~1.11.0", |
||||||
|
"morgan": "~1.9.0", |
||||||
|
"mysql": "^2.15.0", |
||||||
|
"mysql2": "^1.5.1", |
||||||
|
"sequelize": "^4.32.2", |
||||||
|
"sequelize-cli": "^3.2.0", |
||||||
|
"serve-favicon": "~2.4.5" |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,57 @@ |
|||||||
|
body { |
||||||
|
font: 16px monospace; |
||||||
|
width: 1024px; |
||||||
|
margin: 0 auto 1em; |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
color: #a7393d; |
||||||
|
} |
||||||
|
|
||||||
|
a:hover { |
||||||
|
color: #bf5c5f; |
||||||
|
} |
||||||
|
|
||||||
|
.header h1 a { |
||||||
|
color: black; |
||||||
|
text-decoration: none; |
||||||
|
align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.header h1 a img { |
||||||
|
height: 2em; |
||||||
|
position: relative; |
||||||
|
margin-right: .5em; |
||||||
|
} |
||||||
|
|
||||||
|
.header form input[type="submit"] { |
||||||
|
padding: .2em .7em; |
||||||
|
} |
||||||
|
|
||||||
|
@-moz-document url-prefix() { |
||||||
|
.header form input[type="submit"] { |
||||||
|
padding: .15em .7em; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.header form input[type="text"] { |
||||||
|
padding: .2em; |
||||||
|
} |
||||||
|
|
||||||
|
.header h1 a span { |
||||||
|
position: relative; |
||||||
|
top: -0.5em; |
||||||
|
} |
||||||
|
|
||||||
|
.header h1 a span:hover { |
||||||
|
border-bottom: 3px solid; |
||||||
|
border-bottom-color: #CF7F7F; |
||||||
|
} |
||||||
|
|
||||||
|
.content { |
||||||
|
margin-top: 2em; |
||||||
|
} |
||||||
|
|
||||||
|
.capitalize { |
||||||
|
text-transform: capitalize; |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
var models = require('../models'); |
||||||
|
var express = require('express'); |
||||||
|
var router = express.Router(); |
||||||
|
|
||||||
|
/* GET home page. */ |
||||||
|
router.get('/:address', function(req, res, next) { |
||||||
|
|
||||||
|
const address = encodeURI(req.params.address); |
||||||
|
|
||||||
|
models.Address.findOne({ |
||||||
|
where: { |
||||||
|
address, |
||||||
|
}, |
||||||
|
include: { |
||||||
|
model: models.Vout, |
||||||
|
include: { |
||||||
|
model: models.Transaction, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
.then((address) => { |
||||||
|
if (address === null) { |
||||||
|
res.status(404).render('404'); |
||||||
|
return; |
||||||
|
} |
||||||
|
const txes = []; |
||||||
|
address.Vouts.forEach((vout) => txes.push(vout.Transaction.txid)); |
||||||
|
res.render('address', { |
||||||
|
address: address.address, |
||||||
|
txes, |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = router; |
@ -0,0 +1,28 @@ |
|||||||
|
var models = require('../models'); |
||||||
|
var express = require('express'); |
||||||
|
var router = express.Router(); |
||||||
|
|
||||||
|
/* GET home page. */ |
||||||
|
router.get('/:hash', function(req, res, next) { |
||||||
|
const hash = encodeURI(req.params.hash); |
||||||
|
models.Block.findOne({ |
||||||
|
where: { |
||||||
|
hash, |
||||||
|
}, |
||||||
|
include: { |
||||||
|
model: models.Transaction, |
||||||
|
}, |
||||||
|
}) |
||||||
|
.then((block) => { |
||||||
|
if (block === null) { |
||||||
|
res.status(404).render('404'); |
||||||
|
return; |
||||||
|
} |
||||||
|
block.dataValues.time = block.time.toUTCString(); |
||||||
|
res.render('block', { |
||||||
|
block, |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = router; |
@ -0,0 +1,19 @@ |
|||||||
|
var models = require('../models'); |
||||||
|
var express = require('express'); |
||||||
|
var router = express.Router(); |
||||||
|
|
||||||
|
/* GET home page. */ |
||||||
|
router.get('/', function(req, res, next) { |
||||||
|
models.Block.findAll({ |
||||||
|
order: [['height', 'DESC']], |
||||||
|
limit: 30, |
||||||
|
}) |
||||||
|
.then((blocks) => { |
||||||
|
res.render('index', { |
||||||
|
blocks, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = router; |
@ -0,0 +1,46 @@ |
|||||||
|
var models = require('../models'); |
||||||
|
var express = require('express'); |
||||||
|
var router = express.Router(); |
||||||
|
|
||||||
|
/* GET home page. */ |
||||||
|
router.post('/', function(req, res, next) { |
||||||
|
|
||||||
|
const search = encodeURI(req.body.search); |
||||||
|
|
||||||
|
models.Address.findOne({ |
||||||
|
where: { |
||||||
|
address: search, |
||||||
|
}, |
||||||
|
}) |
||||||
|
.then((address) => { |
||||||
|
if (address) { |
||||||
|
res.redirect(`/address/${address.address}`); |
||||||
|
return; |
||||||
|
} |
||||||
|
models.Transaction.findOne({ |
||||||
|
where: { |
||||||
|
txid: search, |
||||||
|
}, |
||||||
|
}) |
||||||
|
.then((transaction) => { |
||||||
|
if (transaction) { |
||||||
|
res.redirect(`/transaction/${transaction.txid}`); |
||||||
|
return; |
||||||
|
} |
||||||
|
models.Block.findOne({ |
||||||
|
where: { |
||||||
|
hash: search, |
||||||
|
}, |
||||||
|
}) |
||||||
|
.then((block) => { |
||||||
|
if (block) { |
||||||
|
res.redirect(`/block/${block.hash}`); |
||||||
|
return; |
||||||
|
} |
||||||
|
res.status(404).render('404'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = router; |
@ -0,0 +1,48 @@ |
|||||||
|
var models = require('../models'); |
||||||
|
var express = require('express'); |
||||||
|
var router = express.Router(); |
||||||
|
|
||||||
|
/* GET home page. */ |
||||||
|
router.get('/:txid', function(req, res, next) { |
||||||
|
const txid = encodeURI(req.params.txid); |
||||||
|
|
||||||
|
models.Transaction.findOne({ |
||||||
|
where: { |
||||||
|
txid, |
||||||
|
}, |
||||||
|
include: [{ |
||||||
|
attributes: ['hash'], |
||||||
|
model: models.Block, |
||||||
|
},{ |
||||||
|
model: models.Vout, |
||||||
|
include: { |
||||||
|
model: models.Address, |
||||||
|
} |
||||||
|
}, { |
||||||
|
model: models.Transaction, |
||||||
|
as: 'txtx', |
||||||
|
}], |
||||||
|
}) |
||||||
|
.then((transaction) => { |
||||||
|
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, |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
res.render('transaction', { |
||||||
|
transaction, |
||||||
|
vouts, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = router; |
@ -0,0 +1,11 @@ |
|||||||
|
extends layout |
||||||
|
|
||||||
|
block content |
||||||
|
h3 Address |
||||||
|
div= address |
||||||
|
|
||||||
|
h3 Transactions |
||||||
|
each val in txes |
||||||
|
div |
||||||
|
a(href='/transaction/#{val}/') #{val} |
||||||
|
|
@ -0,0 +1,18 @@ |
|||||||
|
extends layout |
||||||
|
|
||||||
|
block content |
||||||
|
h3 Block |
||||||
|
|
||||||
|
table |
||||||
|
each key in Object.keys(block.dataValues) |
||||||
|
if (key === 'Transactions') |
||||||
|
-continue |
||||||
|
tr |
||||||
|
td.capitalize #{key} |
||||||
|
td #{block[key]} |
||||||
|
|
||||||
|
h3 Transactions |
||||||
|
each tx in block.Transactions |
||||||
|
div |
||||||
|
a(href='/transaction/#{tx.txid}/') #{tx.txid} |
||||||
|
|
@ -0,0 +1,6 @@ |
|||||||
|
extends layout |
||||||
|
|
||||||
|
block content |
||||||
|
h1= message |
||||||
|
h2= error.status |
||||||
|
pre #{error.stack} |
@ -0,0 +1,11 @@ |
|||||||
|
extends layout |
||||||
|
|
||||||
|
block content |
||||||
|
h3 Last 30 blocks |
||||||
|
|
||||||
|
table |
||||||
|
each block in blocks |
||||||
|
tr |
||||||
|
td #{block.height} |
||||||
|
td |
||||||
|
a(href='/block/#{block.hash}/') #{block.hash} |
@ -0,0 +1,18 @@ |
|||||||
|
doctype html |
||||||
|
html |
||||||
|
head |
||||||
|
title GOSTcoin blockchain explorer |
||||||
|
link(rel='stylesheet', href='/stylesheets/style.css') |
||||||
|
link(rel='shortcut icon', href='/favicon.ico') |
||||||
|
body |
||||||
|
div.header |
||||||
|
h1 |
||||||
|
a(href='/') |
||||||
|
img(src='/images/gostcoin-b.png') |
||||||
|
span GOSTcoin blockchain explorer |
||||||
|
div |
||||||
|
form(action="/search/", method="POST") |
||||||
|
input(type="text", name="search" placeholder="Search by transaction id, block hash or address", size="68") |
||||||
|
input(type="submit", value=">>") |
||||||
|
div.content |
||||||
|
block content |
@ -0,0 +1,31 @@ |
|||||||
|
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} |
||||||
|
|
||||||
|
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} |
Loading…
Reference in new issue