commit
2962c4e30f
|
@ -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
|
|
@ -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/*
|
||||
|
||||
|
|
|
@ -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.
|
||||
}
|
|
@ -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']);
|
||||
};
|
|
@ -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);
|
||||
|
137
README.md
137
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.
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
exports.render = function(req, res) {
|
||||
res.render('index');
|
||||
};
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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);
|
|
@ -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);
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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')
|
|
@ -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')
|
||||
|
|
@ -0,0 +1 @@
|
|||
.navbar.navbar-default.navbar-fixed-top(data-ng-include="'views/header.html'", role='navigation')
|
|
@ -0,0 +1,4 @@
|
|||
extends layouts/default
|
||||
|
||||
block content
|
||||
section.container(data-ng-view)
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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') || {});
|
|
@ -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
|
||||
}
|
|
@ -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',
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
db: "mongodb://localhost/mystery",
|
||||
app: {
|
||||
name: "Mystery - Production"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
db: "mongodb://localhost/mystery-test",
|
||||
port: 3001,
|
||||
app: {
|
||||
name: "Mystery - Test"
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
};
|
|
@ -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);
|
||||
|
||||
};
|
|
@ -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.*
|
||||
|
||||
|
||||
|
||||
|
|
@ -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();
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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%;
|
||||
}
|
|
@ -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', []);
|
|
@ -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('!');
|
||||
}
|
||||
]);
|
|
@ -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;
|
||||
}]);
|
|
@ -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;
|
||||
}]);
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
}]);
|
|
@ -0,0 +1 @@
|
|||
'use strict';
|
|
@ -0,0 +1 @@
|
|||
'use strict';
|
|
@ -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']);
|
||||
});
|
|
@ -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');
|
||||
}]);
|
|
@ -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;
|
||||
}
|
||||
]);
|
|
@ -0,0 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('mystery.index').factory('Index', ['$resource', function($resource) {
|
||||
return $resource('/last_blocks');
|
||||
}]);
|
|
@ -0,0 +1,23 @@
|
|||
<section data-ng-controller="BlocksController" data-ng-init="findOne()">
|
||||
<div class="page-header">
|
||||
<h1>Block Page</h1>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<th>Height</th>
|
||||
<th>Age</th>
|
||||
<th>Transactions</th>
|
||||
<th>Confirmations</th>
|
||||
<th>Size (kB)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{block.height}}</td>
|
||||
<td>{{block.time | date:'short'}}</td>
|
||||
<td>{{block.tx.length }}</td>
|
||||
<td>{{block.confirmations}}</td>
|
||||
<td>{{block.size / 1024}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
|
@ -0,0 +1,10 @@
|
|||
<section data-ng-controller="BlocksController" data-ng-init="list_blocks()">
|
||||
<div class="page-header">
|
||||
<h1>Blocks by Date</h1>
|
||||
</div>
|
||||
<ul>
|
||||
<li data-ng-repeat="block in blocks">
|
||||
<span>{{block.hash}}</span> {{block.time}}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
|
@ -0,0 +1,10 @@
|
|||
<section data-ng-controller="BlocksController" data-ng-init="list_blocks_date()">
|
||||
<div class="page-header">
|
||||
<h1>Blocks by defined date</h1>
|
||||
</div>
|
||||
<ul>
|
||||
<li data-ng-repeat="block in blocks">
|
||||
<span>{{block.hash}}</span> {{block.time}}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
|
@ -0,0 +1,18 @@
|
|||
<div class="container" data-ng-controller="HeaderController">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#!">Mystery</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" ng-class="{active: $uiRoute}">
|
||||
<a href="#!/{{item.link}}">{{item.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
<section data-ng-controller="IndexController" data-ng-init="last_blocks()">
|
||||
<div class="page-header">
|
||||
<h1>Hello BitPay!</h1>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<th>Height</th>
|
||||
<th>Age</th>
|
||||
<th>Transactions</th>
|
||||
<th>Confirmations</th>
|
||||
<th>Size (kB)</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="block in blocks">
|
||||
<td><a href="#!/block/{{block.hash}}">{{block.height}}</a></td>
|
||||
<td>{{block.time | date:'short'}}</td>
|
||||
<td>{{block.tx.length }}</td>
|
||||
<td>{{block.confirmations}}</td>
|
||||
<td>{{block.size / 1024}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
|
@ -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 <port>
|
||||
var port = process.env.PORT || config.port;
|
||||
app.listen(port);
|
||||
console.log('Express app started on port ' + port);
|
||||
|
||||
//expose app
|
||||
exports = module.exports = app;
|
|
@ -0,0 +1,4 @@
|
|||
--require should
|
||||
-R spec
|
||||
--ui bdd
|
||||
|
|
@ -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));
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -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!');
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue