diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..47ad6673 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "public/lib" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c2cdfb8a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 341f15f2..daa35293 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # from https://github.com/github/gitignore/blob/master/Node.gitignore - lib-cov *.seed *.log @@ -8,6 +7,7 @@ lib-cov *.out *.pid *.gz +*.swp pids logs @@ -20,4 +20,12 @@ node_modules *.swp *~ .project +peerdb.json + +npm-debug.log +node_modules +.nodemonignore + +.DS_Store +public/lib/* diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..bd532c2d --- /dev/null +++ b/.jshintrc @@ -0,0 +1,40 @@ +{ + "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. + "browser": true, // Standard browser globals e.g. `window`, `document`. + "esnext": true, // Allow ES.next specific features such as `const` and `let`. + "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). + "camelcase": false, // Permit only camelcase for `var` and `object indexes`. + "curly": false, // Require {} for every new block or scope. + "eqeqeq": true, // Require triple equals i.e. `===`. + "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` + "latedef": true, // Prohibit variable use before definition. + "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`. + "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. + "quotmark": "single", // Define quotes to string values. + "regexp": true, // Prohibit `.` and `[^...]` in regular expressions. + "undef": true, // Require all non-global variables be declared before they are used. + "unused": true, // Warn unused variables. + "strict": true, // Require `use strict` pragma in every file. + "trailing": true, // Prohibit trailing whitespaces. + "smarttabs": false, // Suppresses warnings about mixed tabs and spaces + "globals": { // Globals variables. + "angular": true + }, + "predef": [ // Extra globals. + "define", + "require", + "exports", + "module", + "describe", + "before", + "beforeEach", + "after", + "afterEach", + "it", + "inject", + "expect" + ], + "indent": 2, // Specify indentation spacing + "devel": true, // Allow development statements e.g. `console.log();`. + "noempty": true // Prohibit use of empty blocks. +} diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..ab534a16 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,94 @@ +'use strict'; + +module.exports = function(grunt) { + // Project Configuration + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + watch: { + jade: { + files: ['app/views/**'], + options: { + livereload: true, + }, + }, + js: { + files: ['Gruntfile.js', 'server.js', 'app/**/*.js', 'public/js/**'], + tasks: ['jshint'], + options: { + livereload: true, + }, + }, + html: { + files: ['public/views/**'], + options: { + livereload: true, + }, + }, + css: { + files: ['public/css/**'], + options: { + livereload: true + } + } + }, + jshint: { + all: { + src: ['Gruntfile.js', 'server.js', 'app/**/*.js', 'public/js/**'], + options: { + jshintrc: true + } + } + }, + mochaTest: { + options: { + reporter: 'spec', + }, + src: ['test/*.js'] + }, + + nodemon: { + dev: { + options: { + file: 'server.js', + args: [], + ignoredFiles: ['public/**'], + watchedExtensions: ['js'], + nodeArgs: ['--debug'], + delayTime: 1, + env: { + PORT: 3000 + }, + cwd: __dirname + } + } + }, + concurrent: { + tasks: ['nodemon', 'watch'], + options: { + logConcurrentOutput: true + } + }, + env: { + test: { + NODE_ENV: 'test' + } + } + }); + + //Load NPM tasks + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-mocha-test'); + grunt.loadNpmTasks('grunt-nodemon'); + grunt.loadNpmTasks('grunt-concurrent'); + grunt.loadNpmTasks('grunt-env'); + + //Making grunt default to force in order not to break the project. + grunt.option('force', true); + + //Default task(s). + grunt.registerTask('default', ['jshint','concurrent']); + + //Test task. + grunt.registerTask('test', ['env:test', 'mochaTest']); +}; diff --git a/HeaderDB.js b/HeaderDB.js new file mode 100644 index 00000000..a751cd42 --- /dev/null +++ b/HeaderDB.js @@ -0,0 +1,213 @@ +require('classtool'); + +function ClassSpec(b) { + var assert = require('assert'); + var fs = require('fs'); + var Block = require('bitcore/Block').class(); + var Deserialize = require('bitcore/Deserialize'); + var Parser = require('bitcore/util/BinaryParser').class(); + var coinUtil = require('bitcore/util/util'); + + function HeaderDB(b) { + this.network = b.network; + this.fd = null; + this.blocks = {}; + this.byHeight = []; + this.bestBlock = null; + this.cached_size = 0; + } + + HeaderDB.prototype.size = function() { + this.cached_size = Object.keys(this.blocks).length; + return this.cached_size; + }; + + HeaderDB.prototype.locator = function(block) { + if (!block) + block = this.bestBlock; + + var step = 1; + var start = 0; + var loc = []; + // see https://en.bitcoin.it/wiki/Protocol_specification#getblocks + for (var i = block.height; i > 0; i -= step, ++start) { + if (start >= 10) + step *= 2; + loc.push(this.byHeight[i]); + } + assert.equal(this.byHeight[0].toString(), + this.network.genesisBlock.hash.toString()); + loc.push(this.byHeight[0]); + console.log("Requesting more headers. Current height: " + block.height ); + return loc; + }; + + HeaderDB.prototype.add = function(block) { + var hash = block.calcHash(); + block.hash = hash; + var curWork = Deserialize.intFromCompact(block.bits); + + if (hash in this.blocks) { + var old = this.blocks[hash]; + throw new Error("duplicate block (was at height " + old.height + ")"); + } + + var bestChain = false; + + var reorg = { + oldBest: null, + conn: 0, + disconn: 0, + }; + + if (this.cached_size == 0) { + if (this.network.genesisBlock.hash.toString() != + hash.toString()) + throw new Error("Invalid genesis block"); + + block.height = 0; + block.work = curWork; + bestChain = true; + this.cached_size++; + } else { + var prevBlock = this.blocks[block.prev_hash]; + if (!prevBlock) + throw new Error("orphan block; prev not found"); + + block.height = prevBlock.height + 1; + block.work = prevBlock.work + curWork; + this.cached_size++; + + if (block.work > this.bestBlock.work) + bestChain = true; + } + + + // add to by-hash index + this.blocks[hash] = block; + + if (bestChain) { + var oldBest = this.bestBlock; + var newBest = block; + + reorg.oldBest = oldBest; + + // likely case: new best chain has greater height + if (!oldBest) { + while (newBest) { + newBest = this.blocks[newBest.prev_hash]; + reorg.conn++; + } + } else { + while (newBest && + (newBest.height > oldBest.height)) { + newBest = this.blocks[newBest.prev_hash]; + reorg.conn++; + } + } + + // unlikely: old best chain has greater height + while (oldBest && newBest && + (oldBest.height > newBest.height)) { + oldBest = this.blocks[oldBest.prev_hash]; + reorg.disconn++; + } + + // height matches, but still walking parallel + while (oldBest && newBest && (oldBest != newBest)) { + newBest = this.blocks[newBest.prev_hash]; + reorg.conn++; + + oldBest = this.blocks[oldBest.prev_hash]; + reorg.disconn++; + } + + var shuf = (reorg.conn > reorg.disconn) ? + reorg.conn : reorg.disconn; + + // reorg analyzed, updated best-chain pointer + this.bestBlock = block; + + // update by-height index + var ptr = block; + var updated = []; + for (var idx = block.height; + idx > (block.height - shuf); idx--) { + if (idx < 0) + break; + var update = [ idx, ptr ]; + updated.push(update); + ptr = this.blocks[ptr.prev_hash]; + } + + updated.reverse(); + + for (var i = 0; i < updated.length; i++) { + var update = updated[i]; + var idx = update[0]; + var ptr = update[1]; + + if (idx < this.byHeight.length) + this.byHeight[idx] = ptr.hash; + else + this.byHeight.push(ptr.hash); + } + } + return reorg; + }; + + HeaderDB.prototype.addBuf = function(buf) { + var block = new Block(); + var parser = new Parser(buf); + block.parse(parser, true); + this.add(block); + + }; + + + HeaderDB.prototype.readFile = function(filename) { + var fd = fs.openSync(filename, 'r'); + var stats = fs.fstatSync(fd); + if (stats.size % 80 != 0) + throw new Error("Corrupted header db"); + + while (1) { + var buf = new Buffer(80); + var bread = fs.readSync(fd, buf, 0, 80, null); + if (bread < 80) + break; + + this.addBuf(buf); + + if ( ! ( this.cached_size % 1000 )) { + console.log("\tblock..." + this.cached_size ) ; + } + } + + fs.closeSync(fd); + }; + + HeaderDB.prototype.writeFile = function(filename) { + var block = this.bestBlock; + var data = []; + while (block) { + var s = block.getHeader(); + data.push(s); + block = this.blocks[block.prev_hash]; + } + + data.reverse(); + + var fd = fs.openSync(filename, 'w'); + + data.forEach(function(datum) { + fs.writeSync(fd, datum, 0, 80, null); + }); + + fs.closeSync(fd); + }; + + return HeaderDB; +}; +module.defineClass(ClassSpec); + diff --git a/README.md b/README.md index 17e46baf..c9868152 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,135 @@ -mystery -======= +# Mystery + +Project description. + +## Prerequisites +* Node.js - Download and Install [Node.js](http://www.nodejs.org/download/). You can also follow [this gist](https://gist.github.com/isaacs/579814) for a quick and easy way to install Node.js and npm +* MongoDB - Download and Install [MongoDB](http://www.mongodb.org/downloads) - Make sure it's running on the default port (27017). + +### Tools Prerequisites +* NPM - Node.js package manager, should be installed when you install node.js. +* Grunt - Download and Install [Grunt](http://gruntjs.com). +* Bower - Web package manager, installing [Bower](http://bower.io/) is simple when you have npm: + +``` +$ npm install -g bower +``` + +## Additional Packages +* Express - Defined as npm module in the [package.json](package.json) file. +* Mongoose - Defined as npm module in the [package.json](package.json) file. +* AngularJS - Defined as bower module in the [bower.json](bower.json) file. +* Twitter Bootstrap - Defined as bower module in the [bower.json](bower.json) file. +* UI Bootstrap - Defined as bower module in the [bower.json](bower.json) file. + +## Quick Install + The quickest way to get started with MEAN is to clone the project and utilize it like this: + + Grunt Command Line Interface: + + $ sudo npm -g install grunt-cli + + Install dependencies: + + $ npm install + + We use [Grunt](https://github.com/gruntjs/grunt-cli) to start the server: + + $ grunt + + When not using grunt you can use (for example in production): + + $ node server + + Then open a browser and go to: + + http://localhost:3000 + + + + +## API + +## Prerequisites + Get bitcore from github repository: + $ git clone https://github.com/bitpay/bitcore.git + $ cd bitcore + $ npm install + + Run sync from mystery repository: + $ utils/sync.js + +check utils/sync.js --help for options. + + +### Blocks +``` + /block/[:hash] + /block/00000000a967199a2fad0877433c93df785a8d8ce062e5f9b451cd1397bdbf62 +``` +### Transactions +``` + /tx/[:txid] + /tx/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c +``` + + + + + +## Troubleshooting +If you did not get all library during grunt command, please use the follow command: + + $ bower install + +## Configuration +All configuration is specified in the [config](config/) folder, particularly the [config.js](config/config.js) file and the [env](config/env/) files. Here you will need to specify your application name and database name. + +### bitcoind + +There is a bitcoind configuration sample at: +``` + etc/mystery/bitcoin.conf +``` + +If you want to use a external bitcoind server set BITCOIND_HOST / BITCOIND_PORT enviroment variables. Make sure that bitcoind is configured to accept incomming connections using 'rpcallowip' decribed in https://en.bitcoin.it/wiki/Running_Bitcoin. + + +### Environmental Settings + +There are three environments provided by default, __development__, __test__, and __production__. Each of these environments has the following configuration options: +* __db__ - This is the name of the MongoDB database to use, and is set by default to __mystery-dev__ for the development environment. +* __app.name__ - This is the name of your app or website, and can be different for each environment. You can tell which environment you are running by looking at the TITLE attribute that your app generates. + +To run with a different environment, just specify NODE_ENV as you call grunt: + + $ NODE_ENV=test grunt + +If you are using node instead of grunt, it is very similar: + + $ NODE_ENV=test node server + +## Github +[Mystery](https://github.com/bitpay/mystery) + +## License +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Sync.js b/Sync.js new file mode 100644 index 00000000..ae7d23ef --- /dev/null +++ b/Sync.js @@ -0,0 +1,219 @@ +require('classtool'); + +function spec(b) { + var mongoose = require('mongoose'); + var util = require('util'); + + var RpcClient = require('bitcore/RpcClient').class(); + var networks = require('bitcore/networks'); + var async = require('async'); + + var config = require('./config/config'); + var Block = require('./app/models/Block'); + var Transaction=require('./app/models/Transaction'); + + function Sync(config) { + this.network = config.networkName == 'testnet' ? networks.testnet : networks.livenet; + } + + var progress_bar = function(string, current, total) { + console.log( util.format("\t%s %d/%d [%d%%]", + string, current, total, parseInt(100 * current/total)) + ); + } + + Sync.prototype.getNextBlock = function (blockHash,cb) { + var that = this; + + if ( !blockHash ) { + return cb(); + } + + this.rpc.getBlock(blockHash, function(err, blockInfo) { + if (err) return cb(err); + + if ( ! ( blockInfo.result.height % 1000) ) { + var h = blockInfo.result.height, + d = blockInfo.result.confirmations; + progress_bar('height', h, h + d); + } + + Block.create( blockInfo.result, function(err, inBlock) { + + // E11000 => already exists + if (err && ! err.toString().match(/E11000/)) { + return cb(err); + } + + if (inBlock) { + inBlock.explodeTransactions(function (err) { + return that.getNextBlock(blockInfo.result.nextblockhash, cb); + }); + } + else + return that.getNextBlock(blockInfo.result.nextblockhash, cb); + }); + }); + } + + Sync.prototype.syncBlocks = function (reindex, cb) { + + var that = this; + var genesisHash = this.network.genesisBlock.hash.reverse().toString('hex'); + + console.log("Syncing Blocks..."); + if (reindex) + return this.getNextBlock(genesisHash, cb); + + + Block.findOne({}, {}, { sort: { 'confirmations' : 1 } }, function(err, block) { + if (err) return cb(err); + + var nextHash = + block && block.hash + ? block.hash + : genesisHash + ; + + + console.log('\tStarting at hash: ' + nextHash); + return that.getNextBlock(nextHash, cb); + }); + } + + + Sync.prototype.syncTXs = function (reindex, cb) { + + var that = this; + + console.log("Syncing TXs..."); + if (reindex) { + // TODO? + } + + + Transaction.find({blockhash: null}, function(err, txs) { + if (err) return cb(err); + + var read = 0; + var pull = 0; + var write = 0; + var total = txs.length; + console.log("\tneed to pull %d txs", total); + + if (!total) return cb(); + + async.each(txs, + function(tx, next){ + if (! tx.txid) { + console.log("NO TXID skipping...", tx); + return next(); + } + + if ( ! ( read++ % 1000) ) + progress_bar('read', read, total); + + + that.rpc.getRawTransaction(tx.txid, 1, function(err, txInfo) { + + if ( ! ( pull++ % 1000) ) + progress_bar('\tpull', pull, total); + + if (!err && txInfo) { + Transaction.update({txid: tx.txid}, txInfo.result, function(err) { + if (err) return next(err); + + if ( ! ( write++ % 1000) ) + progress_bar('\t\twrite', write, total); + + return next(); + }); + } + else return next(); + }); + }, + function(err){ + if (err) return cb(err); + return cb(err); + } + ); + }); + } + + Sync.prototype.start = function (opts, next) { + + + mongoose.connect(config.db); + var db = mongoose.connection; + this.rpc = new RpcClient(config.bitcoind); + var that = this; + + + db.on('error', console.error.bind(console, 'connection error:')); + + db.once('open', function (){ + + async.series([ + function(cb){ + if (opts.destroy) { + console.log("Deleting Blocks..."); + return Block.remove().exec(cb); + } + return cb(); + }, + function(cb){ + if (opts.destroy) { + console.log("Deleting TXs..."); + return Transaction.remove().exec(cb); + } + return cb(); + }, + function(cb) { + + if (! opts.skip_blocks) { + that.syncBlocks(opts.reindex, function(err) { + if (err) { + return cb(err); + + } + console.log("\tBlocks done."); + + return cb(); + }); + } + else { + return cb(); + } + }, + function(cb) { + if (! opts.skip_txs) { + that.syncTXs(opts.reindex, function(err) { + if (err) { + return cb(err); + + } + return cb(); + }); + } + else { + return cb(); + } + }, + function(cb) { + db.close(); + return cb(); + }, + ], + function(err) { + if (err) { + db.close(); + return next(err); + } + return next(); + }); + }); + } + return Sync; +}; +module.defineClass(spec); + diff --git a/app/controllers/blocks.js b/app/controllers/blocks.js new file mode 100644 index 00000000..d9c9f89c --- /dev/null +++ b/app/controllers/blocks.js @@ -0,0 +1,69 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var mongoose = require('mongoose'), + Block = mongoose.model('Block'); +//, _ = require('lodash'); + + +/** + * Find block by hash ... + */ +exports.block = function(req, res, next, hash) { + Block.fromHash(hash, function(err, block) { + if (err) return next(err); + if (!block) return next(new Error('Failed to load block ' + hash)); + req.block = block; + next(); + }); +}; + + +/** + * Show block + */ +exports.show = function(req, res) { + res.jsonp(req.block); +}; + +/** + * List of blocks at HomePage + */ +exports.last_blocks = function(req, res) { + Block.find().sort({time:-1}).limit(7).exec(function(err, blocks) { + if (err) { + res.render('error', { + status: 500 + }); + } else { + res.jsonp(blocks); + } + }); +}; + +/** + * List of blocks by date + */ +exports.list = function(req, res) { + var findParam = {}; + + if (req.query.blockDate) { + findParam = {}; + } + + Block + .find(findParam) + .limit(5) + .exec(function(err, blocks) { + if (err) { + res.render('error', { + status: 500 + }); + } else { + res.jsonp(blocks); + } + }); +}; diff --git a/app/controllers/index.js b/app/controllers/index.js new file mode 100644 index 00000000..f71d55c5 --- /dev/null +++ b/app/controllers/index.js @@ -0,0 +1,5 @@ +'use strict'; + +exports.render = function(req, res) { + res.render('index'); +}; diff --git a/app/controllers/transactions.js b/app/controllers/transactions.js new file mode 100644 index 00000000..8bf4635c --- /dev/null +++ b/app/controllers/transactions.js @@ -0,0 +1,33 @@ +'use strict'; + + +var Transaction = require('../models/Transaction'); +//, _ = require('lodash'); + + + +/** + * Module dependencies. + */ + + +/** + * Find block by hash ... + */ +exports.transaction = function(req, res, next, txid) { + Transaction.fromID(txid, function(err, tx) { + if (err) return next(err); + if (!tx) return next(new Error('Failed to load TX ' + txid)); + req.transaction = tx; + next(); + }); +}; + + +/** + * Show block + */ +exports.show = function(req, res) { + res.jsonp(req.transaction); +}; + diff --git a/app/models/Block.js b/app/models/Block.js new file mode 100644 index 00000000..ee68b94d --- /dev/null +++ b/app/models/Block.js @@ -0,0 +1,97 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + +var async = require('async'); +var Transaction = require('./Transaction'); + +/** + * Block Schema + */ +var BlockSchema = new Schema({ + hash: { + type: String, + index: true, + unique: true, + }, + size: Number, + height: Number, + confirmations: Number, + version: Number, + merkleroot: String, + tx: [ String ], + time: Date, + nonce: Number, + bits: String, + difficulty: Number, + chainwork: String, + previousblockhash: { + type: String, + index: true, + unique: true, + }, + nextblockhash: { + type: String, + index: true, + unique: true, + }, +}); + +BlockSchema.methods.explodeTransactions = function(next) { + + // console.log('exploding %s', this.hash, typeof this.tx); + + async.forEach( this.tx, + function(tx, callback) { + // console.log('procesing TX %s', tx); + Transaction.create({ txid: tx }, function(err) { + if (err && ! err.toString().match(/E11000/)) { + return callback(); + } + if (err) { + + return callback(err); + } + return callback(); + + }); + }, + function(err) { + if (err) return next(err); + return next(); + } + ); +}; + +/** + * Validations + */ + +/* +BlockSchema.path('title').validate(function(title) { + return title.length; +},'Title cannot be blank'); +*/ + +/** + * Statics + */ + +BlockSchema.statics.load = function(id, cb) { + this.findOne({ + _id: id + }).exec(cb); +}; + + +BlockSchema.statics.fromHash = function(hash, cb) { + this.findOne({ + hash: hash, + }).exec(cb); +}; + +module.exports = mongoose.model('Block', BlockSchema); diff --git a/app/models/Transaction.js b/app/models/Transaction.js new file mode 100644 index 00000000..566a480a --- /dev/null +++ b/app/models/Transaction.js @@ -0,0 +1,64 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + + +/** + */ +var TransactionSchema = new Schema({ + txid: { + type: String, + index: true, + unique: true, + }, + version: Number, + locktime: Number, + vin: { + type: Array, + default: [], + }, + vout: { + type: Array, + default: [], + }, + blockhash: { + type: String, + index: true, + default: null, + }, + confirmations: Number, + time: Number, + blocktime: Number, +}); + +/** + * Statics + */ + +TransactionSchema.statics.load = function(id, cb) { + this.findOne({ + _id: id + }).exec(cb); +}; + + +TransactionSchema.statics.fromID = function(txid, cb) { + this.findOne({ + txid: txid, + }).exec(cb); +}; + +/* + * virtual + */ + +// ugly? new object every call? +TransactionSchema.virtual('date').get(function () { + return new Date(this.time); +}); + +module.exports = mongoose.model('Transaction', TransactionSchema); diff --git a/app/views/404.jade b/app/views/404.jade new file mode 100755 index 00000000..2f0d9e86 --- /dev/null +++ b/app/views/404.jade @@ -0,0 +1,13 @@ +extends layouts/default + +block main + h1 Oops something went wrong + br + span 404 + +block content + #error-message-box + #error-stack-trace + pre + code!= error + diff --git a/app/views/500.jade b/app/views/500.jade new file mode 100755 index 00000000..491b0008 --- /dev/null +++ b/app/views/500.jade @@ -0,0 +1,12 @@ +extends layouts/default + +block main + h1 Oops something went wrong + br + span 500 + +block content + #error-message-box + #error-stack-trace + pre + code!= error diff --git a/app/views/includes/foot.jade b/app/views/includes/foot.jade new file mode 100755 index 00000000..edca962e --- /dev/null +++ b/app/views/includes/foot.jade @@ -0,0 +1,34 @@ +#footer + .container + p.text-muted Place sticky footer content here. + +//script(type='text/javascript', src='/lib/jquery/jquery.min.js') +//script(type='text/javascript', src='/lib/bootstrap/dist/js/bootstrap.min.js') + +//AngularJS +script(type='text/javascript', src='/lib/angular/angular.js') +script(type='text/javascript', src='/lib/angular-cookies/angular-cookies.js') +script(type='text/javascript', src='/lib/angular-resource/angular-resource.js') +script(type='text/javascript', src='/lib/angular-route/angular-route.js') + +//Angular UI +script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap.js') +script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap-tpls.js') +script(type='text/javascript', src='/lib/angular-ui-utils/ui-utils.js') + +//Application Init +script(type='text/javascript', src='/js/app.js') +script(type='text/javascript', src='/js/config.js') +script(type='text/javascript', src='/js/directives.js') +script(type='text/javascript', src='/js/filters.js') + +//Application Services +script(type='text/javascript', src='/js/services/blocks.js') +script(type='text/javascript', src='/js/services/global.js') +script(type='text/javascript', src='/js/services/index.js') + +//Application Controllers +script(type='text/javascript', src='/js/controllers/index.js') +script(type='text/javascript', src='/js/controllers/header.js') +script(type='text/javascript', src='/js/controllers/blocks.js') +script(type='text/javascript', src='/js/init.js') diff --git a/app/views/includes/head.jade b/app/views/includes/head.jade new file mode 100755 index 00000000..090c3e2c --- /dev/null +++ b/app/views/includes/head.jade @@ -0,0 +1,15 @@ +head + meta(charset='utf-8') + meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') + meta(name='viewport', content='width=device-width,initial-scale=1.0') + + title Mystery + meta(http-equiv='Content-type', content='text/html;charset=UTF-8') + meta(name="keywords", content="node.js, express, mongoose, mongodb, angularjs") + meta(name="description", content="Mystery") + + link(href='/img/icons/favicon.ico', rel='shortcut icon', type='image/x-icon') + + link(rel='stylesheet', href='/lib/bootstrap/dist/css/bootstrap.min.css') + link(rel='stylesheet', href='/css/common.css') + diff --git a/app/views/includes/navbar.jade b/app/views/includes/navbar.jade new file mode 100644 index 00000000..a1b2b1c8 --- /dev/null +++ b/app/views/includes/navbar.jade @@ -0,0 +1 @@ +.navbar.navbar-default.navbar-fixed-top(data-ng-include="'views/header.html'", role='navigation') diff --git a/app/views/index.jade b/app/views/index.jade new file mode 100755 index 00000000..a74f1733 --- /dev/null +++ b/app/views/index.jade @@ -0,0 +1,4 @@ +extends layouts/default + +block content + section.container(data-ng-view) diff --git a/app/views/layouts/default.jade b/app/views/layouts/default.jade new file mode 100755 index 00000000..64e2713b --- /dev/null +++ b/app/views/layouts/default.jade @@ -0,0 +1,8 @@ +doctype html +html(lang='en', xmlns='http://www.w3.org/1999/xhtml') + include ../includes/head + body + #wrap + include ../includes/navbar + block content + include ../includes/foot diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..46be4c59 --- /dev/null +++ b/bower.json @@ -0,0 +1,14 @@ +{ + "name": "Mystery", + "version": "0.0.1", + "dependencies": { + "angular": "latest", + "angular-resource": "latest", + "angular-cookies": "latest", + "angular-mocks": "latest", + "angular-route": "latest", + "bootstrap": "3.0.3", + "angular-bootstrap": "0.9.0", + "angular-ui-utils": "0.1.0" + } +} \ No newline at end of file diff --git a/config/config.js b/config/config.js new file mode 100644 index 00000000..2f0e7c93 --- /dev/null +++ b/config/config.js @@ -0,0 +1,9 @@ +'use strict'; + +var _ = require('lodash'); + +// Load app configuration + +module.exports = _.extend( + require(__dirname + '/../config/env/all.js'), + require(__dirname + '/../config/env/' + process.env.NODE_ENV + '.js') || {}); diff --git a/config/env/all.js b/config/env/all.js new file mode 100755 index 00000000..7f53a56b --- /dev/null +++ b/config/env/all.js @@ -0,0 +1,10 @@ +'use strict'; + +var path = require('path'), +rootPath = path.normalize(__dirname + '/../..'); + +module.exports = { + root: rootPath, + port: process.env.PORT || 3000, + db: process.env.MONGOHQ_URL +} diff --git a/config/env/development.js b/config/env/development.js new file mode 100755 index 00000000..6836a43c --- /dev/null +++ b/config/env/development.js @@ -0,0 +1,15 @@ +'use strict'; + +module.exports = { + db: "mongodb://localhost/mystery-dev", + app: { + name: "Mystery - Development" + }, + bitcoind: { + user: 'mystery', + pass: 'real_mystery', + protocol: 'http', + host: process.env.BITCOIND_HOST || '127.0.0.1', + port: process.env.BITCOIND_PORT || '8332', + } +} diff --git a/config/env/production.js b/config/env/production.js new file mode 100755 index 00000000..5d2c3b49 --- /dev/null +++ b/config/env/production.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = { + db: "mongodb://localhost/mystery", + app: { + name: "Mystery - Production" + } +} \ No newline at end of file diff --git a/config/env/test.js b/config/env/test.js new file mode 100755 index 00000000..dabf6021 --- /dev/null +++ b/config/env/test.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + db: "mongodb://localhost/mystery-test", + port: 3001, + app: { + name: "Mystery - Test" + } +} \ No newline at end of file diff --git a/config/express.js b/config/express.js new file mode 100644 index 00000000..1abe1908 --- /dev/null +++ b/config/express.js @@ -0,0 +1,69 @@ +'use strict'; + +/** + * Module dependencies. + */ +var express = require('express'), + config = require('./config'); + +module.exports = function(app, passport, db) { + app.set('showStackError', true); + + //Prettify HTML + app.locals.pretty = true; + + //Should be placed before express.static + app.use(express.compress({ + filter: function(req, res) { + return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); + }, + level: 9 + })); + + //Set views path, template engine and default layout + app.set('views', config.root + '/app/views'); + app.set('view engine', 'jade'); + + //Enable jsonp + app.enable("jsonp callback"); + + app.configure(function() { + //cookieParser should be above session + app.use(express.cookieParser()); + + // request body parsing middleware should be above methodOverride + app.use(express.urlencoded()); + app.use(express.json()); + app.use(express.methodOverride()); + + //routes should be at the last + app.use(app.router); + + //Setting the fav icon and static folder + app.use(express.favicon()); + app.use(express.static(config.root + '/public')); + + //Assume "not found" in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc. + app.use(function(err, req, res, next) { + //Treat as 404 + if (~err.message.indexOf('not found')) return next(); + + //Log it + console.error(err.stack); + + //Error page + res.status(500).render('500', { + error: err.stack + }); + }); + + //Assume 404 since no middleware responded + app.use(function(req, res, next) { + res.status(404).render('404', { + url: req.originalUrl, + error: 'Not found' + }); + }); + + }); +}; \ No newline at end of file diff --git a/config/routes.js b/config/routes.js new file mode 100644 index 00000000..ab4c9b18 --- /dev/null +++ b/config/routes.js @@ -0,0 +1,21 @@ +'use strict'; + +module.exports = function(app) { + + //Home route + var index = require('../app/controllers/index'); + app.get('/', index.render); + + //Block routes + var blocks = require('../app/controllers/blocks'); + app.get('/api/blocks', blocks.list); + app.get('/api/block/:blockHash', blocks.show); + app.param('blockHash', blocks.block); + app.get('/last_blocks', blocks.last_blocks); + + var transactions = require('../app/controllers/transactions'); + app.get('/tx/:txid', transactions.show); + + app.param('txid', transactions.transaction); + +}; diff --git a/etc/bitcoind/bitcoin.conf b/etc/bitcoind/bitcoin.conf new file mode 100644 index 00000000..062c09e0 --- /dev/null +++ b/etc/bitcoind/bitcoin.conf @@ -0,0 +1,13 @@ +rpcuser=mystery +rpcpassword=real_mystery +server=1 +rpcport=8332 +testnet=3 +txindex=1 + +# Allow connections outsite localhost? +# rpcallowip=192.168.0.* + + + + diff --git a/p2p.js b/p2p.js new file mode 100755 index 00000000..f7474a94 --- /dev/null +++ b/p2p.js @@ -0,0 +1,220 @@ +var fs = require('fs'); +var HeaderDB = require('./HeaderDB').class(); +var Block = require('bitcore/Block').class(); +var CoinConst = require('bitcore/const'); +var coinUtil = require('bitcore/util/util'); +var networks = require('bitcore/networks'); +var Parser = require('bitcore/util/BinaryParser').class(); + +var peerdb_fn = 'peerdb.json'; + +var peerdb = undefined; +var hdrdb = undefined; +var network = networks.testnet; +var config = { + network : network.name +}; +var PeerManager = require('bitcore/PeerManager').createClass({ + config : config +}); +var Peer = require('bitcore/Peer').class(); + +var syncState = 'init'; + +function peerdb_load() { + try { + peerdb = JSON.parse(fs.readFileSync(peerdb_fn)); + } catch (d) { + console.warn('Unable to read peer db', peerdb_fn, 'creating new one.'); + peerdb = [ { + ipv4 : '127.0.0.1', + port : 18333 + }, ]; + + fs.writeFileSync(peerdb_fn, JSON.stringify(peerdb)); + } +} + +function hdrdb_load() +{ + hdrdb = new HeaderDB({network: network}); +} + +function get_more_headers(info) { + var conn = info.conn; + var loc = hdrdb.locator(); + conn.sendGetHeaders(loc, coinUtil.NULL_HASH); +} + +function add_header(info, block) { + var hashStr = coinUtil.formatHashFull(block.calcHash()); + + try { + hdrdb.add(block); + } catch (e) { + return; + } +} + +function handle_headers(info) { + console.log("handle headers"); + var conn = info.conn; + var headers = info.message.headers; + + headers.forEach(function(hdr) { + add_header(info, hdr); + }); + + // We persist the header DB after each batch + //hdrdb.writeFile(hdrdb_fn); + + // Only one request per batch of headers we receive. + get_more_headers(info); +} + +function handle_block(info) { + console.log("handle block") + var block = info.message.block; + add_header(info, block); + + if (syncState === 'init') { + syncState = 'headers'; + get_more_headers(info); + } +} + +function handle_verack(info) { + var inv = { + type : CoinConst.MSG.BLOCK, + hash : network.genesisBlock.hash, + }; + var invs = [ inv ]; + console.log('p2psync: Asking for the genesis block'); + + // Asks for the genesis block + info.conn.sendGetData(invs); +} + +function handle_connected(data) { + var peerman = data.pm; + var peers_n = peerman.peers.length; + console.log('p2psync: Connected to ' + peers_n + ' peer' + + (peers_n != 1 ? 's' : '')); +} + +function p2psync() { + var peerman = new PeerManager(); + + peerdb.forEach(function(datum) { + var peer = new Peer(datum.ipv4, datum.port); + peerman.addPeer(peer); + }); + + peerman.on('connection', function(conn) { + conn.on('verack', handle_verack); + conn.on('block', handle_block); + conn.on('headers', handle_headers); + }); + peerman.on('connect', handle_connected); + + peerman.start(); +} + +function filesync_block_buf(blkdir, fn, buf) { + var parser = new Parser(buf); + var block = new Block(); + block.parse(parser, true); + + var hashStr = coinUtil.formatHashFull(block.calcHash()); + + try { + hdrdb.add(block); + } catch (e) { + var height = hdrdb.size(); + console.log('HeaderDB failed adding block #' + height + ', ' + hashStr); + console.log(' Reason: ' + e); + return; + } + + var height = hdrdb.size() - 1; + if ((height % 1000) == 0) + console.log('HeaderDB added block #' + height + ', ' + hashStr); +} + +function filesync_open_cb(err, fd, blkdir, fn) { + if (err) + throw err; + + var hdrbuf = new Buffer(4 * 2); + while (1) { + // read 2x 32-bit header + var bread = fs.readSync(fd, hdrbuf, 0, 4 * 2, null); + if (bread < (4 * 2)) { + console.log('Short read/EOF, ending scan of ' + fn); + break; + } + + // check magic matches + var magic = hdrbuf.slice(0, 4); + if (magic.toString() != network.magic.toString()) { + console.log('Invalid network magic, ending scan of ' + fn); + break; + } + + // block size + var blkSize = hdrbuf.readUInt32LE(4); + if (blkSize > (1 * 1024 * 1024)) + throw new Error('Invalid block size ' + blkSize); + + // read raw block data + var blkBuf = new Buffer(blkSize); + bread = fs.readSync(fd, blkBuf, 0, blkSize, null); + if (bread != blkSize) + throw new Error('Failed to read block'); + + // process block + filesync_block_buf(blkdir, fn, blkBuf); + } + + fs.closeSync(fd); + + hdrdb.writeFile(hdrdb_fn); + console.log('Wrote header db'); +} + +function filesync_block_file(blkdir, fn) { + console.log('Scanning ' + fn + ' for block data.'); + + var pathname = blkdir + '/' + fn; + fs.open(pathname, 'r', function(err, fd) { + filesync_open_cb(err, fd, blkdir, fn); + }); +} + +function cmd_filesync_rd(err, files, blkdir) { + if (err) + throw err; + + files = files.sort(); + + var scanned = 0; + files.forEach(function(fn) { + var re = /^blk\d+\.dat$/; + if (fn.match(re)) { + filesync_block_file(blkdir, fn); + scanned++; + } + }); + + console.log('Scanned ' + scanned + ' of ' + files.length + ' files in ' + + blkdir); +} + +function main() { + peerdb_load(); + hdrdb_load(); + + p2psync(); +} + +main(); diff --git a/package.json b/package.json new file mode 100644 index 00000000..1dd908e4 --- /dev/null +++ b/package.json @@ -0,0 +1,66 @@ +{ + "name": "mystery", + "version": "0.0.1", + "private": true, + "author": { + "name": "Ryan X Charles", + "email": "ryan@bitpay.com" + }, + "repository": "git://github.com/bitpay/mystery.git", + "contributors": [ + { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + } + ], + "bugs": { + "url": "https://github.com/bitpay/mystery/issues" + }, + "homepage": "https://github.com/bitpay/mystery", + "license": "MIT", + "keywords": [ + "mystery", + "secret", + "enigma", + "riddle", + "mystification", + "puzzle", + "conundrum" + ], + "engines": { + "node": "*" + }, + "scripts": { + "start": "node node_modules/grunt-cli/bin/grunt", + "postinstall": "node node_modules/bower/bin/bower install" + }, + "dependencies": { + "async": "*", + "classtool": "*", + "commander": "*", + "express": "~3.4.7", + "jade": "~1.0.2", + "mongoose": "~3.8.3", + "lodash": "~2.4.1", + "bower": "~1.2.8", + "bitcore": "*", + "buffertools": "*", + "grunt": "~0.4.2", + "grunt-cli": "~0.1.11", + "grunt-env": "~0.4.1", + "grunt-contrib-jshint": "~0.8.0", + "grunt-contrib-watch": "~0.5.3", + "grunt-concurrent": "~0.4.2", + "grunt-nodemon": "~0.1.2", + "grunt-mocha-test": "~0.8.1", + "should": "~2.1.1" + }, + "devDependencies": { + "grunt-contrib-watch": "latest", + "grunt-contrib-jshint": "latest", + "grunt-nodemon": "latest", + "grunt-concurrent": "latest", + "grunt-mocha-test": "latest", + "should": "latest" + } +} diff --git a/public/css/common.css b/public/css/common.css new file mode 100644 index 00000000..7ca093ad --- /dev/null +++ b/public/css/common.css @@ -0,0 +1,45 @@ +/* Sticky footer styles +-------------------------------------------------- */ + +html, +body { + height: 100%; + /* The html and body elements cannot have any padding or margin. */ +} + +/* Wrapper for page content to push down footer */ +#wrap { + min-height: 100%; + height: auto; + /* Negative indent footer by its height */ + margin: 0 auto -60px; + /* Pad bottom by footer height */ + padding: 0 0 60px; +} + +/* Set the fixed height of the footer here */ +#footer { + height: 60px; + background-color: #f5f5f5; +} + + +/* Custom page CSS +-------------------------------------------------- */ +/* Not required for template or sticky footer method. */ + +#wrap > .container { + padding: 60px 15px 0; +} +.container .text-muted { + margin: 20px 0; +} + +#footer > .container { + padding-left: 15px; + padding-right: 15px; +} + +code { + font-size: 80%; +} diff --git a/public/img/.gitignore b/public/img/.gitignore new file mode 100755 index 00000000..e69de29b diff --git a/public/js/app.js b/public/js/app.js new file mode 100755 index 00000000..2b6bc682 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('mystery', ['ngCookies', 'ngResource', 'ngRoute', 'ui.bootstrap', 'ui.route', 'mystery.system', 'mystery.index', 'mystery.blocks']); + +angular.module('mystery.system', []); +angular.module('mystery.index', []); +angular.module('mystery.blocks', []); diff --git a/public/js/config.js b/public/js/config.js new file mode 100755 index 00000000..35fe3836 --- /dev/null +++ b/public/js/config.js @@ -0,0 +1,30 @@ +'use strict'; + +//Setting up route +angular.module('mystery').config(['$routeProvider', + function($routeProvider) { + $routeProvider. + when('/block/:blockHash', { + templateUrl: 'views/block.html' + }). + when('/', { + templateUrl: 'views/index.html' + }). + when('/blocks', { + templateUrl: 'views/blocks/list.html' + }). + when('/blocks-date/:blockDate', { + templateUrl: 'views/blocks/list_date.html' + }). + otherwise({ + redirectTo: '/' + }); + } +]); + +//Setting HTML5 Location Mode +angular.module('mystery').config(['$locationProvider', + function($locationProvider) { + $locationProvider.hashPrefix('!'); + } +]); diff --git a/public/js/controllers/blocks.js b/public/js/controllers/blocks.js new file mode 100644 index 00000000..98e88010 --- /dev/null +++ b/public/js/controllers/blocks.js @@ -0,0 +1,30 @@ +'use strict'; + +angular.module('mystery.blocks').controller('BlocksController', ['$scope', '$routeParams', '$location', 'Global', 'Block', 'Blocks', function ($scope, $routeParams, $location, Global, Block, Blocks) { + $scope.global = Global; + + $scope.list_blocks = function() { + Blocks.query(function(blocks) { + $scope.blocks = blocks; + }); + }; + + $scope.list_blocks_date = function() { + Blocks.query({ + blockDate: $routeParams.blockDate + }, function(blocks) { + $scope.blocks = blocks; + }); + }; + + $scope.findOne = function() { + Block.get({ + blockHash: $routeParams.blockHash + }, function(block) { + $scope.block = block; + }); + }; + + // for avoid warning. please remove when you use Blocks + $scope.blocks = Blocks; +}]); diff --git a/public/js/controllers/header.js b/public/js/controllers/header.js new file mode 100755 index 00000000..8dddc143 --- /dev/null +++ b/public/js/controllers/header.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('mystery.system').controller('HeaderController', ['$scope', 'Global', function ($scope, Global) { + $scope.global = Global; + + $scope.menu = [{ + 'title': 'Blocks', + 'link': 'blocks' + }]; + + $scope.isCollapsed = false; +}]); diff --git a/public/js/controllers/index.js b/public/js/controllers/index.js new file mode 100755 index 00000000..bec0f0ad --- /dev/null +++ b/public/js/controllers/index.js @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('mystery.system').controller('IndexController', ['$scope', 'Global', 'Index', function ($scope, Global, Index) { + $scope.global = Global; + $scope.last_blocks = function() { + Index.query(function(blocks) { + $scope.blocks = blocks; + }); + }; +}]); diff --git a/public/js/directives.js b/public/js/directives.js new file mode 100755 index 00000000..a726efc4 --- /dev/null +++ b/public/js/directives.js @@ -0,0 +1 @@ +'use strict'; \ No newline at end of file diff --git a/public/js/filters.js b/public/js/filters.js new file mode 100755 index 00000000..a726efc4 --- /dev/null +++ b/public/js/filters.js @@ -0,0 +1 @@ +'use strict'; \ No newline at end of file diff --git a/public/js/init.js b/public/js/init.js new file mode 100755 index 00000000..b226e6fd --- /dev/null +++ b/public/js/init.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.element(document).ready(function() { + //Fixing facebook bug with redirect + if (window.location.hash === '#_=_') window.location.hash = '#!'; + + //Then init the app + angular.bootstrap(document, ['mystery']); +}); diff --git a/public/js/services/blocks.js b/public/js/services/blocks.js new file mode 100644 index 00000000..7747ff32 --- /dev/null +++ b/public/js/services/blocks.js @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('mystery.blocks').factory('Block', ['$resource', function($resource) { + return $resource('/api/block/:blockHash', { + blockHash: '@blockHash' + }); +}]); + +angular.module('mystery.blocks').factory('Blocks', ['$resource', function($resource) { + return $resource('/api/blocks'); +}]); diff --git a/public/js/services/global.js b/public/js/services/global.js new file mode 100755 index 00000000..79006526 --- /dev/null +++ b/public/js/services/global.js @@ -0,0 +1,14 @@ +'use strict'; + +//Global service for global variables +angular.module('mystery.system').factory('Global', [ + function() { + var _this = this; + _this._data = { + user: window.user, + authenticated: !! window.user + }; + + return _this._data; + } +]); diff --git a/public/js/services/index.js b/public/js/services/index.js new file mode 100644 index 00000000..f961642e --- /dev/null +++ b/public/js/services/index.js @@ -0,0 +1,5 @@ +'use strict'; + +angular.module('mystery.index').factory('Index', ['$resource', function($resource) { + return $resource('/last_blocks'); +}]); diff --git a/public/views/block.html b/public/views/block.html new file mode 100644 index 00000000..686a6ade --- /dev/null +++ b/public/views/block.html @@ -0,0 +1,23 @@ +
+ + + + + + + + + + + + + + + + + + +
HeightAgeTransactionsConfirmationsSize (kB)
{{block.height}}{{block.time | date:'short'}}{{block.tx.length }}{{block.confirmations}}{{block.size / 1024}}
+
\ No newline at end of file diff --git a/public/views/blocks/list.html b/public/views/blocks/list.html new file mode 100644 index 00000000..f258705a --- /dev/null +++ b/public/views/blocks/list.html @@ -0,0 +1,10 @@ +
+ + +
\ No newline at end of file diff --git a/public/views/blocks/list_date.html b/public/views/blocks/list_date.html new file mode 100644 index 00000000..349857cf --- /dev/null +++ b/public/views/blocks/list_date.html @@ -0,0 +1,10 @@ +
+ + +
diff --git a/public/views/header.html b/public/views/header.html new file mode 100755 index 00000000..fb034edb --- /dev/null +++ b/public/views/header.html @@ -0,0 +1,18 @@ +
+ + +
diff --git a/public/views/index.html b/public/views/index.html new file mode 100644 index 00000000..953dc31b --- /dev/null +++ b/public/views/index.html @@ -0,0 +1,23 @@ +
+ + + + + + + + + + + + + + + + + + +
HeightAgeTransactionsConfirmationsSize (kB)
{{block.height}}{{block.time | date:'short'}}{{block.tx.length }}{{block.confirmations}}{{block.size / 1024}}
+
diff --git a/server.js b/server.js new file mode 100644 index 00000000..de2a3497 --- /dev/null +++ b/server.js @@ -0,0 +1,55 @@ +'use strict'; + +/** + * Module dependencies. + */ +var express = require('express'), + fs = require('fs'); + +/** + * Main application entry file. + */ + +//Load configurations +//Set the node enviornment variable if not set before +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +//Initializing system variables +var config = require('./config/config'), + mongoose = require('mongoose'); + +//Bootstrap db connection +var db = mongoose.connect(config.db); + +//Bootstrap models +var models_path = __dirname + '/app/models'; +var walk = function(path) { + fs.readdirSync(path).forEach(function(file) { + var newPath = path + '/' + file; + var stat = fs.statSync(newPath); + if (stat.isFile()) { + if (/(.*)\.(js$|coffee$)/.test(file)) { + require(newPath); + } + } else if (stat.isDirectory()) { + walk(newPath); + } + }); +}; +walk(models_path); + +var app = express(); + +//express settings +require('./config/express')(app, db); + +//Bootstrap routes +require('./config/routes')(app); + +//Start the app by listening on +var port = process.env.PORT || config.port; +app.listen(port); +console.log('Express app started on port ' + port); + +//expose app +exports = module.exports = app; diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 00000000..74590dc4 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,4 @@ +--require should +-R spec +--ui bdd + diff --git a/test/test.js b/test/test.js new file mode 100644 index 00000000..bab25b64 --- /dev/null +++ b/test/test.js @@ -0,0 +1,9 @@ +var assert = require("assert") +describe('Array', function(){ + describe('#indexOf()', function(){ + it('should return -1 when the value is not present', function(){ + assert.equal(-1, [1,2,3].indexOf(5)); + assert.equal(-1, [1,2,3].indexOf(0)); + }) + }) +}) diff --git a/util/get_block.js b/util/get_block.js new file mode 100755 index 00000000..783d7cf7 --- /dev/null +++ b/util/get_block.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var RpcClient = require('../node_modules/bitcore/RpcClient').class(); + +var config = require('../config/config'); + + +var block_hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4'; + + +var rpc = new RpcClient(config.bitcoind); + +var block = rpc.getBlock(block_hash, function(err, block) { + + console.log("Err:"); + console.log(err); + + + console.log("Block info:"); + console.log(block); +}); + + + diff --git a/util/sync.js b/util/sync.js new file mode 100755 index 00000000..22d1a53d --- /dev/null +++ b/util/sync.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +require('buffertools').extend(); + +var SYNC_VERSION = '0.1'; +var program = require('commander'); +var Sync = require('../Sync').class(); + +program + .version(SYNC_VERSION) + .option('-N --network [livenet]', 'Set bitcoin network [testnet]', 'testnet') + .option('-R --reindex', 'Force reindexing', '0') + .option('-D --destroy', 'Remove current DB', '0') + .option('--skip_blocks', 'Sync blocks') + .option('--skip_txs', 'Sync transactions') + .parse(process.argv); + +var sync = new Sync({ networkName: program.network }); + +if (program.remove) { + +} + +sync.start( program, function(err){ + if (err) { + console.log("CRITICAL ERROR: ", err); + } + else { + console.log('Done!'); + } +}); +