mirror of
https://github.com/GOSTSec/gostexplr
synced 2025-03-09 20:11:13 +00:00
init
This commit is contained in:
commit
5686c01015
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
package-lock.json
|
22
README.md
Normal file
22
README.md
Normal file
@ -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
|
52
app.js
Normal file
52
app.js
Normal file
@ -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;
|
51
bin/initdb.js
Normal file
51
bin/initdb.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
14
bin/reinitTables.js
Normal file
14
bin/reinitTables.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
154
bin/syncBlockchain.babel.js
Normal file
154
bin/syncBlockchain.babel.js
Normal file
@ -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();
|
2
bin/syncBlockchain.js
Normal file
2
bin/syncBlockchain.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
require('babel-register');
|
||||||
|
require('./syncBlockchain.babel.js');
|
92
bin/www
Executable file
92
bin/www
Executable file
@ -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);
|
||||||
|
}
|
25
config/config.json
Normal file
25
config/config.json
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
21
models/address.js
Normal file
21
models/address.js
Normal file
@ -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;
|
||||||
|
};
|
33
models/block.js
Normal file
33
models/block.js
Normal file
@ -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;
|
||||||
|
};
|
12
models/failure.js
Normal file
12
models/failure.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const Failure = sequelize.define('Failure', {
|
||||||
|
msg: DataTypes.STRING,
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
updatedAt: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Failure;
|
||||||
|
};
|
36
models/index.js
Normal file
36
models/index.js
Normal file
@ -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;
|
32
models/transaction.js
Normal file
32
models/transaction.js
Normal file
@ -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;
|
||||||
|
};
|
21
models/vout.js
Normal file
21
models/vout.js
Normal file
@ -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;
|
||||||
|
};
|
25
package.json
Normal file
25
package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
public/images/gostcoin-b.png
Normal file
BIN
public/images/gostcoin-b.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
57
public/stylesheets/style.css
Normal file
57
public/stylesheets/style.css
Normal file
@ -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;
|
||||||
|
}
|
35
routes/address.js
Normal file
35
routes/address.js
Normal file
@ -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;
|
28
routes/block.js
Normal file
28
routes/block.js
Normal file
@ -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;
|
19
routes/index.js
Normal file
19
routes/index.js
Normal file
@ -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;
|
46
routes/search.js
Normal file
46
routes/search.js
Normal file
@ -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;
|
48
routes/transaction.js
Normal file
48
routes/transaction.js
Normal file
@ -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;
|
4
views/404.jade
Normal file
4
views/404.jade
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h3 Not found
|
11
views/address.jade
Normal file
11
views/address.jade
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h3 Address
|
||||||
|
div= address
|
||||||
|
|
||||||
|
h3 Transactions
|
||||||
|
each val in txes
|
||||||
|
div
|
||||||
|
a(href='/transaction/#{val}/') #{val}
|
||||||
|
|
18
views/block.jade
Normal file
18
views/block.jade
Normal file
@ -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}
|
||||||
|
|
6
views/error.jade
Normal file
6
views/error.jade
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1= message
|
||||||
|
h2= error.status
|
||||||
|
pre #{error.stack}
|
11
views/index.jade
Normal file
11
views/index.jade
Normal file
@ -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}
|
18
views/layout.jade
Normal file
18
views/layout.jade
Normal file
@ -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
|
31
views/transaction.jade
Normal file
31
views/transaction.jade
Normal file
@ -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…
x
Reference in New Issue
Block a user