diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dce93d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# from https://github.com/github/gitignore/blob/master/Node.gitignore +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.swp +tags +pids +logs +results +build + +node_modules + +# extras +*.swp +*.swo +*~ +.project +.idea +peerdb.json + +npm-debug.log +.nodemonignore + +.DS_Store +db/txs/* +db/txs +db/testnet/txs/* +db/testnet/txs +db/blocks/* +db/blocks +db/testnet/blocks/* +db/testnet/blocks + +README.html +public diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..48c745a --- /dev/null +++ b/.jshintrc @@ -0,0 +1,43 @@ +{ + "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", + "$", + "io", + "app", + "moment" + ], + "indent": false, // Specify indentation spacing + "devel": true, // Allow development statements e.g. `console.log();`. + "noempty": true // Prohibit use of empty blocks. +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..dc94fd0 --- /dev/null +++ b/.npmignore @@ -0,0 +1,40 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.swp +tags +pids +logs +results +build + +node_modules + +# extras +*.swp +*.swo +*~ +.project +peerdb.json + +npm-debug.log +.nodemonignore + +.DS_Store +db/txs/* +db/txs +db/testnet/txs/* +db/testnet/txs +db/blocks/* +db/blocks +db/testnet/blocks/* +db/testnet/blocks + +README.html +k* +public diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f070f6e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: +- '0.10' +install: npm install diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..80c6464 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,94 @@ +'use strict'; + +module.exports = function(grunt) { + + //Load NPM tasks + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-mocha-test'); + grunt.loadNpmTasks('grunt-nodemon'); + grunt.loadNpmTasks('grunt-concurrent'); + grunt.loadNpmTasks('grunt-env'); + grunt.loadNpmTasks('grunt-markdown'); + + // Project Configuration + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + watch: { + readme: { + files: ['README.md'], + tasks: ['markdown'] + }, + js: { + files: ['Gruntfile.js', 'insight.js', 'app/**/*.js'], + options: { + livereload: true, + }, + }, + test: { + files: ['test/**/*.js', 'test/*.js','app/**/*.js'], + tasks: ['test'], + } + }, + jshint: { + all: { + src: ['Gruntfile.js', 'insight.js', 'app/**/*.js', 'lib/*.js', 'config/*.js'], + options: { + jshintrc: true + } + } + }, + mochaTest: { + options: { + reporter: 'spec', + }, + src: ['test/**/*.js'], + }, + nodemon: { + dev: { + script: 'insight.js', + options: { + args: [], + ignore: ['test/**/*', 'util/**/*', 'dev-util/**/*'], + // nodeArgs: ['--debug'], + delayTime: 1, + env: { + PORT: 3000 + }, + cwd: __dirname + } + } + }, + concurrent: { + tasks: ['nodemon', 'watch'], + options: { + logConcurrentOutput: true + } + }, + env: { + test: { + NODE_ENV: 'test' + } + }, + markdown: { + all: { + files: [ + { + expand: true, + src: 'README.md', + dest: '.', + ext: '.html' + } + ] + } + } + }); + + //Making grunt default to force in order not to break the project. + grunt.option('force', true); + + //Default task(s). + grunt.registerTask('default', ['concurrent']); + + //Test task. + grunt.registerTask('test', ['env:test', 'mochaTest']); +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..54650f7 --- /dev/null +++ b/README.md @@ -0,0 +1,393 @@ +# *Reddsight API* + +*Reddsight API* is an open-source Reddcoin blockchain REST +and websocket API. Reddsight API runs in NodeJS and uses LevelDB for storage. + +This is a backend-only service. If you're looking for a web frontend application, +take a look at our official blockchain explorer [Reddsight](https://github.com/reddcoin-project/reddsight). + +*Reddsight API* allows everyone to develop Reddcoin-related applications (such as wallets) that +require certain information from the blockchain that reddcoind does not provide. + + +## Prerequisites + +* **reddcoind** - Download and install [Reddcoin](https://github.com/reddcoin-project/reddcoin). + +*Reddsight API* needs a *trusted* reddcoind node to run. *Reddsight API* will connect to the node +through the RPC API, Reddcoin peer-to-peer protocol, and will even read its raw block .dat files for syncing. + +Configure reddcoind to listen to RPC calls and set `txindex` to true. reddcoind must be running and must have +finished downloading the blockchain **before** running *Reddsight API*. + +* **Node.js v0.10.x** - Download and Install [Node.js](http://www.nodejs.org/download/). + +* **NPM** - Node.js package manager, should be automatically installed when you get Node.js. + + +## Quick Install + Check the Prerequisites section above before installing. + + To install *Reddsight API*, clone the main repository: + + $ git clone https://github.com/reddcoin-project/reddsight-api.git && cd reddsight-api + + Install dependencies: + + $ npm install + + Run the main application: + + $ node insight.js + + Then open a browser and go to: + + http://localhost:3000 + + Please note that the app will need to sync its internal database + with the blockchain state, which may take some time. You can check + sync progress at http://localhost:3000/api/sync. + + +## Configuration + +All configuration is specified in the [config](config/) folder, particularly the [config.js](config/config.js) file. +There you can specify your application name and database name. Certain configuration values are pulled from environment +variables if they are defined: + +``` +BITCOIND_HOST # RPC reddcoind host +BITCOIND_PORT # RPC reddcoind Port +BITCOIND_P2P_HOST # P2P reddcoind Host (will default to BITCOIND_HOST, if specified) +BITCOIND_P2P_PORT # P2P reddcoind Port +BITCOIND_USER # RPC username +BITCOIND_PASS # RPC password +BITCOIND_DATADIR # reddcoind datadir. 'testnet' will be appended automatically if testnet is used. NEED to finish with '/'. e.g: `/vol/data/` +INSIGHT_NETWORK [= 'livenet' | 'testnet'] +INSIGHT_PORT # insight api port +INSIGHT_DB # Path where to store the internal DB. (defaults to $HOME/.reddsight) +INSIGHT_SAFE_CONFIRMATIONS=6 # Nr. of confirmation needed to start caching transaction information +INSIGHT_IGNORE_CACHE # True to ignore cache of spents in transaction, with more than INSIGHT_SAFE_CONFIRMATIONS confirmations. This is useful for tracking double spents for old transactions. +ENABLE_MAILBOX # if "true" will enable mailbox plugin +ENABLE_CLEANER # if "true" will enable message db cleaner plugin +ENABLE_MONITOR # if "true" will enable message db monitor plugin +ENABLE_EMAILSTORE # if "true" will enable a plugin to store data with a validated email address +ENABLE_RATELIMITER # if "true" will enable the ratelimiter plugin +LOGGER_LEVEL # defaults to 'info', can be 'debug','verbose','error', etc. +ENABLE_HTTPS # if "true" it will server using SSL/HTTPS + +``` + +Make sure that reddcoind is configured to [accept incoming connections using 'rpcallowip'](https://en.bitcoin.it/wiki/Running_Bitcoin). + +In case the network is changed (testnet to livenet or vice versa) levelDB database needs to be deleted. This can be performed running: +```util/sync.js -D``` and waiting for *Reddsight API* to synchronize again. Once the database is deleted, +the sync.js process can be safely interrupted (CTRL+C) and continued from the synchronization process embedded in main app. + + +## Synchronization + +The initial synchronization process scans the blockchain from the paired reddcoind server to update addresses and balances. +*reddsight-api* needs exactly one trusted reddcoind node to run. This node must have finished downloading the blockchain +before running *reddsight-api*. + +While *reddsight-api* is synchronizing the website can be accessed (the sync process is embedded in the webserver), +but there may be missing data or incorrect balances for addresses. The 'sync' status is shown at the `/api/sync` endpoint. + +The blockchain can be read from reddcoind's raw `.dat` files or RPC interface. +Reading the information from the `.dat` files is much faster so it's the +recommended (and default) alternative. `.dat` files are scanned in the default +location for each platform (for example, `~/.reddcoin` on Linux). In case a +non-standard location is used, it needs to be defined (see the Configuration section). + +While synchronizing the blockchain, *reddsight-api* listens for new blocks and +transactions relayed by the reddcoind node. Those are also stored on *reddsight-api*'s database. +In case *reddsight-api* is shutdown for a period of time, restarting it will trigger +a partial (historic) synchronization of the blockchain. Depending on the size of +that synchronization task, a reverse RPC or forward `.dat` syncing strategy will be used. + +If reddcoind is shutdown, *reddsight-api* needs to be stopped and restarted +once reddcoind is restarted. + + +### Syncing old blockchain data manually + + Old blockchain data can be manually synced issuing: + + $ util/sync.js + + Check util/sync.js --help for options, particularly -D to erase the current DB. + + *NOTE*: there is no need to run this manually since the historic synchronization + is built in into the web application. Running *reddsight-api* normally will trigger + the historic sync automatically. + + +### DB storage requirement + +To store the blockchain and address related information, *reddsight-api* uses LevelDB. +Two DBs are created: txs and blocks. By default these are stored on + + ``~/.reddsight/`` + +This can be changed at config/config.js. + + +## Development + +To run *reddsight-api* locally for development with grunt: + +```$ NODE_ENV=development grunt``` + +To run the tests + +```$ grunt test``` + + +Contributions and suggestions are welcome at [reddsight-api github repository](https://github.com/reddcoin-project/reddsight-api). + +## Caching schema + +Since v0.2 a new cache schema has been introduced. Only information from transactions with +INSIGHT_SAFE_CONFIRMATIONS settings will be cached (by default SAFE_CONFIRMATIONS=6). There +are 3 different caches: + * Number of confirmations + * Transaction output spent/unspent status + * scriptPubKey for unspent transactions + +Cache data is only populated on request, i.e., only after accessing the required data for +the first time, the information is cached, there is not pre-caching procedure. To ignore +cache by default, use INSIGHT_IGNORE_CACHE. Also, address related calls support `?noCache=1` +to ignore the cache in a particular API request. + +## API + +By default, *reddsight-api* provides a REST API at `/api`, but this prefix is configurable from the var `apiPrefix` in the `config.js` file. + +The end-points are: + + +### Block +``` + /api/block/[:hash] + /api/block/00000000a967199a2fad0877433c93df785a8d8ce062e5f9b451cd1397bdbf62 +``` +### Transaction +``` + /api/tx/[:txid] + /api/tx/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c +``` +### Address +``` + /api/addr/[:addr][?noTxList=1&noCache=1] + /api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1 +``` +### Address Properties +``` + /api/addr/[:addr]/balance + /api/addr/[:addr]/totalReceived + /api/addr/[:addr]/totalSent + /api/addr/[:addr]/unconfirmedBalance +``` +The response contains the value in Satoshis. +### Unspent Outputs +``` + /api/addr/[:addr]/utxo[?noCache=1] +``` +Sample return: +``` json +[ + { + address: "n2PuaAguxZqLddRbTnAoAuwKYgN2w2hZk7", + txid: "dbfdc2a0d22a8282c4e7be0452d595695f3a39173bed4f48e590877382b112fc", + vout: 0, + ts: 1401276201, + scriptPubKey: "76a914e50575162795cd77366fb80d728e3216bd52deac88ac", + amount: 0.001, + confirmations: 3 + }, + { + address: "n2PuaAguxZqLddRbTnAoAuwKYgN2w2hZk7", + txid: "e2b82af55d64f12fd0dd075d0922ee7d6a300f58fe60a23cbb5831b31d1d58b4", + vout: 0, + ts: 1401226410, + scriptPubKey: "76a914e50575162795cd77366fb80d728e3216bd52deac88ac", + amount: 0.001, + confirmation: 6 + confirmationsFromCache: true, + } +] +``` +Please note that in case confirmations are cached (which happens by default when the number of confirmations is bigger +that INSIGHT_SAFE_CONFIRMATIONS) the response will include the pair confirmationsFromCache:true, and confirmations will +equal INSIGHT_SAFE_CONFIRMATIONS. See noCache and INSIGHT_IGNORE_CACHE options for details. + + +### Unspent Outputs for multiple addresses +GET method: +``` + /api/addrs/[:addrs]/utxo + /api/addrs/2NF2baYuJAkCKo5onjUKEPdARQkZ6SYyKd5,2NAre8sX2povnjy4aeiHKeEh97Qhn97tB1f/utxo +``` + +POST method: +``` + /api/addrs/utxo +``` + +POST params: +``` +addrs: 2NF2baYuJAkCKo5onjUKEPdARQkZ6SYyKd5,2NAre8sX2povnjy4aeiHKeEh97Qhn97tB1f +``` + +### Transactions by Block +``` + /api/txs/?block=HASH + /api/txs/?block=00000000fa6cf7367e50ad14eb0ca4737131f256fc4c5841fd3c3f140140e6b6 +``` +### Transactions by Address +``` + /api/txs/?address=ADDR + /api/txs/?address=mmhmMNfBiZZ37g1tgg2t8DDbNoEdqKVxAL +``` +### Transaction broadcasting +POST method: +``` + /api/tx/send +``` +POST params: +``` + rawtx: "signed transaction as hex string" + + eg + + rawtx: 01000000017b1eabe0209b1fe794124575ef807057c77ada2138ae4fa8d6c4de0398a14f3f00000000494830450221008949f0cb400094ad2b5eb399d59d01c14d73d8fe6e96df1a7150deb388ab8935022079656090d7f6bac4c9a94e0aad311a4268e082a725f8aeae0573fb12ff866a5f01ffffffff01f0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000 + +``` +POST response: +``` + { + txid: [:txid] + } + + eg + + { + txid: "c7736a0a0046d5a8cc61c8c3c2821d4d7517f5de2bc66a966011aaa79965ffba" + } +``` + +### Historic blockchain data sync status +``` + /api/sync +``` + +### Live network p2p data sync status +``` + /api/peer +``` + +### Status of the Reddcoin network +``` + /api/status?q=xxx +``` + +Where "xxx" can be: + + * getInfo + * getDifficulty + * getTxOutSetInfo + * getBestBlockHash + * getLastBlockHash + +## Web Socket API +The web socket API is served using [socket.io](http://socket.io). + +The following are the events published by *Reddsight API*: + +'tx': new transaction received from network. This event is published in the 'inv' room. Data will be a app/models/Transaction object. +Sample output: +``` +{ + "txid":"00c1b1acb310b87085c7deaaeba478cef5dc9519fab87a4d943ecbb39bd5b053", + "processed":false + ... +} +``` + + +'block': new block received from network. This event is published in the 'inv' room. Data will be a app/models/Block object. +Sample output: +``` +{ + "hash":"000000004a3d187c430cd6a5e988aca3b19e1f1d1727a50dead6c8ac26899b96", + "time":1389789343, + ... +} +``` + +'': new transaction concerning received from network. This event is published in the '' room. + +'status': every 1% increment on the sync task, this event will be triggered. This event is published in the 'sync' room. + +Sample output: +``` +{ + blocksToSync: 164141, + syncedBlocks: 475, + upToExisting: true, + scanningBackward: true, + isEndGenesis: true, + end: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", + isStartGenesis: false, + start: "000000009f929800556a8f3cfdbe57c187f2f679e351b12f7011bfc276c41b6d" +} +``` + +### Example Usage + +The following html page connects to the socket.io Reddsight API and listens for new transactions. + +html +``` + + + + + + +``` + +## 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/app/controllers/addresses.js b/app/controllers/addresses.js new file mode 100644 index 0000000..7678d4f --- /dev/null +++ b/app/controllers/addresses.js @@ -0,0 +1,139 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var Address = require('../models/Address'), + common = require('./common'), + async = require('async'); + +var getAddr = function(req, res, next) { + var a; + try { + var addr = req.param('addr'); + a = new Address(addr); + } catch (e) { + common.handleErrors({ + message: 'Invalid address:' + e.message, + code: 1 + }, res, next); + return null; + } + return a; +}; + +var getAddrs = function(req, res, next) { + var as = []; + try { + var addrStrs = req.param('addrs'); + var s = addrStrs.split(','); + if (s.length === 0) return as; + for (var i = 0; i < s.length; i++) { + var a = new Address(s[i]); + as.push(a); + } + } catch (e) { + common.handleErrors({ + message: 'Invalid address:' + e.message, + code: 1 + }, res, next); + return null; + } + return as; +}; + +exports.show = function(req, res, next) { + var a = getAddr(req, res, next); + + if (a) { + a.update(function(err) { + if (err) { + return common.handleErrors(err, res); + } else { + return res.jsonp(a.getObj()); + } + }, {txLimit: req.query.noTxList?0:-1, ignoreCache: req.param('noCache')}); + } +}; + + + +exports.utxo = function(req, res, next) { + var a = getAddr(req, res, next); + if (a) { + a.update(function(err) { + if (err) + return common.handleErrors(err, res); + else { + return res.jsonp(a.unspent); + } + }, {onlyUnspent:1, ignoreCache: req.param('noCache')}); + } +}; + +exports.multiutxo = function(req, res, next) { + var as = getAddrs(req, res, next); + if (as) { + var utxos = []; + async.each(as, function(a, callback) { + a.update(function(err) { + if (err) callback(err); + utxos = utxos.concat(a.unspent); + callback(); + }, {onlyUnspent:1, ignoreCache: req.param('noCache')}); + }, function(err) { // finished callback + if (err) return common.handleErrors(err, res); + res.jsonp(utxos); + }); + } +}; + + +exports.balance = function(req, res, next) { + var a = getAddr(req, res, next); + if (a) + a.update(function(err) { + if (err) { + return common.handleErrors(err, res); + } else { + return res.jsonp(a.balanceSat); + } + }, {ignoreCache: req.param('noCache')}); +}; + +exports.totalReceived = function(req, res, next) { + var a = getAddr(req, res, next); + if (a) + a.update(function(err) { + if (err) { + return common.handleErrors(err, res); + } else { + return res.jsonp(a.totalReceivedSat); + } + }, {ignoreCache: req.param('noCache')}); +}; + +exports.totalSent = function(req, res, next) { + var a = getAddr(req, res, next); + if (a) + a.update(function(err) { + if (err) { + return common.handleErrors(err, res); + } else { + return res.jsonp(a.totalSentSat); + } + }, {ignoreCache: req.param('noCache')}); +}; + +exports.unconfirmedBalance = function(req, res, next) { + var a = getAddr(req, res, next); + if (a) + a.update(function(err) { + if (err) { + return common.handleErrors(err, res); + } else { + return res.jsonp(a.unconfirmedBalanceSat); + } + }, {ignoreCache: req.param('noCache')}); +}; diff --git a/app/controllers/blocks.js b/app/controllers/blocks.js new file mode 100644 index 0000000..2de390d --- /dev/null +++ b/app/controllers/blocks.js @@ -0,0 +1,176 @@ +'use strict'; + +/** + * Module dependencies. + */ +var common = require('./common'), + async = require('async'), + BlockDb = require('../../lib/BlockDb'), + TransactionDb = require('../../lib/TransactionDb'); + +var bdb = new BlockDb(); +var tdb = new TransactionDb(); + +/** + * Find block by hash ... + */ +exports.block = function(req, res, next, hash) { + bdb.fromHashWithInfo(hash, function(err, block) { + if (err || !block) + return common.handleErrors(err, res, next); + else { + tdb.getPoolInfo(block.info.tx[0], function(info) { + block.info.poolInfo = info; + req.block = block.info; + return next(); + }); + } + }); +}; + + +/** + * Show block + */ +exports.show = function(req, res) { + if (req.block) { + res.jsonp(req.block); + } +}; + +/** + * Show block by Height + */ +exports.blockindex = function(req, res, next, height) { + bdb.blockIndex(height, function(err, hashStr) { + if (err) { + console.log(err); + res.status(400).send('Bad Request'); // TODO + } else { + res.jsonp(hashStr); + } + }); +}; + +var getBlock = function(blockhash, cb) { + bdb.fromHashWithInfo(blockhash, function(err, block) { + if (err) { + console.log(err); + return cb(err); + } + + // TODO + if (!block.info) { + console.log('Could not get %s from RPC. Orphan? Error?', blockhash); //TODO + // Probably orphan + block.info = { + hash: blockhash, + isOrphan: 1, + }; + } + + tdb.getPoolInfo(block.info.tx[0], function(info) { + block.info.poolInfo = info; + return cb(err, block.info); + }); + + }); +}; + +/** + * List of blocks by date + */ + +var DFLT_LIMIT=200; + // in testnet, this number is much bigger, we dont support + // exploring blocks by date. + +exports.list = function(req, res) { + var isToday = false; + + //helper to convert timestamps to yyyy-mm-dd format + var formatTimestamp = function(date) { + var yyyy = date.getUTCFullYear().toString(); + var mm = (date.getUTCMonth() + 1).toString(); // getMonth() is zero-based + var dd = date.getUTCDate().toString(); + + return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]); //padding + }; + + var dateStr; + var todayStr = formatTimestamp(new Date()); + + if (req.query.blockDate) { + // TODO: Validate format yyyy-mm-dd + dateStr = req.query.blockDate; + isToday = dateStr === todayStr; + } else { + dateStr = todayStr; + isToday = true; + } + var gte = Math.round((new Date(dateStr)).getTime() / 1000); + + //pagination + var lte = parseInt(req.query.startTimestamp) || gte + 86400; + var prev = formatTimestamp(new Date((gte - 86400) * 1000)); + var next = lte ? formatTimestamp(new Date(lte * 1000)) :null; + var limit = parseInt(req.query.limit || DFLT_LIMIT) + 1; + var more; + + bdb.getBlocksByDate(gte, lte, limit, function(err, blockList) { + + if (err) { + res.status(500).send(err); + } else { + var l = blockList.length; + + if (l===limit) { + more = true; + blockList.pop; + } + + var moreTs=lte; + async.mapSeries(blockList, + function(b, cb) { + getBlock(b.hash, function(err, info) { + if (err) { + console.log(err); + return cb(err); + } + if (b.ts < moreTs) moreTs = b.ts; + return cb(err, { + height: info.height, + size: info.size, + hash: b.hash, + time: b.ts || info.time, + txlength: info.tx.length, + poolInfo: info.poolInfo + }); + }); + }, function(err, allblocks) { + + // sort blocks by height + allblocks.sort( + function compare(a,b) { + if (a.height < b.height) return 1; + if (a.height > b.height) return -1; + return 0; + }); + + res.jsonp({ + blocks: allblocks, + length: allblocks.length, + pagination: { + next: next, + prev: prev, + currentTs: lte - 1, + current: dateStr, + isToday: isToday, + more: more, + moreTs: moreTs, + } + }); + }); + } + }); +}; diff --git a/app/controllers/common.js b/app/controllers/common.js new file mode 100644 index 0000000..b44756b --- /dev/null +++ b/app/controllers/common.js @@ -0,0 +1,16 @@ +'use strict'; + + +exports.handleErrors = function (err, res) { + if (err) { + if (err.code) { + res.status(400).send(err.message + '. Code:' + err.code); + } + else { + res.status(503).send(err.message); + } + } + else { + res.status(404).send('Not found'); + } +}; diff --git a/app/controllers/currency.js b/app/controllers/currency.js new file mode 100644 index 0000000..244ef32 --- /dev/null +++ b/app/controllers/currency.js @@ -0,0 +1,60 @@ +'use strict'; + +var config = require('../../config/config'); + +// Set the initial vars +var timestamp = +new Date(), + delay = config.currencyRefresh * 60000, + bitstampRate = 0; + +exports.index = function(req, res) { + + var _xhr = function() { + if (typeof XMLHttpRequest !== 'undefined' && XMLHttpRequest !== null) { + return new XMLHttpRequest(); + } else if (typeof require !== 'undefined' && require !== null) { + var XMLhttprequest = require('xmlhttprequest').XMLHttpRequest; + return new XMLhttprequest(); + } + }; + + var _request = function(url, cb) { + var request; + request = _xhr(); + request.open('GET', url, true); + request.onreadystatechange = function() { + if (request.readyState === 4) { + if (request.status === 200) { + return cb(false, request.responseText); + } + + return cb(true, { + status: request.status, + message: 'Request error' + }); + } + }; + + return request.send(null); + }; + + // Init + var currentTime = +new Date(); + if (bitstampRate === 0 || currentTime >= (timestamp + delay)) { + timestamp = currentTime; + + _request('https://www.bitstamp.net/api/ticker/', function(err, data) { + if (!err) bitstampRate = parseFloat(JSON.parse(data).last); + + res.jsonp({ + status: 200, + data: { bitstamp: bitstampRate } + }); + }); + } else { + res.jsonp({ + status: 200, + data: { bitstamp: bitstampRate } + }); + } +}; diff --git a/app/controllers/index.js b/app/controllers/index.js new file mode 100644 index 0000000..a31286d --- /dev/null +++ b/app/controllers/index.js @@ -0,0 +1,23 @@ +'use strict'; + +var config = require('../../config/config'); + +var _getVersion = function() { + var pjson = require('../../package.json'); + return pjson.version; +}; + +exports.render = function(req, res) { + if (config.publicPath) { + return res.sendfile(config.publicPath + '/index.html'); + } + + var version = _getVersion(); + res.send('insight API v' + version); +}; + +exports.version = function(req, res) { + var version = _getVersion(); + res.json({ version: version }); +}; + diff --git a/app/controllers/messages.js b/app/controllers/messages.js new file mode 100644 index 0000000..5c2fbfc --- /dev/null +++ b/app/controllers/messages.js @@ -0,0 +1,27 @@ +'use strict'; + +var common = require('./common'); +var Rpc = require('../../lib/Rpc'); + + +exports.verify = function(req, res) { + var address = req.param('address'), + signature = req.param('signature'), + message = req.param('message'); + + if(typeof(address) == 'undefined' + || typeof(signature) == 'undefined' + || typeof(message) == 'undefined') { + return common.handleErrors({ + message: 'Missing parameters (expected "address", "signature" and "message")', + code: 1 + }, res); + } + + Rpc.verifyMessage(address, signature, message, function(err, result) { + if (err) { + return common.handleErrors(err, res); + } + res.json({'result' : result}); + }); +}; diff --git a/app/controllers/socket.js b/app/controllers/socket.js new file mode 100644 index 0000000..b5ff162 --- /dev/null +++ b/app/controllers/socket.js @@ -0,0 +1,73 @@ +'use strict'; + +// server-side socket behaviour +var ios = null; // io is already taken in express +var util = require('reddcore').util; +var logger = require('../../lib/logger').logger; + +module.exports.init = function(io_ext) { + ios = io_ext; + if (ios) { + // when a new socket connects + ios.sockets.on('connection', function(socket) { + logger.verbose('New connection from ' + socket.id); + // when it subscribes, make it join the according room + socket.on('subscribe', function(topic) { + logger.debug('subscribe to ' + topic); + socket.join(topic); + socket.emit('subscribed'); + }); + + // disconnect handler + socket.on('disconnect', function() { + logger.verbose('disconnected ' + socket.id); + }); + + }); + } + return ios; +}; + +var simpleTx = function(tx) { + return { + txid: tx + }; +}; + +var fullTx = function(tx) { + var t = { + txid: tx.txid, + size: tx.size, + }; + // Outputs + var valueOut = 0; + tx.vout.forEach(function(o) { + valueOut += o.valueSat; + }); + + t.valueOut = (valueOut.toFixed(8) / util.COIN); + return t; +}; + +module.exports.broadcastTx = function(tx) { + if (ios) { + var t = (typeof tx === 'string') ? simpleTx(tx) : fullTx(tx); + ios.sockets.in('inv').emit('tx', t); + } +}; + +module.exports.broadcastBlock = function(block) { + if (ios) + ios.sockets.in('inv').emit('block', block); +}; + +module.exports.broadcastAddressTx = function(txid, address) { + if (ios) { + ios.sockets.in(address).emit(address, txid); + } +}; + +module.exports.broadcastSyncInfo = function(historicSync) { + if (ios) + ios.sockets.in('sync').emit('status', historicSync); +}; diff --git a/app/controllers/status.js b/app/controllers/status.js new file mode 100644 index 0000000..322135f --- /dev/null +++ b/app/controllers/status.js @@ -0,0 +1,62 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var Status = require('../models/Status'), + common = require('./common'); + +/** + * Status + */ +exports.show = function(req, res) { + + if (! req.query.q) { + res.status(400).send('Bad Request'); + } + else { + var option = req.query.q; + var statusObject = new Status(); + + var returnJsonp = function (err) { + if (err || ! statusObject) + return common.handleErrors(err, res); + else { + res.jsonp(statusObject); + } + }; + + switch(option) { + case 'getInfo': + statusObject.getInfo(returnJsonp); + break; + case 'getDifficulty': + statusObject.getDifficulty(returnJsonp); + break; + case 'getTxOutSetInfo': + statusObject.getTxOutSetInfo(returnJsonp); + break; + case 'getLastBlockHash': + statusObject.getLastBlockHash(returnJsonp); + break; + case 'getBestBlockHash': + statusObject.getBestBlockHash(returnJsonp); + break; + default: + res.status(400).send('Bad Request'); + } + } +}; + +exports.sync = function(req, res) { + if (req.historicSync) + res.jsonp(req.historicSync.info()); +}; + +exports.peer = function(req, res) { + if (req.peerSync) { + var info = req.peerSync.info(); + res.jsonp(info); + } +}; diff --git a/app/controllers/transactions.js b/app/controllers/transactions.js new file mode 100644 index 0000000..ed01c32 --- /dev/null +++ b/app/controllers/transactions.js @@ -0,0 +1,166 @@ +'use strict'; + +/** + * Module dependencies. + */ +var Address = require('../models/Address'); +var async = require('async'); +var common = require('./common'); +var util = require('util'); + +var Rpc = require('../../lib/Rpc'); + +var tDb = require('../../lib/TransactionDb').default(); +var bdb = require('../../lib/BlockDb').default(); + +exports.send = function(req, res) { + Rpc.sendRawTransaction(req.body.rawtx, function(err, txid) { + if (err) { + var message; + if(err.code == -25) { + message = util.format( + 'Generic error %s (code %s)', + err.message, err.code); + } else if(err.code == -26) { + message = util.format( + 'Transaction rejected by network (code %s). Reason: %s', + err.code, err.message); + } else { + message = util.format('%s (code %s)', err.message, err.code); + } + return res.status(400).send(message); + } + res.json({'txid' : txid}); + }); +}; + + +/** + * Find transaction by hash ... + */ +exports.transaction = function(req, res, next, txid) { + + tDb.fromIdWithInfo(txid, function(err, tx) { + if (err || ! tx) + return common.handleErrors(err, res); + else { + req.transaction = tx.info; + return next(); + } + }); +}; + + +/** + * Show transaction + */ +exports.show = function(req, res) { + + if (req.transaction) { + res.jsonp(req.transaction); + } +}; + + +var getTransaction = function(txid, cb) { + + tDb.fromIdWithInfo(txid, function(err, tx) { + if (err) console.log(err); + + if (!tx || !tx.info) { + console.log('[transactions.js.48]:: TXid %s not found in RPC. CHECK THIS.', txid); + return ({ txid: txid }); + } + + return cb(null, tx.info); + }); +}; + + +/** + * List of transaction + */ +exports.list = function(req, res, next) { + var bId = req.query.block; + var addrStr = req.query.address; + var page = req.query.pageNum; + var pageLength = 10; + var pagesTotal = 1; + var txLength; + var txs; + + if (bId) { + bdb.fromHashWithInfo(bId, function(err, block) { + if (err) { + console.log(err); + return res.status(500).send('Internal Server Error'); + } + + if (! block) { + return res.status(404).send('Not found'); + } + + txLength = block.info.tx.length; + + if (page) { + var spliceInit = page * pageLength; + txs = block.info.tx.splice(spliceInit, pageLength); + pagesTotal = Math.ceil(txLength / pageLength); + } + else { + txs = block.info.tx; + } + + async.mapSeries(txs, getTransaction, function(err, results) { + if (err) { + console.log(err); + res.status(404).send('TX not found'); + } + + res.jsonp({ + pagesTotal: pagesTotal, + txs: results + }); + }); + }); + } + else if (addrStr) { + var a = new Address(addrStr); + + a.update(function(err) { + if (err && !a.totalReceivedSat) { + console.log(err); + res.status(404).send('Invalid address'); + return next(); + } + + txLength = a.transactions.length; + + if (page) { + var spliceInit = page * pageLength; + txs = a.transactions.splice(spliceInit, pageLength); + pagesTotal = Math.ceil(txLength / pageLength); + } + else { + txs = a.transactions; + } + + async.mapSeries(txs, getTransaction, function(err, results) { + if (err) { + console.log(err); + res.status(404).send('TX not found'); + } + + res.jsonp({ + pagesTotal: pagesTotal, + txs: results + }); + }); + }); + } + else { + res.jsonp({ + txs: [] + }); + } +}; diff --git a/app/models/Address.js b/app/models/Address.js new file mode 100644 index 0000000..c4a0bd7 --- /dev/null +++ b/app/models/Address.js @@ -0,0 +1,199 @@ +'use strict'; + +var imports = require('soop').imports(); +var async = require('async'); +var bitcore = require('reddcore'); +var BitcoreAddress = bitcore.Address; +var BitcoreTransaction = bitcore.Transaction; +var BitcoreUtil = bitcore.util; +var Parser = bitcore.BinaryParser; +var Buffer = bitcore.Buffer; +var TransactionDb = imports.TransactionDb || require('../../lib/TransactionDb').default(); +var BlockDb = imports.BlockDb || require('../../lib/BlockDb').default(); +var config = require('../../config/config'); +var CONCURRENCY = 5; + +function Address(addrStr) { + this.balanceSat = 0; + this.totalReceivedSat = 0; + this.totalSentSat = 0; + + this.unconfirmedBalanceSat = 0; + + this.txApperances = 0; + this.unconfirmedTxApperances= 0; + this.seen = {}; + + // TODO store only txids? +index? +all? + this.transactions = []; + this.unspent = []; + + var a = new BitcoreAddress(addrStr); + a.validate(); + this.addrStr = addrStr; + + Object.defineProperty(this, 'totalSent', { + get: function() { + return parseFloat(this.totalSentSat) / parseFloat(BitcoreUtil.COIN); + }, + set: function(i) { + this.totalSentSat = i * BitcoreUtil.COIN; + }, + enumerable: 1 + }); + + Object.defineProperty(this, 'balance', { + get: function() { + return parseFloat(this.balanceSat) / parseFloat(BitcoreUtil.COIN); + }, + set: function(i) { + this.balance = i * BitcoreUtil.COIN; + }, + enumerable: 1 + }); + + Object.defineProperty(this, 'totalReceived', { + get: function() { + return parseFloat(this.totalReceivedSat) / parseFloat(BitcoreUtil.COIN); + }, + set: function(i) { + this.totalReceived = i * BitcoreUtil.COIN; + }, + enumerable: 1 + }); + + + Object.defineProperty(this, 'unconfirmedBalance', { + get: function() { + return parseFloat(this.unconfirmedBalanceSat) / parseFloat(BitcoreUtil.COIN); + }, + set: function(i) { + this.unconfirmedBalanceSat = i * BitcoreUtil.COIN; + }, + enumerable: 1 + }); + +} + +Address.prototype.getObj = function() { + // Normalize json address + return { + 'addrStr': this.addrStr, + 'balance': this.balance, + 'balanceSat': this.balanceSat, + 'totalReceived': this.totalReceived, + 'totalReceivedSat': this.totalReceivedSat, + 'totalSent': this.totalSent, + 'totalSentSat': this.totalSentSat, + 'unconfirmedBalance': this.unconfirmedBalance, + 'unconfirmedBalanceSat': this.unconfirmedBalanceSat, + 'unconfirmedTxApperances': this.unconfirmedTxApperances, + 'txApperances': this.txApperances, + 'transactions': this.transactions + }; +}; + +Address.prototype._addTxItem = function(txItem, txList) { + var add=0, addSpend= 0; + var v = txItem.value_sat; + var seen = this.seen; + + // Founding tx + if ( !seen[txItem.txid] ) { + seen[txItem.txid]=1; + add=1; + + if (txList) + txList.push(txItem.txid); + } + + // Spent tx + if (txItem.spentTxId && !seen[txItem.spentTxId] ) { + if (txList) { + txList.push(txItem.spentTxId); + } + seen[txItem.spentTxId]=1; + addSpend=1; + } + if (txItem.isConfirmed) { + this.txApperances += add; + this.totalReceivedSat += v; + if (!txItem.spentTxId) { + //unspent + this.balanceSat += v; + } else if(!txItem.spentIsConfirmed) { + // unspent + this.balanceSat += v; + this.unconfirmedBalanceSat -= v; + this.unconfirmedTxApperances += addSpend; + } + else { + // spent + this.totalSentSat += v; + this.txApperances += addSpend; + } + } + else { + this.unconfirmedBalanceSat += v; + this.unconfirmedTxApperances += add; + } +}; + +// opts are +// .onlyUnspent +// .txLimit (=0 -> no txs, => -1 no limit) +// +Address.prototype.update = function(next, opts) { + var self = this; + if (!self.addrStr) return next(); + opts = opts || {}; + + if (! ('ignoreCache' in opts) ) + opts.ignoreCache = config.ignoreCache; + + // should collect txList from address? + var txList = opts.txLimit === 0 ? null: []; + + var tDb = TransactionDb; + var bDb = BlockDb; + tDb.fromAddr(self.addrStr, opts, function(err, txOut){ + if (err) return next(err); + + bDb.fillConfirmations(txOut, function(err) { + if (err) return next(err); + + tDb.cacheConfirmations(txOut, function(err) { +// console.log('[Address.js.161:txOut:]',txOut); //TODO + if (err) return next(err); + if (opts.onlyUnspent) { + txOut = txOut.filter(function(x) { return !x.spentTxId; }); + tDb.fillScriptPubKey(txOut, function() { + self.unspent = txOut.map(function(x){ + return { + address: self.addrStr, + txid: x.txid, + vout: x.index, + ts: x.ts, + scriptPubKey: x.scriptPubKey, + amount: x.value_sat / BitcoreUtil.COIN, + confirmations: x.isConfirmedCached ? (config.safeConfirmations) : x.confirmations, + confirmationsFromCache: !!x.isConfirmedCached + }; + }); + return next(); + }); + } else { + txOut.forEach(function(txItem){ + self._addTxItem(txItem, txList); + }); + if (txList) + self.transactions = txList; + + return next(); + } + }); + }); + }); +}; + +module.exports = require('soop')(Address); diff --git a/app/models/Status.js b/app/models/Status.js new file mode 100644 index 0000000..e205dc3 --- /dev/null +++ b/app/models/Status.js @@ -0,0 +1,105 @@ +'use strict'; +//var imports = require('soop').imports(); + +var async = require('async'); +var bitcore = require('reddcore'); +var RpcClient = bitcore.RpcClient; +var config = require('../../config/config'); +var rpc = new RpcClient(config.bitcoind); +var bDb = require('../../lib/BlockDb').default(); + +function Status() {} + +Status.prototype.getInfo = function(next) { + var that = this; + async.series([ + function (cb) { + rpc.getInfo(function(err, info){ + if (err) return cb(err); + + that.info = info.result; + return cb(); + }); + }, + ], function (err) { + return next(err); + }); +}; + +Status.prototype.getDifficulty = function(next) { + var that = this; + async.series([ + function (cb) { + rpc.getDifficulty(function(err, df){ + if (err) return cb(err); + + that.difficulty = df.result; + return cb(); + }); + } + ], function (err) { + return next(err); + }); +}; + +Status.prototype.getTxOutSetInfo = function(next) { + var that = this; + async.series([ + function (cb) { + rpc.getTxOutSetInfo(function(err, txout){ + if (err) return cb(err); + + that.txoutsetinfo = txout.result; + return cb(); + }); + } + ], function (err) { + return next(err); + }); +}; + +Status.prototype.getBestBlockHash = function(next) { + var that = this; + async.series([ + function (cb) { + rpc.getBestBlockHash(function(err, bbh){ + if (err) return cb(err); + + that.bestblockhash = bbh.result; + return cb(); + }); + }, + + ], function (err) { + return next(err); + }); +}; + +Status.prototype.getLastBlockHash = function(next) { + var that = this; + bDb.getTip(function(err,tip) { + that.syncTipHash = tip; + async.waterfall( + [ + function(callback){ + rpc.getBlockCount(function(err, bc){ + if (err) return callback(err); + callback(null, bc.result); + }); + }, + function(bc, callback){ + rpc.getBlockHash(bc, function(err, bh){ + if (err) return callback(err); + callback(null, bh.result); + }); + } + ], + function (err, result) { + that.lastblockhash = result; + return next(); + } + ); + }); +}; + +module.exports = require('soop')(Status); diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..b4e9d56 --- /dev/null +++ b/config/config.js @@ -0,0 +1,133 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var mkdirp = require('mkdirp'); + +var rootPath = path.normalize(__dirname + '/..'), + env, + db, + port, + b_port, + p2p_port; + +var packageStr = fs.readFileSync(rootPath + '/package.json'); +var version = JSON.parse(packageStr).version; + + +function getUserHome() { + return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; +} + +var home = process.env.INSIGHT_DB || (getUserHome() + '/.potsight'); + +if (process.env.INSIGHT_NETWORK === 'livenet') { + env = 'livenet'; + db = home; + port = '3000'; + b_port = '8332'; + p2p_port = '4200'; +} else { + env = 'testnet'; + db = home + '/testnet'; + port = '3001'; + b_port = '55443'; + p2p_port = '55444'; +} +port = parseInt(process.env.INSIGHT_PORT) || port; + + +switch (process.env.NODE_ENV) { + case 'production': + env += ''; + break; + case 'test': + env += ' - test environment'; + break; + default: + env += ' - development'; + break; +} + +var network = process.env.INSIGHT_NETWORK || 'testnet'; + +var dataDir = process.env.BITCOIND_DATADIR; +var isWin = /^win/.test(process.platform); +var isMac = /^darwin/.test(process.platform); +var isLinux = /^linux/.test(process.platform); +if (!dataDir) { + if (isWin) dataDir = '%APPDATA%\\Reddcoin\\'; + if (isMac) dataDir = process.env.HOME + '/Library/Application Support/Potcoin/'; + if (isLinux) dataDir = process.env.HOME + '/.potcoin2/'; +} +dataDir += network === 'testnet' ? 'testnet' : ''; + +var safeConfirmations = process.env.INSIGHT_SAFE_CONFIRMATIONS || 6; +var ignoreCache = process.env.INSIGHT_IGNORE_CACHE || 0; + + +var bitcoindConf = { + protocol: process.env.BITCOIND_PROTO || 'http', + user: process.env.BITCOIND_USER || 'user', + pass: process.env.BITCOIND_PASS || 'pass', + host: process.env.BITCOIND_HOST || '127.0.0.1', + port: process.env.BITCOIND_PORT || b_port, + p2pPort: process.env.BITCOIND_P2P_PORT || p2p_port, + p2pHost: process.env.BITCOIND_P2P_HOST || process.env.BITCOIND_HOST || '127.0.0.1', + dataDir: dataDir, + // DO NOT CHANGE THIS! + disableAgent: true +}; + +var enableMonitor = process.env.ENABLE_MONITOR === 'true'; +var enableCleaner = process.env.ENABLE_CLEANER === 'true'; +var enableMailbox = process.env.ENABLE_MAILBOX === 'true'; +var enableRatelimiter = process.env.ENABLE_RATELIMITER === 'true'; +var enableCredentialstore = process.env.ENABLE_CREDSTORE === 'true'; +var enableEmailstore = process.env.ENABLE_EMAILSTORE === 'true'; +var enablePublicInfo = process.env.ENABLE_PUBLICINFO === 'true'; +var loggerLevel = process.env.LOGGER_LEVEL || 'info'; +var enableHTTPS = process.env.ENABLE_HTTPS === 'true'; + +if (!fs.existsSync(db)) { + mkdirp.sync(db); +} + +module.exports = { + enableMonitor: enableMonitor, + monitor: require('../plugins/config-monitor.js'), + enableCleaner: enableCleaner, + cleaner: require('../plugins/config-cleaner.js'), + enableMailbox: enableMailbox, + mailbox: require('../plugins/config-mailbox.js'), + enableRatelimiter: enableRatelimiter, + ratelimiter: require('../plugins/config-ratelimiter.js'), + enableCredentialstore: enableCredentialstore, + credentialstore: require('../plugins/config-credentialstore'), + enableEmailstore: enableEmailstore, + emailstore: require('../plugins/config-emailstore'), + enablePublicInfo: enablePublicInfo, + publicInfo: require('../plugins/publicInfo/config'), + loggerLevel: loggerLevel, + enableHTTPS: enableHTTPS, + version: version, + root: rootPath, + publicPath: process.env.INSIGHT_PUBLIC_PATH || false, + appName: 'Insight ' + env, + apiPrefix: '/api', + port: port, + leveldb: db, + bitcoind: bitcoindConf, + network: network, + disableP2pSync: false, + disableHistoricSync: false, + poolMatchFile: rootPath + '/etc/minersPoolStrings.json', + + // Time to refresh the currency rate. In minutes + currencyRefresh: 10, + keys: { + segmentio: process.env.INSIGHT_SEGMENTIO_KEY + }, + safeConfirmations: safeConfirmations, // PLEASE NOTE THAT *FULL RESYNC* IS NEEDED TO CHANGE safeConfirmations + ignoreCache: ignoreCache, +}; diff --git a/config/express.js b/config/express.js new file mode 100644 index 0000000..a096ef7 --- /dev/null +++ b/config/express.js @@ -0,0 +1,86 @@ +'use strict'; + +/** + * Module dependencies. + */ +var express = require('express'); +var config = require('./config'); +var path = require('path'); +var logger = require('../lib/logger').logger; + +module.exports = function(app, historicSync, peerSync) { + + + //custom middleware + var setHistoric = function(req, res, next) { + req.historicSync = historicSync; + next(); + }; + + var setPeer = function(req, res, next) { + req.peerSync = peerSync; + next(); + }; + + app.set('showStackError', true); + app.set('json spaces', 0); + + app.enable('jsonp callback'); + app.use(config.apiPrefix + '/sync', setHistoric); + app.use(config.apiPrefix + '/peer', setPeer); + app.use(express.logger('dev')); + app.use(express.json()); + app.use(express.urlencoded()); + app.use(express.methodOverride()); + app.use(express.compress()); + + if (config.enableEmailstore) { + var allowCopayCrossDomain = function(req, res, next) { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); + res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization'); + + + if ('OPTIONS' == req.method) { + res.send(200); + res.end(); + return; + } + next(); + } + app.use(allowCopayCrossDomain); + } + + if (config.publicPath) { + var staticPath = path.normalize(config.rootPath + '/../' + config.publicPath); + //IMPORTANT: for html5mode, this line must to be before app.router + app.use(express.static(staticPath)); + } + + app.use(function(req, res, next) { + app.locals.config = config; + next(); + }); + + //routes should be at the last + app.use(app.router); + + //Assume "not found" in the error msgs is a 404 + app.use(function(err, req, res, next) { + if (~err.message.indexOf('not found')) return next(); + console.error(err.stack); + res.status(500).jsonp({ + status: 500, + error: err.stack + }); + }); + + //Assume 404 since no middleware responded + app.use(function(req, res) { + res.status(404).jsonp({ + status: 404, + url: req.originalUrl, + error: 'Not found' + }); + }); +}; diff --git a/config/headers.js b/config/headers.js new file mode 100644 index 0000000..9614cef --- /dev/null +++ b/config/headers.js @@ -0,0 +1,13 @@ +'use strict'; + +var logger = require('../lib/logger').logger; + +module.exports = function(app) { + + app.use(function(req, res, next) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); + next(); + }); +}; diff --git a/config/routes.js b/config/routes.js new file mode 100644 index 0000000..cd44940 --- /dev/null +++ b/config/routes.js @@ -0,0 +1,74 @@ +'use strict'; + +/** + * Module dependencies. + */ +var config = require('./config'); + +module.exports = function(app) { + + var apiPrefix = config.apiPrefix; + + //Block routes + var blocks = require('../app/controllers/blocks'); + app.get(apiPrefix + '/blocks', blocks.list); + + + app.get(apiPrefix + '/block/:blockHash', blocks.show); + app.param('blockHash', blocks.block); + + app.get(apiPrefix + '/block-index/:height', blocks.blockindex); + app.param('height', blocks.blockindex); + + // Transaction routes + var transactions = require('../app/controllers/transactions'); + app.get(apiPrefix + '/tx/:txid', transactions.show); + app.param('txid', transactions.transaction); + app.get(apiPrefix + '/txs', transactions.list); + app.post(apiPrefix + '/tx/send', transactions.send); + + // Address routes + var addresses = require('../app/controllers/addresses'); + app.get(apiPrefix + '/addr/:addr', addresses.show); + app.get(apiPrefix + '/addr/:addr/utxo', addresses.utxo); + app.get(apiPrefix + '/addrs/:addrs/utxo', addresses.multiutxo); + app.post(apiPrefix + '/addrs/utxo', addresses.multiutxo); + + // Address property routes + app.get(apiPrefix + '/addr/:addr/balance', addresses.balance); + app.get(apiPrefix + '/addr/:addr/totalReceived', addresses.totalReceived); + app.get(apiPrefix + '/addr/:addr/totalSent', addresses.totalSent); + app.get(apiPrefix + '/addr/:addr/unconfirmedBalance', addresses.unconfirmedBalance); + + // Status route + var st = require('../app/controllers/status'); + app.get(apiPrefix + '/status', st.show); + + app.get(apiPrefix + '/sync', st.sync); + app.get(apiPrefix + '/peer', st.peer); + + // Currency + var currency = require('../app/controllers/currency'); + app.get(apiPrefix + '/currency', currency.index); + + // Email store plugin + if (config.enableEmailstore) { + var emailPlugin = require('../plugins/emailstore'); + app.post(apiPrefix + '/email/register', emailPlugin.post); + app.post(apiPrefix + '/email/validate', emailPlugin.validate); + app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.get); + app.get(apiPrefix + '/email/retrieve', emailPlugin.retrieve); + app.get(apiPrefix + '/email/validate', emailPlugin.validate); + app.post(apiPrefix + '/email/change_passphrase', emailPlugin.changePassphrase); + } + + // Address routes + var messages = require('../app/controllers/messages'); + app.get(apiPrefix + '/messages/verify', messages.verify); + app.post(apiPrefix + '/messages/verify', messages.verify); + + //Home route + var index = require('../app/controllers/index'); + app.get(apiPrefix + '/version', index.version); + app.get('*', index.render); +}; diff --git a/dev-util/block-level.js b/dev-util/block-level.js new file mode 100755 index 0000000..559717f --- /dev/null +++ b/dev-util/block-level.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +var + config = require('../config/config'), + levelup = require('levelup'); + + +db = levelup(config.leveldb + '/blocks'); + +db.createReadStream({start: 'b-'}) + .on('data', function (data) { +console.log('[block-level.js.11:data:]',data); //TODO + if (data==false) c++; + }) + .on('error', function (err) { + return cb(err); + }) + .on('close', function () { + return cb(null); + }) + .on('end', function () { + return cb(null); + }); + + diff --git a/dev-util/explode_tx.js b/dev-util/explode_tx.js new file mode 100755 index 0000000..f9849c2 --- /dev/null +++ b/dev-util/explode_tx.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node +'use strict'; + +var util = require('util'); +var mongoose= require('mongoose'), + config = require('../config/config'); + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var T = require('../app/models/TransactionOut'); + + +// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4'; +var hash = process.argv[2] || '6749762ae220c10705556799dcec9bb6a54a7b881eb4b961323a3363b00db518'; + + + + +mongoose.connect(config.db); + +mongoose.connection.on('error', function(err) { console.log(err); }); + + +mongoose.connection.on('open', function() { + + var b = new Buffer(hash,'hex'); + + T.createFromTxs([hash], function(err, ret) { + + console.log('Err:'); + console.log(err); + + + console.log('Ret:'); + console.log(util.inspect(ret,{depth:null})); + mongoose.connection.close(); + }); +}); + + + diff --git a/dev-util/find_ref.sh b/dev-util/find_ref.sh new file mode 100755 index 0000000..2656eb2 --- /dev/null +++ b/dev-util/find_ref.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +FIND='find'; + +##if [[ "$OSTYPE" =~ "darwin" ]] +##then +## FIND='gfind' +##fi + + +if [ -z "$1" ] +then + echo "$0 : find functions references " + echo "Usage $0 function_name " + exit; +fi + +EXTRA='' + + +CMD="grep -rnH" + +if [ "$2" != '--nocolor' ] +then + CMD="$CMD --color=always" +fi + + +$FIND -L . -name \*.json -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + \ + -o -name \*.html -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + \ + -o -name \*.jade -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + \ + -o -name \*.js -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + + diff --git a/dev-util/getAddr.js b/dev-util/getAddr.js new file mode 100755 index 0000000..e0f086c --- /dev/null +++ b/dev-util/getAddr.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node +'use strict'; + +var util = require('util'), + config = require('../config/config'); + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var A = require('../app/models/Address'); + +// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4'; +var hash = process.argv[2] || 'mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS'; + +var a= new A(hash); +a.update(function(err) { + console.log('Err:'); + console.log(err); + + console.log('Ret:'); + console.log(util.inspect(a,{depth:null})); + +}) + + + diff --git a/dev-util/getTx.js b/dev-util/getTx.js new file mode 100755 index 0000000..2a0ac83 --- /dev/null +++ b/dev-util/getTx.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node +'use strict'; + +var util = require('util'), + config = require('../config/config'); + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var TransactionDb = require('../lib/TransactionDb.js').default(); +var hash = process.argv[2] || '4286d6fc82a314348af4e9d3ce649f78ce4569937e9ad6613563755f0d14e3d1'; + +var t= TransactionDb.fromIdWithInfo(hash,function(err,tx) { + console.log('Err:'); + console.log(err); + + console.log('Ret:'); + console.log(util.inspect(tx,{depth:null})); +}); + + + diff --git a/dev-util/get_block.js b/dev-util/get_block.js new file mode 100755 index 0000000..9a04943 --- /dev/null +++ b/dev-util/get_block.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node +'use strict'; + +var util = require('util'); + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var RpcClient = require('../node_modules/reddcore/RpcClient'); + +var config = require('../config/config'); + + + var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4'; +//var hash = process.argv[2] || 'f6c2901f39fd07f2f2e503183d76f73ecc1aee9ac9216fde58e867bc29ce674e'; + +//hash = 'e2253359458db3e732c82a43fc62f56979ff59928f25a2df34dfa443e9a41160'; + +var rpc = new RpcClient(config.bitcoind); + +rpc.getBlock( hash, function(err, ret) { + + console.log('Err:'); + console.log(err); + + + console.log('Ret:'); + console.log(util.inspect(ret, { depth: 10} )); +}); + + + diff --git a/dev-util/level-put.js b/dev-util/level-put.js new file mode 100755 index 0000000..45cdb31 --- /dev/null +++ b/dev-util/level-put.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node +'use strict'; + +var config = require('../config/config'), + levelup = require('levelup'); + + + +var k = process.argv[2]; +var v = process.argv[3]; +var isBlock = process.argv[4] === '1'; + + +var dbPath = config.leveldb + (isBlock ? '/blocks' : '/txs'); +console.log('DB: ',dbPath); //TODO + + + +var db = levelup(dbPath ); + + +if (v) { + db.put(k,v,function(err) { + console.log('[PUT done]',err); //TODO + }); +} +else { + db.del(k,function(err) { + console.log('[DEL done]',err); //TODO + }); +} + + + diff --git a/dev-util/level.js b/dev-util/level.js new file mode 100755 index 0000000..f897c2a --- /dev/null +++ b/dev-util/level.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node +'use strict'; + +var config = require('../config/config'), + levelup = require('levelup'); + + + +var s = process.argv[2]; +var isBlock = process.argv[3] === '1'; + + +var dbPath = config.leveldb + (isBlock ? '/blocks' : '/txs'); +console.log('DB: ',dbPath); //TODO + + + +var db = levelup(dbPath ); + + +db.createReadStream({start: s, end: s+'~'}) + .on('data', function (data) { + console.log(data.key + ' => ' + data.value); //TODO + }) + .on('error', function () { + }) + .on('end', function () { + }); + + diff --git a/dev-util/node-tick-report b/dev-util/node-tick-report new file mode 100644 index 0000000..3f9461d --- /dev/null +++ b/dev-util/node-tick-report @@ -0,0 +1,1328 @@ +Code move event for unknown code: 0x278907110560 +Code move event for unknown code: 0x27890730a4e0 +Statistical profiling result from v8.log, (24141 ticks, 452 unaccounted, 0 excluded). + + [Unknown]: + ticks total nonlib name + 452 1.9% + + [Shared libraries]: + ticks total nonlib name + 9018 37.4% 0.0% /usr/local/bin/node + 8405 34.8% 0.0% /usr/lib/system/libsystem_kernel.dylib + 606 2.5% 0.0% /usr/lib/system/libsystem_c.dylib + 509 2.1% 0.0% /usr/lib/system/libsystem_malloc.dylib + 375 1.6% 0.0% /usr/lib/system/libsystem_platform.dylib + 187 0.8% 0.0% /usr/lib/system/libsystem_pthread.dylib + 75 0.3% 0.0% /usr/lib/system/libsystem_m.dylib + 73 0.3% 0.0% /usr/lib/libc++abi.dylib + 31 0.1% 0.0% /usr/lib/libstdc++.6.dylib + + [JavaScript]: + ticks total nonlib name + 133 0.6% 2.7% LazyCompile: formatValue util.js:204 + 122 0.5% 2.5% LazyCompile: *formatProperty util.js:353 + 113 0.5% 2.3% Stub: CEntryStub + 96 0.4% 2.0% Builtin: A builtin from the snapshot {3} + 95 0.4% 2.0% Builtin: A builtin from the snapshot {2} + 92 0.4% 1.9% LazyCompile: *Batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:13 + 87 0.4% 1.8% LazyCompile: *ConvertDescriptorArrayToDescriptor native v8natives.js:581 + 84 0.3% 1.7% LazyCompile: *map native array.js:1215 + 81 0.3% 1.7% Stub: StringAddStub + 72 0.3% 1.5% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 65 0.3% 1.3% LazyCompile: *inspect buffer.js:283 + 64 0.3% 1.3% LazyCompile: native messages.js:1157 + 60 0.2% 1.2% LazyCompile: ToObject native runtime.js:567 + 58 0.2% 1.2% Stub: FastNewClosureStub {1} + 58 0.2% 1.2% LazyCompile: *FromPropertyDescriptor native v8natives.js:373 + 51 0.2% 1.0% Builtin: A builtin from the snapshot {1} + 50 0.2% 1.0% LazyCompile: stringify native json.js:308 + 50 0.2% 1.0% LazyCompile: join native array.js:410 + 47 0.2% 1.0% KeyedLoadIC: A keyed load IC from the snapshot + 46 0.2% 0.9% LazyCompile: *BuildResultFromMatchInfo native regexp.js:130 + 45 0.2% 0.9% Stub: CallFunctionStub_Args0_Recording + 43 0.2% 0.9% Stub: CallConstructStub + 42 0.2% 0.9% LazyCompile: *ArraySlice native array.js:621 + 40 0.2% 0.8% Stub: JSEntryStub + 40 0.2% 0.8% LazyCompile: *PropertyDescriptor native v8natives.js:482 + 39 0.2% 0.8% Stub: KeyedStoreElementStub + 39 0.2% 0.8% Stub: CompareStub + 39 0.2% 0.8% Stub: CallConstructStub {1} + 39 0.2% 0.8% LazyCompile: Join native array.js:119 + 39 0.2% 0.8% LazyCompile: *forEach native array.js:1087 + 37 0.2% 0.8% Stub: CallFunctionStub {1} + 36 0.1% 0.7% RegExp: ^"([a-zA-Z_][a-zA-Z_0-9]*)"$ + 36 0.1% 0.7% LazyCompile: Batch.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:19 + 34 0.1% 0.7% LazyCompile: *BasicJSONSerialize native json.js:274 + 34 0.1% 0.7% Builtin: A builtin from the snapshot + 33 0.1% 0.7% Stub: RegExpExecStub + 32 0.1% 0.7% Stub: SubStringStub + 32 0.1% 0.7% LazyCompile: *substr native string.js:695 + 31 0.1% 0.6% Stub: InstanceofStub + 30 0.1% 0.6% Stub: ToBooleanStub {2} + 30 0.1% 0.6% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 30 0.1% 0.6% LazyCompile: *Buffer.slice buffer.js:539 + 28 0.1% 0.6% LazyCompile: toString native v8natives.js:223 + 28 0.1% 0.6% LazyCompile: IN native runtime.js:354 + 28 0.1% 0.6% LazyCompile: *LevelUP.get /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:163 + 28 0.1% 0.6% LazyCompile: *GetOwnProperty native v8natives.js:637 + 27 0.1% 0.6% Stub: CompareICStub {4} + 27 0.1% 0.6% LazyCompile: native string.js:36 + 27 0.1% 0.6% LazyCompile: *getOwnPropertyDescriptor native v8natives.js:946 + 25 0.1% 0.5% LazyCompile: *toString native v8natives.js:1378 + 25 0.1% 0.5% LazyCompile: *isCoinBase /Users/ematiu/devel/node/bitcore/lib/Transaction.js:49 + 25 0.1% 0.5% LazyCompile: *formatPrimitive util.js:304 + 24 0.1% 0.5% LazyCompile: ToString native runtime.js:550 + 23 0.1% 0.5% Stub: CompareICStub + 23 0.1% 0.5% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:58 + 23 0.1% 0.5% LazyCompile: *indexOf native array.js:1261 + 23 0.1% 0.5% LazyCompile: *LevelUP.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:210 + 23 0.1% 0.5% LazyCompile: *Buffer.copy buffer.js:509 + 22 0.1% 0.5% LazyCompile: captureStackTrace native messages.js:1113 + 22 0.1% 0.5% LazyCompile: *split native string.js:554 + 21 0.1% 0.4% Stub: KeyedLoadElementStub {1} + 21 0.1% 0.4% LazyCompile: *Buffer.write buffer.js:315 + 20 0.1% 0.4% Stub: StringAddStub {1} + 20 0.1% 0.4% LazyCompile: *reduce native array.js:1381 + 20 0.1% 0.4% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 19 0.1% 0.4% RegExp: ^"|"$ + 19 0.1% 0.4% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:229 + 19 0.1% 0.4% LazyCompile: *match native string.js:182 + 18 0.1% 0.4% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 18 0.1% 0.4% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:238 + 18 0.1% 0.4% LazyCompile: *Script /Users/ematiu/devel/node/bitcore/lib/Script.js:25 + 18 0.1% 0.4% Builtin: A builtin from the snapshot {5} + 17 0.1% 0.3% Stub: FastNewContextStub + 17 0.1% 0.3% Stub: FastCloneShallowObjectStub + 17 0.1% 0.3% LazyCompile: ~indexOf native array.js:1261 + 17 0.1% 0.3% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 17 0.1% 0.3% LazyCompile: APPLY_PREPARE native runtime.js:438 + 17 0.1% 0.3% LazyCompile: *parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 17 0.1% 0.3% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 16 0.1% 0.3% Stub: FastNewClosureStub + 16 0.1% 0.3% Stub: CallFunctionStub_Args1 + 16 0.1% 0.3% LazyCompile: ~Script.capture /Users/ematiu/devel/node/bitcore/lib/Script.js:235 + 16 0.1% 0.3% LazyCompile: hasOwnProperty native v8natives.js:249 + 16 0.1% 0.3% LazyCompile: *indexOf native string.js:118 + 16 0.1% 0.3% LazyCompile: *ceil native math.js:79 + 15 0.1% 0.3% Stub: FastCloneShallowArrayStub {1} + 15 0.1% 0.3% Stub: BinaryOpStub + 15 0.1% 0.3% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:66 + 15 0.1% 0.3% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 15 0.1% 0.3% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 15 0.1% 0.3% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 15 0.1% 0.3% LazyCompile: *exports.formatHashFull /Users/ematiu/devel/node/bitcore/util/util.js:70 + 15 0.1% 0.3% LazyCompile: *arrayToHash util.js:193 + 15 0.1% 0.3% LazyCompile: *UseSparseVariant native array.js:111 + 15 0.1% 0.3% LazyCompile: *IsDataDescriptor native v8natives.js:354 + 15 0.1% 0.3% LazyCompile: *DefineObjectProperty native v8natives.js:695 + 14 0.1% 0.3% Stub: ToBooleanStub {1} + 14 0.1% 0.3% LazyCompile: ~EventEmitter.emit events.js:53 + 14 0.1% 0.3% LazyCompile: *inspect util.js:118 + 14 0.1% 0.3% LazyCompile: *formatArray util.js:333 + 14 0.1% 0.3% LazyCompile: *encodeKey /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:146 + 14 0.1% 0.3% LazyCompile: *Hash crypto.js:185 + 14 0.1% 0.3% LazyCompile: * util.js:293 + 13 0.1% 0.3% Stub: RecordWriteStub {1} + 13 0.1% 0.3% Stub: CallFunctionStub + 13 0.1% 0.3% LazyCompile: VersionedData /Users/ematiu/devel/node/bitcore/util/VersionedData.js:6 + 13 0.1% 0.3% LazyCompile: SimpleSlice native array.js:324 + 13 0.1% 0.3% LazyCompile: *keys native v8natives.js:333 + 13 0.1% 0.3% LazyCompile: *ToPropertyDescriptor native v8natives.js:420 + 13 0.1% 0.3% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 13 0.1% 0.3% LazyCompile: *$Array.enumerable_ native v8natives.js:523 + 12 0.0% 0.2% Stub: FastNewContextStub {1} + 12 0.0% 0.2% LazyCompile: ~exports.setImmediate timers.js:337 + 12 0.0% 0.2% LazyCompile: *replace native string.js:221 + 12 0.0% 0.2% LazyCompile: *Buffer.toString buffer.js:392 + 12 0.0% 0.2% LazyCompile: *Buffer.concat buffer.js:476 + 12 0.0% 0.2% Builtin: A builtin from the snapshot {7} + 11 0.0% 0.2% Stub: RegExpConstructResultStub + 11 0.0% 0.2% Stub: KeyedLoadElementStub + 11 0.0% 0.2% LazyCompile: ~prr /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/prr/prr.js:44 + 11 0.0% 0.2% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:204 + 11 0.0% 0.2% LazyCompile: *ToUint32 native runtime.js:586 + 11 0.0% 0.2% LazyCompile: *BlockDb.has /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:175 + 11 0.0% 0.2% LazyCompile: * /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:97 + 10 0.0% 0.2% LazyCompile: ~exports.format util.js:23 + 10 0.0% 0.2% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 10 0.0% 0.2% LazyCompile: NonNumberToNumber native runtime.js:538 + 10 0.0% 0.2% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 10 0.0% 0.2% LazyCompile: *isDate util.js:448 + 10 0.0% 0.2% LazyCompile: *isArray native array.js:1488 + 10 0.0% 0.2% LazyCompile: *exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 10 0.0% 0.2% LazyCompile: *encodings.utf8.encodings.utf-8.encode /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:40 + 10 0.0% 0.2% LazyCompile: *HistoricSync._fromBuffer /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:143 + 10 0.0% 0.2% LazyCompile: *$Array.enumerable_ native v8natives.js:516 + 9 0.0% 0.2% Stub: ToBooleanStub_UndefinedSpecObject + 9 0.0% 0.2% Stub: FastNewContextStub {2} + 9 0.0% 0.2% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:345 + 9 0.0% 0.2% LazyCompile: *serialize /Users/ematiu/devel/node/bitcore/lib/Transaction.js:152 + 9 0.0% 0.2% LazyCompile: *reduceToSingleString util.js:412 + 9 0.0% 0.2% LazyCompile: *isFinite native v8natives.js:103 + 9 0.0% 0.2% LazyCompile: *isBuffer buffer.js:277 + 9 0.0% 0.2% LazyCompile: *isArray util.js:435 + 9 0.0% 0.2% LazyCompile: *buffer /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:12 + 9 0.0% 0.2% LazyCompile: *Hash.update crypto.js:205 + 9 0.0% 0.2% LazyCompile: * util.js:414 + 9 0.0% 0.2% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 9 0.0% 0.2% LazyCompile: *$Array.writable_ native v8natives.js:530 + 9 0.0% 0.2% Builtin: A builtin from the snapshot {10} + 8 0.0% 0.2% Stub: ToBooleanStub + 8 0.0% 0.2% Stub: NumberToStringStub + 8 0.0% 0.2% Stub: BinaryOpStub_ADD_OverwriteLeft_BothStrings + 8 0.0% 0.2% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:155 + 8 0.0% 0.2% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 8 0.0% 0.2% LazyCompile: STRING_ADD_LEFT native runtime.js:183 + 8 0.0% 0.2% LazyCompile: *varIntBuf /Users/ematiu/devel/node/bitcore/util/util.js:444 + 8 0.0% 0.2% LazyCompile: *parseInt native v8natives.js:110 + 8 0.0% 0.2% LazyCompile: *isError util.js:454 + 8 0.0% 0.2% LazyCompile: *RegExpExecNoTests native regexp.js:157 + 8 0.0% 0.2% LazyCompile: * util.js:196 + 7 0.0% 0.1% Stub: ToBooleanStub_String + 7 0.0% 0.1% Stub: ToBooleanStub_SmiSpecObjectStringHeapNumber + 7 0.0% 0.1% Stub: CallFunctionStub {2} + 7 0.0% 0.1% Stub: ArgumentsAccessStub + 7 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/bitcore/util/VersionedData.js:23 + 7 0.0% 0.1% LazyCompile: toString native messages.js:1248 + 7 0.0% 0.1% LazyCompile: TransactionDb._addScript /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:462 + 7 0.0% 0.1% LazyCompile: Address /Users/ematiu/devel/node/bitcore/lib/Address.js:39 + 7 0.0% 0.1% LazyCompile: *stylizeNoColor util.js:188 + 7 0.0% 0.1% LazyCompile: *isRegExp util.js:442 + 7 0.0% 0.1% LazyCompile: *hasKeys /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/has-keys.js:3 + 7 0.0% 0.1% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Block.js:85 + 7 0.0% 0.1% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 7 0.0% 0.1% LazyCompile: *decodeLEu /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:61 + 7 0.0% 0.1% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:152 + 7 0.0% 0.1% LazyCompile: *$Array.get_ native v8natives.js:546 + 7 0.0% 0.1% LazyCompile: *$Array.configurable_ native v8natives.js:536 + 7 0.0% 0.1% KeyedStoreIC: A keyed store IC from the snapshot + 7 0.0% 0.1% Builtin: A builtin from the snapshot {9} + 7 0.0% 0.1% Builtin: A builtin from the snapshot {6} + 6 0.0% 0.1% Stub: ToBooleanStub_UndefinedString + 6 0.0% 0.1% Stub: StringAddStub {2} + 6 0.0% 0.1% Stub: RecordWriteStub {9} + 6 0.0% 0.1% Stub: RecordWriteStub {4} + 6 0.0% 0.1% Stub: RecordWriteStub {2} + 6 0.0% 0.1% Stub: CompareICStub {1} + 6 0.0% 0.1% LazyCompile: ~GetPropertyWithoutInvokingMonkeyGetters native messages.js:1203 + 6 0.0% 0.1% LazyCompile: *toLowerCase native string.js:739 + 6 0.0% 0.1% LazyCompile: *serialize /Users/ematiu/devel/node/bitcore/lib/Transaction.js:56 + 6 0.0% 0.1% LazyCompile: *isValueAsBuffer /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:162 + 6 0.0% 0.1% LazyCompile: *_map /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:53 + 6 0.0% 0.1% LazyCompile: *LevelUP._isOpening /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:155 + 6 0.0% 0.1% LazyCompile: *Hash.digest crypto.js:214 + 6 0.0% 0.1% LazyCompile: *$Array.writable_ native v8natives.js:526 + 6 0.0% 0.1% LazyCompile: *$Array.configurable_ native v8natives.js:540 + 5 0.0% 0.1% Stub: RecordWriteStub {16} + 5 0.0% 0.1% Stub: RecordWriteStub {11} + 5 0.0% 0.1% Stub: RecordWriteStub + 5 0.0% 0.1% Stub: KeyedStoreElementStub {2} + 5 0.0% 0.1% Stub: FastNewContextStub {4} + 5 0.0% 0.1% Stub: CompareStub_GE + 5 0.0% 0.1% Stub: CallFunctionStub_Args5_Recording + 5 0.0% 0.1% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:92 + 5 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:157 + 5 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/bitcore/util/VersionedData.js:32 + 5 0.0% 0.1% LazyCompile: NonStringToString native runtime.js:558 + 5 0.0% 0.1% LazyCompile: INSTANCE_OF native runtime.js:367 + 5 0.0% 0.1% LazyCompile: ErrorToStringDetectCycle native messages.js:1227 + 5 0.0% 0.1% LazyCompile: DefaultString native runtime.js:645 + 5 0.0% 0.1% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 5 0.0% 0.1% LazyCompile: *global.setImmediate node.js:192 + 5 0.0% 0.1% LazyCompile: *encodeValue /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:150 + 5 0.0% 0.1% LazyCompile: *constructor.super /Users/ematiu/devel/node/bitcore/node_modules/soop/soop.js:22 + 5 0.0% 0.1% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 5 0.0% 0.1% LazyCompile: *Sync.storeTipBlock /Users/ematiu/devel/node/insight-api/lib/Sync.js:79 + 5 0.0% 0.1% LazyCompile: *Script.isPubkey /Users/ematiu/devel/node/bitcore/lib/Script.js:89 + 5 0.0% 0.1% LazyCompile: *LazyTransform crypto.js:153 + 5 0.0% 0.1% LazyCompile: *IsAccessorDescriptor native v8natives.js:347 + 5 0.0% 0.1% LazyCompile: *HistoricSync.getStandardizedBlock /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:184 + 5 0.0% 0.1% LazyCompile: *EncodedData.doAsBinary /Users/ematiu/devel/node/bitcore/util/EncodedData.js:75 + 5 0.0% 0.1% LazyCompile: *Buffer.writeUInt8 buffer.js:792 + 5 0.0% 0.1% LazyCompile: *Block /Users/ematiu/devel/node/bitcore/lib/Block.js:20 + 5 0.0% 0.1% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 5 0.0% 0.1% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:237 + 5 0.0% 0.1% KeyedLoadIC: args_count: 0 {5} + 5 0.0% 0.1% KeyedLoadIC: args_count: 0 {3} + 5 0.0% 0.1% KeyedLoadIC: A keyed load IC from the snapshot {1} + 4 0.0% 0.1% Stub: ToBooleanStub_UndefinedSmi + 4 0.0% 0.1% Stub: RecordWriteStub {8} + 4 0.0% 0.1% Stub: RecordWriteStub {7} + 4 0.0% 0.1% Stub: RecordWriteStub {3} + 4 0.0% 0.1% Stub: FastCloneShallowObjectStub {1} + 4 0.0% 0.1% Stub: CompareICStub {5} + 4 0.0% 0.1% Stub: CompareICStub {3} + 4 0.0% 0.1% RegExp: ^\\d+$ + 4 0.0% 0.1% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 4 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:102 + 4 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:177 + 4 0.0% 0.1% LazyCompile: FILTER_KEY native runtime.js:398 + 4 0.0% 0.1% LazyCompile: DefineOneShotAccessor native messages.js:767 + 4 0.0% 0.1% LazyCompile: ConfigureTemplateInstance native apinatives.js:105 + 4 0.0% 0.1% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 4 0.0% 0.1% LazyCompile: *init _linklist.js:22 + 4 0.0% 0.1% LazyCompile: *hasOwnProperty util.js:577 + 4 0.0% 0.1% LazyCompile: *VersionedData.payload /Users/ematiu/devel/node/bitcore/util/VersionedData.js:30 + 4 0.0% 0.1% LazyCompile: *Transaction /Users/ematiu/devel/node/bitcore/lib/Transaction.js:108 + 4 0.0% 0.1% LazyCompile: *BlockDb.setLastFileIndex /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:112 + 4 0.0% 0.1% LazyCompile: *BlockDb.getTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:66 + 4 0.0% 0.1% LazyCompile: *$Array.enumerable_ native v8natives.js:520 + 4 0.0% 0.1% KeyedStoreIC: args_count: 0 {2} + 3 0.0% 0.1% Stub: StringAddStub {3} + 3 0.0% 0.1% Stub: RecordWriteStub {12} + 3 0.0% 0.1% Stub: RecordWriteStub {10} + 3 0.0% 0.1% Stub: JSEntryStub {1} + 3 0.0% 0.1% Stub: FastCloneShallowArrayStub + 3 0.0% 0.1% Stub: CompareStub_LT + 3 0.0% 0.1% Stub: CompareICStub {8} + 3 0.0% 0.1% Stub: ArgumentsAccessStub_NewStrict + 3 0.0% 0.1% LazyCompile: ~Buffer buffer.js:156 + 3 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:678 + 3 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:94 + 3 0.0% 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:448 + 3 0.0% 0.1% LazyCompile: *test native regexp.js:217 + 3 0.0% 0.1% LazyCompile: *VersionedData.version /Users/ematiu/devel/node/bitcore/util/VersionedData.js:21 + 3 0.0% 0.1% LazyCompile: *HistoricSync.getStandardizedTx /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:148 + 3 0.0% 0.1% LazyCompile: *Buffer.writeUInt32LE buffer.js:839 + 3 0.0% 0.1% LazyCompile: *$Array.get_ native v8natives.js:553 + 3 0.0% 0.1% KeyedLoadIC: args_count: 0 {8} + 3 0.0% 0.1% KeyedLoadIC: A keyed load IC from the snapshot {2} + 2 0.0% 0.0% Stub: ToBooleanStub_UndefinedNull + 2 0.0% 0.0% Stub: ToBooleanStub_Smi + 2 0.0% 0.0% Stub: RecordWriteStub {6} + 2 0.0% 0.0% Stub: RecordWriteStub {15} + 2 0.0% 0.0% Stub: RecordWriteStub {14} + 2 0.0% 0.0% Stub: MathPowStub + 2 0.0% 0.0% Stub: FastNewContextStub {5} + 2 0.0% 0.0% Stub: FastNewContextStub {3} + 2 0.0% 0.0% Stub: FastCloneShallowArrayStub {2} + 2 0.0% 0.0% Stub: ConstructStub + 2 0.0% 0.0% Stub: CompareICStub {7} + 2 0.0% 0.0% Stub: CompareICStub {6} + 2 0.0% 0.0% Stub: CompareICStub {2} + 2 0.0% 0.0% Stub: BinaryOpStub_SHR_Alloc_SMI + 2 0.0% 0.0% Stub: BinaryOpStub_ADD_OverwriteLeft_Strings + 2 0.0% 0.0% Stub: BinaryOpStub {1} + 2 0.0% 0.0% RegExp: ^(\\/?|)([\\s\\S]*?)((?:\\.{1\,2}|[^\\/]+?|)(\\.[^.\\/]*|))(?:[\\/]*)$ + 2 0.0% 0.0% LazyCompile: ~parseInt native v8natives.js:110 + 2 0.0% 0.0% LazyCompile: ~parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 2 0.0% 0.0% LazyCompile: ~formatValue util.js:204 + 2 0.0% 0.0% LazyCompile: ~formatArray util.js:333 + 2 0.0% 0.0% LazyCompile: ~exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 2 0.0% 0.0% LazyCompile: ~charAt native string.js:66 + 2 0.0% 0.0% LazyCompile: ~async.series.self.processReorg.height /Users/ematiu/devel/node/insight-api/lib/Sync.js:122 + 2 0.0% 0.0% LazyCompile: ~async.series.self.bDb.getNext.oldNext /Users/ematiu/devel/node/insight-api/lib/Sync.js:108 + 2 0.0% 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:161 + 2 0.0% 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:150 + 2 0.0% 0.0% LazyCompile: ~SimpleSlice native array.js:324 + 2 0.0% 0.0% LazyCompile: ~LevelUP.get /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:163 + 2 0.0% 0.0% LazyCompile: ~ArraySlice native array.js:621 + 2 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:440 + 2 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:150 + 2 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:129 + 2 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:154 + 2 0.0% 0.0% LazyCompile: defineProperty native v8natives.js:1050 + 2 0.0% 0.0% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 2 0.0% 0.0% LazyCompile: ToPrimitive native runtime.js:506 + 2 0.0% 0.0% LazyCompile: RegExpConstructor native regexp.js:86 + 2 0.0% 0.0% LazyCompile: COMPARE native runtime.js:120 + 2 0.0% 0.0% LazyCompile: *str util.js:383 + 2 0.0% 0.0% LazyCompile: *makeOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/prr/prr.js:25 + 2 0.0% 0.0% LazyCompile: *getOutpointIndex /Users/ematiu/devel/node/bitcore/lib/Transaction.js:72 + 2 0.0% 0.0% LazyCompile: *getOutpointHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:65 + 2 0.0% 0.0% LazyCompile: *async.series /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:605 + 2 0.0% 0.0% LazyCompile: *append _linklist.js:63 + 2 0.0% 0.0% LazyCompile: *IsInconsistentDescriptor native v8natives.js:367 + 2 0.0% 0.0% LazyCompile: *EncodedData.encoding /Users/ematiu/devel/node/bitcore/util/EncodedData.js:20 + 2 0.0% 0.0% LazyCompile: *BlockExtractor.getNextBlock /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:90 + 2 0.0% 0.0% LazyCompile: *BlockDb.setTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:82 + 2 0.0% 0.0% KeyedStoreIC: hash + 2 0.0% 0.0% KeyedLoadMegamorphicIC: args_count: 0 {7} + 2 0.0% 0.0% KeyedLoadIC: name + 2 0.0% 0.0% KeyedLoadIC: args_count: 0 {4} + 2 0.0% 0.0% KeyedLoadIC: args_count: 0 + 2 0.0% 0.0% CallMegamorphic: args_count: 2 + 2 0.0% 0.0% Builtin: A builtin from the snapshot {4} + 2 0.0% 0.0% Builtin: A builtin from the snapshot {13} + 1 0.0% 0.0% Stub: ToBooleanStub_SmiHeapNumber + 1 0.0% 0.0% Stub: ToBooleanStub_Null + 1 0.0% 0.0% Stub: RecordWriteStub {5} + 1 0.0% 0.0% Stub: RecordWriteStub {20} + 1 0.0% 0.0% Stub: RecordWriteStub {19} + 1 0.0% 0.0% Stub: RecordWriteStub {18} + 1 0.0% 0.0% Stub: RecordWriteStub {17} + 1 0.0% 0.0% Stub: RecordWriteStub {13} + 1 0.0% 0.0% Stub: KeyedStoreElementStub {1} + 1 0.0% 0.0% Stub: FastNewContextStub {6} + 1 0.0% 0.0% Stub: FastCloneShallowObjectStub {2} + 1 0.0% 0.0% Stub: CompareICStub {9} + 1 0.0% 0.0% Stub: CallFunctionStub_Args3 + 1 0.0% 0.0% Stub: CallFunctionStub_Args2 + 1 0.0% 0.0% Stub: CallFunctionStub_Args0 + 1 0.0% 0.0% Stub: BinaryOpStub_MUL_Alloc_SMI + 1 0.0% 0.0% Stub: BinaryOpStub_BIT_OR_Alloc_Oddball + 1 0.0% 0.0% RegExp: notfound {1} + 1 0.0% 0.0% RegExp: notfound + 1 0.0% 0.0% LazyCompile: ~tryPackage module.js:122 + 1 0.0% 0.0% LazyCompile: ~reduce native array.js:1381 + 1 0.0% 0.0% LazyCompile: ~parse native json.js:55 + 1 0.0% 0.0% LazyCompile: ~onError /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:140 + 1 0.0% 0.0% LazyCompile: ~indexOf native string.js:118 + 1 0.0% 0.0% LazyCompile: ~exports.resolve path.js:304 + 1 0.0% 0.0% LazyCompile: ~async.series.self.cachedLastHash /Users/ematiu/devel/node/insight-api/lib/Sync.js:130 + 1 0.0% 0.0% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:99 + 1 0.0% 0.0% LazyCompile: ~ToPropertyDescriptor native v8natives.js:420 + 1 0.0% 0.0% LazyCompile: ~Script.parse /Users/ematiu/devel/node/bitcore/lib/Script.js:41 + 1 0.0% 0.0% LazyCompile: ~Script.isMultiSig /Users/ematiu/devel/node/bitcore/lib/Script.js:110 + 1 0.0% 0.0% LazyCompile: ~NativeModule.compile node.js:888 + 1 0.0% 0.0% LazyCompile: ~Module._resolveFilename module.js:323 + 1 0.0% 0.0% LazyCompile: ~HistoricSync.getStandardizedTx /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:148 + 1 0.0% 0.0% LazyCompile: ~HistoricSync._fromBuffer /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:143 + 1 0.0% 0.0% LazyCompile: ~FromPropertyDescriptor native v8natives.js:373 + 1 0.0% 0.0% LazyCompile: ~Buffer.toString buffer.js:392 + 1 0.0% 0.0% LazyCompile: ~BlockDb.getTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:66 + 1 0.0% 0.0% LazyCompile: ~Block /Users/ematiu/devel/node/bitcore/lib/Block.js:20 + 1 0.0% 0.0% LazyCompile: ~Batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:13 + 1 0.0% 0.0% LazyCompile: ~BasicJSONSerialize native json.js:274 + 1 0.0% 0.0% LazyCompile: ~ArrayConcat native array.js:471 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:253 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:168 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:151 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:110 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:446 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:96 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 1 0.0% 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:84 + 1 0.0% 0.0% LazyCompile: statPath module.js:88 + 1 0.0% 0.0% LazyCompile: fs.readFileSync fs.js:271 + 1 0.0% 0.0% LazyCompile: ToBoolean native runtime.js:517 + 1 0.0% 0.0% LazyCompile: *shift _linklist.js:38 + 1 0.0% 0.0% LazyCompile: *processImmediate timers.js:320 + 1 0.0% 0.0% LazyCompile: *init /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:3 + 1 0.0% 0.0% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:194 + 1 0.0% 0.0% LazyCompile: *getCallback /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:30 + 1 0.0% 0.0% LazyCompile: *exports.dirname path.js:415 + 1 0.0% 0.0% LazyCompile: *eof /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:56 + 1 0.0% 0.0% LazyCompile: *dispatchError /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:129 + 1 0.0% 0.0% LazyCompile: *calcHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:183 + 1 0.0% 0.0% LazyCompile: *async.whilst /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:676 + 1 0.0% 0.0% LazyCompile: *Parser.varInt /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:123 + 1 0.0% 0.0% LazyCompile: *LevelUP.isOpen /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:151 + 1 0.0% 0.0% LazyCompile: *DefineOwnProperty native v8natives.js:924 + 1 0.0% 0.0% LazyCompile: *BlockExtractor.readCurrentFileSync /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:59 + 1 0.0% 0.0% LazyCompile: *BlockDb.setNext /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:153 + 1 0.0% 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:316 + 1 0.0% 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:192 + 1 0.0% 0.0% LazyCompile: * /Users/ematiu/devel/node/bitcore/lib/Transaction.js:160 + 1 0.0% 0.0% KeyedStoreIC: createIfMissing {1} + 1 0.0% 0.0% KeyedStoreIC: args_count: 0 {11} + 1 0.0% 0.0% KeyedStoreIC: args_count: 0 {10} + 1 0.0% 0.0% KeyedLoadMegamorphicIC: args_count: 0 {9} + 1 0.0% 0.0% KeyedLoadIC: log + 1 0.0% 0.0% KeyedLoadIC: info + 1 0.0% 0.0% KeyedLoadIC: errorIfExists {1} + 1 0.0% 0.0% KeyedLoadIC: cause + 1 0.0% 0.0% KeyedLoadIC: args_count: 0 {6} + 1 0.0% 0.0% KeyedLoadIC: args_count: 0 {1} + 1 0.0% 0.0% Function: /Users/ematiu/devel/node/bitcore/node_modules/bignum/index.js:1 + 1 0.0% 0.0% CallMegamorphic: args_count: 1 + 1 0.0% 0.0% Builtin: A builtin from the snapshot {8} + 1 0.0% 0.0% Builtin: A builtin from the snapshot {14} + 1 0.0% 0.0% Builtin: A builtin from the snapshot {11} + + [C++]: + ticks total nonlib name + + [GC]: + ticks total nonlib name + 880 3.6% + + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 2.0% are not shown. + + ticks parent name + 9018 37.4% /usr/local/bin/node + 452 5.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:58 + 452 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:66 + 333 3.7% LazyCompile: *keys native v8natives.js:333 + 196 58.9% LazyCompile: formatValue util.js:204 + 183 93.4% LazyCompile: *formatProperty util.js:353 + 146 79.8% LazyCompile: * util.js:293 + 146 100.0% LazyCompile: *map native array.js:1215 + 33 18.0% LazyCompile: *formatArray util.js:333 + 33 100.0% LazyCompile: formatValue util.js:204 + 4 2.2% LazyCompile: ~formatArray util.js:333 + 4 100.0% LazyCompile: formatValue util.js:204 + 13 6.6% LazyCompile: *inspect util.js:118 + 13 100.0% LazyCompile: ~exports.format util.js:23 + 13 100.0% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 133 39.9% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 133 100.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 47 35.3% LazyCompile: Batch.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:19 + 46 97.9% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 1 2.1% LazyCompile: ~BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 38 28.6% LazyCompile: *LevelUP.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:210 + 19 50.0% LazyCompile: *BlockDb.setTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:82 + 19 50.0% LazyCompile: *BlockDb.setNext /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:153 + 34 25.6% LazyCompile: *LevelUP.get /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:163 + 34 100.0% LazyCompile: *BlockDb.has /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:175 + 11 8.3% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 11 100.0% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 260 2.9% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 113 43.5% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:151 + 113 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:345 + 45 17.3% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:162 + 45 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:154 + 45 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:238 + 43 16.5% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:157 + 43 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:84 + 43 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:238 + 19 7.3% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:94 + 19 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:177 + 19 100.0% LazyCompile: *dispatchError /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:129 + 19 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 16 6.2% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:58 + 16 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:66 + 6 2.3% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 6 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 6 100.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 250 2.8% LazyCompile: *GetOwnProperty native v8natives.js:637 + 250 100.0% LazyCompile: *getOwnPropertyDescriptor native v8natives.js:946 + 250 100.0% LazyCompile: *formatProperty util.js:353 + 230 92.0% LazyCompile: * util.js:293 + 228 99.1% LazyCompile: *map native array.js:1215 + 19 7.6% LazyCompile: *formatArray util.js:333 + 19 100.0% LazyCompile: formatValue util.js:204 + 249 2.8% LazyCompile: * util.js:196 + 248 99.6% LazyCompile: *forEach native array.js:1087 + 248 100.0% LazyCompile: *arrayToHash util.js:193 + 247 99.6% LazyCompile: formatValue util.js:204 + 209 84.6% LazyCompile: *formatProperty util.js:353 + 38 15.4% LazyCompile: *inspect util.js:118 + 225 2.5% LazyCompile: captureStackTrace native messages.js:1113 + 213 94.7% LazyCompile: native messages.js:1157 + 107 50.2% LazyCompile: native messages.js:1157 + 18 16.8% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 18 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 106 49.8% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 106 100.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 104 98.1% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 11 4.9% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 11 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 224 2.5% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 224 100.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 129 57.6% LazyCompile: Batch.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:19 + 127 98.4% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 127 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 44 19.6% LazyCompile: *LevelUP.get /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:163 + 44 100.0% LazyCompile: *BlockDb.has /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:175 + 44 100.0% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:92 + 37 16.5% LazyCompile: *LevelUP.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:210 + 21 56.8% LazyCompile: *BlockDb.setTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:82 + 21 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:155 + 16 43.2% LazyCompile: *BlockDb.setNext /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:153 + 16 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:161 + 11 4.9% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 10 90.9% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 10 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 1 9.1% LazyCompile: ~TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 1 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 220 2.4% LazyCompile: native messages.js:1157 + 205 93.2% LazyCompile: native messages.js:1157 + 15 6.8% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 15 100.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 15 100.0% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 15 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 212 2.4% LazyCompile: Join native array.js:119 + 211 99.5% LazyCompile: join native array.js:410 + 94 44.5% LazyCompile: *reduceToSingleString util.js:412 + 92 97.9% LazyCompile: formatValue util.js:204 + 71 77.2% LazyCompile: *formatProperty util.js:353 + 21 22.8% LazyCompile: *inspect util.js:118 + 2 2.1% LazyCompile: ~formatValue util.js:204 + 2 100.0% LazyCompile: *formatProperty util.js:353 + 88 41.7% LazyCompile: *formatProperty util.js:353 + 51 58.0% LazyCompile: *formatArray util.js:333 + 51 100.0% LazyCompile: formatValue util.js:204 + 35 39.8% LazyCompile: * util.js:293 + 35 100.0% LazyCompile: *map native array.js:1215 + 2 2.3% LazyCompile: ~formatArray util.js:333 + 2 100.0% LazyCompile: formatValue util.js:204 + 25 11.8% LazyCompile: ~exports.format util.js:23 + 25 100.0% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 25 100.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 210 2.3% LazyCompile: DefineOneShotAccessor native messages.js:767 + 210 100.0% LazyCompile: captureStackTrace native messages.js:1113 + 180 85.7% LazyCompile: native messages.js:1157 + 97 53.9% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 96 99.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 83 46.1% LazyCompile: native messages.js:1157 + 30 36.1% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 4 4.8% LazyCompile: *fs.statSync fs.js:682 + 2 2.4% LazyCompile: ~fs.statSync fs.js:682 + 30 14.3% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 30 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 209 2.3% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 196 93.8% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 196 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 196 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:150 + 196 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 10 4.8% LazyCompile: ~TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 10 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 10 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:150 + 10 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 209 2.3% LazyCompile: *LevelUP.get /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:163 + 209 100.0% LazyCompile: *BlockDb.has /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:175 + 209 100.0% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:92 + 209 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 209 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 189 2.1% LazyCompile: *Hash crypto.js:185 + 187 98.9% LazyCompile: *Hash crypto.js:185 + 112 59.9% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 83 74.1% LazyCompile: *parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 83 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 20 17.9% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Block.js:85 + 20 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 7 6.3% LazyCompile: *calcHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:183 + 7 100.0% LazyCompile: ~Transaction.parse /Users/ematiu/devel/node/bitcore/lib/Transaction.js:532 + 41 21.9% LazyCompile: *exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 41 100.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 41 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 29 15.5% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 29 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 29 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + + 8405 34.8% /usr/lib/system/libsystem_kernel.dylib + + 606 2.5% /usr/lib/system/libsystem_c.dylib + 38 6.3% LazyCompile: DefaultString native runtime.js:645 + 38 100.0% LazyCompile: NonStringToString native runtime.js:558 + 38 100.0% LazyCompile: *test native regexp.js:217 + 38 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 25 4.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:177 + 25 100.0% LazyCompile: *dispatchError /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:129 + 25 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 20 3.3% LazyCompile: *Hash.digest crypto.js:214 + 12 60.0% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 10 83.3% LazyCompile: *parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 10 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 10 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 1 8.3% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Block.js:85 + 1 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 1 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 1 8.3% LazyCompile: *calcHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:183 + 1 100.0% LazyCompile: ~Transaction.parse /Users/ematiu/devel/node/bitcore/lib/Transaction.js:532 + 1 100.0% LazyCompile: ~parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 5 25.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 5 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 5 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 5 100.0% LazyCompile: *map native array.js:1215 + 3 15.0% LazyCompile: *exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 3 100.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 3 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 3 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 15 2.5% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 15 100.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 12 80.0% LazyCompile: Batch.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:19 + 12 100.0% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 12 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 2 13.3% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 2 100.0% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 2 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 1 6.7% LazyCompile: *LevelUP.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:210 + 1 100.0% LazyCompile: *BlockDb.setTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:82 + 1 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:155 + + 509 2.1% /usr/lib/system/libsystem_malloc.dylib + 98 19.3% LazyCompile: *toString native v8natives.js:1378 + 98 100.0% LazyCompile: *inspect buffer.js:283 + 98 100.0% LazyCompile: formatValue util.js:204 + 98 100.0% LazyCompile: *formatProperty util.js:353 + 98 100.0% LazyCompile: * util.js:293 + 73 14.3% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 45 61.6% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:151 + 45 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:345 + 14 19.2% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:162 + 14 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:154 + 14 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:238 + 6 8.2% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:58 + 6 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:66 + 5 6.8% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:157 + 5 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:84 + 5 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:238 + 3 4.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:94 + 3 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:177 + 3 100.0% LazyCompile: *dispatchError /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:129 + 3 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 43 8.4% LazyCompile: Batch.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:19 + 41 95.3% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 41 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 41 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 41 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 2 4.7% LazyCompile: ~BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 2 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 2 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 2 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 35 6.9% LazyCompile: captureStackTrace native messages.js:1113 + 35 100.0% LazyCompile: native messages.js:1157 + 25 71.4% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 25 100.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 25 100.0% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 10 28.6% LazyCompile: native messages.js:1157 + 9 90.0% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 9 100.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 1 10.0% LazyCompile: *fs.statSync fs.js:682 + 1 100.0% LazyCompile: statPath module.js:88 + 34 6.7% LazyCompile: *Hash.digest crypto.js:214 + 19 55.9% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 10 52.6% LazyCompile: *parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 10 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 10 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 7 36.8% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Block.js:85 + 7 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 7 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 1 5.3% LazyCompile: ~parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 1 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 1 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 1 5.3% LazyCompile: *calcHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:183 + 1 100.0% LazyCompile: ~Transaction.parse /Users/ematiu/devel/node/bitcore/lib/Transaction.js:532 + 1 100.0% LazyCompile: ~parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 7 20.6% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 7 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 7 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 7 100.0% LazyCompile: *map native array.js:1215 + 7 20.6% LazyCompile: *exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 7 100.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 7 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 7 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 1 2.9% LazyCompile: ~exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 1 100.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 1 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 1 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 33 6.5% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 30 90.9% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 30 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 30 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:150 + 30 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 3 9.1% LazyCompile: ~TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 3 100.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 3 100.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:150 + 3 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 23 4.5% LazyCompile: *Buffer.toString buffer.js:392 + 19 82.6% LazyCompile: *HistoricSync._fromBuffer /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:143 + 19 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 19 100.0% LazyCompile: *map native array.js:1215 + 19 100.0% LazyCompile: *HistoricSync.getStandardizedTx /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:148 + 4 17.4% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:152 + 4 100.0% LazyCompile: *map native array.js:1215 + 4 100.0% LazyCompile: *HistoricSync.getStandardizedTx /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:148 + 4 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:192 + 22 4.3% LazyCompile: *Buffer.write buffer.js:315 + 12 54.5% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 8 66.7% LazyCompile: *parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 8 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 8 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 3 25.0% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Block.js:85 + 3 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 3 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 1 8.3% LazyCompile: ~parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 1 100.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 1 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6 27.3% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 6 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 6 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 6 100.0% LazyCompile: *map native array.js:1215 + 4 18.2% LazyCompile: *exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 4 100.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 4 100.0% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 4 100.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + + + [Top down (heavy) profile]: + Note: callees occupying less than 0.1% are not shown. + + inclusive self name + ticks total ticks total + 7965 33.0% 7965 33.0% /usr/lib/system/libsystem_kernel.dylib + + 7124 29.5% 5 0.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:189 + 6450 26.7% 1 0.0% LazyCompile: *dispatchError /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:129 + 6445 26.7% 4 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:177 + 6306 26.1% 3 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:94 + 6301 26.1% 5 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 6262 25.9% 3 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 6258 25.9% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 6255 25.9% 1 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 6251 25.9% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 6250 25.9% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6248 25.9% 1 0.0% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:99 + 6243 25.9% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 6238 25.8% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 6236 25.8% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 6235 25.8% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 6234 25.8% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 6233 25.8% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6230 25.8% 2 0.0% LazyCompile: ~async.series.self.bDb.getNext.oldNext /Users/ematiu/devel/node/insight-api/lib/Sync.js:108 + 6223 25.8% 4 0.0% LazyCompile: *BlockDb.getTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:66 + 6159 25.5% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:110 + 6150 25.5% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 6145 25.5% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 6145 25.5% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 6143 25.4% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 6143 25.4% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 6141 25.4% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6135 25.4% 2 0.0% LazyCompile: ~async.series.self.processReorg.height /Users/ematiu/devel/node/insight-api/lib/Sync.js:122 + 6131 25.4% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 6128 25.4% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 6127 25.4% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 6127 25.4% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 6127 25.4% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 6120 25.4% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6119 25.3% 1 0.0% LazyCompile: ~async.series.self.cachedLastHash /Users/ematiu/devel/node/insight-api/lib/Sync.js:130 + 6115 25.3% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 6112 25.3% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 6089 25.2% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 6087 25.2% 1 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 6085 25.2% 3 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 6082 25.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 6079 25.2% 8 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 5986 24.8% 20 0.1% LazyCompile: *BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 5212 21.6% 4 0.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 5205 21.6% 18 0.1% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 4869 20.2% 9 0.0% LazyCompile: ~exports.format util.js:23 + 4809 19.9% 14 0.1% LazyCompile: *inspect util.js:118 + 4776 19.8% 23 0.1% LazyCompile: formatValue util.js:204 + 4479 18.6% 9 0.0% LazyCompile: *map native array.js:1215 + 4449 18.4% 2 0.0% LazyCompile: * util.js:293 + 4445 18.4% 27 0.1% LazyCompile: *formatProperty util.js:353 + 3938 16.3% 18 0.1% LazyCompile: formatValue util.js:204 + 3214 13.3% 12 0.0% LazyCompile: *formatArray util.js:333 + 3125 12.9% 12 0.0% LazyCompile: *formatProperty util.js:353 + 2828 11.7% 15 0.1% LazyCompile: formatValue util.js:204 + 2342 9.7% 24 0.1% LazyCompile: *map native array.js:1215 + 2248 9.3% 10 0.0% LazyCompile: * util.js:293 + 2232 9.2% 70 0.3% LazyCompile: *formatProperty util.js:353 + 1268 5.3% 70 0.3% LazyCompile: formatValue util.js:204 + 558 2.3% 63 0.3% LazyCompile: *inspect buffer.js:283 + 318 1.3% 23 0.1% LazyCompile: *toString native v8natives.js:1378 + 114 0.5% 114 0.5% /usr/local/bin/node + 86 0.4% 86 0.4% /usr/lib/system/libsystem_malloc.dylib + 35 0.1% 35 0.1% /usr/lib/system/libsystem_m.dylib + 33 0.1% 33 0.1% /usr/lib/system/libsystem_platform.dylib + 95 0.4% 95 0.4% /usr/local/bin/node + 29 0.1% 25 0.1% LazyCompile: join native array.js:410 + 27 0.1% 27 0.1% Stub: KeyedStoreElementStub + 149 0.6% 8 0.0% LazyCompile: *arrayToHash util.js:193 + 131 0.5% 11 0.0% LazyCompile: *forEach native array.js:1087 + 62 0.3% 3 0.0% LazyCompile: * util.js:196 + 56 0.2% 56 0.2% /usr/local/bin/node + 145 0.6% 5 0.0% LazyCompile: *formatPrimitive util.js:304 + 62 0.3% 4 0.0% LazyCompile: STRING_ADD_LEFT native runtime.js:183 + 33 0.1% 33 0.1% /usr/local/bin/node + 41 0.2% 2 0.0% LazyCompile: *replace native string.js:221 + 32 0.1% 32 0.1% /usr/local/bin/node + 119 0.5% 2 0.0% LazyCompile: *keys native v8natives.js:333 + 114 0.5% 114 0.5% /usr/local/bin/node + 56 0.2% 56 0.2% /usr/local/bin/node + 42 0.2% 5 0.0% LazyCompile: *isRegExp util.js:442 + 40 0.2% 7 0.0% LazyCompile: *isError util.js:454 + 445 1.8% 20 0.1% LazyCompile: *getOwnPropertyDescriptor native v8natives.js:946 + 348 1.4% 15 0.1% LazyCompile: *GetOwnProperty native v8natives.js:637 + 168 0.7% 168 0.7% /usr/local/bin/node + 129 0.5% 53 0.2% LazyCompile: *ConvertDescriptorArrayToDescriptor native v8natives.js:581 + 76 0.3% 41 0.2% LazyCompile: *FromPropertyDescriptor native v8natives.js:373 + 124 0.5% 27 0.1% LazyCompile: stringify native json.js:308 + 74 0.3% 14 0.1% LazyCompile: *BasicJSONSerialize native json.js:274 + 57 0.2% 57 0.2% /usr/local/bin/node + 103 0.4% 6 0.0% LazyCompile: *match native string.js:182 + 96 0.4% 4 0.0% LazyCompile: *RegExpExecNoTests native regexp.js:157 + 50 0.2% 21 0.1% LazyCompile: *BuildResultFromMatchInfo native regexp.js:130 + 64 0.3% 7 0.0% LazyCompile: *indexOf native string.js:118 + 50 0.2% 50 0.2% /usr/local/bin/node + 46 0.2% 9 0.0% LazyCompile: hasOwnProperty native v8natives.js:249 + 36 0.1% 36 0.1% /usr/local/bin/node + 30 0.1% 15 0.1% LazyCompile: *indexOf native array.js:1261 + 29 0.1% 29 0.1% /usr/local/bin/node + 25 0.1% 4 0.0% LazyCompile: IN native runtime.js:354 + 204 0.8% 3 0.0% LazyCompile: *reduceToSingleString util.js:412 + 96 0.4% 12 0.0% LazyCompile: *reduce native array.js:1381 + 48 0.2% 8 0.0% LazyCompile: * util.js:414 + 40 0.2% 1 0.0% LazyCompile: *indexOf native string.js:118 + 34 0.1% 34 0.1% /usr/local/bin/node + 95 0.4% 3 0.0% LazyCompile: join native array.js:410 + 92 0.4% 20 0.1% LazyCompile: Join native array.js:119 + 59 0.2% 59 0.2% /usr/local/bin/node + 159 0.7% 0 0.0% LazyCompile: *arrayToHash util.js:193 + 156 0.6% 5 0.0% LazyCompile: *forEach native array.js:1087 + 121 0.5% 1 0.0% LazyCompile: * util.js:196 + 116 0.5% 116 0.5% /usr/local/bin/node + 35 0.1% 0 0.0% LazyCompile: *keys native v8natives.js:333 + 33 0.1% 33 0.1% /usr/local/bin/node + 70 0.3% 2 0.0% LazyCompile: join native array.js:410 + 65 0.3% 6 0.0% LazyCompile: Join native array.js:119 + 51 0.2% 51 0.2% /usr/local/bin/node + 51 0.2% 6 0.0% LazyCompile: *split native string.js:554 + 45 0.2% 45 0.2% /usr/local/bin/node + 48 0.2% 1 0.0% LazyCompile: *getOwnPropertyDescriptor native v8natives.js:946 + 42 0.2% 6 0.0% LazyCompile: *GetOwnProperty native v8natives.js:637 + 46 0.2% 9 0.0% LazyCompile: *map native array.js:1215 + 332 1.4% 2 0.0% LazyCompile: ~formatArray util.js:333 + 321 1.3% 0 0.0% LazyCompile: *formatProperty util.js:353 + 287 1.2% 0 0.0% LazyCompile: formatValue util.js:204 + 239 1.0% 0 0.0% LazyCompile: *map native array.js:1215 + 230 1.0% 2 0.0% LazyCompile: * util.js:293 + 226 0.9% 7 0.0% LazyCompile: *formatProperty util.js:353 + 123 0.5% 4 0.0% LazyCompile: formatValue util.js:204 + 52 0.2% 2 0.0% LazyCompile: *inspect buffer.js:283 + 28 0.1% 1 0.0% LazyCompile: *toString native v8natives.js:1378 + 37 0.2% 0 0.0% LazyCompile: *getOwnPropertyDescriptor native v8natives.js:946 + 31 0.1% 3 0.0% LazyCompile: *GetOwnProperty native v8natives.js:637 + 200 0.8% 15 0.1% LazyCompile: *formatPrimitive util.js:304 + 95 0.4% 10 0.0% LazyCompile: *replace native string.js:221 + 69 0.3% 69 0.3% /usr/local/bin/node + 43 0.2% 9 0.0% LazyCompile: stringify native json.js:308 + 29 0.1% 6 0.0% LazyCompile: *BasicJSONSerialize native json.js:274 + 30 0.1% 3 0.0% LazyCompile: STRING_ADD_LEFT native runtime.js:183 + 44 0.2% 44 0.2% /usr/local/bin/node + 41 0.2% 4 0.0% LazyCompile: *reduceToSingleString util.js:412 + 31 0.1% 0 0.0% LazyCompile: *arrayToHash util.js:193 + 28 0.1% 0 0.0% LazyCompile: *forEach native array.js:1087 + 142 0.6% 6 0.0% LazyCompile: *getOwnPropertyDescriptor native v8natives.js:946 + 115 0.5% 4 0.0% LazyCompile: *GetOwnProperty native v8natives.js:637 + 54 0.2% 18 0.1% LazyCompile: *ConvertDescriptorArrayToDescriptor native v8natives.js:581 + 47 0.2% 47 0.2% /usr/local/bin/node + 60 0.2% 9 0.0% LazyCompile: *map native array.js:1215 + 25 0.1% 25 0.1% /usr/local/bin/node + 50 0.2% 4 0.0% LazyCompile: *split native string.js:554 + 45 0.2% 45 0.2% /usr/local/bin/node + 49 0.2% 7 0.0% LazyCompile: *match native string.js:182 + 42 0.2% 1 0.0% LazyCompile: *RegExpExecNoTests native regexp.js:157 + 46 0.2% 3 0.0% LazyCompile: join native array.js:410 + 42 0.2% 2 0.0% LazyCompile: Join native array.js:119 + 35 0.1% 35 0.1% /usr/local/bin/node + 45 0.2% 3 0.0% LazyCompile: *indexOf native string.js:118 + 33 0.1% 33 0.1% /usr/local/bin/node + 33 0.1% 6 0.0% LazyCompile: stringify native json.js:308 + 94 0.4% 0 0.0% LazyCompile: *reduceToSingleString util.js:412 + 55 0.2% 5 0.0% LazyCompile: *reduce native array.js:1381 + 37 0.2% 1 0.0% LazyCompile: * util.js:414 + 36 0.1% 0 0.0% LazyCompile: *indexOf native string.js:118 + 27 0.1% 27 0.1% /usr/local/bin/node + 36 0.1% 5 0.0% LazyCompile: join native array.js:410 + 31 0.1% 3 0.0% LazyCompile: Join native array.js:119 + 75 0.3% 2 0.0% LazyCompile: *arrayToHash util.js:193 + 68 0.3% 3 0.0% LazyCompile: *forEach native array.js:1087 + 52 0.2% 2 0.0% LazyCompile: * util.js:196 + 38 0.2% 38 0.2% /usr/local/bin/node + 41 0.2% 1 0.0% LazyCompile: join native array.js:410 + 39 0.2% 2 0.0% LazyCompile: Join native array.js:119 + 259 1.1% 2 0.0% LazyCompile: native messages.js:1157 + 239 1.0% 2 0.0% LazyCompile: captureStackTrace native messages.js:1113 + 104 0.4% 104 0.4% /usr/local/bin/node + 98 0.4% 1 0.0% LazyCompile: DefineOneShotAccessor native messages.js:767 + 94 0.4% 94 0.4% /usr/local/bin/node + 25 0.1% 25 0.1% /usr/lib/system/libsystem_malloc.dylib + 29 0.1% 29 0.1% /usr/local/bin/node + 439 1.8% 35 0.1% LazyCompile: Batch.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:19 + 242 1.0% 6 0.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 230 1.0% 5 0.0% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 127 0.5% 127 0.5% /usr/local/bin/node + 48 0.2% 2 0.0% LazyCompile: *keys native v8natives.js:333 + 46 0.2% 46 0.2% /usr/local/bin/node + 50 0.2% 50 0.2% /usr/local/bin/node + 41 0.2% 41 0.2% /usr/lib/system/libsystem_malloc.dylib + 176 0.7% 4 0.0% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 155 0.6% 92 0.4% LazyCompile: *Batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:13 + 55 0.2% 55 0.2% /usr/local/bin/node + 63 0.3% 63 0.3% /usr/local/bin/node + 56 0.2% 56 0.2% /usr/lib/system/libsystem_kernel.dylib + 83 0.3% 0 0.0% LazyCompile: ~BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 65 0.3% 0 0.0% LazyCompile: *winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 65 0.3% 0 0.0% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 60 0.2% 1 0.0% LazyCompile: ~exports.format util.js:23 + 51 0.2% 0 0.0% LazyCompile: ~inspect util.js:118 + 42 0.2% 0 0.0% LazyCompile: ~formatValue util.js:204 + 38 0.2% 0 0.0% LazyCompile: *map native array.js:1215 + 38 0.2% 0 0.0% LazyCompile: * util.js:293 + 38 0.2% 0 0.0% LazyCompile: *formatProperty util.js:353 + 28 0.1% 1 0.0% LazyCompile: ~formatValue util.js:204 + 43 0.2% 12 0.0% LazyCompile: *split native string.js:554 + 30 0.1% 30 0.1% /usr/local/bin/node + 48 0.2% 48 0.2% /usr/local/bin/node + 48 0.2% 48 0.2% /usr/lib/system/libsystem_kernel.dylib + 25 0.1% 25 0.1% /usr/lib/system/libsystem_c.dylib + 303 1.3% 3 0.0% LazyCompile: *test native regexp.js:217 + 290 1.2% 3 0.0% LazyCompile: NonStringToString native runtime.js:558 + 280 1.2% 5 0.0% LazyCompile: DefaultString native runtime.js:645 + 114 0.5% 114 0.5% /usr/lib/system/libsystem_kernel.dylib + 56 0.2% 56 0.2% /usr/local/bin/node + 55 0.2% 7 0.0% LazyCompile: toString native messages.js:1248 + 48 0.2% 5 0.0% LazyCompile: ErrorToStringDetectCycle native messages.js:1227 + 27 0.1% 6 0.0% LazyCompile: ~GetPropertyWithoutInvokingMonkeyGetters native messages.js:1203 + 38 0.2% 38 0.2% /usr/lib/system/libsystem_c.dylib + 273 1.1% 4 0.0% LazyCompile: ~err /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:25 + 144 0.6% 1 0.0% LazyCompile: *init /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/errno/custom.js:3 + 143 0.6% 11 0.0% LazyCompile: ~prr /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/prr/prr.js:44 + 109 0.5% 0 0.0% LazyCompile: *Object.defineProperty.function.obj.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/prr/prr.js:16 + 105 0.4% 2 0.0% LazyCompile: defineProperty native v8natives.js:1050 + 63 0.3% 13 0.1% LazyCompile: *ToPropertyDescriptor native v8natives.js:420 + 39 0.2% 1 0.0% LazyCompile: *DefineOwnProperty native v8natives.js:924 + 38 0.2% 13 0.1% LazyCompile: *DefineObjectProperty native v8natives.js:695 + 66 0.3% 0 0.0% LazyCompile: native messages.js:1157 + 66 0.3% 0 0.0% LazyCompile: native messages.js:1157 + 64 0.3% 0 0.0% LazyCompile: captureStackTrace native messages.js:1113 + 35 0.1% 2 0.0% LazyCompile: DefineOneShotAccessor native messages.js:767 + 30 0.1% 30 0.1% /usr/local/bin/node + 43 0.2% 0 0.0% LazyCompile: captureStackTrace native messages.js:1113 + 32 0.1% 0 0.0% LazyCompile: DefineOneShotAccessor native messages.js:767 + 30 0.1% 30 0.1% /usr/local/bin/node + 67 0.3% 0 0.0% LazyCompile: ~dispatchError /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:129 + 51 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:177 + 48 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:94 + 48 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 47 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 47 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 41 0.2% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 41 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 41 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 41 0.2% 0 0.0% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:99 + 41 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 41 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 41 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 41 0.2% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 41 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 41 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 41 0.2% 0 0.0% LazyCompile: ~async.series.self.bDb.getNext.oldNext /Users/ematiu/devel/node/insight-api/lib/Sync.js:108 + 41 0.2% 0 0.0% LazyCompile: ~BlockDb.getTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:66 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:110 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 40 0.2% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 40 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 40 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 40 0.2% 0 0.0% LazyCompile: ~async.series.self.processReorg.height /Users/ematiu/devel/node/insight-api/lib/Sync.js:122 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 40 0.2% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 40 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 40 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 40 0.2% 0 0.0% LazyCompile: ~async.series.self.cachedLastHash /Users/ematiu/devel/node/insight-api/lib/Sync.js:130 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 40 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 40 0.2% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 40 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 40 0.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 40 0.2% 0 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:146 + 40 0.2% 0 0.0% LazyCompile: ~BlockDb.add /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:48 + 34 0.1% 0 0.0% LazyCompile: ~winston.(anonymous function) /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston.js:84 + 34 0.1% 0 0.0% LazyCompile: ~Logger.log /Users/ematiu/devel/node/insight-api/node_modules/winston/lib/winston/logger.js:123 + 33 0.1% 0 0.0% LazyCompile: ~exports.format util.js:23 + 33 0.1% 0 0.0% LazyCompile: ~inspect util.js:118 + 29 0.1% 0 0.0% LazyCompile: ~formatValue util.js:204 + 27 0.1% 0 0.0% LazyCompile: ~map native array.js:1215 + 27 0.1% 0 0.0% LazyCompile: * util.js:293 + + 3215 13.3% 1 0.0% LazyCompile: *processImmediate timers.js:320 + 3213 13.3% 3 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:448 + 3210 13.3% 3 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:678 + 3207 13.3% 0 0.0% LazyCompile: *async.whilst /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:676 + 3202 13.3% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:439 + 3202 13.3% 0 0.0% LazyCompile: *HistoricSync.getBlockFromFile /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:198 + 3200 13.3% 2 0.0% LazyCompile: *BlockExtractor.getNextBlock /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:90 + 3198 13.2% 0 0.0% LazyCompile: *async.series /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:605 + 3196 13.2% 2 0.0% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:229 + 3161 13.1% 1 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 3160 13.1% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 3158 13.1% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 3157 13.1% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:96 + 3155 13.1% 1 0.0% LazyCompile: *async.whilst /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:676 + 3152 13.1% 4 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:102 + 3106 12.9% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:678 + 3106 12.9% 0 0.0% LazyCompile: *async.whilst /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:676 + 3104 12.9% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 3097 12.8% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 3097 12.8% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 3097 12.8% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 3097 12.8% 2 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 3095 12.8% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 3094 12.8% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:129 + 3080 12.8% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 3078 12.8% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 3075 12.7% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 3075 12.7% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 3075 12.7% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 3075 12.7% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 3074 12.7% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:142 + 2130 8.8% 3 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 2117 8.8% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 2114 8.8% 4 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 2109 8.7% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:253 + 2106 8.7% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:150 + 2102 8.7% 11 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:204 + 1388 5.7% 5 0.0% LazyCompile: *HistoricSync.getStandardizedBlock /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:184 + 1317 5.5% 6 0.0% LazyCompile: *map native array.js:1215 + 1272 5.3% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:192 + 1270 5.3% 3 0.0% LazyCompile: *HistoricSync.getStandardizedTx /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:148 + 1178 4.9% 10 0.0% LazyCompile: *map native array.js:1215 + 975 4.0% 13 0.1% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:164 + 711 2.9% 17 0.1% LazyCompile: ~Address.fromScriptPubKey /Users/ematiu/devel/node/bitcore/lib/Address.js:97 + 352 1.5% 7 0.0% LazyCompile: *exports.sha256ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:54 + 166 0.7% 6 0.0% LazyCompile: *exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 50 0.2% 0 0.0% LazyCompile: *Hash crypto.js:185 + 50 0.2% 2 0.0% LazyCompile: *Hash crypto.js:185 + 41 0.2% 41 0.2% /usr/local/bin/node + 45 0.2% 1 0.0% LazyCompile: *Hash.digest crypto.js:214 + 32 0.1% 32 0.1% /usr/local/bin/node + 37 0.2% 3 0.0% LazyCompile: *Buffer.write buffer.js:315 + 47 0.2% 7 0.0% LazyCompile: *Buffer.write buffer.js:315 + 41 0.2% 0 0.0% LazyCompile: *Hash crypto.js:185 + 36 0.1% 4 0.0% LazyCompile: *Hash crypto.js:185 + 29 0.1% 29 0.1% /usr/local/bin/node + 34 0.1% 0 0.0% LazyCompile: *Hash.digest crypto.js:214 + 26 0.1% 1 0.0% LazyCompile: ~exports.ripe160 /Users/ematiu/devel/node/bitcore/util/util.js:29 + 222 0.9% 6 0.0% LazyCompile: Address /Users/ematiu/devel/node/bitcore/lib/Address.js:39 + 211 0.9% 5 0.0% LazyCompile: *constructor.super /Users/ematiu/devel/node/bitcore/node_modules/soop/soop.js:22 + 171 0.7% 13 0.1% LazyCompile: VersionedData /Users/ematiu/devel/node/bitcore/util/VersionedData.js:6 + 68 0.3% 68 0.3% /usr/local/bin/node + 46 0.2% 4 0.0% LazyCompile: *VersionedData.payload /Users/ematiu/devel/node/bitcore/util/VersionedData.js:30 + 37 0.2% 2 0.0% LazyCompile: *EncodedData.doAsBinary /Users/ematiu/devel/node/bitcore/util/EncodedData.js:75 + 30 0.1% 5 0.0% LazyCompile: ~ /Users/ematiu/devel/node/bitcore/util/VersionedData.js:32 + 25 0.1% 6 0.0% LazyCompile: *Buffer.copy buffer.js:509 + 28 0.1% 2 0.0% LazyCompile: *VersionedData.version /Users/ematiu/devel/node/bitcore/util/VersionedData.js:21 + 49 0.2% 49 0.2% /usr/local/bin/node + 42 0.2% 16 0.1% LazyCompile: ~Script.capture /Users/ematiu/devel/node/bitcore/lib/Script.js:235 + 122 0.5% 10 0.0% LazyCompile: *HistoricSync._fromBuffer /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:143 + 64 0.3% 5 0.0% LazyCompile: *Buffer.toString buffer.js:392 + 117 0.5% 16 0.1% LazyCompile: *Script /Users/ematiu/devel/node/bitcore/lib/Script.js:25 + 66 0.3% 3 0.0% LazyCompile: * /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:97 + 60 0.2% 0 0.0% LazyCompile: *buffer /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:12 + 60 0.2% 8 0.0% LazyCompile: *Buffer.slice buffer.js:539 + 31 0.1% 31 0.1% /usr/local/bin/node + 126 0.5% 7 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:152 + 53 0.2% 23 0.1% LazyCompile: *isCoinBase /Users/ematiu/devel/node/bitcore/lib/Transaction.js:49 + 27 0.1% 2 0.0% LazyCompile: *Buffer.toString buffer.js:392 + 48 0.2% 48 0.2% /usr/local/bin/node + 62 0.3% 6 0.0% LazyCompile: *exports.formatHashFull /Users/ematiu/devel/node/bitcore/util/util.js:70 + 25 0.1% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:192 + 50 0.2% 7 0.0% LazyCompile: *exports.formatHashFull /Users/ematiu/devel/node/bitcore/util/util.js:70 + 694 2.9% 1 0.0% LazyCompile: *BlockDb.setLastFileIndex /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:112 + 690 2.9% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:207 + 690 2.9% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:440 + 666 2.8% 5 0.0% LazyCompile: *Sync.storeTipBlock /Users/ematiu/devel/node/insight-api/lib/Sync.js:79 + 626 2.6% 1 0.0% LazyCompile: *async.series /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:605 + 620 2.6% 17 0.1% LazyCompile: /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:229 + 510 2.1% 4 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 506 2.1% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 504 2.1% 2 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 499 2.1% 4 0.0% LazyCompile: ~async.series.self.bDb.getTip.oldTip /Users/ematiu/devel/node/insight-api/lib/Sync.js:92 + 492 2.0% 10 0.0% LazyCompile: *BlockDb.has /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:175 + 477 2.0% 28 0.1% LazyCompile: *LevelUP.get /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:163 + 209 0.9% 209 0.9% /usr/local/bin/node + 100 0.4% 3 0.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 95 0.4% 3 0.0% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 44 0.2% 44 0.2% /usr/local/bin/node + 35 0.1% 0 0.0% LazyCompile: *keys native v8natives.js:333 + 34 0.1% 34 0.1% /usr/local/bin/node + 78 0.3% 78 0.3% /usr/lib/system/libsystem_kernel.dylib + 55 0.2% 2 0.0% LazyCompile: *_map /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:53 + 52 0.2% 9 0.0% LazyCompile: *map native array.js:1215 + 27 0.1% 27 0.1% /usr/local/bin/node + 31 0.1% 31 0.1% /usr/local/bin/node + 715 3.0% 17 0.1% LazyCompile: *parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 372 1.5% 7 0.0% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 119 0.5% 0 0.0% LazyCompile: *Hash crypto.js:185 + 110 0.5% 7 0.0% LazyCompile: *Hash crypto.js:185 + 83 0.3% 83 0.3% /usr/local/bin/node + 84 0.3% 5 0.0% LazyCompile: *Buffer.write buffer.js:315 + 44 0.2% 44 0.2% /usr/local/bin/node + 67 0.3% 2 0.0% LazyCompile: *Hash.digest crypto.js:214 + 38 0.2% 38 0.2% /usr/local/bin/node + 51 0.2% 6 0.0% LazyCompile: *Hash.update crypto.js:205 + 42 0.2% 42 0.2% /usr/local/bin/node + 197 0.8% 8 0.0% LazyCompile: *serialize /Users/ematiu/devel/node/bitcore/lib/Transaction.js:152 + 126 0.5% 8 0.0% LazyCompile: *forEach native array.js:1087 + 63 0.3% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/bitcore/lib/Transaction.js:160 + 62 0.3% 5 0.0% LazyCompile: *serialize /Users/ematiu/devel/node/bitcore/lib/Transaction.js:56 + 38 0.2% 5 0.0% LazyCompile: *Buffer.concat buffer.js:476 + 43 0.2% 0 0.0% LazyCompile: *serialize /Users/ematiu/devel/node/bitcore/lib/Transaction.js:103 + 26 0.1% 5 0.0% LazyCompile: *Buffer.concat buffer.js:476 + 27 0.1% 2 0.0% LazyCompile: *Buffer.concat buffer.js:476 + 45 0.2% 2 0.0% LazyCompile: *buffer /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:12 + 43 0.2% 8 0.0% LazyCompile: *Buffer.slice buffer.js:539 + 36 0.1% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:97 + 28 0.1% 1 0.0% LazyCompile: *buffer /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:12 + 27 0.1% 4 0.0% LazyCompile: *Buffer.slice buffer.js:539 + 26 0.1% 1 0.0% LazyCompile: *Parser.varInt /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:123 + 25 0.1% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/bitcore/util/BinaryParser.js:97 + 137 0.6% 5 0.0% LazyCompile: *getHash /Users/ematiu/devel/node/bitcore/lib/Block.js:85 + 119 0.5% 5 0.0% LazyCompile: *exports.twoSha256 /Users/ematiu/devel/node/bitcore/util/util.js:50 + 29 0.1% 1 0.0% LazyCompile: *Hash.digest crypto.js:214 + 26 0.1% 5 0.0% LazyCompile: *Buffer.write buffer.js:315 + 78 0.3% 2 0.0% LazyCompile: ~parse /Users/ematiu/devel/node/bitcore/lib/Block.js:51 + 55 0.2% 0 0.0% LazyCompile: ~Transaction.parse /Users/ematiu/devel/node/bitcore/lib/Transaction.js:532 + 46 0.2% 1 0.0% LazyCompile: *calcHash /Users/ematiu/devel/node/bitcore/lib/Transaction.js:183 + 25 0.1% 0 0.0% LazyCompile: *serialize /Users/ematiu/devel/node/bitcore/lib/Transaction.js:152 + + 1930 8.0% 1930 8.0% /usr/local/bin/node + + 1282 5.3% 15 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/batch.js:66 + 1098 4.5% 23 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:58 + 596 2.5% 7 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 546 2.3% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 543 2.2% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 540 2.2% 0 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 539 2.2% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 538 2.2% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 537 2.2% 2 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:150 + 532 2.2% 5 0.0% LazyCompile: *TransactionDb.createFromBlock /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:582 + 491 2.0% 2 0.0% LazyCompile: TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 371 1.5% 25 0.1% LazyCompile: ~LevelUP.batch /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:285 + 196 0.8% 196 0.8% /usr/local/bin/node + 31 0.1% 1 0.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 30 0.1% 2 0.0% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 30 0.1% 30 0.1% /usr/lib/system/libsystem_malloc.dylib + 54 0.2% 6 0.0% LazyCompile: TransactionDb._addScript /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:462 + 44 0.2% 44 0.2% /usr/local/bin/node + 29 0.1% 0 0.0% LazyCompile: ~TransactionDb.createFromObjs /Users/ematiu/devel/node/insight-api/lib/TransactionDb.js:552 + 452 1.9% 452 1.9% /usr/local/bin/node + 64 0.3% 64 0.3% /usr/local/bin/node + 40 0.2% 8 0.0% LazyCompile: ~EventEmitter.emit events.js:53 + 26 0.1% 26 0.1% /usr/local/bin/node + + 514 2.1% 37 0.2% LazyCompile: native messages.js:1157 + 429 1.8% 25 0.1% LazyCompile: native messages.js:1157 + 204 0.8% 204 0.8% /usr/local/bin/node + 173 0.7% 19 0.1% LazyCompile: captureStackTrace native messages.js:1113 + 87 0.4% 87 0.4% /usr/local/bin/node + 48 0.2% 1 0.0% LazyCompile: DefineOneShotAccessor native messages.js:767 + 46 0.2% 46 0.2% /usr/local/bin/node + + 478 2.0% 9 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:345 + 444 1.8% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:151 + 440 1.8% 16 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 175 0.7% 6 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 168 0.7% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 165 0.7% 1 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 162 0.7% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 160 0.7% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 160 0.7% 8 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:155 + 146 0.6% 2 0.0% LazyCompile: *BlockDb.setTip /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:82 + 138 0.6% 17 0.1% LazyCompile: *LevelUP.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:210 + 56 0.2% 3 0.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 51 0.2% 2 0.0% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 32 0.1% 32 0.1% /usr/local/bin/node + 113 0.5% 113 0.5% /usr/local/bin/node + 45 0.2% 45 0.2% /usr/lib/system/libsystem_malloc.dylib + 31 0.1% 31 0.1% /usr/lib/libc++abi.dylib + + 475 2.0% 18 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:238 + 217 0.9% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:84 + 213 0.9% 5 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:157 + 203 0.8% 19 0.1% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 115 0.5% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 113 0.5% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 112 0.5% 1 0.0% LazyCompile: *iterate /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:148 + 111 0.5% 0 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:248 + 111 0.5% 1 0.0% LazyCompile: * /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:608 + 108 0.4% 2 0.0% LazyCompile: ~async.series.err /Users/ematiu/devel/node/insight-api/lib/Sync.js:161 + 101 0.4% 1 0.0% LazyCompile: *BlockDb.setNext /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:153 + 99 0.4% 6 0.0% LazyCompile: *LevelUP.put /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:210 + 46 0.2% 1 0.0% LazyCompile: *getOptions /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/util.js:87 + 44 0.2% 3 0.0% LazyCompile: *extend /Users/ematiu/devel/node/insight-api/node_modules/levelup/node_modules/xtend/index.js:6 + 25 0.1% 25 0.1% /usr/local/bin/node + 43 0.2% 43 0.2% /usr/local/bin/node + 162 0.7% 2 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:154 + 156 0.6% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:162 + 155 0.6% 12 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:610 + 66 0.3% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:249 + 65 0.3% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:149 + 63 0.3% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:253 + 60 0.2% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:168 + 57 0.2% 1 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:446 + 54 0.2% 5 0.0% LazyCompile: *global.setImmediate node.js:192 + 46 0.2% 11 0.0% LazyCompile: ~exports.setImmediate timers.js:337 + 45 0.2% 45 0.2% /usr/local/bin/node + 29 0.1% 29 0.1% /usr/local/bin/node + + 415 1.7% 415 1.7% /usr/lib/system/libsystem_c.dylib + + 244 1.0% 0 0.0% Function: ~ node.js:27 + 243 1.0% 0 0.0% LazyCompile: ~startup node.js:30 + 227 0.9% 0 0.0% LazyCompile: ~Module.runMain module.js:495 + 227 0.9% 0 0.0% LazyCompile: Module._load module.js:275 + 216 0.9% 0 0.0% LazyCompile: ~Module.load module.js:346 + 214 0.9% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 213 0.9% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 212 0.9% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/util/sync.js:1 + 146 0.6% 0 0.0% LazyCompile: ~require module.js:379 + 146 0.6% 0 0.0% LazyCompile: ~Module.require module.js:361 + 146 0.6% 0 0.0% LazyCompile: Module._load module.js:275 + 143 0.6% 0 0.0% LazyCompile: ~Module.load module.js:346 + 143 0.6% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 142 0.6% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 134 0.6% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:1 + 82 0.3% 0 0.0% LazyCompile: ~require module.js:379 + 82 0.3% 0 0.0% LazyCompile: ~Module.require module.js:361 + 82 0.3% 0 0.0% LazyCompile: Module._load module.js:275 + 75 0.3% 0 0.0% LazyCompile: ~Module.load module.js:346 + 75 0.3% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 74 0.3% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 31 0.1% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/lib/BlockExtractor.js:1 + 29 0.1% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/lib/Sync.js:1 + 29 0.1% 0 0.0% LazyCompile: ~require module.js:379 + 29 0.1% 0 0.0% LazyCompile: ~Module.require module.js:361 + 29 0.1% 0 0.0% LazyCompile: Module._load module.js:275 + 28 0.1% 0 0.0% LazyCompile: ~Module.load module.js:346 + 28 0.1% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 28 0.1% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 28 0.1% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/lib/logger.js:1 + 49 0.2% 0 0.0% LazyCompile: ~Object.defineProperty.get /Users/ematiu/devel/node/bitcore/bitcore.js:10 + 49 0.2% 0 0.0% LazyCompile: ~require module.js:379 + 49 0.2% 0 0.0% LazyCompile: ~Module.require module.js:361 + 49 0.2% 0 0.0% LazyCompile: Module._load module.js:275 + 47 0.2% 0 0.0% LazyCompile: ~Module.load module.js:346 + 47 0.2% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 47 0.2% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 29 0.1% 0 0.0% Function: ~ /Users/ematiu/devel/node/bitcore/lib/Script.js:1 + 29 0.1% 0 0.0% LazyCompile: ~require module.js:379 + 29 0.1% 0 0.0% LazyCompile: ~Module.require module.js:361 + 29 0.1% 0 0.0% LazyCompile: Module._load module.js:275 + 62 0.3% 0 0.0% LazyCompile: ~HistoricSync /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:42 + 62 0.3% 0 0.0% LazyCompile: ~Sync /Users/ematiu/devel/node/insight-api/lib/Sync.js:18 + 61 0.3% 0 0.0% LazyCompile: ~require module.js:379 + 61 0.3% 0 0.0% LazyCompile: ~Module.require module.js:361 + 61 0.3% 0 0.0% LazyCompile: Module._load module.js:275 + 59 0.2% 0 0.0% LazyCompile: ~Module.load module.js:346 + 59 0.2% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 59 0.2% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 56 0.2% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/lib/BlockDb.js:1 + 41 0.2% 0 0.0% LazyCompile: ~require module.js:379 + 41 0.2% 0 0.0% LazyCompile: ~Module.require module.js:361 + 41 0.2% 0 0.0% LazyCompile: Module._load module.js:275 + 39 0.2% 0 0.0% LazyCompile: ~Module.load module.js:346 + 39 0.2% 0 0.0% LazyCompile: ~Module._extensions..js module.js:472 + 39 0.2% 0 0.0% LazyCompile: ~Module._compile module.js:374 + 38 0.2% 0 0.0% Function: ~ /Users/ematiu/devel/node/insight-api/node_modules/levelup/lib/levelup.js:1 + + 66 0.3% 66 0.3% /usr/lib/system/libsystem_malloc.dylib + + 65 0.3% 65 0.3% /usr/lib/system/libsystem_platform.dylib + + 45 0.2% 41 0.2% LazyCompile: *ArraySlice native array.js:621 + + 42 0.2% 0 0.0% LazyCompile: ~processImmediate timers.js:320 + 42 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/lib/HistoricSync.js:448 + 42 0.2% 0 0.0% LazyCompile: ~ /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:678 + 25 0.1% 0 0.0% LazyCompile: *async.whilst /Users/ematiu/devel/node/insight-api/node_modules/async/lib/async.js:676 + + 33 0.1% 33 0.1% Builtin: A builtin from the snapshot + + 26 0.1% 26 0.1% Stub: JSEntryStub + diff --git a/dev-util/read_block.js b/dev-util/read_block.js new file mode 100755 index 0000000..a7da132 --- /dev/null +++ b/dev-util/read_block.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var assert = require('assert'), + config = require('../config/config'), + BlockExtractor = require('../lib/BlockExtractor').class(), + networks = require('reddcore/networks'), + util = require('reddcore/util/util'); + + var be = new BlockExtractor(config.bitcoind.dataDir, config.network); + var network = config.network === 'testnet' ? networks.testnet: networks.livenet; +// console.log('[read_block.js.13]', be.nextFile() ); + + var c=0; + while (c++ < 100) { + be.getNextBlock(function(err, b) { + console.log('[read_block.js.14]',err, c, b?util.formatHashAlt(b.hash):''); //TODO + }); + } + + + + diff --git a/dev-util/stats b/dev-util/stats new file mode 100644 index 0000000..73399ec --- /dev/null +++ b/dev-util/stats @@ -0,0 +1,70 @@ + + +first 5% + + => with data + mongo + w/RPC for blocks: 48.8s + => with RPC + mongo: 2m26s + => with files + mongo + wo/RPC for blocks: 36.7s + => with files + mongo + wo/RPC for blocks + wo/mongoIndexes: + + +first 10% + + => sin RPC, sin Tx, sin store block => 0.7s + => sin RPC, sin grabar, procesando TX => 8.5s + => sin RPC, sin TX processing, sin grabar => 12s28 + => con RPC, TX processing, sin Grabar Tx, grabando bloques => 29s + => con RPC, sin TX processing, sin Grabar Tx, grabando bloques => 35s + => con RPC, TX processing, sin Grabar Tx, grabando bloques => 43s + + => TX processing, sin RPC, sin saves TX, y blocks => 11.6s + => TX processing, CON RPC, sin saves TX, y blocks => 35s + => con RPC, TX processing, sin saves TX => 45s + => con RPC, TX processing, Grabarndo todo => 78s + => con RPC, TX processing, Grabarndo todo => 78s + (18k blocks, 36k txouts) + +//LEVEL DB + => sin RPC, TX processing, todo en level => 14s + => con RPC, TX processing, todo en level => 39.7s + => con RPC, TX processing, tx mongo, blocks en level => 64s + + + => sin RPC, TX processing, todo en level, handling REORGs, more data => 28s + => sin RPC, TX processing, todo en level, handling REORGs, more data, tx ts => 34t s + + +//FROM blk00002.dat (more txs), 5% + + => now total : 1m13s + => removing block writes => 1m8s + => sacando los contenidos adentro de getblock from file de => 4.5s!! + + => con base58 cpp => 21s + => toda la testnet => 17m !! + +10% de blk2 + => 50s con base58cpp + => 41s commentando todo addr + => 5s commentando todo get HistoricSync.prototype.getBlockFromFile = function(cb) { + => 15s commentando todo get HistoricSync.prototype.getBlockFromFile = function(cb) { + +10% de blk 1 + => 59s + => 15s comentando desde b.getStandardizedObject() + => 39s comentando dps b.getStandardizedObject() + + +Mon Mar 10 11:59:25 ART 2014 +10% de blk 0 (testnet) + => 37s + +Thu May 22 13:42:50 ART 2014 (base58check + toString opts + custom getStandardizedObject) +10% testnet + => 29s + + +100% testnet + => 17m10s + + diff --git a/dev-util/status_info.js b/dev-util/status_info.js new file mode 100755 index 0000000..0337f84 --- /dev/null +++ b/dev-util/status_info.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var RpcClient = require('../node_modules/reddcore/RpcClient').class(); + +var config = require('../config/config'); + +var rpc = new RpcClient(config.bitcoind); + +var block = rpc.getInfo(function(err, block) { + if (err) { + console.log("Err:"); + console.log(err); + } + + console.log("Block info:"); + console.log(block); +}); + + diff --git a/dev-util/sync-level.js b/dev-util/sync-level.js new file mode 100644 index 0000000..2f2f95b --- /dev/null +++ b/dev-util/sync-level.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node +'use strict'; + +var Sync = require('../lib/Sync').class(); + + +var s = new Sync(); + + +s.setOrphan( + '0000000000c2b1e8dab92a72741289e5ef0d4f375fd1b26f729da2ba979c028a', + '000000000228f9d02654459e09998c7557afa9082784c11226853f5feb805df9', + function (err) { + console.log('[sync-level.js.15]',err); //TODO + }); + + diff --git a/etc/minersPoolStrings.json b/etc/minersPoolStrings.json new file mode 100644 index 0000000..1ad3892 --- /dev/null +++ b/etc/minersPoolStrings.json @@ -0,0 +1,227 @@ +[ + { + "poolName":"50BTC", + "url":"https://50btc.com/", + "searchStrings":[ + "50BTC.com", + "50btc.com" + ] + }, + { + "poolName":"175btc", + "url":"http://www.175btc.com/", + "searchStrings":[ + "Mined By 175btc.com" + ] + }, + { + "poolName":"ASICminer", + "url":"https://bitcointalk.org/index.php?topic=99497.0", + "searchStrings":[ + "Mined By ASICMiner" + ] + }, + { + "poolName":"AntMiner", + "url":"https://bitmaintech.com/", + "searchStrings":[ + "AntPool" + ] + }, + { + "poolName":"agentD", + "url":"http://", + "searchStrings":[ + "agentD" + ] + }, + { + "poolName":"Bitfury", + "url":"http://bitfury.org/", + "searchStrings":[ + "2av0id51pct" + ] + }, + { + "poolName":"BitMinter", + "url":"https://bitminter.com/", + "searchStrings":[ + "BitMinter" + ] + }, + { + "poolName":"Bitparking", + "url":"http://bitparking.com/", + "searchStrings":[ + "bitparking" + ] + }, + { + "poolName":"BTC Guild", + "url":"https://www.btcguild.com/", + "searchStrings":[ + "Mined by BTC Guild", + "BTC Guild" + ] + }, + { + "poolName":"bcpool.io", + "url":"https://bcpool.io/", + "searchStrings":[ + "bcpool" + ] + }, + { + "poolName":"Discus Fish", + "url":"http://f2pool.com/", + "searchStrings":[ + "七彩神仙鱼", + "Made in China", + "Mined by user" + ] + }, + { + "poolName":"Discus Fish Solo", + "url":"http://f2pool.com/", + "searchStrings":[ + "For Pierce and Paul" + ] + }, + { + "poolName":"Cointerra", + "url":"http://cointerra.com/", + "searchStrings":[ + "cointerra" + ] + }, + { + "poolName":"Eligius", + "url":"http://eligius.st/", + "searchStrings":[ + "Eligius" + ] + }, + { + "poolName":"EclipseMC", + "url":"https://eclipsemc.com/", + "searchStrings":[ + "Josh Zerlan was here!", + "EclipseMC", + "Aluminum Falcon" + ] + }, + { + "poolName":"GIVE-ME-COINS", + "url":"https://give-me-coins.com/", + "searchStrings":[ + "Mined at GIVE-ME-COINS.com" + ] + }, + { + "poolName":"ghash.io", + "url":"https://ghash.io/", + "searchStrings":[ + "ghash.io", + "GHash.IO" + ] + }, + { + "poolName":"HHTT", + "url":"http://hhtt.1209k.com/", + "searchStrings":[ + "HHTT" + ] + }, + { + "poolName":"KNCminer", + "url":"https://www.kncminer.com/", + "searchStrings":[ + "KnCMiner" + ] + }, + { + "poolName":"Megabigpower", + "url":"http://megabigpower.com/", + "searchStrings":[ + "megabigpower.com" + ] + }, + { + "poolName":"MultiCoin", + "url":"https://multicoin.co/", + "searchStrings":[ + "MultiCoin.co" + ] + }, + { + "poolName":"Mt Red", + "url":"https://mtred.com/‎", + "searchStrings":[ + "/mtred/" + ] + }, + { + "poolName":"MaxBTC", + "url":"https://www.maxbtc.com", + "searchStrings":[ + "MaxBTC" + ] + }, + { + "poolName":"NMCbit", + "url":"http://nmcbit.com/", + "searchStrings":[ + "nmcbit.com" + ] + }, + { + "poolName":"ozcoin", + "url":"https://ozco.in/", + "searchStrings":[ + "ozco.in", + "ozcoin" + ] + }, + { + "poolName":"Polmine.pl", + "url":"https://polmine.pl/‎", + "searchStrings":[ + "by polmine.pl" + ] + }, + { + "poolName":"simplecoin", + "url":"http://simplecoin.us/", + "searchStrings":[ + "simplecoin.us ftw" + ] + }, + { + "poolName":"Slush", + "url":"https://mining.bitcoin.cz/", + "searchStrings":[ + "slush" + ] + }, + { + "poolName":"TripleMining", + "url":"https://www.triplemining.com/", + "searchStrings":[ + "Triplemining.com" + ] + }, + { + "poolName":"wizkid057", + "url":"http://wizkid057.com/btc", + "searchStrings":[ + "wizkid057" + ] + }, + { + "poolName":"Yourbtc.net", + "url":"http://yourbtc.net/", + "searchStrings":[ + "yourbtc.net" + ] + } +] diff --git a/etc/test-cert.pem b/etc/test-cert.pem new file mode 100644 index 0000000..4e95885 --- /dev/null +++ b/etc/test-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICMjCCAZugAwIBAgIJAK9dKmjfxq+BMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV +BAYTAkFSMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVDb3BheTAeFw0x +NDA4MjExNzQyMTBaFw0xNDA5MjAxNzQyMTBaMDIxCzAJBgNVBAYTAkFSMRMwEQYD +VQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVDb3BheTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEA1BbMI6V06LKoBrcf5bJ8LH7EjwqbEacIOpiY7B+8W3sAM1bB +6hA2IlPvKL3qTdhMMKFZGZMYypmlAQTI1N+VNSwJHNjyepFbtkdNytSC8qw8bhak +yt4TByYEw1NMYx7I0OOdjh/DKsS+EOIgQDT9zSB+NgErKb0mKrginwgk5XkCAwEA +AaNQME4wHQYDVR0OBBYEFM0G1agUfY4zRNfxJ+0sHV3EsoGKMB8GA1UdIwQYMBaA +FM0G1agUfY4zRNfxJ+0sHV3EsoGKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADgYEAOg7n1RCyB1BJ6TuF99i25H7kpGUSL57ajNyyCKDciTPmpxVJ5knAjPYa +hbXX+dlq2B8QEnfkE5FMDLkO3RS3xU8YfekIDHofDuXR9boD/4rRlsN8md2Jmkr6 +MyRtYPtsPWVeoz0WmG5f1yobHmh7mYf17oN+uRJKX68s8G6b/SQ= +-----END CERTIFICATE----- diff --git a/etc/test-key.pem b/etc/test-key.pem new file mode 100644 index 0000000..7bb97c2 --- /dev/null +++ b/etc/test-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDUFswjpXTosqgGtx/lsnwsfsSPCpsRpwg6mJjsH7xbewAzVsHq +EDYiU+8ovepN2EwwoVkZkxjKmaUBBMjU35U1LAkc2PJ6kVu2R03K1ILyrDxuFqTK +3hMHJgTDU0xjHsjQ452OH8MqxL4Q4iBANP3NIH42ASspvSYquCKfCCTleQIDAQAB +AoGAMUzDUx3o2RZ+XGFA9uHQX39wLVfnx+itzwEduvV9kT48Q7LNDJ2MF9qu4yeS +SVoYC83Vqk45Gw8v/dag4GrAgdk1NHZZ56Z/G55m06Y45xS6ZarBdbe0N1jdZEab +RG3FgxyPSUiZ5aLIMxMMtgt/DRv9BPpIeLNDMgyQRjVWlMkCQQDzlLwkp4bo+CAY +UMcsSN+KGurEMsuF0qc/+TLqpKDoOaLtd1F+Ntn20tQqeH0YLWktFvzAgY7wYXrb +lhMuAxa7AkEA3ucGEXNqwu1qVP4fXfEN1E0Y5X/euXMsfgNG8IK82hF3h83hnqNM +3FcGFOyKnL7E5TfRlJfxhAGqUfCe+2zjWwJBAKA6CID8CkyZW1NjX4EL9q+8AQ5K +c4J2DTqRzCJ5ZLcdosUeJecmYb5w9MtzMqaCyJq2clCXaNVK6iwjzj4IHh0CQQCY +sgwvIjCtrfQcmyUjtoExwUrf1LPfuK1u+ZG8KuNyQ2rtxjTb9qQtgRPye4QNEoZR +O+a/c0MImhdyIHLYa+RnAkEAwfLD4q+FDx4eX0ANO7/PI/XiJGqi6x1cYUwyRg9o +2S6hN5RnUD/nf2HKHU0esp34UMY/UWMrodCRDZj/ijg4UA== +-----END RSA PRIVATE KEY----- diff --git a/examples/bitcore.js b/examples/bitcore.js new file mode 100644 index 0000000..3f55d37 --- /dev/null +++ b/examples/bitcore.js @@ -0,0 +1,41841 @@ +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o prevnonce; false otherwise +AuthMessage._noncegt = function(nonce, prevnonce) { + var noncep1 = nonce.slice(0, 4).readUInt32BE(0); + var prevnoncep1 = prevnonce.slice(0, 4).readUInt32BE(0); + + if (noncep1 > prevnoncep1) + return true; + + if (noncep1 < prevnoncep1) + return false; + + var noncep2 = nonce.slice(4, 8).readUInt32BE(0); + var prevnoncep2 = prevnonce.slice(4, 8).readUInt32BE(0); + + if (noncep2 > prevnoncep2) + return true; + + return false; +}; + +AuthMessage._encrypt = function(topubkey, payload, r, iv) { + var encrypted = ECIES.encrypt(topubkey, payload, r, iv); + return encrypted; +}; + +AuthMessage._decrypt = function(privkey, encrypted) { + var decrypted = ECIES.decrypt(privkey, encrypted); + return decrypted; +}; + +AuthMessage._sign = function(key, payload) { + var sig = Message.sign(payload, key); + return sig; +}; + +AuthMessage._verify = function(pubkey, signature, payload) { + var v = Message.verifyWithPubKey(pubkey, payload, signature); + return v; +}; + +module.exports = AuthMessage; + +}).call(this,require("buffer").Buffer) +},{"./ECIES":"0Qraa1","./Key":"ALJ4PS","./Message":"CBDCgz","buffer":95,"preconditions":163}],"./lib/AuthMessage":[function(require,module,exports){ +module.exports=require('cBnJMk'); +},{}],"./lib/BIP39":[function(require,module,exports){ +module.exports=require('82LilS'); +},{}],"82LilS":[function(require,module,exports){ +(function (Buffer){ +var coinUtil = require('../util'); +var sjcl = require('./sjcl'); +var SecureRandom = require('./SecureRandom'); + +var hmacSHA512 = function(key) { + var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha512); + this.encrypt = function() { + return hasher.encrypt.apply(hasher, arguments); + }; +}; + +var pbkdf2Sync_sha512 = function(password, salt, iterations, keylen) { + var derivedKey = sjcl.misc.pbkdf2(password, salt, iterations, 512, hmacSHA512); + return sjcl.codec.hex.fromBits(derivedKey) +}; + +var BIP39 = function() {}; + +BIP39.mnemonic = function(wordlist, bits) { + if (!bits) + bits = 128; + if (bits % 32 != 0) + throw new Error("bits must be multiple of 32"); + var buf = SecureRandom.getRandomBuffer(bits / 8); + return BIP39.entropy2mnemonic(wordlist, buf); +} + +BIP39.entropy2mnemonic = function(wordlist, buf) { + var hash = coinUtil.sha256(buf); + var bin = ""; + var bits = buf.length * 8; + for (var i = 0; i < buf.length; i++) { + bin = bin + ("00000000" + buf[i].toString(2)).slice(-8); + } + var hashbits = hash[0].toString(2); + hashbits = ("00000000" + hashbits).slice(-8).slice(0, bits / 32); + bin = bin + hashbits; + if (bin.length % 11 != 0) + throw new Error("internal error - entropy not an even multiple of 11 bits - " + bin.length); + var mnemonic = ""; + for (var i = 0; i < bin.length / 11; i++) { + if (mnemonic != "") + mnemonic = mnemonic + " "; + var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); + mnemonic = mnemonic + wordlist[wi]; + } + return mnemonic; +} + +BIP39.check = function(wordlist, mnemonic) { + var words = mnemonic.split(' '); + var bin = ""; + for (var i = 0; i < words.length; i++) { + var ind = wordlist.indexOf(words[i]); + if (ind < 0) + return false; + bin = bin + ("00000000000" + ind.toString(2)).slice(-11); + } + + if (bin.length % 11 != 0) { + throw new Error("internal error - entropy not an even multiple of 11 bits - " + bin.length); + } + var cs = bin.length / 33; + var hash_bits = bin.slice(-cs); + var nonhash_bits = bin.slice(0, bin.length - cs); + var buf = new Buffer(nonhash_bits.length / 8); + for (var i = 0; i < nonhash_bits.length / 8; i++) { + buf.writeUInt8(parseInt(bin.slice(i * 8, (i + 1) * 8), 2), i); + } + var hash = coinUtil.sha256(buf); + var expected_hash_bits = hash[0].toString(2); + expected_hash_bits = ("00000000" + expected_hash_bits).slice(-8).slice(0, cs); + return expected_hash_bits == hash_bits; +} + +BIP39.mnemonic2seed = function(mnemonic, passphrase) { + if (!passphrase) + passphrase = ""; + var hex = pbkdf2Sync_sha512(mnemonic, "mnemonic" + passphrase, 2048, 64); + var buf = new Buffer(hex, 'hex'); + return buf; +} + +module.exports = BIP39; + +}).call(this,require("buffer").Buffer) +},{"../util":191,"./SecureRandom":"p4SiC2","./sjcl":"oLMOpG","buffer":95}],"./lib/BIP39WordlistEn":[function(require,module,exports){ +module.exports=require('sp4vFZ'); +},{}],"sp4vFZ":[function(require,module,exports){ +var BIP39WordlistEn = ["abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abuse", "access", "accident", "account", "accuse", "achieve", "acid", "acoustic", "acquire", "across", "act", "action", "actor", "actress", "actual", "adapt", "add", "addict", "address", "adjust", "admit", "adult", "advance", "advice", "aerobic", "affair", "afford", "afraid", "again", "age", "agent", "agree", "ahead", "aim", "air", "airport", "aisle", "alarm", "album", "alcohol", "alert", "alien", "all", "alley", "allow", "almost", "alone", "alpha", "already", "also", "alter", "always", "amateur", "amazing", "among", "amount", "amused", "analyst", "anchor", "ancient", "anger", "angle", "angry", "animal", "ankle", "announce", "annual", "another", "answer", "antenna", "antique", "anxiety", "any", "apart", "apology", "appear", "apple", "approve", "april", "arch", "arctic", "area", "arena", "argue", "arm", "armed", "armor", "army", "around", "arrange", "arrest", "arrive", "arrow", "art", "artefact", "artist", "artwork", "ask", "aspect", "assault", "asset", "assist", "assume", "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract", "auction", "audit", "august", "aunt", "author", "auto", "autumn", "average", "avocado", "avoid", "awake", "aware", "away", "awesome", "awful", "awkward", "axis", "baby", "bachelor", "bacon", "badge", "bag", "balance", "balcony", "ball", "bamboo", "banana", "banner", "bar", "barely", "bargain", "barrel", "base", "basic", "basket", "battle", "beach", "bean", "beauty", "because", "become", "beef", "before", "begin", "behave", "behind", "believe", "below", "belt", "bench", "benefit", "best", "betray", "better", "between", "beyond", "bicycle", "bid", "bike", "bind", "biology", "bird", "birth", "bitter", "black", "blade", "blame", "blanket", "blast", "bleak", "bless", "blind", "blood", "blossom", "blouse", "blue", "blur", "blush", "board", "boat", "body", "boil", "bomb", "bone", "bonus", "book", "boost", "border", "boring", "borrow", "boss", "bottom", "bounce", "box", "boy", "bracket", "brain", "brand", "brass", "brave", "bread", "breeze", "brick", "bridge", "brief", "bright", "bring", "brisk", "broccoli", "broken", "bronze", "broom", "brother", "brown", "brush", "bubble", "buddy", "budget", "buffalo", "build", "bulb", "bulk", "bullet", "bundle", "bunker", "burden", "burger", "burst", "bus", "business", "busy", "butter", "buyer", "buzz", "cabbage", "cabin", "cable", "cactus", "cage", "cake", "call", "calm", "camera", "camp", "can", "canal", "cancel", "candy", "cannon", "canoe", "canvas", "canyon", "capable", "capital", "captain", "car", "carbon", "card", "cargo", "carpet", "carry", "cart", "case", "cash", "casino", "castle", "casual", "cat", "catalog", "catch", "category", "cattle", "caught", "cause", "caution", "cave", "ceiling", "celery", "cement", "census", "century", "cereal", "certain", "chair", "chalk", "champion", "change", "chaos", "chapter", "charge", "chase", "chat", "cheap", "check", "cheese", "chef", "cherry", "chest", "chicken", "chief", "child", "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", "cinnamon", "circle", "citizen", "city", "civil", "claim", "clap", "clarify", "claw", "clay", "clean", "clerk", "clever", "click", "client", "cliff", "climb", "clinic", "clip", "clock", "clog", "close", "cloth", "cloud", "clown", "club", "clump", "cluster", "clutch", "coach", "coast", "coconut", "code", "coffee", "coil", "coin", "collect", "color", "column", "combine", "come", "comfort", "comic", "common", "company", "concert", "conduct", "confirm", "congress", "connect", "consider", "control", "convince", "cook", "cool", "copper", "copy", "coral", "core", "corn", "correct", "cost", "cotton", "couch", "country", "couple", "course", "cousin", "cover", "coyote", "crack", "cradle", "craft", "cram", "crane", "crash", "crater", "crawl", "crazy", "cream", "credit", "creek", "crew", "cricket", "crime", "crisp", "critic", "crop", "cross", "crouch", "crowd", "crucial", "cruel", "cruise", "crumble", "crunch", "crush", "cry", "crystal", "cube", "culture", "cup", "cupboard", "curious", "current", "curtain", "curve", "cushion", "custom", "cute", "cycle", "dad", "damage", "damp", "dance", "danger", "daring", "dash", "daughter", "dawn", "day", "deal", "debate", "debris", "decade", "december", "decide", "decline", "decorate", "decrease", "deer", "defense", "define", "defy", "degree", "delay", "deliver", "demand", "demise", "denial", "dentist", "deny", "depart", "depend", "deposit", "depth", "deputy", "derive", "describe", "desert", "design", "desk", "despair", "destroy", "detail", "detect", "develop", "device", "devote", "diagram", "dial", "diamond", "diary", "dice", "diesel", "diet", "differ", "digital", "dignity", "dilemma", "dinner", "dinosaur", "direct", "dirt", "disagree", "discover", "disease", "dish", "dismiss", "disorder", "display", "distance", "divert", "divide", "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin", "domain", "donate", "donkey", "donor", "door", "dose", "double", "dove", "draft", "dragon", "drama", "drastic", "draw", "dream", "dress", "drift", "drill", "drink", "drip", "drive", "drop", "drum", "dry", "duck", "dumb", "dune", "during", "dust", "dutch", "duty", "dwarf", "dynamic", "eager", "eagle", "early", "earn", "earth", "easily", "east", "easy", "echo", "ecology", "economy", "edge", "edit", "educate", "effort", "egg", "eight", "either", "elbow", "elder", "electric", "elegant", "element", "elephant", "elevator", "elite", "else", "embark", "embody", "embrace", "emerge", "emotion", "employ", "empower", "empty", "enable", "enact", "end", "endless", "endorse", "enemy", "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist", "enough", "enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope", "episode", "equal", "equip", "era", "erase", "erode", "erosion", "error", "erupt", "escape", "essay", "essence", "estate", "eternal", "ethics", "evidence", "evil", "evoke", "evolve", "exact", "example", "excess", "exchange", "excite", "exclude", "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist", "exit", "exotic", "expand", "expect", "expire", "explain", "expose", "express", "extend", "extra", "eye", "eyebrow", "fabric", "face", "faculty", "fade", "faint", "faith", "fall", "false", "fame", "family", "famous", "fan", "fancy", "fantasy", "farm", "fashion", "fat", "fatal", "father", "fatigue", "fault", "favorite", "feature", "february", "federal", "fee", "feed", "feel", "female", "fence", "festival", "fetch", "fever", "few", "fiber", "fiction", "field", "figure", "file", "film", "filter", "final", "find", "fine", "finger", "finish", "fire", "firm", "first", "fiscal", "fish", "fit", "fitness", "fix", "flag", "flame", "flash", "flat", "flavor", "flee", "flight", "flip", "float", "flock", "floor", "flower", "fluid", "flush", "fly", "foam", "focus", "fog", "foil", "fold", "follow", "food", "foot", "force", "forest", "forget", "fork", "fortune", "forum", "forward", "fossil", "foster", "found", "fox", "fragile", "frame", "frequent", "fresh", "friend", "fringe", "frog", "front", "frost", "frown", "frozen", "fruit", "fuel", "fun", "funny", "furnace", "fury", "future", "gadget", "gain", "galaxy", "gallery", "game", "gap", "garage", "garbage", "garden", "garlic", "garment", "gas", "gasp", "gate", "gather", "gauge", "gaze", "general", "genius", "genre", "gentle", "genuine", "gesture", "ghost", "giant", "gift", "giggle", "ginger", "giraffe", "girl", "give", "glad", "glance", "glare", "glass", "glide", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glue", "goat", "goddess", "gold", "good", "goose", "gorilla", "gospel", "gossip", "govern", "gown", "grab", "grace", "grain", "grant", "grape", "grass", "gravity", "great", "green", "grid", "grief", "grit", "grocery", "group", "grow", "grunt", "guard", "guess", "guide", "guilt", "guitar", "gun", "gym", "habit", "hair", "half", "hammer", "hamster", "hand", "happy", "harbor", "hard", "harsh", "harvest", "hat", "have", "hawk", "hazard", "head", "health", "heart", "heavy", "hedgehog", "height", "hello", "helmet", "help", "hen", "hero", "hidden", "high", "hill", "hint", "hip", "hire", "history", "hobby", "hockey", "hold", "hole", "holiday", "hollow", "home", "honey", "hood", "hope", "horn", "horror", "horse", "hospital", "host", "hotel", "hour", "hover", "hub", "huge", "human", "humble", "humor", "hundred", "hungry", "hunt", "hurdle", "hurry", "hurt", "husband", "hybrid", "ice", "icon", "idea", "identify", "idle", "ignore", "ill", "illegal", "illness", "image", "imitate", "immense", "immune", "impact", "impose", "improve", "impulse", "inch", "include", "income", "increase", "index", "indicate", "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit", "initial", "inject", "injury", "inmate", "inner", "innocent", "input", "inquiry", "insane", "insect", "inside", "inspire", "install", "intact", "interest", "into", "invest", "invite", "involve", "iron", "island", "isolate", "issue", "item", "ivory", "jacket", "jaguar", "jar", "jazz", "jealous", "jeans", "jelly", "jewel", "job", "join", "joke", "journey", "joy", "judge", "juice", "jump", "jungle", "junior", "junk", "just", "kangaroo", "keen", "keep", "ketchup", "key", "kick", "kid", "kidney", "kind", "kingdom", "kiss", "kit", "kitchen", "kite", "kitten", "kiwi", "knee", "knife", "knock", "know", "lab", "label", "labor", "ladder", "lady", "lake", "lamp", "language", "laptop", "large", "later", "latin", "laugh", "laundry", "lava", "law", "lawn", "lawsuit", "layer", "lazy", "leader", "leaf", "learn", "leave", "lecture", "left", "leg", "legal", "legend", "leisure", "lemon", "lend", "length", "lens", "leopard", "lesson", "letter", "level", "liar", "liberty", "library", "license", "life", "lift", "light", "like", "limb", "limit", "link", "lion", "liquid", "list", "little", "live", "lizard", "load", "loan", "lobster", "local", "lock", "logic", "lonely", "long", "loop", "lottery", "loud", "lounge", "love", "loyal", "lucky", "luggage", "lumber", "lunar", "lunch", "luxury", "lyrics", "machine", "mad", "magic", "magnet", "maid", "mail", "main", "major", "make", "mammal", "man", "manage", "mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", "marine", "market", "marriage", "mask", "mass", "master", "match", "material", "math", "matrix", "matter", "maximum", "maze", "meadow", "mean", "measure", "meat", "mechanic", "medal", "media", "melody", "melt", "member", "memory", "mention", "menu", "mercy", "merge", "merit", "merry", "mesh", "message", "metal", "method", "middle", "midnight", "milk", "million", "mimic", "mind", "minimum", "minor", "minute", "miracle", "mirror", "misery", "miss", "mistake", "mix", "mixed", "mixture", "mobile", "model", "modify", "mom", "moment", "monitor", "monkey", "monster", "month", "moon", "moral", "more", "morning", "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move", "movie", "much", "muffin", "mule", "multiply", "muscle", "museum", "mushroom", "music", "must", "mutual", "myself", "mystery", "myth", "naive", "name", "napkin", "narrow", "nasty", "nation", "nature", "near", "neck", "need", "negative", "neglect", "neither", "nephew", "nerve", "nest", "net", "network", "neutral", "never", "news", "next", "nice", "night", "noble", "noise", "nominee", "noodle", "normal", "north", "nose", "notable", "note", "nothing", "notice", "novel", "now", "nuclear", "number", "nurse", "nut", "oak", "obey", "object", "oblige", "obscure", "observe", "obtain", "obvious", "occur", "ocean", "october", "odor", "off", "offer", "office", "often", "oil", "okay", "old", "olive", "olympic", "omit", "once", "one", "onion", "online", "only", "open", "opera", "opinion", "oppose", "option", "orange", "orbit", "orchard", "order", "ordinary", "organ", "orient", "original", "orphan", "ostrich", "other", "outdoor", "outer", "output", "outside", "oval", "oven", "over", "own", "owner", "oxygen", "oyster", "ozone", "pact", "paddle", "page", "pair", "palace", "palm", "panda", "panel", "panic", "panther", "paper", "parade", "parent", "park", "parrot", "party", "pass", "patch", "path", "patient", "patrol", "pattern", "pause", "pave", "payment", "peace", "peanut", "pear", "peasant", "pelican", "pen", "penalty", "pencil", "people", "pepper", "perfect", "permit", "person", "pet", "phone", "photo", "phrase", "physical", "piano", "picnic", "picture", "piece", "pig", "pigeon", "pill", "pilot", "pink", "pioneer", "pipe", "pistol", "pitch", "pizza", "place", "planet", "plastic", "plate", "play", "please", "pledge", "pluck", "plug", "plunge", "poem", "poet", "point", "polar", "pole", "police", "pond", "pony", "pool", "popular", "portion", "position", "possible", "post", "potato", "pottery", "poverty", "powder", "power", "practice", "praise", "predict", "prefer", "prepare", "present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", "prison", "private", "prize", "problem", "process", "produce", "profit", "program", "project", "promote", "proof", "property", "prosper", "protect", "proud", "provide", "public", "pudding", "pull", "pulp", "pulse", "pumpkin", "punch", "pupil", "puppy", "purchase", "purity", "purpose", "purse", "push", "put", "puzzle", "pyramid", "quality", "quantum", "quarter", "question", "quick", "quit", "quiz", "quote", "rabbit", "raccoon", "race", "rack", "radar", "radio", "rail", "rain", "raise", "rally", "ramp", "ranch", "random", "range", "rapid", "rare", "rate", "rather", "raven", "raw", "razor", "ready", "real", "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record", "recycle", "reduce", "reflect", "reform", "refuse", "region", "regret", "regular", "reject", "relax", "release", "relief", "rely", "remain", "remember", "remind", "remove", "render", "renew", "rent", "reopen", "repair", "repeat", "replace", "report", "require", "rescue", "resemble", "resist", "resource", "response", "result", "retire", "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", "rib", "ribbon", "rice", "rich", "ride", "ridge", "rifle", "right", "rigid", "ring", "riot", "ripple", "risk", "ritual", "rival", "river", "road", "roast", "robot", "robust", "rocket", "romance", "roof", "rookie", "room", "rose", "rotate", "rough", "round", "route", "royal", "rubber", "rude", "rug", "rule", "run", "runway", "rural", "sad", "saddle", "sadness", "safe", "sail", "salad", "salmon", "salon", "salt", "salute", "same", "sample", "sand", "satisfy", "satoshi", "sauce", "sausage", "save", "say", "scale", "scan", "scare", "scatter", "scene", "scheme", "school", "science", "scissors", "scorpion", "scout", "scrap", "screen", "script", "scrub", "sea", "search", "season", "seat", "second", "secret", "section", "security", "seed", "seek", "segment", "select", "sell", "seminar", "senior", "sense", "sentence", "series", "service", "session", "settle", "setup", "seven", "shadow", "shaft", "shallow", "share", "shed", "shell", "sheriff", "shield", "shift", "shine", "ship", "shiver", "shock", "shoe", "shoot", "shop", "short", "shoulder", "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick", "side", "siege", "sight", "sign", "silent", "silk", "silly", "silver", "similar", "simple", "since", "sing", "siren", "sister", "situate", "six", "size", "skate", "sketch", "ski", "skill", "skin", "skirt", "skull", "slab", "slam", "sleep", "slender", "slice", "slide", "slight", "slim", "slogan", "slot", "slow", "slush", "small", "smart", "smile", "smoke", "smooth", "snack", "snake", "snap", "sniff", "snow", "soap", "soccer", "social", "sock", "soda", "soft", "solar", "soldier", "solid", "solution", "solve", "someone", "song", "soon", "sorry", "sort", "soul", "sound", "soup", "source", "south", "space", "spare", "spatial", "spawn", "speak", "special", "speed", "spell", "spend", "sphere", "spice", "spider", "spike", "spin", "spirit", "split", "spoil", "sponsor", "spoon", "sport", "spot", "spray", "spread", "spring", "spy", "square", "squeeze", "squirrel", "stable", "stadium", "staff", "stage", "stairs", "stamp", "stand", "start", "state", "stay", "steak", "steel", "stem", "step", "stereo", "stick", "still", "sting", "stock", "stomach", "stone", "stool", "story", "stove", "strategy", "street", "strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", "submit", "subway", "success", "such", "sudden", "suffer", "sugar", "suggest", "suit", "summer", "sun", "sunny", "sunset", "super", "supply", "supreme", "sure", "surface", "surge", "surprise", "surround", "survey", "suspect", "sustain", "swallow", "swamp", "swap", "swarm", "swear", "sweet", "swift", "swim", "swing", "switch", "sword", "symbol", "symptom", "syrup", "system", "table", "tackle", "tag", "tail", "talent", "talk", "tank", "tape", "target", "task", "taste", "tattoo", "taxi", "teach", "team", "tell", "ten", "tenant", "tennis", "tent", "term", "test", "text", "thank", "that", "theme", "then", "theory", "there", "they", "thing", "this", "thought", "three", "thrive", "throw", "thumb", "thunder", "ticket", "tide", "tiger", "tilt", "timber", "time", "tiny", "tip", "tired", "tissue", "title", "toast", "tobacco", "today", "toddler", "toe", "together", "toilet", "token", "tomato", "tomorrow", "tone", "tongue", "tonight", "tool", "tooth", "top", "topic", "topple", "torch", "tornado", "tortoise", "toss", "total", "tourist", "toward", "tower", "town", "toy", "track", "trade", "traffic", "tragic", "train", "transfer", "trap", "trash", "travel", "tray", "treat", "tree", "trend", "trial", "tribe", "trick", "trigger", "trim", "trip", "trophy", "trouble", "truck", "true", "truly", "trumpet", "trust", "truth", "try", "tube", "tuition", "tumble", "tuna", "tunnel", "turkey", "turn", "turtle", "twelve", "twenty", "twice", "twin", "twist", "two", "type", "typical", "ugly", "umbrella", "unable", "unaware", "uncle", "uncover", "under", "undo", "unfair", "unfold", "unhappy", "uniform", "unique", "unit", "universe", "unknown", "unlock", "until", "unusual", "unveil", "update", "upgrade", "uphold", "upon", "upper", "upset", "urban", "urge", "usage", "use", "used", "useful", "useless", "usual", "utility", "vacant", "vacuum", "vague", "valid", "valley", "valve", "van", "vanish", "vapor", "various", "vast", "vault", "vehicle", "velvet", "vendor", "venture", "venue", "verb", "verify", "version", "very", "vessel", "veteran", "viable", "vibrant", "vicious", "victory", "video", "view", "village", "vintage", "violin", "virtual", "virus", "visa", "visit", "visual", "vital", "vivid", "vocal", "voice", "void", "volcano", "volume", "vote", "voyage", "wage", "wagon", "wait", "walk", "wall", "walnut", "want", "warfare", "warm", "warrior", "wash", "wasp", "waste", "water", "wave", "way", "wealth", "weapon", "wear", "weasel", "weather", "web", "wedding", "weekend", "weird", "welcome", "west", "wet", "whale", "what", "wheat", "wheel", "when", "where", "whip", "whisper", "wide", "width", "wife", "wild", "will", "win", "window", "wine", "wing", "wink", "winner", "winter", "wire", "wisdom", "wise", "wish", "witness", "wolf", "woman", "wonder", "wood", "wool", "word", "work", "world", "worry", "worth", "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone", "zoo"]; +module.exports = BIP39WordlistEn; + +},{}],"./lib/Base58":[function(require,module,exports){ +module.exports=require('6VqyzY'); +},{}],"6VqyzY":[function(require,module,exports){ +(function (Buffer){ +var crypto = require('crypto'); +var bignum = require('bignum'); + +var globalBuffer = new Buffer(1024); +var zerobuf = new Buffer(0); +var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; +var ALPHABET_ZERO = ALPHABET[0]; +var ALPHABET_BUF = new Buffer(ALPHABET, 'ascii'); +var ALPHABET_INV = {}; +for (var i = 0; i < ALPHABET.length; i++) { + ALPHABET_INV[ALPHABET[i]] = i; +}; + +// Vanilla Base58 Encoding +var base58 = { + encode: function(buf) { + var str; + var x = bignum.fromBuffer(buf); + var r; + + if (buf.length < 512) { + str = globalBuffer; + } else { + str = new Buffer(buf.length << 1); + } + var i = str.length - 1; + while (x.gt(0)) { + r = x.mod(58); + x = x.div(58); + str[i] = ALPHABET_BUF[r.toNumber()]; + i--; + } + + // deal with leading zeros + var j = 0; + while (buf[j] == 0) { + str[i] = ALPHABET_BUF[0]; + j++; + i--; + } + + return str.slice(i + 1, str.length).toString('ascii'); + }, + + decode: function(str) { + if (str.length == 0) return zerobuf; + var answer = bignum(0); + for (var i = 0; i < str.length; i++) { + answer = answer.mul(58); + answer = answer.add(ALPHABET_INV[str[i]]); + }; + var i = 0; + while (i < str.length && str[i] == ALPHABET_ZERO) { + i++; + } + if (i > 0) { + var zb = new Buffer(i); + zb.fill(0); + if (i == str.length) return zb; + answer = answer.toBuffer(); + return Buffer.concat([zb, answer], i + answer.length); + } else { + return answer.toBuffer(); + } + }, +}; + +// Base58Check Encoding +function sha256(data) { + return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary'); +}; + +function doubleSHA256(data) { + return sha256(sha256(data)); +}; + +var base58Check = { + encode: function(buf) { + var checkedBuf = new Buffer(buf.length + 4); + var hash = doubleSHA256(buf); + buf.copy(checkedBuf); + hash.copy(checkedBuf, buf.length); + return base58.encode(checkedBuf); + }, + + decode: function(s) { + var buf = base58.decode(s); + if (buf.length < 4) { + throw new Error("invalid input: too short"); + } + + var data = buf.slice(0, -4); + var csum = buf.slice(-4); + + var hash = doubleSHA256(data); + var hash4 = hash.slice(0, 4); + + if (csum.toString('hex') !== hash4.toString('hex')) { + throw new Error("checksum mismatch"); + } + + return data; + }, +}; + +// if you frequently do base58 encodings with data larger +// than 512 bytes, you can use this method to expand the +// size of the reusable buffer +exports.setBuffer = function(buf) { + globalBuffer = buf; +}; + +exports.base58 = base58; +exports.base58Check = base58Check; +exports.encode = base58.encode; +exports.decode = base58.decode; + +}).call(this,require("buffer").Buffer) +},{"bignum":61,"buffer":95,"crypto":99}],"./lib/Block":[function(require,module,exports){ +module.exports=require('pJEQEB'); +},{}],"pJEQEB":[function(require,module,exports){ +(function (Buffer){ +var util = require('../util'); +var Script = require('./Script'); +var Bignum = require('bignum'); +var Binary = require('binary'); +var Step = require('step'); +var buffertools = require('buffertools'); +var Transaction = require('./Transaction'); +var TransactionIn = Transaction.In; +var TransactionOut = Transaction.Out; +var COINBASE_OP = Transaction.COINBASE_OP; +var VerificationError = require('../util/error').VerificationError; +var BlockRules = { + maxTimeOffset: 2 * 60 * 60, // How far block timestamps can be into the future + //largestHash: (new Bignum(2)).pow(256) + //largestHash: new Bignum('115792089237316195423570985008687907853269984665640564039457584007913129639936') // = 2^256 + largestHash: new Bignum('10000000000000000000000000000000000000000000000000000000000000000', 16) +}; + +function Block(data) { + if ("object" !== typeof data) { + data = {}; + } + this.hash = data.hash || null; + this.prev_hash = data.prev_hash || util.NULL_HASH; + this.merkle_root = data.merkle_root || util.NULL_HASH; + this.timestamp = data.timestamp || 0; + this.bits = data.bits || 0; + this.nonce = data.nonce || 0; + this.version = data.version || 0; + this.height = data.height || 0; + this.size = data.size || 0; + this.active = data.active || false; + this.chainWork = data.chainWork || util.EMPTY_BUFFER; + this.txs = data.txs || []; +} + +Block.prototype.getHeader = function getHeader() { + var buf = new Buffer(80); + var ofs = 0; + buf.writeUInt32LE(this.version, ofs); + ofs += 4; + this.prev_hash.copy(buf, ofs); + ofs += 32; + this.merkle_root.copy(buf, ofs); + ofs += 32; + buf.writeUInt32LE(this.timestamp, ofs); + ofs += 4; + buf.writeUInt32LE(this.bits, ofs); + ofs += 4; + buf.writeUInt32LE(this.nonce, ofs); + ofs += 4; + return buf; +}; + +Block.prototype.parse = function parse(parser, headerOnly) { + this.version = parser.word32le(); + this.prev_hash = parser.buffer(32); + this.merkle_root = parser.buffer(32); + this.timestamp = parser.word32le(); + this.bits = parser.word32le(); + this.nonce = parser.word32le(); + + this.txs = []; + this.size = 0; + + if (headerOnly) + return; + + var txCount = parser.varInt(); + + for (var i = 0; i < txCount; i++) { + var tx = new Transaction(); + tx.parse(parser); + this.txs.push(tx); + } +}; + +Block.prototype.calcHash = function calcHash() { + var header = this.getHeader(); + + return util.twoSha256(header); +}; + +Block.prototype.checkHash = function checkHash() { + if (!this.hash || !this.hash.length) return false; + return buffertools.compare(this.calcHash(), this.hash) == 0; +}; + +Block.prototype.getHash = function getHash() { + if (!this.hash || !this.hash.length) this.hash = this.calcHash(); + + return this.hash; +}; + +Block.prototype.checkProofOfWork = function checkProofOfWork() { + var target = util.decodeDiffBits(this.bits); + + // TODO: Create a compare method in node-buffertools that uses the correct + // endian so we don't have to reverse both buffers before comparing. + var reverseHash = buffertools.reverse(this.hash); + if (buffertools.compare(reverseHash, target) > 0) { + throw new VerificationError('Difficulty target not met'); + } + + return true; +}; + +/** + * Returns the amount of work that went into this block. + * + * Work is defined as the average number of tries required to meet this + * block's difficulty target. For example a target that is greater than 5% + * of all possible hashes would mean that 20 "work" is required to meet it. + */ +Block.prototype.getWork = function getWork() { + var target = util.decodeDiffBits(this.bits, true); + return BlockRules.largestHash.div(target.add(1)); +}; + +Block.prototype.checkTimestamp = function checkTimestamp() { + var currentTime = new Date().getTime() / 1000; + if (this.timestamp > currentTime + BlockRules.maxTimeOffset) { + throw new VerificationError('Timestamp too far into the future'); + } + + return true; +}; + +Block.prototype.checkTransactions = function checkTransactions(txs) { + if (!Array.isArray(txs) || txs.length <= 0) { + throw new VerificationError('No transactions'); + } + if (!txs[0].isCoinBase()) { + throw new VerificationError('First tx must be coinbase'); + } + for (var i = 1; i < txs.length; i++) { + if (txs[i].isCoinBase()) { + throw new VerificationError('Tx index ' + i + ' must not be coinbase'); + } + } + + return true; +}; + +/** + * Build merkle tree. + * + * Ported from Java. Original code: BitcoinJ by Mike Hearn + * Copyright (c) 2011 Google Inc. + */ +Block.prototype.getMerkleTree = function getMerkleTree(txs) { + // The merkle hash is based on a tree of hashes calculated from the transactions: + // + // merkleHash + // /\ + // / \ + // A B + // / \ / \ + // tx1 tx2 tx3 tx4 + // + // Basically transactions are hashed, then the hashes of the transactions are hashed + // again and so on upwards into the tree. The point of this scheme is to allow for + // disk space savings later on. + // + // This function is a direct translation of CBlock::BuildMerkleTree(). + + if (txs.length == 0) { + return [util.NULL_HASH.slice(0)]; + } + + // Start by adding all the hashes of the transactions as leaves of the tree. + var tree = txs.map(function(tx) { + return tx instanceof Transaction ? tx.getHash() : tx; + }); + + var j = 0; + // Now step through each level ... + for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) { + // and for each leaf on that level .. + for (var i = 0; i < size; i += 2) { + var i2 = Math.min(i + 1, size - 1); + var a = tree[j + i]; + var b = tree[j + i2]; + tree.push(util.twoSha256(Buffer.concat([a, b]))); + } + j += size; + } + + return tree; +}; + +Block.prototype.calcMerkleRoot = function calcMerkleRoot(txs) { + var tree = this.getMerkleTree(txs); + return tree[tree.length - 1]; +}; + +Block.prototype.checkMerkleRoot = function checkMerkleRoot(txs) { + if (!this.merkle_root || !this.merkle_root.length) { + throw new VerificationError('No merkle root'); + } + + if (buffertools.compare(this.calcMerkleRoot(txs), new Buffer(this.merkle_root)) !== 0) { + throw new VerificationError('Merkle root incorrect'); + } + + return true; +}; + +Block.prototype.checkBlock = function checkBlock(txs) { + if (!this.checkHash()) { + throw new VerificationError("Block hash invalid"); + } + this.checkProofOfWork(); + this.checkTimestamp(); + + if (txs) { + this.checkTransactions(txs); + if (!this.checkMerkleRoot(txs)) { + throw new VerificationError("Merkle hash invalid"); + } + } + return true; +}; + +Block.getBlockValue = function getBlockValue(height) { + var subsidy = 50 * util.COIN; + subsidy = subsidy / (Math.pow(2, Math.floor(height / 210000))); + subsidy = Math.floor(subsidy); + subsidy = new Bignum(subsidy); + return subsidy; +}; + +Block.prototype.getBlockValue = function getBlockValue() { + return Block.getBlockValue(this.height); +}; + +Block.prototype.toString = function toString() { + return ""; +}; + + +Block.prototype.createCoinbaseTx = + function createCoinbaseTx(beneficiary) { + var tx = new Transaction(); + tx.ins.push(new TransactionIn({ + s: util.EMPTY_BUFFER, + q: 0xffffffff, + o: COINBASE_OP + })); + tx.outs.push(new TransactionOut({ + v: util.bigIntToValue(this.getBlockValue()), + s: Script.createPubKeyOut(beneficiary).getBuffer() + })); + return tx; +}; + +Block.prototype.solve = function solve(miner, callback) { + var header = this.getHeader(); + var target = util.decodeDiffBits(this.bits); + miner.solve(header, target, callback); +}; + +/** + * Returns an object with the same field names as jgarzik's getblock patch. + */ +Block.prototype.getStandardizedObject = + function getStandardizedObject(txs) { + var block = { + hash: util.formatHashFull(this.getHash()), + version: this.version, + prev_block: util.formatHashFull(this.prev_hash), + mrkl_root: util.formatHashFull(this.merkle_root), + time: this.timestamp, + bits: this.bits, + nonce: this.nonce, + height: this.height + }; + + + if (txs) { + var mrkl_tree = this.getMerkleTree(txs).map(function(buffer) { + return util.formatHashFull(buffer); + }); + block.mrkl_root = mrkl_tree[mrkl_tree.length - 1]; + + block.n_tx = txs.length; + var totalSize = 80; // Block header + totalSize += util.getVarIntSize(txs.length); // txn_count + txs = txs.map(function(tx) { + tx = tx.getStandardizedObject(); + totalSize += tx.size; + return tx; + }); + block.size = totalSize; + block.tx = txs; + + block.mrkl_tree = mrkl_tree; + } else { + block.size = this.size; + } + return block; +}; + +module.exports = Block; + +}).call(this,require("buffer").Buffer) +},{"../util":191,"../util/error":190,"./Script":"hQ0t76","./Transaction":"LJhYtm","bignum":61,"binary":83,"buffer":95,"buffertools":"fugeBw","step":178}],"KifRG4":[function(require,module,exports){ +var MAX_BLOOM_FILTER_SIZE = 36000; // bytes +var MAX_HASH_FUNCS = 50; +var LN2SQUARED = 0.4804530139182014246671025263266649717305529515945455; +var LN2 = 0.6931471805599453094172321214581765680755001343602552; +var bit_mask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]; + +function Bloom() { + this.data = ''; + this.hashFuncs = 0; +}; + +function ROTL32(x, r) { + return (x << r) | (x >> (32 - r)); +}; + +function getBlockU32(blockIdx, data) { + var idx = blockIdx * 4; + var v = (data[idx + 0] << (0 * 8)) | + (data[idx + 1] << (1 * 8)) | + (data[idx + 2] << (2 * 8)) | + (data[idx + 3] << (3 * 8)); + return v; +}; + +Bloom.prototype.hash = function(hashNum, data) { + var h1 = hashNum * (0xffffffff / (this.hashFuncs - 1)); + var c1 = 0xcc9e2d51; + var c2 = 0x1b873593; + var nBlocks = data.length / 4; + + // data body + for (var i = -nBlocks; i; i++) { + var k1 = getBlockU32(i); + + k1 *= c1; + k1 = ROTLF32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTFL(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + // tail (trailing 1-3 bytes) + var tail = data.slice(nBlocks * 4); + + var k1 = 0; + + switch (data.length & 3) { + case 3: + k1 ^= tail[2] << 16; + case 2: + k1 ^= tail[1] << 8; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + h1 ^= k1; + } + + // finalize + h1 ^= data.length; + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1 % (this.data.length * 8); +}; + +Bloom.prototype.insert = function(data) { + for (var i = 0; i < this.hashFuncs; i++) { + var index = this.hash(i, data); + this.data[index >> 3] |= bit_mask[7 & index]; + } +}; + +Bloom.prototype.contains = function(data) { + for (var i = 0; i < this.hashFuncs; i++) { + var index = this.hash(i, data); + if (!(this.data[index >> 3] & bit_mask[7 & index])) + return false; + } + + return true; +}; + +Bloom.prototype.sizeOk = function() { + return this.data.length <= MAX_BLOOM_FILTER_SIZE && + this.hashFuncs <= MAX_HASH_FUNCS; +}; + +function toInt(v) { + return~~ v; +} + +function min(a, b) { + if (a < b) + return a; + return b; +} + +Bloom.prototype.init = function(elements, FPRate) { + var filterSize = min(toInt(-1.0 / LN2SQUARED * elements * Math.log(FPRate)), + MAX_BLOOM_FILTER_SIZE * 8) / 8; + this.data[filterSize] = 0; + this.hashFuncs = min(toInt(this.data.length * 8 / elements * LN2), + MAX_HASH_FUNCS); +}; + + +module.exports = Bloom; + +},{}],"./lib/Bloom":[function(require,module,exports){ +module.exports=require('KifRG4'); +},{}],"./lib/Connection":[function(require,module,exports){ +module.exports=require('DB/p3X'); +},{}],"DB/p3X":[function(require,module,exports){ +(function (Buffer){ +var log = require('../util/log'); + +var MAX_RECEIVE_BUFFER = 10000000; +var PROTOCOL_VERSION = 70000; + +var Put = require('bufferput'); +var Buffers = require('buffers'); +require('../patches/Buffers.monkey').patch(Buffers); + +var bitcoreDefaults = require('../config'); +var networks = require('../networks'); +var Block = require('./Block'); +var Transaction = require('./Transaction'); +var util = require('../util'); +var Parser = require('../util/BinaryParser'); +var buffertools = require('buffertools'); +var doubleSha256 = util.twoSha256; +var SecureRandom = require('./SecureRandom'); +var nonce = SecureRandom.getPseudoRandomBuffer(8); +var nodeUtil = require('util'); +var EventEmitter = require('events').EventEmitter; + +var BIP0031_VERSION = 60000; + +function Connection(socket, peer, opts) { + this.config = opts || bitcoreDefaults; + + this.network = networks[this.config.network] || networks.livenet; + this.socket = socket; + this.peer = peer; + + // check for socks5 proxy options and construct a proxied socket + if (this.config.proxy) { + var Socks5Client = require('socks5-client'); + this.socket = new Socks5Client(this.config.proxy.host, this.config.proxy.port); + } + + // A connection is considered "active" once we have received verack + this.active = false; + // The version incoming packages are interpreted as + this.recvVer = 0; + // The version outgoing packages are sent as + this.sendVer = 0; + // The (claimed) height of the remote peer's block chain + this.bestHeight = 0; + // Is this an inbound connection? + this.inbound = !!this.socket.server; + // Have we sent a getaddr on this connection? + this.getaddr = false; + + // Receive buffer + this.buffers = new Buffers(); + + // Starting 20 Feb 2012, Version 0.2 is obsolete + // This is the same behavior as the official client + if (new Date().getTime() > 1329696000000) { + this.recvVer = 209; + this.sendVer = 209; + } + + this.setupHandlers(); +} +nodeUtil.inherits(Connection, EventEmitter); +Connection.prototype.open = function(callback) { + if (typeof callback === 'function') this.once('connect', callback); + this.socket.connect(this.peer.port, this.peer.host); + return this; +}; + +Connection.prototype.setupHandlers = function() { + this.socket.addListener('connect', this.handleConnect.bind(this)); + this.socket.addListener('error', this.handleError.bind(this)); + this.socket.addListener('end', this.handleDisconnect.bind(this)); + this.socket.addListener('data', (function(data) { + var dumpLen = 35; + log.debug('[' + this.peer + '] ' + + 'Recieved ' + data.length + ' bytes of data:'); + log.debug('... ' + buffertools.toHex(data.slice(0, dumpLen > data.length ? + data.length : dumpLen)) + + (data.length > dumpLen ? '...' : '')); + }).bind(this)); + this.socket.addListener('data', this.handleData.bind(this)); +}; + +Connection.prototype.handleConnect = function() { + if (!this.inbound) { + this.sendVersion(); + } + this.emit('connect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; + +Connection.prototype.handleError = function(err) { + if (err.errno == 110 || err.errno == 'ETIMEDOUT') { + log.info('connection timed out for ' + this.peer); + } else if (err.errno == 111 || err.errno == 'ECONNREFUSED') { + log.info('connection refused for ' + this.peer); + } else { + log.warn('connection with ' + this.peer + ' ' + err.toString()); + } + this.emit('error', { + conn: this, + socket: this.socket, + peer: this.peer, + err: err + }); +}; + +Connection.prototype.handleDisconnect = function() { + this.emit('disconnect', { + conn: this, + socket: this.socket, + peer: this.peer + }); +}; + +Connection.prototype.handleMessage = function(message) { + if (!message) { + // Parser was unable to make sense of the message, drop it + return; + } + + try { + switch (message.command) { + case 'version': + // Did we connect to ourself? + if (buffertools.compare(nonce, message.nonce) === 0) { + this.socket.end(); + return; + } + + if (this.inbound) { + this.sendVersion(); + } + + if (message.version >= 209) { + this.sendMessage('verack', new Buffer([])); + } + this.sendVer = Math.min(message.version, PROTOCOL_VERSION); + if (message.version < 209) { + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + } else { + // We won't start expecting a checksum until after we've received + // the 'verack' message. + this.once('verack', (function() { + this.recvVer = message.version; + }).bind(this)); + } + this.bestHeight = message.start_height; + break; + + case 'verack': + this.recvVer = Math.min(message.version, PROTOCOL_VERSION); + this.active = true; + break; + + case 'ping': + if ('object' === typeof message.nonce) { + this.sendPong(message.nonce); + } + break; + } + } catch (e) { + log.err('Error while handling "' + message.command + '" message from ' + + this.peer + ':\n' + + (e.stack ? e.stack : e.toString())); + return; + } + this.emit(message.command, { + conn: this, + socket: this.socket, + peer: this.peer, + message: message + }); +}; + +Connection.prototype.sendPong = function(nonce) { + this.sendMessage('pong', nonce); +}; + +Connection.prototype.sendVersion = function() { + var subversion = '/BitcoinX:0.1/'; + + var put = new Put(); + put.word32le(PROTOCOL_VERSION); // version + put.word64le(1); // services + put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp + put.pad(26); // addr_me + put.pad(26); // addr_you + put.put(nonce); + put.varint(subversion.length); + put.put(new Buffer(subversion, 'ascii')); + put.word32le(0); + + this.sendMessage('version', put.buffer()); +}; + +Connection.prototype.sendGetBlocks = function(starts, stop, wantHeaders) { + // Default value for stop is 0 to get as many blocks as possible (500) + stop = stop || util.NULL_HASH; + + var put = new Put(); + + // https://en.bitcoin.it/wiki/Protocol_specification#getblocks + put.word32le(this.sendVer); + put.varint(starts.length); + + for (var i = 0; i < starts.length; i++) { + if (starts[i].length != 32) { + throw new Error('Invalid hash length'); + } + + put.put(starts[i]); + } + + var stopBuffer = new Buffer(stop, 'binary'); + if (stopBuffer.length != 32) { + throw new Error('Invalid hash length'); + } + + put.put(stopBuffer); + + var command = 'getblocks'; + if (wantHeaders) + command = 'getheaders'; + this.sendMessage(command, put.buffer()); +}; + +Connection.prototype.sendGetHeaders = function(starts, stop) { + this.sendGetBlocks(starts, stop, true); +}; + +Connection.prototype.sendGetData = function(invs) { + var put = new Put(); + put.varint(invs.length); + for (var i = 0; i < invs.length; i++) { + put.word32le(invs[i].type); + put.put(invs[i].hash); + } + this.sendMessage('getdata', put.buffer()); +}; + +Connection.prototype.sendGetAddr = function(invs) { + var put = new Put(); + this.sendMessage('getaddr', put.buffer()); +}; + +Connection.prototype.sendInv = function(data) { + if (!Array.isArray(data)) data = [data]; + var put = new Put(); + put.varint(data.length); + data.forEach(function(value) { + if (value instanceof Block) { + // Block + put.word32le(2); // MSG_BLOCK + } else { + // Transaction + put.word32le(1); // MSG_TX + } + put.put(value.getHash()); + }); + this.sendMessage('inv', put.buffer()); +}; + +Connection.prototype.sendHeaders = function(headers) { + var put = new Put(); + put.varint(headers.length); + headers.forEach(function(header) { + put.put(header); + + // Indicate 0 transactions + put.word8(0); + }); + this.sendMessage('headers', put.buffer()); +}; + +Connection.prototype.sendTx = function(tx) { + this.sendMessage('tx', tx.serialize()); +}; + +Connection.prototype.sendBlock = function(block, txs) { + var put = new Put(); + + // Block header + put.put(block.getHeader()); + + // List of transactions + put.varint(txs.length); + txs.forEach(function(tx) { + put.put(tx.serialize()); + }); + + this.sendMessage('block', put.buffer()); +}; + +Connection.prototype.sendMessage = function(command, payload) { + try { + var magic = this.network.magic; + var commandBuf = new Buffer(command, 'ascii'); + if (commandBuf.length > 12) throw 'Command name too long'; + + var checksum; + if (this.sendVer >= 209) { + checksum = doubleSha256(payload).slice(0, 4); + } else { + checksum = new Buffer([]); + } + + var message = new Put(); // -- HEADER -- + message.put(magic); // magic bytes + message.put(commandBuf); // command name + message.pad(12 - commandBuf.length); // zero-padded + message.word32le(payload.length); // payload length + message.put(checksum); // checksum + // -- BODY -- + message.put(payload); // payload data + + var buffer = message.buffer(); + + log.debug('[' + this.peer + '] ' + + 'Sending message ' + command + ' (' + payload.length + ' bytes)'); + + this.socket.write(buffer); + } catch (err) { + // TODO: We should catch this error one level higher in order to better + // determine how to react to it. For now though, ignoring it will do. + log.err('Error while sending message to peer ' + this.peer + ': ' + + (err.stack ? err.stack : err.toString())); + } +}; + +Connection.prototype.handleData = function(data) { + this.buffers.push(data); + + if (this.buffers.length > MAX_RECEIVE_BUFFER) { + log.err('Peer ' + this.peer + ' exceeded maxreceivebuffer, disconnecting.' + + (err.stack ? err.stack : err.toString())); + this.socket.destroy(); + return; + } + + this.processData(); +}; + +Connection.prototype.processData = function() { + // If there are less than 20 bytes there can't be a message yet. + if (this.buffers.length < 20) return; + + var magic = this.network.magic; + var i = 0; + for (;;) { + if (this.buffers.get(i) === magic[0] && + this.buffers.get(i + 1) === magic[1] && + this.buffers.get(i + 2) === magic[2] && + this.buffers.get(i + 3) === magic[3]) { + if (i !== 0) { + log.debug('[' + this.peer + '] ' + + 'Received ' + i + + ' bytes of inter-message garbage: '); + log.debug('... ' + this.buffers.slice(0, i)); + + this.buffers.skip(i); + } + break; + } + + if (i > (this.buffers.length - 4)) { + this.buffers.skip(i); + return; + } + i++; + } + + var payloadLen = (this.buffers.get(16)) + + (this.buffers.get(17) << 8) + + (this.buffers.get(18) << 16) + + (this.buffers.get(19) << 24); + + var startPos = (this.recvVer >= 209) ? 24 : 20; + var endPos = startPos + payloadLen; + + if (this.buffers.length < endPos) return; + + var command = this.buffers.slice(4, 16).toString('ascii').replace(/\0+$/, ''); + var payload = this.buffers.slice(startPos, endPos); + var checksum = (this.recvVer >= 209) ? this.buffers.slice(20, 24) : null; + + log.debug('[' + this.peer + '] ' + + 'Received message ' + command + + ' (' + payloadLen + ' bytes)'); + + if (checksum !== null) { + var checksumConfirm = doubleSha256(payload).slice(0, 4); + if (buffertools.compare(checksumConfirm, checksum) !== 0) { + log.err('[' + this.peer + '] ' + + 'Checksum failed', { + cmd: command, + expected: checksumConfirm.toString('hex'), + actual: checksum.toString('hex') + }); + return; + } + } + + var message; + try { + message = this.parseMessage(command, payload); + } catch (e) { + log.err('Error while parsing message ' + command + ' from ' + + this.peer + ':\n' + + (e.stack ? e.stack : e.toString())); + } + + if (message) { + this.handleMessage(message); + } + + this.buffers.skip(endPos); + this.processData(); +}; + +Connection.prototype.parseMessage = function(command, payload) { + var parser = new Parser(payload); + + var data = { + command: command + }; + + var i; + + switch (command) { + case 'version': // https://en.bitcoin.it/wiki/Protocol_specification#version + data.version = parser.word32le(); + data.services = parser.word64le(); + data.timestamp = parser.word64le(); + data.addr_me = parser.buffer(26); + data.addr_you = parser.buffer(26); + data.nonce = parser.buffer(8); + data.subversion = parser.varStr(); + data.start_height = parser.word32le(); + break; + + case 'inv': + case 'getdata': + data.count = parser.varInt(); + + data.invs = []; + for (i = 0; i < data.count; i++) { + data.invs.push({ + type: parser.word32le(), + hash: parser.buffer(32) + }); + } + break; + + case 'headers': + data.count = parser.varInt(); + + data.headers = []; + for (i = 0; i < data.count; i++) { + var header = new Block(); + header.parse(parser); + data.headers.push(header); + } + break; + + case 'block': + var block = new Block(); + block.parse(parser); + + data.block = block; + data.version = block.version; + data.prev_hash = block.prev_hash; + data.merkle_root = block.merkle_root; + data.timestamp = block.timestamp; + data.bits = block.bits; + data.nonce = block.nonce; + + data.txs = block.txs; + + data.size = payload.length; + break; + + case 'tx': + var tx = new Transaction(); + tx.parse(parser); + return { + command: command, + version: tx.version, + lock_time: tx.lock_time, + ins: tx.ins, + outs: tx.outs, + tx: tx, + }; + + case 'getblocks': + case 'getheaders': + // parse out the version + data.version = parser.word32le(); + + // TODO: Limit block locator size? + // reference implementation limits to 500 results + var startCount = parser.varInt(); + + data.starts = []; + for (i = 0; i < startCount; i++) { + data.starts.push(parser.buffer(32)); + } + data.stop = parser.buffer(32); + break; + + case 'addr': + var addrCount = parser.varInt(); + + // Enforce a maximum number of addresses per message + if (addrCount > 1000) { + addrCount = 1000; + } + + data.addrs = []; + for (i = 0; i < addrCount; i++) { + // TODO: Time actually depends on the version of the other peer (>=31402) + data.addrs.push({ + time: parser.word32le(), + services: parser.word64le(), + ip: parser.buffer(16), + port: parser.word16be() + }); + } + break; + + case 'alert': + data.payload = parser.varStr(); + data.signature = parser.varStr(); + break; + + case 'ping': + if (this.recvVer > BIP0031_VERSION) { + data.nonce = parser.buffer(8); + } + break; + + case 'getaddr': + case 'verack': + case 'reject': + // Empty message, nothing to parse + break; + + default: + log.err('Connection.parseMessage(): Command not implemented', { + cmd: command + }); + + // This tells the calling function not to issue an event + return null; + } + + return data; +}; + +module.exports = Connection; + +}).call(this,require("buffer").Buffer) +},{"../config":"4itQ50","../networks":"ULNIu2","../patches/Buffers.monkey":"kytKTK","../util":191,"../util/BinaryParser":"b3ZSD7","../util/log":"AdF7pF","./Block":"pJEQEB","./SecureRandom":"p4SiC2","./Transaction":"LJhYtm","buffer":95,"bufferput":"aXRuS6","buffers":"OBo3aV","buffertools":"fugeBw","events":"T9Wsc/","socks5-client":172,"util":128}],"ez/meX":[function(require,module,exports){ +exports.intFromCompact = function(c) { + var bytes = ((c >>> 24) & 0xff) >>> 0; + var v = ((c & 0xffffff) << (8 * (bytes - 3))) >>> 0; + return v; +} + +},{}],"./lib/Deserialize":[function(require,module,exports){ +module.exports=require('ez/meX'); +},{}],"./lib/Electrum":[function(require,module,exports){ +module.exports=require('hdzBvq'); +},{}],"hdzBvq":[function(require,module,exports){ +(function (Buffer){ +var Key = require('./Key'), + Point = require('./Point'), + twoSha256 = require('../util').twoSha256, + buffertools = require('buffertools'), + bignum = require('bignum'); + +/** + * Pre-BIP32 Electrum public key derivation (electrum <2.0) + * + * For now, this class can only understands master public keys. + * It doesn't support derivation from a private master key (TODO). + * + * @example examples/ElectrumMPK.js + */ +function Electrum(master_public_key) { + this.mpk = new Buffer(master_public_key, 'hex'); +} + +Electrum.prototype.getSequence = function(for_change, n) { + var mode = for_change ? 1 : 0; + var buf = Buffer.concat([new Buffer(n + ':' + mode + ':', 'utf8'), this.mpk]); + return bignum.fromBuffer(twoSha256(buf)); +}; + +Electrum.prototype.generatePubKey = function(n, for_change) { + var x = bignum.fromBuffer(this.mpk.slice(0, 32), { + size: 32 + }); + var y = bignum.fromBuffer(this.mpk.slice(32, 64), { + size: 32 + }); + var mpk_pt = new Point(x, y); + + var sequence = this.getSequence(for_change, n); + var sequence_key = new Key(); + sequence_key.private = sequence.toBuffer(); + sequence_key.regenerateSync(); + sequence_key.compressed = false; + + var sequence_pt = Point.fromUncompressedPubKey(sequence_key.public); + + pt = Point.add(mpk_pt, sequence_pt); + + var xbuf = pt.x.toBuffer({ + size: 32 + }); + var ybuf = pt.y.toBuffer({ + size: 32 + }); + var prefix = new Buffer([0x04]); + + var key = new Key(); + key.compressed = false; + key.public = Buffer.concat([prefix, xbuf, ybuf]); + + return key.public; +}; + +Electrum.prototype.generateChangePubKey = function(sequence) { + return this.generatePubKey(sequence, true); +}; + +module.exports = Electrum; + +}).call(this,require("buffer").Buffer) +},{"../util":191,"./Key":"ALJ4PS","./Point":"6tXgqr","bignum":61,"buffer":95,"buffertools":"fugeBw"}],"./lib/HierarchicalKey":[function(require,module,exports){ +module.exports=require('x1O6JW'); +},{}],"x1O6JW":[function(require,module,exports){ +(function (Buffer){ +var base58 = require('./Base58').base58; +var coinUtil = require('../util'); +var Key = require('./Key'); +var Point = require('./Point'); +var SecureRandom = require('./SecureRandom'); +var bignum = require('bignum'); +var networks = require('../networks'); + +var secp256k1_n = new bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16); +var secp256k1_Gx = new bignum('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16); + +/* +random new HierarchicalKey: new HierarchicalKey(); +from extended public or private key: new HierarchicalKey(str); +new blank HierarchicalKey: new HierarchicalKey(null); +*/ +var HierarchicalKey = function(bytes) { + if (typeof bytes == 'undefined' || bytes == 'mainnet' || bytes == 'livenet') { + bytes = 'livenet'; + this.version = networks['livenet'].hkeyPrivateVersion; + } else if (bytes == 'testnet') { + this.version = networks['testnet'].hkeyPrivateVersion; + } + if (bytes == 'livenet' || bytes == 'testnet') { + this.depth = 0x00; + this.parentFingerprint = new Buffer([0, 0, 0, 0]); + this.childIndex = new Buffer([0, 0, 0, 0]); + this.chainCode = SecureRandom.getRandomBuffer(32); + this.eckey = Key.generateSync(); + this.hasPrivateKey = true; + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.buildExtendedPublicKey(); + this.buildExtendedPrivateKey(); + return; + } + + // decode base58 + if (typeof bytes === 'string') { + var decoded = base58.decode(bytes); + if (decoded.length != 82) + throw new Error('Not enough data, expected 82 and received ' + decoded.length); + var checksum = decoded.slice(78, 82); + bytes = decoded.slice(0, 78); + + var hash = coinUtil.sha256(coinUtil.sha256(bytes)); + + if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) { + throw new Error('Invalid checksum'); + } + } + + if (bytes !== undefined && bytes !== null) + this.initFromBytes(bytes); +} + +HierarchicalKey.seed = function(bytes, network) { + if (!network) + network = 'livenet'; + + if (!Buffer.isBuffer(bytes)) + bytes = new Buffer(bytes, 'hex'); //if not buffer, assume hex + if (bytes.length < 128 / 8) + return false; //need more entropy + if (bytes.length > 512 / 8) + return false; + var hash = coinUtil.sha512hmac(bytes, new Buffer('Bitcoin seed')); + + var hkey = new HierarchicalKey(null); + hkey.depth = 0x00; + hkey.parentFingerprint = new Buffer([0, 0, 0, 0]); + hkey.childIndex = new Buffer([0, 0, 0, 0]); + hkey.chainCode = hash.slice(32, 64); + hkey.version = networks[network].hkeyPrivateVersion; + hkey.eckey = new Key(); + hkey.eckey.private = hash.slice(0, 32); + hkey.eckey.regenerateSync(); + hkey.hasPrivateKey = true; + hkey.pubKeyHash = coinUtil.sha256ripe160(hkey.eckey.public); + + hkey.buildExtendedPublicKey(); + hkey.buildExtendedPrivateKey(); + + return hkey; +}; + +HierarchicalKey.prototype.initFromBytes = function(bytes) { + // Both pub and private extended keys are 78 bytes + if (bytes.length != 78) throw new Error('not enough data'); + + this.version = u32(bytes.slice(0, 4)); + this.depth = u8(bytes.slice(4, 5)); + this.parentFingerprint = bytes.slice(5, 9); + this.childIndex = u32(bytes.slice(9, 13)); + this.chainCode = bytes.slice(13, 45); + + var keyBytes = bytes.slice(45, 78); + + var isPrivate = + (this.version == networks['livenet'].hkeyPrivateVersion || + this.version == networks['testnet'].hkeyPrivateVersion); + + var isPublic = + (this.version == networks['livenet'].hkeyPublicVersion || + this.version == networks['testnet'].hkeyPublicVersion); + + if (isPrivate && keyBytes[0] == 0) { + this.eckey = new Key(); + this.eckey.private = keyBytes.slice(1, 33); + this.eckey.compressed = true; + this.eckey.regenerateSync(); + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.hasPrivateKey = true; + } else if (isPublic && (keyBytes[0] == 0x02 || keyBytes[0] == 0x03)) { + this.eckey = new Key(); + this.eckey.public = keyBytes; + this.pubKeyHash = coinUtil.sha256ripe160(this.eckey.public); + this.hasPrivateKey = false; + } else { + throw new Error('Invalid key'); + } + + this.buildExtendedPublicKey(); + this.buildExtendedPrivateKey(); +} + +HierarchicalKey.prototype.buildExtendedPublicKey = function() { + this.extendedPublicKey = new Buffer([]); + + var v = null; + switch (this.version) { + case networks['livenet'].hkeyPublicVersion: + case networks['livenet'].hkeyPrivateVersion: + v = networks['livenet'].hkeyPublicVersion; + break; + case networks['testnet'].hkeyPublicVersion: + case networks['testnet'].hkeyPrivateVersion: + v = networks['testnet'].hkeyPublicVersion; + break; + default: + throw new Error('Unknown version'); + } + + // Version + this.extendedPublicKey = Buffer.concat([ + new Buffer([v >> 24]), + new Buffer([(v >> 16) & 0xff]), + new Buffer([(v >> 8) & 0xff]), + new Buffer([v & 0xff]), + new Buffer([this.depth]), + this.parentFingerprint, + new Buffer([this.childIndex >>> 24]), + new Buffer([(this.childIndex >>> 16) & 0xff]), + new Buffer([(this.childIndex >>> 8) & 0xff]), + new Buffer([this.childIndex & 0xff]), + this.chainCode, + this.eckey.public + ]); +} + +HierarchicalKey.prototype.extendedPublicKeyString = function(format) { + if (format === undefined || format === 'base58') { + var hash = coinUtil.sha256(coinUtil.sha256(this.extendedPublicKey)); + var checksum = hash.slice(0, 4); + var data = Buffer.concat([this.extendedPublicKey, checksum]); + return base58.encode(data); + } else if (format === 'hex') { + return this.extendedPublicKey.toString('hex');; + } else { + throw new Error('bad format'); + } +} + +HierarchicalKey.prototype.buildExtendedPrivateKey = function() { + if (!this.hasPrivateKey) return; + this.extendedPrivateKey = new Buffer([]); + + var v = this.version; + + this.extendedPrivateKey = Buffer.concat([ + new Buffer([v >> 24]), + new Buffer([(v >> 16) & 0xff]), + new Buffer([(v >> 8) & 0xff]), + new Buffer([v & 0xff]), + new Buffer([this.depth]), + this.parentFingerprint, + new Buffer([this.childIndex >>> 24]), + new Buffer([(this.childIndex >>> 16) & 0xff]), + new Buffer([(this.childIndex >>> 8) & 0xff]), + new Buffer([this.childIndex & 0xff]), + this.chainCode, + new Buffer([0]), + this.eckey.private + ]); +} + +HierarchicalKey.prototype.extendedPrivateKeyString = function(format) { + if (format === undefined || format === 'base58') { + var hash = coinUtil.sha256(coinUtil.sha256(this.extendedPrivateKey)); + var checksum = hash.slice(0, 4); + var data = Buffer.concat([this.extendedPrivateKey, checksum]); + return base58.encode(data); + } else if (format === 'hex') { + return this.extendedPrivateKey.toString('hex'); + } else { + throw new Error('bad format'); + } +} + + +HierarchicalKey.prototype.derive = function(path) { + var e = path.split('/'); + + // Special cases: + if (path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'') + return this; + + var hkey = this; + for (var i in e) { + var c = e[i]; + + if (i == 0) { + if (c != 'm') throw new Error('invalid path'); + continue; + } + + var usePrivate = (c.length > 1) && (c[c.length - 1] == '\''); + var childIndex = parseInt(usePrivate ? c.slice(0, c.length - 1) : c) & 0x7fffffff; + + if (usePrivate) + childIndex += 0x80000000; + + hkey = hkey.deriveChild(childIndex); + } + + return hkey; +} + +HierarchicalKey.prototype.deriveChild = function(i) { + var ib = []; + ib.push((i >> 24) & 0xff); + ib.push((i >> 16) & 0xff); + ib.push((i >> 8) & 0xff); + ib.push(i & 0xff); + ib = new Buffer(ib); + + var usePrivate = (i & 0x80000000) != 0; + + var isPrivate = + (this.version == networks['livenet'].hkeyPrivateVersion || + this.version == networks['testnet'].hkeyPrivateVersion); + + if (usePrivate && (!this.hasPrivateKey || !isPrivate)) + throw new Error('Cannot do private key derivation without private key'); + + var ret = null; + if (this.hasPrivateKey) { + var data = null; + + if (usePrivate) { + data = Buffer.concat([new Buffer([0]), this.eckey.private, ib]); + } else { + data = Buffer.concat([this.eckey.public, ib]); + } + + var hash = coinUtil.sha512hmac(data, this.chainCode); + var il = bignum.fromBuffer(hash.slice(0, 32), { + size: 32 + }); + var ir = hash.slice(32, 64); + + // ki = IL + kpar (mod n). + var priv = bignum.fromBuffer(this.eckey.private, { + size: 32 + }); + var k = il.add(priv).mod(secp256k1_n); + + ret = new HierarchicalKey(null); + ret.chainCode = ir; + + ret.eckey = new Key(); + ret.eckey.private = k.toBuffer({ + size: 32 + }); + ret.eckey.regenerateSync(); + ret.hasPrivateKey = true; + + } else { + var data = Buffer.concat([this.eckey.public, ib]); + var hash = coinUtil.sha512hmac(data, this.chainCode); + var il = hash.slice(0, 32); + var ir = hash.slice(32, 64); + + // Ki = (IL + kpar)*G = IL*G + Kpar + var ilGkey = new Key(); + ilGkey.private = il; + ilGkey.regenerateSync(); + ilGkey.compressed = false; + var ilG = Point.fromUncompressedPubKey(ilGkey.public); + var oldkey = new Key(); + oldkey.public = this.eckey.public; + oldkey.compressed = false; + var Kpar = Point.fromUncompressedPubKey(oldkey.public); + var newpub = Point.add(ilG, Kpar).toUncompressedPubKey(); + + ret = new HierarchicalKey(null); + ret.chainCode = new Buffer(ir); + + var eckey = new Key(); + eckey.public = newpub; + eckey.compressed = true; + ret.eckey = eckey; + ret.hasPrivateKey = false; + } + + ret.childIndex = i; + ret.parentFingerprint = this.pubKeyHash.slice(0, 4); + ret.version = this.version; + ret.depth = this.depth + 1; + + ret.eckey.compressed = true; + ret.pubKeyHash = coinUtil.sha256ripe160(ret.eckey.public); + + ret.buildExtendedPublicKey(); + ret.buildExtendedPrivateKey(); + + return ret; +} + + +function uint(f, size) { + if (f.length < size) + throw new Error('not enough data'); + var n = 0; + for (var i = 0; i < size; i++) { + n *= 256; + n += f[i]; + } + return n; +} + +function u8(f) { + return uint(f, 1); +} + +function u16(f) { + return uint(f, 2); +} + +function u32(f) { + return uint(f, 4); +} + +function u64(f) { + return uint(f, 8); +} + +module.exports = HierarchicalKey; + +}).call(this,require("buffer").Buffer) +},{"../networks":"ULNIu2","../util":191,"./Base58":"6VqyzY","./Key":"ALJ4PS","./Point":"6tXgqr","./SecureRandom":"p4SiC2","bignum":61,"buffer":95}],"CBDCgz":[function(require,module,exports){ +(function (Buffer){ +'use strict'; +var coinUtil = require('../util'); +var Key = require('./Key'); + +var Message = function() {}; + +Message.sign = function(str, key) { + var hash = Message.magicHash(str); + var sig = key.signSync(hash); + return sig; +}; + +Message.verifyWithPubKey = function(pubkey, message, sig) { + var hash = Message.magicHash(message); + var key = new Key(); + if (pubkey.length == 65) + key.compressed = false; + key.public = pubkey; + + return key.verifySignatureSync(hash, sig); +}; + +//TODO: Message.verify ... with address, not pubkey + +Message.magicBytes = new Buffer('Bitcoin Signed Message:\n'); + +Message.magicHash = function(str) { + var magicBytes = Message.magicBytes; + var prefix1 = coinUtil.varIntBuf(magicBytes.length); + var message = new Buffer(str); + var prefix2 = coinUtil.varIntBuf(message.length); + + var buf = Buffer.concat([prefix1, magicBytes, prefix2, message]); + + var hash = coinUtil.twoSha256(buf); + + return hash; +}; + +module.exports = Message; + +}).call(this,require("buffer").Buffer) +},{"../util":191,"./Key":"ALJ4PS","buffer":95}],"./lib/Message":[function(require,module,exports){ +module.exports=require('CBDCgz'); +},{}],"./lib/NetworkMonitor":[function(require,module,exports){ +module.exports=require('qYkfjX'); +},{}],"qYkfjX":[function(require,module,exports){ +var log = require('../util/log'); +var networks = require('../networks'); +var Address = require('./Address'); +var Peer = require('./Peer'); +var PeerManager = require('./PeerManager'); +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var preconditions = require('preconditions').singleton(); + +var NetworkMonitor = function(peerman) { + preconditions.checkArgument(peerman); + this.peerman = peerman; + this.networkName = peerman.config.network; + this.init(); +} + +util.inherits(NetworkMonitor, EventEmitter); + +NetworkMonitor.create = function(config) { + this.config = config; + var peerman = new PeerManager({ + network: config.networkName + }); + + peerman.addPeer(new Peer(config.host, config.port)); + return new NetworkMonitor(peerman); +}; + +NetworkMonitor.prototype.init = function() { + var self = this; + var handleInv = function(info) { + var invs = info.message.invs; + info.conn.sendGetData(invs); + }; + + var handleBlock = function(info) { + self.emit('block', info.message); + }; + + var handleTx = function(info) { + var tx = info.message.tx; + self.emit('tx', tx); + + var from = tx.getSendingAddresses(self.networkName); + for (var i = 0; i < from.length; i++) { + var addr = from[i]; + self.emit('out:'+addr, tx); + } + var to = tx.getReceivingAddresses(self.networkName); + for (var i = 0; i < to.length; i++) { + var addr = to[i]; + self.emit('in:'+addr, tx); + } + }; + + this.peerman.on('connection', function(conn) { + if (self.connection) throw new Error('Cant handle more than one connection'); + self.connection = conn; + conn.on('inv', handleInv); + conn.on('block', handleBlock); + conn.on('tx', handleTx); + }); +}; + +NetworkMonitor.prototype.incoming = function(addrStr, callback) { + preconditions.checkArgument(Address.validate(addrStr)); + this.on('in:'+addrStr, callback); +}; + +NetworkMonitor.prototype.outgoing = function(addrStr, callback) { + preconditions.checkArgument(Address.validate(addrStr)); + this.on('out:'+addrStr, callback); +}; + +NetworkMonitor.prototype.start = function() { + this.peerman.start(); +}; + +NetworkMonitor.prototype.stop = function() { + this.peerman.stop(); +}; + +module.exports = NetworkMonitor; + +},{"../networks":"ULNIu2","../util/log":"AdF7pF","./Address":"G+CcXD","./Peer":"oolY81","./PeerManager":"nsqKeP","events":"T9Wsc/","preconditions":163,"util":128}],"Zm7/h9":[function(require,module,exports){ +function Opcode(num) { + this.code = num; +}; + +Opcode.prototype.toString = function() { + return Opcode.reverseMap[this.code]; +}; + +Opcode.map = { + // push value + OP_FALSE: 0, + OP_0: 0, + OP_PUSHDATA1: 76, + OP_PUSHDATA2: 77, + OP_PUSHDATA4: 78, + OP_1NEGATE: 79, + OP_RESERVED: 80, + OP_TRUE: 81, + OP_1: 81, + OP_2: 82, + OP_3: 83, + OP_4: 84, + OP_5: 85, + OP_6: 86, + OP_7: 87, + OP_8: 88, + OP_9: 89, + OP_10: 90, + OP_11: 91, + OP_12: 92, + OP_13: 93, + OP_14: 94, + OP_15: 95, + OP_16: 96, + + // control + OP_NOP: 97, + OP_VER: 98, + OP_IF: 99, + OP_NOTIF: 100, + OP_VERIF: 101, + OP_VERNOTIF: 102, + OP_ELSE: 103, + OP_ENDIF: 104, + OP_VERIFY: 105, + OP_RETURN: 106, + + // stack ops + OP_TOALTSTACK: 107, + OP_FROMALTSTACK: 108, + OP_2DROP: 109, + OP_2DUP: 110, + OP_3DUP: 111, + OP_2OVER: 112, + OP_2ROT: 113, + OP_2SWAP: 114, + OP_IFDUP: 115, + OP_DEPTH: 116, + OP_DROP: 117, + OP_DUP: 118, + OP_NIP: 119, + OP_OVER: 120, + OP_PICK: 121, + OP_ROLL: 122, + OP_ROT: 123, + OP_SWAP: 124, + OP_TUCK: 125, + + // splice ops + OP_CAT: 126, + OP_SUBSTR: 127, + OP_LEFT: 128, + OP_RIGHT: 129, + OP_SIZE: 130, + + // bit logic + OP_INVERT: 131, + OP_AND: 132, + OP_OR: 133, + OP_XOR: 134, + OP_EQUAL: 135, + OP_EQUALVERIFY: 136, + OP_RESERVED1: 137, + OP_RESERVED2: 138, + + // numeric + OP_1ADD: 139, + OP_1SUB: 140, + OP_2MUL: 141, + OP_2DIV: 142, + OP_NEGATE: 143, + OP_ABS: 144, + OP_NOT: 145, + OP_0NOTEQUAL: 146, + + OP_ADD: 147, + OP_SUB: 148, + OP_MUL: 149, + OP_DIV: 150, + OP_MOD: 151, + OP_LSHIFT: 152, + OP_RSHIFT: 153, + + OP_BOOLAND: 154, + OP_BOOLOR: 155, + OP_NUMEQUAL: 156, + OP_NUMEQUALVERIFY: 157, + OP_NUMNOTEQUAL: 158, + OP_LESSTHAN: 159, + OP_GREATERTHAN: 160, + OP_LESSTHANOREQUAL: 161, + OP_GREATERTHANOREQUAL: 162, + OP_MIN: 163, + OP_MAX: 164, + + OP_WITHIN: 165, + + // crypto + OP_RIPEMD160: 166, + OP_SHA1: 167, + OP_SHA256: 168, + OP_HASH160: 169, + OP_HASH256: 170, + OP_CODESEPARATOR: 171, + OP_CHECKSIG: 172, + OP_CHECKSIGVERIFY: 173, + OP_CHECKMULTISIG: 174, + OP_CHECKMULTISIGVERIFY: 175, + + // expansion + OP_NOP1: 176, + OP_NOP2: 177, + OP_NOP3: 178, + OP_NOP4: 179, + OP_NOP5: 180, + OP_NOP6: 181, + OP_NOP7: 182, + OP_NOP8: 183, + OP_NOP9: 184, + OP_NOP10: 185, + + // template matching params + OP_PUBKEYHASH: 253, + OP_PUBKEY: 254, + OP_INVALIDOPCODE: 255 +}; + +Opcode.reverseMap = []; + +for (var k in Opcode.map) { + if (Opcode.map.hasOwnProperty(k)) { + Opcode.reverseMap[Opcode.map[k]] = k.substr(3); + } +} + +Opcode.asList = function() { + var keys = []; + for (var prop in Opcode.map) { + if (Opcode.map.hasOwnProperty(prop)) { + keys.push(prop); + } + } + return keys; +}; + +module.exports = Opcode; + +},{}],"./lib/Opcode":[function(require,module,exports){ +module.exports=require('Zm7/h9'); +},{}],"./lib/Peer":[function(require,module,exports){ +module.exports=require('oolY81'); +},{}],"oolY81":[function(require,module,exports){ +(function (Buffer){ +var Net = require('net'); +var Binary = require('binary'); +var buffertools = require('buffertools'); + +function Peer(host, port, services) { + if ("string" === typeof host) { + if (host.indexOf(':') && !port) { + var parts = host.split(':'); + host = parts[0]; + port = parts[1]; + } + this.host = host; + this.port = +port || 8333; + } else if (host instanceof Peer) { + this.host = host.host; + this.port = host.port; + } else if (Buffer.isBuffer(host)) { + if (buffertools.compare(Peer.IPV6_IPV4_PADDING, host.slice(0, 12)) != 0) { + throw new Error('IPV6 not supported yet! Cannot instantiate host.'); + } + this.host = Array.prototype.slice.apply(host.slice(12)).join('.'); + this.port = +port || 8333; + } else { + throw new Error('Could not instantiate peer, invalid parameter type: ' + + typeof host); + } + + this.services = (services) ? services : null; + this.lastSeen = 0; +}; + +Peer.IPV6_IPV4_PADDING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]); + +Peer.prototype.createConnection = function() { + this.connection = Net.createConnection(this.port, this.host); + return this.connection; +}; + +Peer.prototype.getHostAsBuffer = function() { + return new Buffer(this.host.split('.')); +}; + +Peer.prototype.toString = function() { + return this.host + ":" + this.port; +}; + +Peer.prototype.toBuffer = function() { + var put = Binary.put(); + put.word32le(this.lastSeen); + put.word64le(this.services); + put.put(this.getHostAsBuffer()); + put.word16be(this.port); + return put.buffer(); +}; + +module.exports = Peer; + +}).call(this,require("buffer").Buffer) +},{"binary":83,"buffer":95,"buffertools":"fugeBw","net":91}],"./lib/PeerManager":[function(require,module,exports){ +module.exports=require('nsqKeP'); +},{}],"nsqKeP":[function(require,module,exports){ +var log = require('../util/log'); +var bitcoreDefaults = require('../config'); +var Connection = require('./Connection'); +var Peer = require('./Peer'); +var async = require('async'); +var dns = require('dns'); +var networks = require('../networks'); +var util = require('util'); + +GetAdjustedTime = function() { + // TODO: Implement actual adjustment + return Math.floor(new Date().getTime() / 1000); +}; + +function PeerManager(config) { + // extend defaults with config + this.config = config || {}; + for (var i in bitcoreDefaults) + if (bitcoreDefaults.hasOwnProperty(i) && this.config[i] === undefined) + this.config[i] = bitcoreDefaults[i]; + + this.active = false; + this.timer = null; + + this.peers = []; + this.pool = []; + this.connections = []; + this.isConnected = false; + this.peerDiscovery = false; + + // Move these to the Node's settings object + this.interval = 5000; + this.minConnections = 8; + this.minKnownPeers = 10; + + // keep track of tried seeds and results + this.seeds = { + resolved: [], + failed: [] + }; +} + +var EventEmitter = require('events').EventEmitter; +util.inherits(PeerManager, EventEmitter); +PeerManager.Connection = Connection; + +PeerManager.prototype.start = function() { + this.active = true; + if (!this.timer) { + this.timer = setInterval(this.checkStatus.bind(this), this.interval); + } +}; + +PeerManager.prototype.stop = function() { + this.active = false; + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + for (var i = 0; i < this.connections.length; i++) { + this.connections[i].socket.end(); + }; +}; + +PeerManager.prototype.addPeer = function(peer, port) { + if (peer instanceof Peer) { + this.peers.push(peer); + } else if ("string" == typeof peer) { + this.addPeer(new Peer(peer, port)); + } else { + log.err('Node.addPeer(): Invalid value provided for peer', { + val: peer + }); + throw 'Node.addPeer(): Invalid value provided for peer.'; + } +}; + +PeerManager.prototype.removePeer = function(peer) { + var index = this.peers.indexOf(peer); + var exists = !!~index; + if (exists) this.peers.splice(index, 1); + return exists; +}; + +PeerManager.prototype.checkStatus = function checkStatus() { + // Make sure we are connected to all forcePeers + if (this.peers.length) { + var peerIndex = {}; + this.peers.forEach(function(peer) { + peerIndex[peer.toString()] = peer; + }); + + // Ignore the ones we're already connected to + this.connections.forEach(function(conn) { + var peerName = conn.peer.toString(); + if ("undefined" !== peerIndex[peerName]) { + delete peerIndex[peerName]; + } + }); + + // for debug purposes, print how many of our peers are actually connected + var connected = 0 + this.peers.forEach(function(p) { + if (p.connection && !p.connection._connecting) connected++ + }); + log.debug(connected + ' of ' + this.peers.length + ' peers connected'); + + Object.keys(peerIndex).forEach(function(i) { + this.connectTo(peerIndex[i]); + }.bind(this)); + } +}; + +PeerManager.prototype.connectTo = function(peer) { + log.info('connecting to ' + peer); + try { + return this.addConnection(peer.createConnection(), peer); + } catch (e) { + log.err('creating connection', e); + return null; + } +}; + +PeerManager.prototype.addConnection = function(socketConn, peer) { + var conn = new Connection(socketConn, peer, this.config); + this.connections.push(conn); + this.emit('connection', conn); + + conn.addListener('version', this.handleVersion.bind(this)); + conn.addListener('verack', this.handleReady.bind(this)); + conn.addListener('addr', this.handleAddr.bind(this)); + conn.addListener('getaddr', this.handleGetAddr.bind(this)); + conn.addListener('error', this.handleError.bind(this)); + conn.addListener('disconnect', this.handleDisconnect.bind(this)); + + return conn; +}; + +PeerManager.prototype.handleVersion = function(e) { + e.peer.version = e.message.version; + e.peer.start_height = e.message.start_height; + + if (!e.conn.inbound) { + // TODO: Advertise our address (if listening) + } + // Get recent addresses + if (this.peerDiscovery && + (e.message.version >= 31402 || this.peers.length < 1000)) { + e.conn.sendGetAddr(); + e.conn.getaddr = true; + } +}; + +PeerManager.prototype.handleReady = function(e) { + log.info('connected to ' + e.conn.peer.host + ':' + e.conn.peer.port); + this.emit('connect', { + pm: this, + conn: e.conn, + socket: e.socket, + peer: e.peer + }); + + if (this.isConnected == false) { + this.emit('netConnected', e); + this.isConnected = true; + } +}; + +PeerManager.prototype.handleAddr = function(e) { + if (!this.peerDiscovery) return; + + var now = GetAdjustedTime(); + e.message.addrs.forEach(function(addr) { + try { + // In case of an invalid time, assume "5 days ago" + if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) { + addr.time = now - 5 * 24 * 60 * 60; + } + var peer = new Peer(addr.ip, addr.port, addr.services); + peer.lastSeen = addr.time; + + // TODO: Handle duplicate peers + this.peers.push(peer); + + // TODO: Handle addr relay + } catch (e) { + log.warn("Invalid addr received: " + e.message); + } + }.bind(this)); + if (e.message.addrs.length < 1000) { + e.conn.getaddr = false; + } +}; + +PeerManager.prototype.handleGetAddr = function(e) { + // TODO: Reply with addr message. +}; + +PeerManager.prototype.handleError = function(e) { + log.err('unkown error with peer ' + e.peer + ' (disconnecting): ' + e.err); + this.handleDisconnect.apply(this, [].slice.call(arguments)); +}; + +PeerManager.prototype.handleDisconnect = function(e) { + log.info('disconnected from peer ' + e.peer); + var i = this.connections.indexOf(e.conn); + if (i != -1) this.connections.splice(i, 1); + + this.removePeer(e.peer); + if (this.pool.length) { + log.info('replacing peer using the pool of ' + this.pool.length + ' seeds'); + this.addPeer(this.pool.pop()); + } + + if (!this.connections.length) { + this.emit('netDisconnected'); + this.isConnected = false; + } +}; + +PeerManager.prototype.getActiveConnection = function() { + var activeConnections = this.connections.filter(function(conn) { + return conn.active; + }); + + if (activeConnections.length) { + var randomIndex = Math.floor(Math.random() * activeConnections.length); + var candidate = activeConnections[randomIndex]; + if (candidate.socket.writable) { + return candidate; + } else { + // Socket is not writable, remove it from active connections + activeConnections.splice(randomIndex, 1); + + // Then try again + // TODO: This causes an infinite recursion when all connections are dead, + // although it shouldn't. + return this.getActiveConnection(); + } + } else { + return null; + } +}; + +PeerManager.prototype.getActiveConnections = function() { + return this.connections.slice(0); +}; + +PeerManager.prototype.discover = function(options, callback) { + var self = this; + var seeds = networks[self.config.network].dnsSeeds; + + self.limit = options.limit || 12; + + var dnsExecutor = seeds.map(function(seed) { + return function(done) { + // have we already resolved this seed? + if (~self.seeds.resolved.indexOf(seed)) { + // if so, just pass back cached peer list + return done(null, self.seeds.results[seed]); + } + + // has this seed failed to resolve? + if (~self.seeds.failed.indexOf(seed)) { + // if so, pass back empty results + return done(null, []); + } + + log.info('resolving dns seed ' + seed); + + dns.resolve(seed, function(err, peers) { + if (err) { + log.err('failed to resolve dns seed ' + seed, err); + self.seeds.failed.push(seed); + return done(null, []); + } + + log.info('found ' + peers.length + ' peers from ' + seed); + self.seeds.resolved.push(seed); + + // transform that list into a list of Peer instances + peers = peers.map(function(ip) { + return new Peer(ip, networks[self.config.network].defaultClientPort); + }); + + peers.forEach(function(p) { + if (self.peers.length < self.limit) self.addPeer(p); + else self.pool.push(p); + }); + + self.emit('peers', peers); + + return done(null, peers); + }); + + }; + }); + + // try resolving all seeds + async.parallel(dnsExecutor, function(err, results) { + var peers = []; + + // consolidate all resolved peers into one list + results.forEach(function(peerlist) { + peers = peers.concat(peerlist); + }); + + if (typeof callback === 'function') callback(null, peers); + }); + + return self; +}; + +module.exports = PeerManager; + +},{"../config":"4itQ50","../networks":"ULNIu2","../util/log":"AdF7pF","./Connection":"DB/p3X","./Peer":"oolY81","async":82,"dns":91,"events":"T9Wsc/","util":128}],"izTl9z":[function(require,module,exports){ +(function (Buffer){ + +var VersionedData = require('../util/VersionedData'); +var EncodedData = require('../util/EncodedData'); +var networks = require('../networks'); +var util = require('util'); + +//compressed is true if public key is compressed; false otherwise +function PrivateKey(version, buf, compressed) { + PrivateKey.super_.call(this, version, buf); + if (compressed !== undefined) + this.compressed(compressed); +}; +util.inherits(PrivateKey, VersionedData); +EncodedData.applyEncodingsTo(PrivateKey); + +PrivateKey.prototype.validate = function() { + this.doAsBinary(function() { + PrivateKey.super_.prototype.validate.call(this); + if (this.data.length < 32 || (this.data.length > 1 + 32 && !this.compressed()) || (this.data.length == 1 + 32 + 1 && this.data[1 + 32 + 1 - 1] != 1) || this.data.length > 1 + 32 + 1) + throw new Error('invalid data length'); + }); + if (typeof this.network() === 'undefined') throw new Error('invalid network'); +}; + +// get or set the payload data (as a Buffer object) +// overloaded from VersionedData +PrivateKey.prototype.payload = function(data) { + if (data) { + this.doAsBinary(function() { + data.copy(this.data, 1); + }); + return data; + } + var buf = this.as('binary'); + if (buf.length == 1 + 32 + 1) + return buf.slice(1, 1 + 32); + else if (buf.length == 1 + 32) + return buf.slice(1); +}; + +// get or set whether the corresponding public key is compressed +PrivateKey.prototype.compressed = function(compressed) { + if (compressed !== undefined) { + this.doAsBinary(function() { + var len = 1 + 32 + 1; + if (compressed) { + var data = new Buffer(len); + this.data.copy(data); + this.data = data; + this.data[len - 1] = 1; + } else { + this.data = this.data.slice(0, len - 1); + } + }); + } else { + var len = 1 + 32 + 1; + var data = this.as('binary'); + if (data.length == len && data[len - 1] == 1) + return true; + else if (data.length == len - 1) + return false; + else + throw new Error('invalid private key'); + } +}; + +PrivateKey.prototype.network = function() { + var version = this.version(); + + var livenet = networks.livenet; + var testnet = networks.testnet; + + var answer; + if (version === livenet.privKeyVersion) + answer = livenet; + else if (version === testnet.privKeyVersion) + answer = testnet; + + return answer; +}; + +module.exports = PrivateKey; + +}).call(this,require("buffer").Buffer) +},{"../networks":"ULNIu2","../util/EncodedData":"eLfUFE","../util/VersionedData":"QLzNQg","buffer":95,"util":128}],"./lib/PrivateKey":[function(require,module,exports){ +module.exports=require('izTl9z'); +},{}],"./lib/RpcClient":[function(require,module,exports){ +module.exports=require('7siE1N'); +},{}],"7siE1N":[function(require,module,exports){ +(function (Buffer){ +// RpcClient.js +// MIT/X11-like license. See LICENSE.txt. +// Copyright 2013 BitPay, Inc. +// +var http = require('http'); +var https = require('https'); +var log = require('../util/log'); + +function RpcClient(opts) { + opts = opts || {}; + this.host = opts.host || '127.0.0.1'; + this.port = opts.port || 8332; + this.user = opts.user || 'user'; + this.pass = opts.pass || 'pass'; + this.protocol = (opts.protocol == 'http') ? http : https; + this.batchedCalls = null; + this.disableAgent = opts.disableAgent || false; +} + +RpcClient.prototype.batch = function(batchCallback, resultCallback) { + this.batchedCalls = []; + batchCallback(); + rpc.call(this, this.batchedCalls, resultCallback); + this.batchedCalls = null; +} + +var callspec = { + addMultiSigAddress: '', + addNode: '', + backupWallet: '', + createMultiSig: '', + createRawTransaction: '', + decodeRawTransaction: '', + dumpPrivKey: '', + encryptWallet: '', + getAccount: '', + getAccountAddress: 'str', + getAddedNodeInfo: '', + getAddressesByAccount: '', + getBalance: 'str int', + getBestBlockHash: '', + getBlock: '', + getBlockCount: '', + getBlockHash: 'int', + getBlockNumber: '', + getBlockTemplate: '', + getConnectionCount: '', + getDifficulty: '', + getGenerate: '', + getHashesPerSec: '', + getInfo: '', + getMemoryPool: '', + getMiningInfo: '', + getNewAddress: '', + getPeerInfo: '', + getRawMemPool: '', + getRawTransaction: 'str int', + getReceivedByAccount: 'str int', + getReceivedByAddress: 'str int', + getTransaction: '', + getTxOut: 'str int bool', + getTxOutSetInfo: '', + getWork: '', + help: '', + importAddress: 'str str bool', + importPrivKey: 'str str bool', + keyPoolRefill: '', + listAccounts: 'int', + listAddressGroupings: '', + listReceivedByAccount: 'int bool', + listReceivedByAddress: 'int bool', + listSinceBlock: 'str int', + listTransactions: 'str int int', + listUnspent: 'int int', + listLockUnspent: 'bool', + lockUnspent: '', + move: 'str str float int str', + sendFrom: 'str str float int str str', + sendMany: 'str str int str', //not sure this is will work + sendRawTransaction: '', + sendToAddress: 'str float str str', + setAccount: '', + setGenerate: 'bool int', + setTxFee: 'float', + signMessage: '', + signRawTransaction: '', + stop: '', + submitBlock: '', + validateAddress: '', + verifyMessage: '', + walletLock: '', + walletPassPhrase: 'string int', + walletPassphraseChange: '', +}; + +var slice = function(arr, start, end) { + return Array.prototype.slice.call(arr, start, end); +}; + +function generateRPCMethods(constructor, apiCalls, rpc) { + function createRPCMethod(methodName, argMap) { + return function() { + var limit = arguments.length - 1; + if (this.batchedCalls) var limit = arguments.length; + for (var i = 0; i < limit; i++) { + if (argMap[i]) arguments[i] = argMap[i](arguments[i]); + }; + if (this.batchedCalls) { + this.batchedCalls.push({ + jsonrpc: '2.0', + method: methodName, + params: slice(arguments) + }); + } else { + rpc.call(this, { + method: methodName, + params: slice(arguments, 0, arguments.length - 1) + }, arguments[arguments.length - 1]); + } + }; + }; + + var types = { + str: function(arg) { + return arg.toString(); + }, + int: function(arg) { + return parseFloat(arg); + }, + float: function(arg) { + return parseFloat(arg); + }, + bool: function(arg) { + return (arg === true || arg == '1' || arg == 'true' || arg.toString().toLowerCase() == 'true'); + }, + }; + + for (var k in apiCalls) { + if (apiCalls.hasOwnProperty(k)) { + var spec = apiCalls[k].split(' '); + for (var i = 0; i < spec.length; i++) { + if (types[spec[i]]) { + spec[i] = types[spec[i]]; + } else { + spec[i] = types.string; + } + } + var methodName = k.toLowerCase(); + constructor.prototype[k] = createRPCMethod(methodName, spec); + constructor.prototype[methodName] = constructor.prototype[k]; + } + } +} + +function rpc(request, callback) { + var self = this; + var request; + request = JSON.stringify(request); + var auth = Buffer(self.user + ':' + self.pass).toString('base64'); + + var options = { + host: self.host, + path: '/', + method: 'POST', + port: self.port, + agent: self.disableAgent ? false : undefined, + }; + if (self.httpOptions) { + for (var k in self.httpOptions) { + options[k] = self.httpOptions[k]; + } + } + var err = null; + var req = this.protocol.request(options, function(res) { + + var buf = ''; + res.on('data', function(data) { + buf += data; + }); + res.on('end', function() { + if (res.statusCode == 401) { + callback(new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized')); + return; + } + if (res.statusCode == 403) { + callback(new Error('bitcoin JSON-RPC connection rejected: 403 forbidden')); + return; + } + + if (err) { + callback(err); + return; + } + try { + var parsedBuf = JSON.parse(buf); + } catch (e) { + log.err(e.stack); + log.err(buf); + log.err('HTTP Status code:' + res.statusCode); + callback(e); + return; + } + callback(parsedBuf.error, parsedBuf); + }); + }); + req.on('error', function(e) { + var err = new Error('Could not connect to bitcoin via RPC: ' + e.message); + log.err(err); + callback(err); + }); + + req.setHeader('Content-Length', request.length); + req.setHeader('Content-Type', 'application/json'); + req.setHeader('Authorization', 'Basic ' + auth); + req.write(request); + req.end(); +}; + +generateRPCMethods(RpcClient, callspec, rpc); + +module.exports = RpcClient; + +}).call(this,require("buffer").Buffer) +},{"../util/log":"AdF7pF","buffer":95,"http":106,"https":110}],"./lib/SIN":[function(require,module,exports){ +module.exports=require('tBM27q'); +},{}],"tBM27q":[function(require,module,exports){ +(function (Buffer){ +'use strict'; +var VersionedData = require('../util/VersionedData'); +var EncodedData = require('../util/EncodedData'); +var util = require('util'); +var coinUtil = require('../util'); + +function SIN(type, payload) { + if (typeof type != 'number') { + SIN.super_.call(this, type, payload); + return; + } + if (!Buffer.isBuffer(payload) || payload.length != 20) + throw new Error('Payload must be 20 bytes'); + + this.data = new Buffer(1 + 1 + payload.length); + this.converters = this.encodings['binary'].converters; + this._encoding = this.encodings['binary']._encoding; + this.encoding('binary'); + this.prefix(0x0F); // SIN magic number, in numberspace + this.type(type); + this.payload(payload); +}; + +util.inherits(SIN, VersionedData); +EncodedData.applyEncodingsTo(SIN); + +SIN.SIN_PERSIST_MAINNET = 0x01; // associated with sacrifice TX +SIN.SIN_PERSIST_TESTNET = 0x11; // associated with sacrifice TX +SIN.SIN_EPHEM = 0x02; // generate off-net at any time + +// get or set the prefix data (the first byte of the address) +SIN.prototype.prefix = function(num) { + if (num || (num === 0)) { + this.doAsBinary(function() { + this.data.writeUInt8(num, 0); + }); + return num; + } + return this.as('binary').readUInt8(0); +}; + +// get or set the SIN-type data (the second byte of the address) +SIN.prototype.type = function(num) { + if (num || (num === 0)) { + this.doAsBinary(function() { + this.data.writeUInt8(num, 1); + }); + return num; + } + return this.as('binary').readUInt8(1); +}; + +// get or set the payload data (as a Buffer object) +SIN.prototype.payload = function(data) { + if (data) { + this.doAsBinary(function() { + data.copy(this.data, 2); + }); + return data; + } + return this.as('binary').slice(1); +}; + +SIN.prototype.validate = function() { + this.doAsBinary(function() { + SIN.super_.prototype.validate.call(this); + if (this.data.length != 22) throw new Error('invalid data length'); + }); +}; + + +// create a SIN from a public key +SIN.fromPubKey = function(pubKey, type) { + if (!type) + type = SIN.SIN_EPHEM; + + if (!Buffer.isBuffer(pubKey) || (pubKey.length !== 33 && pubKey.length != 65)) + throw new Error('Invalid public key'); + + var hash = coinUtil.sha256ripe160(pubKey); + return new SIN(hash, type); +}; + +module.exports = SIN; + +}).call(this,require("buffer").Buffer) +},{"../util":191,"../util/EncodedData":"eLfUFE","../util/VersionedData":"QLzNQg","buffer":95,"util":128}],"EyghZQ":[function(require,module,exports){ +var coinUtil = require('../util'); +var timeUtil = require('../util/time'); +var Key = require('./Key'); +var SIN = require('./SIN'); + +function SINKey(cfg) { + if (typeof cfg != 'object') + cfg = {}; + + this.created = cfg.created; + this.privKey = cfg.privKey; +}; + +SINKey.prototype.generate = function() { + this.privKey = Key.generateSync(); + this.created = timeUtil.curtime(); +}; + +SINKey.prototype.pubkeyHash = function() { + return coinUtil.sha256ripe160(this.privKey.public); +}; + +SINKey.prototype.storeObj = function() { + var pubKey = this.privKey.public.toString('hex'); + var pubKeyHash = this.pubkeyHash(); + var sin = new SIN(SIN.SIN_EPHEM, pubKeyHash); + var obj = { + created: this.created, + priv: this.privKey.private.toString('hex'), + pub: pubKey, + sin: sin.toString(), + }; + + return obj; +}; + +module.exports = SINKey; + +},{"../util":191,"../util/time":194,"./Key":"ALJ4PS","./SIN":"tBM27q"}],"./lib/SINKey":[function(require,module,exports){ +module.exports=require('EyghZQ'); +},{}],"./lib/Script":[function(require,module,exports){ +module.exports=require('hQ0t76'); +},{}],"hQ0t76":[function(require,module,exports){ +(function (Buffer){ +var config = require('../config'); +var log = require('../util/log'); +var Opcode = require('./Opcode'); +var buffertools = require('buffertools'); +var util = require('../util/util'); +var Parser = require('../util/BinaryParser'); +var Put = require('bufferput'); + +var TX_UNKNOWN = 0; +var TX_PUBKEY = 1; +var TX_PUBKEYHASH = 2; +var TX_MULTISIG = 3; +var TX_SCRIPTHASH = 4; + +var TX_TYPES = [ + 'unknown', + 'pubkey', + 'pubkeyhash', + 'multisig', + 'scripthash' +]; + +function Script(buffer) { + if (buffer) { + this.buffer = buffer; + } else { + this.buffer = util.EMPTY_BUFFER; + } + this.chunks = []; + this.parse(); +} + +Script.TX_UNKNOWN = TX_UNKNOWN; +Script.TX_PUBKEY = TX_PUBKEY; +Script.TX_PUBKEYHASH = TX_PUBKEYHASH; +Script.TX_MULTISIG = TX_MULTISIG; +Script.TX_SCRIPTHASH = TX_SCRIPTHASH; + +Script.prototype.parse = function() { + this.chunks = []; + + var parser = new Parser(this.buffer); + while (!parser.eof()) { + var opcode = parser.word8(); + + var len, chunk; + if (opcode > 0 && opcode < Opcode.map.OP_PUSHDATA1) { + // Read some bytes of data, opcode value is the length of data + this.chunks.push(parser.buffer(opcode)); + } else if (opcode === Opcode.map.OP_PUSHDATA1) { + len = parser.word8(); + chunk = parser.buffer(len); + this.chunks.push(chunk); + } else if (opcode === Opcode.map.OP_PUSHDATA2) { + len = parser.word16le(); + chunk = parser.buffer(len); + this.chunks.push(chunk); + } else if (opcode === Opcode.map.OP_PUSHDATA4) { + len = parser.word32le(); + chunk = parser.buffer(len); + this.chunks.push(chunk); + } else { + this.chunks.push(opcode); + } + } +}; + +Script.prototype.isPushOnly = function() { + for (var i = 0; i < this.chunks.length; i++) { + var op = this.chunks[i]; + if (!Buffer.isBuffer(op) && op > Opcode.map.OP_16) { + return false; + } + } + + return true; +}; + +Script.prototype.isP2SH = function() { + return (this.chunks.length == 3 && + this.chunks[0] == Opcode.map.OP_HASH160 && + Buffer.isBuffer(this.chunks[1]) && + this.chunks[1].length == 20 && + this.chunks[2] == Opcode.map.OP_EQUAL); +}; + +Script.prototype.isPubkey = function() { + return (this.chunks.length == 2 && + Buffer.isBuffer(this.chunks[0]) && + this.chunks[1] == Opcode.map.OP_CHECKSIG); +}; + +Script.prototype.isPubkeyHash = function() { + return (this.chunks.length == 5 && + this.chunks[0] == Opcode.map.OP_DUP && + this.chunks[1] == Opcode.map.OP_HASH160 && + Buffer.isBuffer(this.chunks[2]) && + this.chunks[2].length == 20 && + this.chunks[3] == Opcode.map.OP_EQUALVERIFY && + this.chunks[4] == Opcode.map.OP_CHECKSIG); +}; + +function isSmallIntOp(opcode) { + return ((opcode == Opcode.map.OP_0) || + ((opcode >= Opcode.map.OP_1) && (opcode <= Opcode.map.OP_16))); +}; + +Script.prototype.isMultiSig = function() { + return (this.chunks.length > 3 && + isSmallIntOp(this.chunks[0]) && + this.chunks.slice(1, this.chunks.length - 2).every(function(i) { + return Buffer.isBuffer(i); + }) && + isSmallIntOp(this.chunks[this.chunks.length - 2]) && + this.chunks[this.chunks.length - 1] == Opcode.map.OP_CHECKMULTISIG); +}; + +Script.prototype.isPubkeyHashScriptSig = function() { + return (this.chunks.length == 2 && + Buffer.isBuffer(this.chunks[0]) && + Buffer.isBuffer(this.chunks[1])); +}; + +Script.prototype.isP2shScriptSig = function() { + if (!isSmallIntOp(this.chunks[0]) || this.chunks[0] !== 0) + return false; + + var redeemScript = new Script(this.chunks[this.chunks.length - 1]); + var type = redeemScript.classify(); + return type !== TX_UNKNOWN; +}; + +Script.prototype.isMultiSigScriptSig = function() { + if (!isSmallIntOp(this.chunks[0]) || this.chunks[0] !== 0) + return false; + return !this.isP2shScriptSig(); +}; +Script.prototype.isPubkeyScriptSig = function() { + return (this.chunks.length == 1 && + Buffer.isBuffer(this.chunks[0])); +}; + +Script.prototype.countSignatures = function() { + var ret = 0; + var l = this.chunks.length; + // Multisig? + if (this.isMultiSigScriptSig()) { + ret = l - 1; + } + // p2sh + else if (this.isP2shScriptSig()) { + ret = l - 2; + } + // p2pubkeyhash + else if (this.isPubkeyHashScriptSig()) { + ret = 1; + } + // p2pubkey + else { + ret = 0; + } + return ret; +}; + + +Script.prototype.getSignatures = function() { + ret = []; + var l = this.chunks.length; + // Multisig? + if (this.isMultiSigScriptSig()) { + for(var i = 1; i marked 0 + redeemScript + } + } + // p2pubkey or p2pubkeyhash + else { + if (buffertools.compare(this.getBuffer(), util.EMPTY_BUFFER) === 0) { + ret = 1; + } + } + return ret; +}; + +Script.prototype.finishedMultiSig = function() { + var missing = this.countMissingSignatures(); + if (missing === null) return null; + + return missing === 0; +}; + +Script.prototype.getMultiSigInfo = function() { + if (!this.isMultiSig()) { + throw new Error("Script.getMultiSigInfo(): Not a multiSig script."); + } + + var nsigs = this.chunks[0] - 80; //see OP_2-OP_16; + var npubkeys = this.chunks[this.chunks.length - 2] - 80; //see OP_2-OP_16; + + var pubkeys = []; + for (var i = 1; i < this.chunks.length - 2; i++) { + pubkeys.push(this.chunks[i]); + } + + if (pubkeys.length != npubkeys) { + throw new Error("Script.getMultiSigInfo(): Amount of PKs does not match what the script specifies."); + } + + return { + nsigs: nsigs, + npubkeys: npubkeys, + pubkeys: pubkeys + } +}; + +Script.prototype.prependOp0 = function() { + var chunks = [0]; + for (i in this.chunks) { + if (this.chunks.hasOwnProperty(i)) { + chunks.push(this.chunks[i]); + } + } + this.chunks = chunks; + this.updateBuffer(); + return this; +}; + +// is this a script form we know? +Script.prototype.classify = function() { + if (this.isPubkeyHash()) + return TX_PUBKEYHASH; + if (this.isP2SH()) + return TX_SCRIPTHASH; + if (this.isMultiSig()) + return TX_MULTISIG; + if (this.isPubkey()) + return TX_PUBKEY; + return TX_UNKNOWN; +}; + +// extract useful data items from known scripts +Script.prototype.capture = function() { + var txType = this.classify(); + var res = []; + switch (txType) { + case TX_PUBKEY: + res.push(this.chunks[0]); + break; + case TX_PUBKEYHASH: + res.push(this.chunks[2]); + break; + case TX_MULTISIG: + for (var i = 1; i < (this.chunks.length - 2); i++) + res.push(this.chunks[i]); + break; + case TX_SCRIPTHASH: + res.push(this.chunks[1]); + break; + + case TX_UNKNOWN: + default: + // do nothing + break; + } + + return res; +}; + +// return first extracted data item from script +Script.prototype.captureOne = function() { + var arr = this.capture(); + return arr[0]; +}; + +Script.prototype.getOutType = function() { + var txType = this.classify(); + switch (txType) { + case TX_PUBKEY: + return 'Pubkey'; + case TX_PUBKEYHASH: + return 'Address'; + default: + return 'Strange'; + } +}; + +Script.prototype.getRawOutType = function() { + return TX_TYPES[this.classify()]; +}; + +Script.prototype.simpleOutHash = function() { + switch (this.getOutType()) { + case 'Address': + return this.chunks[2]; + case 'Pubkey': + return util.sha256ripe160(this.chunks[0]); + default: + log.debug("Encountered non-standard scriptPubKey"); + log.debug("Strange script was: " + this.toString()); + return null; + } +}; + +Script.prototype.getInType = function() { + if (this.chunks.length == 1) { + // Direct IP to IP transactions only have the public key in their scriptSig. + return 'Pubkey'; + } else if (this.chunks.length == 2 && + Buffer.isBuffer(this.chunks[0]) && + Buffer.isBuffer(this.chunks[1])) { + return 'Address'; + } else { + return 'Strange'; + } +}; + +Script.prototype.simpleInPubKey = function() { + switch (this.getInType()) { + case 'Address': + return this.chunks[1]; + case 'Pubkey': + return null; + default: + log.debug("Encountered non-standard scriptSig"); + log.debug("Strange script was: " + this.toString()); + return null; + } +}; + +Script.prototype.getBuffer = function() { + return this.buffer; +}; + +Script.prototype.serialize = Script.prototype.getBuffer; + +Script.prototype.getStringContent = function(truncate, maxEl) { + if (truncate === null) { + truncate = true; + } + + if ('undefined' === typeof maxEl) { + maxEl = 15; + } + + var s = ''; + for (var i = 0, l = this.chunks.length; i < l; i++) { + var chunk = this.chunks[i]; + + if (i > 0) { + s += ' '; + } + + if (Buffer.isBuffer(chunk)) { + s += '0x' + util.formatBuffer(chunk, truncate ? null : 0); + } else { + s += Opcode.reverseMap[chunk]; + } + + if (maxEl && i > maxEl) { + s += ' ...'; + break; + } + } + return s; +}; + +Script.prototype.toString = function(truncate, maxEl) { + var script = " + + + + + + diff --git a/examples/node-client.js b/examples/node-client.js new file mode 100644 index 0000000..57d5735 --- /dev/null +++ b/examples/node-client.js @@ -0,0 +1,34 @@ +var io = require('socket.io-client'); + +var bitcore = require('bitcore'); +var util = bitcore.util; +var Key = bitcore.Key; +var AuthMessage = bitcore.AuthMessage; +var Buffer = bitcore.Buffer; + +var socket = io.connect('http://localhost:3001', { + reconnection: false +}); + +var pk = Key.generateSync(); +var pubkey = pk.public.toString('hex'); +socket.emit('subscribe', pubkey); +socket.emit('sync'); + + + +socket.on('connect', function() { + console.log('connected as ' + pubkey); +}); + +socket.on('message', function(m) { + var data = AuthMessage.decode(pk, m); + console.log('message received ' + data.payload); + var echo = AuthMessage.encode(m.pubkey, pk, data.payload); + socket.emit('message', echo); +}); + + +socket.on('error', function(err) { + console.log(err); +}); diff --git a/insight.js b/insight.js new file mode 100755 index 0000000..8105cc3 --- /dev/null +++ b/insight.js @@ -0,0 +1,161 @@ +#!/usr/bin/env node + +'use strict'; +//Set the node enviornment variable if not set before +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var fs = require('fs'); +var PeerSync = require('./lib/PeerSync'); +var HistoricSync = require('./lib/HistoricSync'); + +var http = require('http'); +var https = require('https'); +var express = require('express'); +var program = require('commander'); + +var config = require('./config/config'); +var logger = require('./lib/logger').logger; +program + .version(config.version); + +// text title +console.log( + '\n\ + ____ _ __ __ ___ _ \n\ + / _/___ _____(_)___ _/ /_ / /_ / | ____ (_)\n\ + / // __ \\/ ___/ / __ `/ __ \\/ __/ / /\| \| / __ \\/ / \n\ + _/ // / / (__ ) / /_/ / / / / /_ / ___ |/ /_/ / / \n\ +/___/_/ /_/____/_/\\__, /_/ /_/\\__/ /_/ |_/ .___/_/ \n\ + /____/ /_/ \n\ +\n\t\t\t\t\t\tv%s\n', config.version); +program.on('--help', function() { + logger.info('\n# Configuration:\n\ +\tINSIGHT_NETWORK (Network): %s\n\ +\tINSIGHT_DB (Database Path): %s\n\ +\tINSIGHT_SAFE_CONFIRMATIONS (Safe Confirmations): %s\n\ +\tINSIGHT_IGNORE_CACHE (Ignore Cache): %s\n\ + # Bicoind Connection configuration:\n\ +\tRPC Username: %s\t\tBITCOIND_USER\n\ +\tRPC Password: %s\tBITCOIND_PASS\n\ +\tRPC Protocol: %s\t\tBITCOIND_PROTO\n\ +\tRPC Host: %s\t\tBITCOIND_HOST\n\ +\tRPC Port: %s\t\t\tBITCOIND_PORT\n\ +\tP2P Port: %s\t\t\tBITCOIND_P2P_PORT\n\ +\tBITCOIND_DATADIR: %s\n\ +\t%s\n\ +\nChange setting by assigning the enviroment variables above. Example:\n\ + $ INSIGHT_NETWORK="testnet" BITCOIND_HOST="123.123.123.123" ./insight.js\ +\n\n', + config.network, config.leveldb, config.safeConfirmations, config.ignoreCache ? 'yes' : 'no', + config.bitcoind.user, + config.bitcoind.pass ? 'Yes(hidden)' : 'No', + config.bitcoind.protocol, + config.bitcoind.host, + config.bitcoind.port, + config.bitcoind.p2pPort, + config.bitcoind.dataDir + (config.network === 'testnet' ? '*' : ''), (config.network === 'testnet' ? '* (/testnet is added automatically)' : '') + ); +}); + +program.parse(process.argv); + +// create express app +var expressApp = express(); + +// setup headers +require('./config/headers')(expressApp); + +// setup http/https base server +var server; +if (config.enableHTTPS) { + var serverOpts = {}; + serverOpts.key = fs.readFileSync('./etc/test-key.pem'); + serverOpts.cert = fs.readFileSync('./etc/test-cert.pem'); + server = https.createServer(serverOpts, expressApp); +} else { + server = http.createServer(expressApp); +} + +// 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$)/.test(file)) { + require(newPath); + } + } else if (stat.isDirectory()) { + walk(newPath); + } + }); +}; + +walk(models_path); + +// p2pSync process +var peerSync = new PeerSync({ + shouldBroadcast: true +}); + +if (!config.disableP2pSync) { + peerSync.run(); +} + +// historic_sync process +var historicSync = new HistoricSync({ + shouldBroadcastSync: true +}); +peerSync.historicSync = historicSync; + +if (!config.disableHistoricSync) { + historicSync.start({}, function(err) { + if (err) { + var txt = 'ABORTED with error: ' + err.message; + console.log('[historic_sync] ' + txt); + } + if (peerSync) peerSync.allowReorgs = true; + }); +} else +if (peerSync) peerSync.allowReorgs = true; + + + +// socket.io +var ios = require('socket.io')(server, config); +require('./app/controllers/socket.js').init(ios); + +// plugins +if (config.enableRatelimiter) { + require('./plugins/ratelimiter').init(expressApp, config.ratelimiter); +} + +if (config.enableMailbox) { + require('./plugins/mailbox').init(ios, config.mailbox); +} + +if (config.enableCleaner) { + require('./plugins/cleaner').init(config.cleaner); +} + +if (config.enableMonitor) { + require('./plugins/monitor').init(config.monitor); +} + +if (config.enableEmailstore) { + require('./plugins/emailstore').init(config.emailstore); +} + +// express settings +require('./config/express')(expressApp, historicSync, peerSync); +require('./config/routes')(expressApp); + + +//Start the app by listening on +server.listen(config.port, function() { + logger.info('insight server listening on port %d in %s mode', server.address().port, process.env.NODE_ENV); +}); + +//expose app +exports = module.exports = expressApp; diff --git a/lib/BlockDb.js b/lib/BlockDb.js new file mode 100644 index 0000000..133fe03 --- /dev/null +++ b/lib/BlockDb.js @@ -0,0 +1,464 @@ +'use strict'; +var imports = require('soop').imports(); +var TIMESTAMP_PREFIX = 'bts-'; // bts- => +var PREV_PREFIX = 'bpr-'; // bpr- => +var NEXT_PREFIX = 'bne-'; // bne- => +var MAIN_PREFIX = 'bma-'; // bma- => (0 is unconnected) +var TIP = 'bti-'; // bti = : last block on the chain +var LAST_FILE_INDEX = 'file-'; // last processed file index + +// txid - blockhash mapping (only for confirmed txs, ONLY FOR BEST BRANCH CHAIN) +var IN_BLK_PREFIX = 'btx-'; //btx- = + + +var MAX_OPEN_FILES = 500; +var CONCURRENCY = 5; +var DFLT_REQUIRED_CONFIRMATIONS = 1; + +/** +* Module dependencies. +*/ +var levelup = require('levelup'); +var config = require('../config/config'); +var db = imports.db || levelup(config.leveldb + '/blocks',{maxOpenFiles: MAX_OPEN_FILES} ); +var Rpc = imports.rpc || require('./Rpc'); +var async = require('async'); +var logger = require('./logger').logger; +var info = logger.info; + +var BlockDb = function(opts) { + this.txDb = require('./TransactionDb').default(); + this.safeConfirmations = config.safeConfirmations || DEFAULT_SAFE_CONFIRMATIONS; + BlockDb.super(this, arguments); +}; + +BlockDb.prototype.close = function(cb) { + db.close(cb); +}; + +BlockDb.prototype.drop = function(cb) { + var path = config.leveldb + '/blocks'; + db.close(function() { + require('leveldown').destroy(path, function () { + db = levelup(path,{maxOpenFiles: MAX_OPEN_FILES} ); + return cb(); + }); + }); +}; + + +BlockDb.prototype._addBlockScript = function(b, height) { + var time_key = TIMESTAMP_PREFIX + + ( b.time || Math.round(new Date().getTime() / 1000) ); + + return [ + { + type: 'put', + key: time_key, + value: b.hash + }, + { + type: 'put', + key: MAIN_PREFIX + b.hash, + value: height + }, + { + type: 'put', + key:PREV_PREFIX + b.hash, + value: b.previousblockhash + }, + ]; +}; + +BlockDb.prototype._delTxsScript = function(txs) { + var dbScript =[]; + + for(var i = 0; i < txs.length; i++) { + dbScript.push({ + type: 'del', + key: IN_BLK_PREFIX + txs[i] + }); + } + return dbScript; +}; + +BlockDb.prototype._addTxsScript = function(txs, hash, height) { + var dbScript =[]; + + for(var i = 0; i < txs.length; i++) { + dbScript.push({ + type: 'put', + key: IN_BLK_PREFIX + txs[i], + value: hash + ':' + height + }); + } + return dbScript; +}; + +// Returns blockHash and height for a given txId (If the tx is on the MAIN chain). +BlockDb.prototype.getBlockForTx = function(txId, cb) { + db.get(IN_BLK_PREFIX + txId,function (err, val) { + if (err && err.notFound) return cb(); + if (err) return cb(err); + + var v = val.split(':'); + return cb(err,v[0],parseInt(v[1])); + }); +}; + +BlockDb.prototype._changeBlockHeight = function(hash, height, cb) { + var self = this; + var dbScript1 = this._setHeightScript(hash,height); + + logger.log('Getting TXS FROM %s to set it Main', hash); + this.fromHashWithInfo(hash, function(err, bi) { + if (!bi || !bi.info || !bi.info.tx) + throw new Error('unable to get info for block:'+ hash); + + var dbScript2; + if (height>=0) { + dbScript2 = self._addTxsScript(bi.info.tx, hash, height); + logger.info('\t%s %d Txs', 'Confirming', bi.info.tx.length); + } else { + dbScript2 = self._delTxsScript(bi.info.tx); + logger.info('\t%s %d Txs', 'Unconfirming', bi.info.tx.length); + } + db.batch(dbScript2.concat(dbScript1),cb); + }); +}; + +BlockDb.prototype.setBlockMain = function(hash, height, cb) { + this._changeBlockHeight(hash,height,cb); +}; + +BlockDb.prototype.setBlockNotMain = function(hash, cb) { + this._changeBlockHeight(hash,-1,cb); +}; + +// adds a block (and its txs). Does not update Next pointer in +// the block prev to the new block, nor TIP pointer +// +BlockDb.prototype.add = function(b, height, cb) { + var txs = typeof b.tx[0] === 'string' ? b.tx : b.tx.map( function(o){ return o.txid; }); + + var dbScript = this._addBlockScript(b,height); + dbScript = dbScript.concat(this._addTxsScript(txs, b.hash, height)); + this.txDb.addMany(b.tx, function(err) { + if (err) return cb(err); + db.batch(dbScript, cb); + }); +}; + +BlockDb.prototype.getTip = function(cb) { + + if (this.cachedTip){ + var v = this.cachedTip.split(':'); + return cb(null,v[0], parseInt(v[1])); + } + + var self = this; + db.get(TIP, function(err, val) { + if (!val) return cb(); + self.cachedTip = val; + var v = val.split(':'); + return cb(err,v[0], parseInt(v[1])); + }); +}; + +BlockDb.prototype.setTip = function(hash, height, cb) { + this.cachedTip = hash + ':' + height; + db.put(TIP, this.cachedTip, function(err) { + return cb(err); + }); +}; + +BlockDb.prototype.getDepth = function(hash, cb) { + var v = this.cachedTip.split(':'); + if (!v) throw new Error('getDepth called with not cachedTip'); + this.getHeight(hash, function(err,h){ + return cb(err,parseInt(v[1]) - h); + }); +}; + +//mainly for testing +BlockDb.prototype.setPrev = function(hash, prevHash, cb) { + db.put(PREV_PREFIX + hash, prevHash, function(err) { + return cb(err); + }); +}; + +BlockDb.prototype.getPrev = function(hash, cb) { + db.get(PREV_PREFIX + hash, function(err,val) { + if (err && err.notFound) { err = null; val = null;} + return cb(err,val); + }); +}; + + +BlockDb.prototype.setLastFileIndex = function(idx, cb) { + var self = this; + if (this.lastFileIndexSaved === idx) return cb(); + + db.put(LAST_FILE_INDEX, idx, function(err) { + self.lastFileIndexSaved = idx; + return cb(err); + }); +}; + +BlockDb.prototype.getLastFileIndex = function(cb) { + db.get(LAST_FILE_INDEX, function(err,val) { + if (err && err.notFound) { err = null; val = null;} + return cb(err,val); + }); +}; + +BlockDb.prototype.getNext = function(hash, cb) { + db.get(NEXT_PREFIX + hash, function(err,val) { + if (err && err.notFound) { err = null; val = null;} + return cb(err,val); + }); +}; + +BlockDb.prototype.getHeight = function(hash, cb) { + db.get(MAIN_PREFIX + hash, function(err, val) { + if (err && err.notFound) { err = null; val = 0;} + return cb(err,parseInt(val)); + }); +}; + +BlockDb.prototype._setHeightScript = function(hash, height) { + logger.log('setHeight: %s #%d', hash,height); + return ([{ + type: 'put', + key: MAIN_PREFIX + hash, + value: height + }]); +}; + +BlockDb.prototype.setNext = function(hash, nextHash, cb) { + db.put(NEXT_PREFIX + hash, nextHash, function(err) { + return cb(err); + }); +}; + +// Unused +BlockDb.prototype.countConnected = function(cb) { + var c = 0; + console.log('Counting connected blocks. This could take some minutes'); + db.createReadStream({start: MAIN_PREFIX, end: MAIN_PREFIX + '~' }) + .on('data', function (data) { + if (data.value !== 0) c++; + }) + .on('error', function (err) { + return cb(err); + }) + .on('end', function () { + return cb(null, c); + }); +}; + +// .has() return true orphans also +BlockDb.prototype.has = function(hash, cb) { + var k = PREV_PREFIX + hash; + db.get(k, function (err) { + var ret = true; + if (err && err.notFound) { + err = null; + ret = false; + } + return cb(err, ret); + }); +}; + +BlockDb.prototype.fromHashWithInfo = function(hash, cb) { + var self = this; + + Rpc.getBlock(hash, function(err, info) { + if (err || !info) return cb(err); + + //TODO can we get this from RPC .height? + self.getHeight(hash, function(err, height) { + if (err) return cb(err); + + info.isMainChain = height >= 0; + + return cb(null, { + hash: hash, + info: info + }); + }); + }); +}; + +BlockDb.prototype.getBlocksByDate = function(start_ts, end_ts, limit, cb) { + var list = []; + var opts = { + start: TIMESTAMP_PREFIX + end_ts, //Inverted since list is reversed + end: TIMESTAMP_PREFIX + start_ts, + limit: limit, + reverse: 1 + }; + + db.createReadStream(opts) + .on('data', function (data) { + var k = data.key.split('-'); + list.push({ + ts: k[1], + hash: data.value + }); + }) + .on('error', function (err) { + return cb(err); + }) + .on('end', function () { + return cb(null, list.reverse()); + }); +}; + +BlockDb.prototype.blockIndex = function(height, cb) { + return Rpc.blockIndex(height,cb); +}; + +BlockDb.prototype._fillConfirmationsOneSpent = function(o, chainHeight, cb) { + var self = this; + if (!o.spentTxId) return cb(); + + if (o.multipleSpentAttempts) { + async.eachLimit(o.multipleSpentAttempts, CONCURRENCY, + function(oi, e_c) { + // Only one will be confirmed + self.getBlockForTx(oi.txid, function(err, hash, height) { + if (err) return; + if (height>=0) { + o.spentTxId = oi.txid; + o.index = oi.index; + o.spentIsConfirmed = chainHeight >= height; + o.spentConfirmations = chainHeight - height +1; + } + return e_c(); + }); + }, cb); + } else { + self.getBlockForTx(o.spentTxId, function(err, hash, height) { + if (err) return cb(err); + if (height >=0 ) { + o.spentIsConfirmed = chainHeight >= height; + o.spentConfirmations = chainHeight - height +1; + } + return cb(); + }); + } +}; + +BlockDb.prototype._fillConfirmationsOne = function(o, chainHeight, cb) { + var self = this; + self.getBlockForTx(o.txid, function(err, hash, height) { + if (err) return cb(err); + if (height>=0) { + o.isConfirmed = chainHeight >= height; + o.confirmations = chainHeight - height +1; + return self._fillConfirmationsOneSpent(o,chainHeight,cb); + } + else return cb(); + }); +}; + +BlockDb.prototype.fillConfirmations = function(txouts, cb) { + var self = this; + this.getTip(function(err, hash, height){ + var txs = txouts.filter(function(x){ + return !x.spentIsConfirmedCached // not 100%cached + && !(x.isConfirmedCached && !x.spentTxId); // and not partial cached but not spent + }); + //console.log('[BlockDb.js.373:txs:]',txs.length, txs.slice(0,5)); //TODO + + async.eachLimit(txs, CONCURRENCY, function(txout, e_c) { + if(txout.isConfirmedCached) { + self._fillConfirmationsOneSpent(txout,height, e_c); + } else { + self._fillConfirmationsOne(txout,height, e_c); + } + + }, cb); + }); +}; + +/* this is only for migration scripts */ +BlockDb.prototype._runScript = function(script, cb) { + db.batch(script,cb); +}; + +BlockDb.prototype.migrateV02 = function(cb) { + var k = 'txb-'; + var dbScript = []; + var c=0; + var c2=0; + var N=50000; + this.txDb._db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + var v = data.value.split(':'); + dbScript.push({ + type: 'put', + key: IN_BLK_PREFIX + k[1], + value: data.value + }); + if (c++>N) { + console.log('\t%dM txs processed', ((c2+=N)/1e6).toFixed(3)); + db.batch(dbScript,function () { + c=0; + dbScript=[]; + }); + } + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function (){ + return cb(); + }); + + +}; + +BlockDb.prototype.migrateV02cleanup = function(cb) { + var self = this; + console.log('## deleting txb- from txs db'); //todo + + var k = 'txb-'; + var d = this.txDb._db; + d.createReadStream({ + start: k, + end: k + '~' + }) + .pipe(d.createWriteStream({type:'del'})) + .on('close', function(err){ + if (err) return cb(err); + console.log('## deleting tx- from txs db'); //todo + + var k = 'tx-'; + var d = self.txDb._db; + d.createReadStream({ + start: k, + end: k + '~' + }) + .pipe(d.createWriteStream({type:'del'})) + .on('close', function(err){ + if (err) return cb(err); + var k = 'txa-'; + var d = self.txDb._db; + d.createReadStream({ + start: k, + end: k + '~' + }) + .pipe(d.createWriteStream({type:'del'})) + .on('close', cb); + }); + }); +}; + + +module.exports = require('soop')(BlockDb); diff --git a/lib/BlockExtractor.js b/lib/BlockExtractor.js new file mode 100644 index 0000000..5cd670d --- /dev/null +++ b/lib/BlockExtractor.js @@ -0,0 +1,142 @@ +'use strict'; +var bitcore = require('reddcore'), + Block = bitcore.Block, + networks = bitcore.networks, + Parser = bitcore.BinaryParser, + fs = require('fs'), + Buffer = bitcore.Buffer, + glob = require('glob'), + async = require('async'); + +function BlockExtractor(dataDir, network) { + var path = dataDir + '/blocks/blk*.dat'; + + this.dataDir = dataDir; + this.files = glob.sync(path); + this.nfiles = this.files.length; + + if (this.nfiles === 0) + throw new Error('Could not find block files at: ' + path); + + this.currentFileIndex = 0; + this.isCurrentRead = false; + this.currentBuffer = null; + this.currentParser = null; + this.network = network === 'testnet' ? networks.testnet: networks.livenet; + this.magic = this.network.magic.toString('hex'); +} + +BlockExtractor.prototype.currentFile = function() { + return this.files[this.currentFileIndex]; +}; + + +BlockExtractor.prototype.nextFile = function() { + if (this.currentFileIndex < 0) return false; + + var ret = true; + + this.isCurrentRead = false; + this.currentBuffer = null; + this.currentParser = null; + + if (this.currentFileIndex < this.nfiles - 1) { + this.currentFileIndex++; + } + else { + this.currentFileIndex=-1; + ret = false; + } + return ret; +}; + +BlockExtractor.prototype.readCurrentFileSync = function() { + if (this.currentFileIndex < 0 || this.isCurrentRead) return; + + this.isCurrentRead = true; + + var fname = this.currentFile(); + if (!fname) return; + + + var stats = fs.statSync(fname); + + var size = stats.size; + + console.log('Reading Blockfile %s [%d MB]', + fname, parseInt(size/1024/1024)); + + var fd = fs.openSync(fname, 'r'); + + var buffer = new Buffer(size); + + fs.readSync(fd, buffer, 0, size, 0); + + this.currentBuffer = buffer; + this.currentParser = new Parser(buffer); +}; + + + + +BlockExtractor.prototype._getMagic = function() { + if (!this.currentParser) + return null; + + var byte0 = this.currentParser ? this.currentParser.buffer(1).toString('hex') : null; + + + + // Grab 3 bytes from block without removing them + var p = this.currentParser.pos; + var bytes123 = this.currentParser.subject.toString('hex',p,p+3); + var magic = byte0 + bytes123; + + if (magic !=='00000000' && magic !== this.magic) { + if(this.errorCount++ > 4) + throw new Error('CRITICAL ERROR: Magic number mismatch: ' + + magic + '!=' + this.magic); + magic=null; + } + + if (magic==='00000000') + magic =null; + + return magic; +}; + +BlockExtractor.prototype.getNextBlock = function(cb) { + var b; + var magic; + var isFinished = 0; + + while(!magic && !isFinished) { + this.readCurrentFileSync(); + magic= this._getMagic(); + + if (!this.currentParser || this.currentParser.eof() ) { + + if (this.nextFile()) { + console.log('Moving forward to file:' + this.currentFile() ); + magic = null; + } else { + console.log('Finished all files'); + isFinished = 1; + } + } + } + if (isFinished) + return cb(); + + // Remove 3 bytes from magic and spacer + this.currentParser.buffer(3+4); + + b = new Block(); + b.parse(this.currentParser); + b.getHash(); + this.errorCount=0; + return cb(null,b); +}; + +module.exports = require('soop')(BlockExtractor); + diff --git a/lib/HistoricSync.js b/lib/HistoricSync.js new file mode 100644 index 0000000..404819c --- /dev/null +++ b/lib/HistoricSync.js @@ -0,0 +1,437 @@ +'use strict'; + +var imports = require('soop').imports(); +var util = require('util'); +var async = require('async'); + +var bitcore = require('reddcore'); +var networks = bitcore.networks; +var config = imports.config || require('../config/config'); +var Sync = require('./Sync'); +var sockets = require('../app/controllers/socket.js'); +var BlockExtractor = require('./BlockExtractor.js'); +var buffertools = require('buffertools'); +var bitcoreUtil = bitcore.util; +var logger = require('./logger').logger; +var info = logger.info; +var error = logger.error; +var PERCENTAGE_TO_START_FROM_RPC = 0.96; + +// TODO TODO TODO +//var PERCENTAGE_TO_START_FROM_RPC = 0.98; + +// var Deserialize = require('bitcore/Deserialize'); +var BAD_GEN_ERROR = 'Bad genesis block. Network mismatch between Insight and bitcoind? Insight is configured for:'; + +var BAD_GEN_ERROR_DB = 'Bad genesis block. Network mismatch between Insight and levelDB? Insight is configured for:'; +function HistoricSync(opts) { + opts = opts || {}; + this.shouldBroadcast = opts.shouldBroadcastSync; + + this.network = config.network === 'testnet' ? networks.testnet: networks.livenet; + + var genesisHashReversed = new Buffer(32); + this.network.genesisBlock.hash.copy(genesisHashReversed); + buffertools.reverse(genesisHashReversed); + this.genesis = genesisHashReversed.toString('hex'); + + var bitcore = require('reddcore'); + var RpcClient = bitcore.RpcClient; + + this.rpc = new RpcClient(config.bitcoind); + this.sync = new Sync(opts); + this.height =0; +} + +HistoricSync.prototype.showProgress = function() { + var self = this; + + if ( self.status ==='syncing' && + ( self.height ) % self.step !== 1) return; + + if (self.error) + error(self.error); + + else { + self.updatePercentage(); + info(util.format('status: [%d%%]', self.syncPercentage)); + } + if (self.shouldBroadcast) { + sockets.broadcastSyncInfo(self.info()); + } + // + // if (self.syncPercentage > 10) { + // process.exit(-1); + // } +}; + + +HistoricSync.prototype.setError = function(err) { + var self = this; + self.error = err.message?err.message:err.toString(); + self.status='error'; + self.showProgress(); + return err; +}; + + + +HistoricSync.prototype.close = function() { + this.sync.close(); +}; + + +HistoricSync.prototype.info = function() { + this.updatePercentage(); + return { + status: this.status, + blockChainHeight: this.blockChainHeight, + syncPercentage: this.syncPercentage, + height: this.height, + syncTipHash: this.sync.tip, + error: this.error, + type: this.type, + startTs: this.startTs, + endTs: this.endTs, + }; +}; + +HistoricSync.prototype.updatePercentage = function() { + var r = this.height / this.blockChainHeight; + this.syncPercentage = parseFloat(100 * r).toFixed(3); + if (this.syncPercentage > 100) this.syncPercentage = 100; +}; + +HistoricSync.prototype.getBlockFromRPC = function(cb) { + var self = this; + + if (!self.currentRpcHash) return cb(); + + var blockInfo; + self.rpc.getBlock(self.currentRpcHash, function(err, ret) { + if (err) return cb(err); + if (ret) { + blockInfo = ret.result; + // this is to match block retreived from file + if (blockInfo.hash === self.genesis) + blockInfo.previousblockhash = + self.network.genesisBlock.prev_hash.toString('hex'); + + self.currentRpcHash = blockInfo.nextblockhash; + } + else { + blockInfo = null; + } + return cb(null, blockInfo); + }); +}; + +HistoricSync.prototype.getStandardizedBlock = function(b) { + var self = this; + + var block = { + hash: bitcoreUtil.formatHashFull(b.getHash()), + previousblockhash: bitcoreUtil.formatHashFull(b.prev_hash), + time: b.timestamp + }; + block.tx = b.txs.map(function(tx){ + var ret = self.sync.txDb.getStandardizedTx(tx, b.timestamp); + return ret; + }); + return block; +}; + +HistoricSync.prototype.getBlockFromFile = function(cb) { + var self = this; + + var blockInfo; + + //get Info + self.blockExtractor.getNextBlock(function(err, b) { + if (err || ! b) return cb(err); + blockInfo = self.getStandardizedBlock(b); + self.sync.bDb.setLastFileIndex(self.blockExtractor.currentFileIndex, function(err) { + return cb(err,blockInfo); + }); + }); +}; + +HistoricSync.prototype.updateBlockChainHeight = function(cb) { + var self = this; + + self.rpc.getBlockCount(function(err, res) { + self.blockChainHeight = res.result; + return cb(err); + }); +}; + + +HistoricSync.prototype.checkNetworkSettings = function(next) { + var self = this; + + self.hasGenesis = false; + + // check network config + self.rpc.getBlockHash(0, function(err, res){ + if (!err && ( res && res.result !== self.genesis)) { + err = new Error(BAD_GEN_ERROR + config.network); + } + if (err) return next(err); + self.sync.bDb.has(self.genesis, function(err, b) { + if (!err && ( res && res.result !== self.genesis)) { + err = new Error(BAD_GEN_ERROR_DB + config.network); + } + self.hasGenesis = b?true:false; + return next(err); + }); + }); +}; + +HistoricSync.prototype.updateStartBlock = function(opts, next) { + var self = this; + + self.startBlock = self.genesis; + + if (opts.startAt) { + self.sync.bDb.fromHashWithInfo(opts.startAt, function(err, bi) { + var blockInfo = bi ? bi.info : {}; + if (blockInfo.height) { + self.startBlock = opts.startAt; + self.height = blockInfo.height; + info('Resuming sync from block: %s #%d',opts.startAt, self.height); + return next(err); + } + }); + } + else { + self.sync.bDb.getTip(function(err,tip, height) { + if (!tip) return next(); + + var blockInfo; + var oldtip; + + //check that the tip is still on the mainchain + async.doWhilst( + function(cb) { + self.sync.bDb.fromHashWithInfo(tip, function(err, bi) { + blockInfo = bi ? bi.info : {}; + if (oldtip) + self.sync.bDb.setBlockNotMain(oldtip, cb); + else + return cb(); + }); + }, + function(err) { + if (err) return next(err); + var ret = false; + + var d = Math.abs(height-blockInfo.height); + if (d>6) { + error('Previous Tip block tip height differs by %d. Please delete and resync (-D)',d); + process.exit(1); + } + if ( self.blockChainHeight === blockInfo.height || + blockInfo.confirmations > 0) { + ret = false; + } + else { + oldtip = tip; + if (!tip) + throw new Error('Previous blockchain tip was not found on bitcoind. Please reset Insight DB. Tip was:'+tip) + tip = blockInfo.previousblockhash; + info('Previous TIP is now orphan. Back to:' + tip); + ret = true; + } + return ret; + }, + function(err) { + self.startBlock = tip; + self.height = height; + info('Resuming sync from block: %s #%d',tip,height); + return next(err); + } + ); + }); + } +}; + +HistoricSync.prototype.prepareFileSync = function(opts, next) { + var self = this; + + if ( opts.forceRPC || !config.bitcoind.dataDir || + self.height > self.blockChainHeight * PERCENTAGE_TO_START_FROM_RPC) return next(); + + + try { + self.blockExtractor = new BlockExtractor(config.bitcoind.dataDir, config.network); + } catch (e) { + info(e.message + '. Disabling file sync.'); + return next(); + } + + self.getFn = self.getBlockFromFile; + self.allowReorgs = true; + self.sync.bDb.getLastFileIndex(function(err, idx) { + + if (opts.forceStartFile) + self.blockExtractor.currentFileIndex = opts.forceStartFile; + else if (idx) self.blockExtractor.currentFileIndex = idx; + + var h = self.genesis; + + info('Seeking file to:' + self.startBlock); + //forward till startBlock + async.whilst( + function() { + return h !== self.startBlock; + }, + function (w_cb) { + self.getBlockFromFile(function(err,b) { + if (!b) return w_cb('Could not find block ' + self.startBlock); + h=b.hash; + setImmediate(function(){ + return w_cb(err); + }); + }); + }, function(err){ + console.log('\tFOUND Starting Block!'); + + // TODO SET HEIGHT + return next(err); + }); + }); +}; + +//NOP +HistoricSync.prototype.prepareRpcSync = function(opts, next) { + var self = this; + + if (self.blockExtractor) return next(); + self.getFn = self.getBlockFromRPC; + self.allowReorgs = true; + self.currentRpcHash = self.startBlock; + return next(); +}; + +HistoricSync.prototype.showSyncStartMessage = function() { + var self = this; + + info('Got ' + self.height + + ' blocks in current DB, out of ' + self.blockChainHeight + ' block at bitcoind'); + + if (self.blockExtractor) { + info('bitcoind dataDir configured...importing blocks from .dat files'); + info('First file index: ' + self.blockExtractor.currentFileIndex); + } + else { + info('syncing from RPC (slow)'); + } + + info('Starting from: ', self.startBlock); + self.showProgress(); +}; + + +HistoricSync.prototype.setupSyncStatus = function() { + var self = this; + + var step = parseInt( (self.blockChainHeight - self.height) / 1000); + if (step < 10) step = 10; + + self.step = step; + self.type = self.blockExtractor?'from .dat Files':'from RPC calls'; + self.status = 'syncing'; + self.startTs = Date.now(); + self.endTs = null; + this.error = null; + this.syncPercentage = 0; +}; + +HistoricSync.prototype.checkDBVersion = function(cb) { + this.sync.txDb.checkVersion02(function(isOk){ + if (!isOk) { + console.log('\n#############################\n\n ## Insight API DB is older that v0.2. Please resync using:\n $ util/sync.js -D\n More information at Insight API\'s Readme.md'); + process.exit(1); + } + // Add more test here in future changes. + return cb(); + }); +}; + + +HistoricSync.prototype.prepareToSync = function(opts, next) { + var self = this; + + self.status = 'starting'; + async.series([ + function(s_c) { + self.checkDBVersion(s_c); + }, + function(s_c) { + self.checkNetworkSettings(s_c); + }, + function(s_c) { + self.updateBlockChainHeight(s_c); + }, + function(s_c) { + self.updateStartBlock(opts,s_c); + }, + function(s_c) { + self.prepareFileSync(opts, s_c); + }, + function(s_c) { + self.prepareRpcSync(opts, s_c); + }, + ], + function(err) { + if (err) return(self.setError(err)); + + self.showSyncStartMessage(); + self.setupSyncStatus(); + return next(); + }); +}; + + +HistoricSync.prototype.start = function(opts, next) { + var self = this; + + if (self.status==='starting' || self.status==='syncing') { + error('## Wont start to sync while status is %s', self.status); + return next(); + } + + self.prepareToSync(opts, function(err) { + if (err) return next(self.setError(err)); + + async.whilst( + function() { + self.showProgress(); + return self.status === 'syncing'; + }, + function (w_cb) { + self.getFn(function(err,blockInfo) { + if (err) return w_cb(self.setError(err)); + + if (blockInfo && blockInfo.hash && (!opts.stopAt || opts.stopAt !== blockInfo.hash)) { + self.sync.storeTipBlock(blockInfo, self.allowReorgs, function(err, height) { + if (err) return w_cb(self.setError(err)); + if (height>=0) self.height=height; + setImmediate(function(){ + return w_cb(err); + }); + }); + } + else { + self.endTs = Date.now(); + self.status = 'finished'; + var info = self.info(); + logger.debug('Done Syncing blockchain', info.type, 'to height', info.height); + return w_cb(err); + } + }); + }, next); + }); +}; + +module.exports = require('soop')(HistoricSync); diff --git a/lib/MessageDb.js b/lib/MessageDb.js new file mode 100644 index 0000000..59782ef --- /dev/null +++ b/lib/MessageDb.js @@ -0,0 +1,169 @@ +'use strict'; +var soop = require('soop'); +var imports = soop.imports(); +var levelup = require('levelup'); +var config = require('../config/config'); +var Rpc = imports.rpc || require('./Rpc'); +var async = require('async'); +var logger = require('./logger').logger; +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var microtime = require('microtime'); +var bitcore = require('reddcore'); +var AuthMessage = bitcore.AuthMessage; +var preconditions = require('preconditions').singleton(); + +var MESSAGE_PREFIX = 'msg-'; // msg-- => + +var MAX_OPEN_FILES = 500; +var CONCURRENCY = 5; + + +var db; +var MessageDb = function(opts) { + opts = opts || {}; + this.path = config.leveldb + '/messages' + (opts.name ? ('-' + opts.name) : ''); + this.db = opts.db || db || levelup(this.path, { + maxOpenFiles: MAX_OPEN_FILES, + valueEncoding: 'json' + }); + this.initEvents(); + db = this.db; +}; +util.inherits(MessageDb, EventEmitter); + +MessageDb.prototype.initEvents = function() { + if (db) return; + var self = this; + this.db.on('put', function(key, value) { + var data = {}; + data.key = key; + data.value = value; + var message = MessageDb.fromStorage(data); + self.emit('message', message); + }); + this.db.on('ready', function() { + //console.log('Database ready!'); + }); +}; + +MessageDb.prototype.close = function(cb) { + this.db.close(cb); +}; + + +var messageKey = function(to, ts) { + preconditions.checkArgument(typeof to === 'string'); + preconditions.checkArgument(to.length === 66); + preconditions.checkArgument(!ts || typeof ts === 'number'); + if (!ts) ts = Math.round(microtime.now()); + return MESSAGE_PREFIX + to.toString() + '-' + ts; +}; + +MessageDb.prototype.addMessage = function(m, cb) { + + if (!this.authenticate(m)) { + cb(new Error('Authentication failed')); + return; + } + + var key = messageKey(m.to); + var value = m; + this.db.put(key, value, cb); +}; + +MessageDb.prototype.authenticate = function(m) { + preconditions.checkArgument(m.pubkey); + preconditions.checkArgument(m.sig); + preconditions.checkArgument(m.encrypted); + + var frompubkey = new Buffer(m.pubkey, 'hex'); + var sig = new Buffer(m.sig, 'hex'); + var encrypted = new Buffer(m.encrypted, 'hex'); + return AuthMessage._verify(frompubkey, sig, encrypted); +}; + +MessageDb.parseKey = function(key) { + var ret = {}; + var spl = key.split('-'); + + ret.to = spl[1]; + ret.ts = +spl[2]; + + return ret; +}; + +MessageDb.fromStorage = function(data) { + var parsed = MessageDb.parseKey(data.key); + var message = data.value; + message.ts = parsed.ts; + message.to = parsed.to; + return message; +}; + +MessageDb.prototype.getMessages = function(to, lower_ts, upper_ts, cb) { + var list = []; + lower_ts = lower_ts || 1; + var opts = { + start: messageKey(to, lower_ts), + end: messageKey(to, upper_ts), + // limit: limit, TODO + reverse: false, + }; + + db.createReadStream(opts) + .on('data', function(data) { + var message = MessageDb.fromStorage(data); + list.push(message); + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function() { + return cb(null, list); + }); +}; + +MessageDb.prototype.getAll = function(cb) { + var list = []; + db.createReadStream() + .on('data', function(data) { + list.push(MessageDb.fromStorage(data)); + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function() { + return cb(null, list); + }); +}; + +MessageDb.prototype.removeUpTo = function(ts, cb) { + preconditions.checkArgument(ts); + preconditions.checkArgument(typeof ts === 'number'); + var opts = {}; + var dels = []; + db.createKeyStream(opts) + .on('data', function(key) { + var parsed = MessageDb.parseKey(key); + if (parsed.ts < ts) { + logger.verbose('Deleting message ' + key); + dels.push({ + type: 'del', + key: key + }); + } + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function() { + db.batch(dels, function(err) { + if (err) return cb(err); + else cb(null, dels.length); + }) + }); + +}; + +module.exports = soop(MessageDb); diff --git a/lib/PeerSync.js b/lib/PeerSync.js new file mode 100644 index 0000000..fa4a18d --- /dev/null +++ b/lib/PeerSync.js @@ -0,0 +1,150 @@ +'use strict'; +var fs = require('fs'); +var bitcore = require('reddcore'); +var bitcoreUtil = bitcore.util; +var Sync = require('./Sync'); +var Peer = bitcore.Peer; +var PeerManager = bitcore.PeerManager; +var config = require('../config/config'); +var networks = bitcore.networks; +var sockets = require('../app/controllers/socket.js'); + +var peerdb_fn = 'peerdb.json'; + +function PeerSync(opts) { + opts = opts|| {}; + this.shouldBroadcast = opts.shouldBroadcast; + this.connected = false; + this.peerdb = undefined; + this.allowReorgs = false; + var pmConfig = { + network: config.network + }; + this.peerman = new PeerManager(pmConfig); + this.load_peers(); + this.sync = new Sync(opts); + this.verbose = opts.verbose || false; +} + +PeerSync.prototype.log = function() { + if (this.verbose) console.log(arguments); +}; + +PeerSync.prototype.load_peers = function() { + this.peerdb = [{ + ipv4: config.bitcoind.p2pHost, + port: config.bitcoind.p2pPort + }]; + + fs.writeFileSync(peerdb_fn, JSON.stringify(this.peerdb)); +}; + +PeerSync.prototype.info = function() { + return { + connected: this.connected, + host: this.peerdb[0].ipv4, + port: this.peerdb[0].port + }; +}; + +PeerSync.prototype.handleInv = function(info) { + var invs = info.message.invs; + info.conn.sendGetData(invs); +}; + +PeerSync.prototype._broadcastAddr = function(txid, addrs) { + if (addrs) { + for(var ii in addrs){ + sockets.broadcastAddressTx(txid, ii); + } + } +}; + + +PeerSync.prototype.handleTx = function(info) { + var self =this; + var tx = this.sync.txDb.getStandardizedTx(info.message.tx); + self.log('[p2p_sync] Handle tx: ' + tx.txid); + tx.time = tx.time || Math.round(new Date().getTime() / 1000); + + this.sync.storeTx(tx, function(err, relatedAddrs) { + if (err) { + self.log('[p2p_sync] Error in handle TX: ' + JSON.stringify(err)); + } + else if (self.shouldBroadcast) { + sockets.broadcastTx(tx); + self._broadcastAddr(tx.txid, relatedAddrs); + } + }); +}; + + +PeerSync.prototype.handleBlock = function(info) { + var self = this; + var block = info.message.block; + var blockHash = bitcoreUtil.formatHashFull(block.calcHash()); + self.log('[p2p_sync] Handle block: %s (allowReorgs: %s)', blockHash, self.allowReorgs); + + var tx_hashes = block.txs.map(function(tx) { + return bitcoreUtil.formatHashFull(tx.hash); + }); + + self.sync.storeTipBlock({ + 'hash': blockHash, + 'tx': tx_hashes, + 'previousblockhash': bitcoreUtil.formatHashFull(block.prev_hash), + }, self.allowReorgs, function(err, height) { + if (err && err.message.match(/NEED_SYNC/) && self.historicSync) { + self.log('[p2p_sync] Orphan block received. Triggering sync'); + self.historicSync.start({forceRPC:1}, function(){ + self.log('[p2p_sync] Done resync.'); + }); + } + else if (err) { + self.log('[p2p_sync] Error in handle Block: ', err); + } + else { + if (self.shouldBroadcast) { + sockets.broadcastBlock(blockHash); + // broadcasting address here is a bad idea. listening to new block + // should be enoght + } + } + }); +}; + +PeerSync.prototype.handleConnected = function(data) { + var peerman = data.pm; + var peers_n = peerman.peers.length; + this.log('[p2p_sync] Connected to ' + peers_n + ' peer' + (peers_n !== 1 ? 's' : '')); +}; + +PeerSync.prototype.run = function() { + var self = this; + + this.peerdb.forEach(function(datum) { + var peer = new Peer(datum.ipv4, datum.port); + self.peerman.addPeer(peer); + }); + + this.peerman.on('connection', function(conn) { + self.connected = true; + conn.on('inv', self.handleInv.bind(self)); + conn.on('block', self.handleBlock.bind(self)); + conn.on('tx', self.handleTx.bind(self)); + }); + this.peerman.on('connect', self.handleConnected.bind(self)); + + this.peerman.on('netDisconnected', function() { + self.connected = false; + }); + + this.peerman.start(); +}; + +PeerSync.prototype.close = function() { + this.sync.close(); +}; + + +module.exports = require('soop')(PeerSync); diff --git a/lib/PoolMatch.js b/lib/PoolMatch.js new file mode 100644 index 0000000..7c52690 --- /dev/null +++ b/lib/PoolMatch.js @@ -0,0 +1,32 @@ +'use strict'; + +var imports = require('soop').imports(); +var fs = require('fs'); +var buffertools = require('buffertools'); +var db = imports.db || JSON.parse( fs.readFileSync(imports.poolMatchFile || './poolMatchFile.json')); + +var PoolMatch = function() { + var self = this; + + self.strings = {}; + db.forEach(function(pool) { + pool.searchStrings.forEach(function(s) { + self.strings[s] = { + poolName: pool.poolName, + url: pool.url + }; + }); + }); +}; + + +PoolMatch.prototype.match = function(buffer) { + var self = this; + for(var k in self.strings) { + if (buffertools.indexOf(buffer, k) >= 0) { + return self.strings[k]; + } + } +}; + +module.exports = require('soop')(PoolMatch); diff --git a/lib/Rpc.js b/lib/Rpc.js new file mode 100644 index 0000000..1888147 --- /dev/null +++ b/lib/Rpc.js @@ -0,0 +1,130 @@ +'use strict'; + +var imports = require('soop').imports(); + +var bitcore = require('reddcore'), + RpcClient = bitcore.RpcClient, + BitcoreBlock = bitcore.Block, + util = require('util'), + config = require('../config/config'); + +var bitcoreRpc = imports.bitcoreRpc || new RpcClient(config.bitcoind); + +function Rpc() { +} + +Rpc._parseTxResult = function(info) { + var b = new Buffer(info.hex,'hex'); + + // remove fields we don't need, to speed and adapt the information + delete info.hex; + + // Inputs => add index + coinBase flag + var n =0; + info.vin.forEach(function(i) { + i.n = n++; + if (i.coinbase) info.isCoinBase = true; + if (i.scriptSig) delete i.scriptSig.hex; + }); + + if (info.isCoinBase && info.vout.length > 0 && info.vout[0].value == 0) { + // empty coinbase + info.isCoinBaseEmpty = true; + } else if (info.vin.length > 0 && (!info.vin[0].coinbase) && info.vout.length >=2 && info.vout[0].value == 0) { + // coinstake marks vout[0] as empty + info.isCoinStake = true; + info.vout.splice(0, 1); + } + + // Outputs => add total + var valueOutSat = 0; + info.vout.forEach( function(o) { + o.value = o.value.toFixed(8); + valueOutSat += o.value * bitcore.util.COIN; + delete o.scriptPubKey.hex; + }); + info.valueOut = valueOutSat.toFixed(0) / bitcore.util.COIN; + info.size = b.length; + + return info; +}; + + +Rpc.errMsg = function(err) { + var e = err; + e.message += util.format(' [Host: %s:%d User:%s Using password:%s]', + bitcoreRpc.host, + bitcoreRpc.port, + bitcoreRpc.user, + bitcoreRpc.pass?'yes':'no' + ); + return e; +}; + +Rpc.getTxInfo = function(txid, doNotParse, cb) { + var self = this; + + if (typeof doNotParse === 'function') { + cb = doNotParse; + doNotParse = false; + } + + bitcoreRpc.getRawTransaction(txid, 1, function(err, txInfo) { + // Not found? + if (err && err.code === -5) return cb(); + if (err) return cb(self.errMsg(err)); + + var info = doNotParse ? txInfo.result : self._parseTxResult(txInfo.result); + return cb(null,info); + }); +}; + + +Rpc.blockIndex = function(height, cb) { + var self = this; + + bitcoreRpc.getBlockHash(height, function(err, bh){ + if (err) return cb(self.errMsg(err)); + cb(null, { blockHash: bh.result }); + }); +}; + +Rpc.getBlock = function(hash, cb) { + var self = this; + + bitcoreRpc.getBlock(hash, function(err,info) { + // Not found? + if (err && err.code === -5) return cb(); + if (err) return cb(self.errMsg(err)); + + + if (info.result.height) + info.result.reward = BitcoreBlock.getBlockValue(info.result.height, config.network) / bitcore.util.COIN ; + + return cb(err,info.result); + }); +}; + +Rpc.sendRawTransaction = function(rawtx, cb) { + bitcoreRpc.sendRawTransaction(rawtx, function(err, txid) { + if (err) return cb(err); + + return cb(err, txid.result); + }); +}; + +Rpc.verifyMessage = function(address, signature, message, cb) { + var self = this; + bitcoreRpc.verifyMessage(address, signature, message, function(err, message) { + if (err && (err.code === -3 || err.code === -5)) + return cb(err); // -3 = invalid address, -5 = malformed base64 / etc. + if (err) + return cb(self.errMsg(err)); + + return cb(err, message.result); + }); +}; + +module.exports = require('soop')(Rpc); + + diff --git a/lib/Sync.js b/lib/Sync.js new file mode 100644 index 0000000..b53eb8d --- /dev/null +++ b/lib/Sync.js @@ -0,0 +1,300 @@ +'use strict'; + +var imports = require('soop').imports(); + +var config = imports.config || require('../config/config'); +var bitcore = require('reddcore'); +var networks = bitcore.networks; +var async = require('async'); + +var logger = require('./logger').logger; +var d = logger.log; +var info = logger.info; + + + +var syncId = 0; + +function Sync(opts) { + this.id = syncId++; + this.opts = opts || {}; + this.bDb = require('./BlockDb').default(); + this.txDb = require('./TransactionDb').default(); + this.network = config.network === 'testnet' ? networks.testnet : networks.livenet; + this.cachedLastHash = null; +} + +Sync.prototype.close = function(cb) { + var self = this; + self.txDb.close(function() { + self.bDb.close(cb); + }); +}; + + +Sync.prototype.destroy = function(next) { + var self = this; + async.series([ + + function(b) { + self.bDb.drop(b); + }, + function(b) { + self.txDb.drop(b); + }, + ], next); +}; + +/* + * Arrives a NEW block, which is the new TIP + * + * Case 0) Simple case + * A-B-C-D-E(TIP)-NEW + * + * Case 1) + * A-B-C-D-E(TIP) + * \ + * NEW + * + * 1) Declare D-E orphans (and possible invalidate TXs on them) + * + * Case 2) + * A-B-C-D-E(TIP) + * \ + * F-G-NEW + * 1) Set F-G as connected (mark TXs as valid) + * 2) Set new heights in F-G-NEW + * 3) Declare D-E orphans (and possible invalidate TXs on them) + * + * + * Case 3) + * + * A-B-C-D-E(TIP) ... NEW + * + * NEW is ignored (if allowReorgs is false) + * + * + */ + +Sync.prototype.storeTipBlock = function(b, allowReorgs, cb) { + + if (typeof allowReorgs === 'function') { + cb = allowReorgs; + allowReorgs = true; + } + if (!b) return cb(); + var self = this; + + if ( self.storingBlock ) { + logger.debug('Storing a block already. Delaying storeTipBlock with:' + + b.hash); + return setTimeout( function() { + logger.debug('Retrying storeTipBlock with: ' + b.hash); + self.storeTipBlock(b,allowReorgs,cb); + }, 1000); + } + + self.storingBlock=1; + var oldTip, oldNext, oldHeight, needReorg = false, height = -1; + var newPrev = b.previousblockhash; + async.series([ + + // This seems unnecesary. + // function(c) { + // // TODO? remove this check? + // self.bDb.has(b.hash, function(err, val) { + // return c(err || + // (val ? new Error('WARN: Ignoring already existing block:' + b.hash) : null)); + // }); + // }, + function(c) { + if (!allowReorgs || newPrev === self.cachedLastHash) return c(); + self.bDb.has(newPrev, function(err, val) { + // Genesis? no problem + if (!val && newPrev.match(/^0+$/)) return c(); + return c(err || + (!val ? new Error('NEED_SYNC Ignoring block with non existing prev:' + b.hash) : null)); + }); + }, + function(c) { + if (!allowReorgs) return c(); + self.bDb.getTip(function(err, hash, h) { + oldTip = hash; + oldHeight = hash ? (h || 0) : -1 + if (oldTip && newPrev !== oldTip) { + needReorg = true; + logger.debug('REORG Triggered, tip mismatch'); + } + return c(); + }); + }, + + function(c) { + if (!needReorg) return c(); + self.bDb.getNext(newPrev, function(err, val) { + if (err) return c(err); + oldNext = val; + return c(); + }); + }, + function(c) { + if (!allowReorgs) return c(); + if (needReorg) { + info('NEW TIP: %s NEED REORG (old tip: %s #%d)', b.hash, oldTip, oldHeight); + self.processReorg(oldTip, oldNext, newPrev, oldHeight, function(err, h) { + if (err) throw err; + + height = h; + return c(); + }); + } + else { + height = oldHeight + 1; + return c(); + } + }, + function(c) { + self.cachedLastHash = b.hash; // just for speed up. + self.bDb.add(b, height, c); + }, + function(c) { + if (!allowReorgs) return c(); + self.bDb.setTip(b.hash, height, function(err) { + return c(err); + }); + }, + function(c) { + self.bDb.setNext(newPrev, b.hash, function(err) { + return c(err); + }); + } + + ], + function(err) { + if (err && err.toString().match(/WARN/)) { + err = null; + } + self.storingBlock=0; + return cb(err, height); + }); +}; + +Sync.prototype.processReorg = function(oldTip, oldNext, newPrev, oldHeight, cb) { + var self = this; + + var orphanizeFrom, newHeight; + + async.series([ + + function(c) { + self.bDb.getHeight(newPrev, function(err, height) { + if (!height) { + // Case 3 + allowReorgs = true + return c(new Error('Could not found block:' + newPrev)); + } + if (height<0) return c(); + + newHeight = height + 1; + info('Reorg Case 1) OldNext: %s NewHeight: %d', oldNext, newHeight); + orphanizeFrom = oldNext; + return c(err); + }); + }, + function(c) { + if (orphanizeFrom) return c(); + + info('Reorg Case 2)'); + self.setBranchConnectedBackwards(newPrev, function(err, yHash, newYHashNext, height) { + if (err) return c(err); + newHeight = height; + self.bDb.getNext(yHash, function(err, yHashNext) { + // Connect the new branch, and orphanize the old one. + orphanizeFrom = yHashNext; + self.bDb.setNext(yHash, newYHashNext, function(err) { + return c(err); + }); + }); + }); + }, + function(c) { + if (!orphanizeFrom) return c(); + self._setBranchOrphan(orphanizeFrom, function(err) { + return c(err); + }); + }, + ], + function(err) { + return cb(err, newHeight); + }); +}; + +Sync.prototype._setBranchOrphan = function(fromHash, cb) { + var self = this, + hashInterator = fromHash; + + async.whilst( + function() { + return hashInterator; + }, + function(c) { + self.bDb.setBlockNotMain(hashInterator, function(err) { + if (err) return cb(err); + self.bDb.getNext(hashInterator, function(err, val) { + hashInterator = val; + return c(err); + }); + }); + }, cb); +}; + +Sync.prototype.setBranchConnectedBackwards = function(fromHash, cb) { + //console.log('[Sync.js.219:setBranchConnectedBackwards:]',fromHash); //TODO + var self = this, + hashInterator = fromHash, + lastHash = fromHash, + yHeight, + branch = []; + + async.doWhilst( + function(c) { + branch.unshift(hashInterator); + + self.bDb.getPrev(hashInterator, function(err, val) { + if (err) return c(err); + lastHash = hashInterator; + hashInterator = val; + self.bDb.getHeight(hashInterator, function(err, height) { + yHeight = height; + return c(); + }); + }); + }, + function() { + return hashInterator && yHeight<=0; + }, + function() { + info('\tFound yBlock: %s #%d', hashInterator, yHeight); + var heightIter = yHeight + 1; + var hashIter; + async.whilst( + function() { + hashIter = branch.shift(); + return hashIter; + }, + function(c) { + self.bDb.setBlockMain(hashIter, heightIter++, c); + }, + function(err) { + return cb(err, hashInterator, lastHash, heightIter); + }); + }); +}; + + +//Store unconfirmed TXs +Sync.prototype.storeTx = function(tx, cb) { + this.txDb.add(tx, cb); +}; + + +module.exports = require('soop')(Sync); diff --git a/lib/TransactionDb.js b/lib/TransactionDb.js new file mode 100644 index 0000000..5fe809b --- /dev/null +++ b/lib/TransactionDb.js @@ -0,0 +1,752 @@ +'use strict'; + +var imports = require('soop').imports(); + + + +// to show tx outs +var OUTS_PREFIX = 'txo-'; //txo-- => [addr, btc_sat] +var SPENT_PREFIX = 'txs-'; //txs---- = ts + +// to sum up addr balance (only outs, spents are gotten later) +var ADDR_PREFIX = 'txa2-'; //txa---- +// tsr = 1e13-js_timestamp +// => + btc_sat [:isConfirmed:[scriptPubKey|isSpendConfirmed:SpentTxid:SpentVout:SpentTs] +// |balance:txApperances + + +// TODO: use bitcore networks module +var genesisTXID = 'b502bc1dc42b07092b9187e92f70e32f9a53247feae16d821bebffa916af79ff'; +var CONCURRENCY = 10; +var DEFAULT_SAFE_CONFIRMATIONS = 6; + +var MAX_OPEN_FILES = 500; +var END_OF_WORLD_TS = 1e13; +// var CONFIRMATION_NR_TO_NOT_CHECK = 10; //Spend +/** + * Module dependencies. + */ + +var bitcore = require('reddcore'), + Rpc = imports.rpc || require('./Rpc'), + util = bitcore.util, + networks = bitcore.networks, + levelup = require('levelup'), + async = require('async'), + config = require('../config/config'), + assert = require('assert'), + Script = bitcore.Script, + bitcoreUtil = bitcore.util, + buffertools = require('buffertools'); + +var logger = require('./logger').logger; + +var db = imports.db || levelup(config.leveldb + '/txs', { + maxOpenFiles: MAX_OPEN_FILES +}); +var PoolMatch = imports.poolMatch || require('soop').load('./PoolMatch', config); +// This is 0.1.2 = > c++ version of base58-native +var base58 = require('base58-native').base58Check; +var encodedData = require('soop').load('reddcore/util/EncodedData', { + base58: base58 +}); +var versionedData = require('soop').load('reddcore/util/VersionedData', { + parent: encodedData +}); + +var Address = require('soop').load('reddcore/lib/Address', { + parent: versionedData +}); + + + +var TransactionDb = function() { + TransactionDb.super(this, arguments); + this.network = config.network === 'testnet' ? networks.testnet : networks.livenet; + this.poolMatch = new PoolMatch(); + this.safeConfirmations = config.safeConfirmations || DEFAULT_SAFE_CONFIRMATIONS; + + this._db = db; // this is only exposed for migration script +}; + +TransactionDb.prototype.close = function(cb) { + db.close(cb); +}; + +TransactionDb.prototype.drop = function(cb) { + var path = config.leveldb + '/txs'; + db.close(function() { + require('leveldown').destroy(path, function() { + db = levelup(path, { + maxOpenFiles: 500 + }); + return cb(); + }); + }); +}; + +TransactionDb.prototype._addSpentInfo = function(r, txid, index, ts) { + if ("object" !== typeof r) { + r = {}; + } + + if (r.spentTxId) { + if (!r.multipleSpentAttempts) { + r.multipleSpentAttempts = [{ + txid: r.spentTxId, + index: r.index + }]; + } + r.multipleSpentAttempts.push({ + txid: txid, + index: parseInt(index) + }); + } else { + r.spentTxId = txid; + r.spentIndex = parseInt(index); + r.spentTs = parseInt(ts); + } +}; + + +// This is not used now +TransactionDb.prototype.fromTxId = function(txid, cb) { + var self = this; + var k = OUTS_PREFIX + txid; + var ret = []; + var idx = {}; + var i = 0; + + // outs. + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + var v = data.value.split(':'); + ret.push({ + addr: v[0], + value_sat: parseInt(v[1]), + index: parseInt(k[2]) + }); + idx[parseInt(k[2])] = i++; + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function() { + + var k = SPENT_PREFIX + txid + '-'; + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + var j = idx[parseInt(k[2])]; + + assert(typeof j !== 'undefined', 'Spent could not be stored: tx ' + txid + + 'spent in TX:' + k[1] + ',' + k[2] + ' j:' + j); + + self._addSpentInfo(ret[j], k[3], k[4], data.value); + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function(err) { + return cb(err, ret); + }); + }); +}; + + +TransactionDb.prototype._fillSpent = function(info, cb) { + var self = this; + + if (!info) return cb(); + + var k = SPENT_PREFIX + info.txid + '-'; + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + self._addSpentInfo(info.vout[k[2]], k[3], k[4], data.value); + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function(err) { + return cb(err); + }); +}; + + +TransactionDb.prototype._fillOutpoints = function(txInfo, cb) { + var self = this; + + if (!txInfo || txInfo.isCoinBase) return cb(); + + var valueIn = 0; + var incompleteInputs = 0; + + async.eachLimit(txInfo.vin, CONCURRENCY, function(i, c_in) { + self.fromTxIdN(i.txid, i.vout, function(err, ret) { + if (!ret || !ret.addr || !ret.valueSat) { + logger.info('Could not get TXouts in %s,%d from %s ', i.txid, i.vout, txInfo.txid); + if (ret) i.unconfirmedInput = ret.unconfirmedInput; + incompleteInputs = 1; + return c_in(); // error not escalated + } + + txInfo.firstSeenTs = ret.ts; + i.unconfirmedInput = ret.unconfirmedInput; + i.addr = ret.addr; + i.valueSat = ret.valueSat; + i.value = ret.valueSat / util.COIN; + valueIn += i.valueSat; + + if (!txInfo.isCoinStake && (ret.multipleSpentAttempts || !ret.spentTxId || + (ret.spentTxId && ret.spentTxId !== txInfo.txid))) { + if (ret.multipleSpentAttempts) { + ret.multipleSpentAttempts.forEach(function(mul) { + if (mul.spentTxId !== txInfo.txid) { + + i.doubleSpentTxID = ret.spentTxId; + i.doubleSpentIndex = ret.spentIndex; + } + }); + } else if (!ret.spentTxId) { + i.dbError = 'Input spent not registered'; + } else { + i.doubleSpentTxID = ret.spentTxId; + i.doubleSpentIndex = ret.spentIndex; + } + } else { + i.doubleSpentTxID = null; + } + return c_in(); + }); + }, + function() { + if (!incompleteInputs) { + txInfo.valueIn = valueIn / util.COIN; + txInfo.fees = (valueIn - (txInfo.valueOut * util.COIN)).toFixed(0) / util.COIN; + } else { + txInfo.incompleteInputs = 1; + } + return cb(); + }); +}; + +TransactionDb.prototype._getInfo = function(txid, next) { + var self = this; + + Rpc.getTxInfo(txid, function(err, txInfo) { + if (err) return next(err); + self._fillOutpoints(txInfo, function() { + self._fillSpent(txInfo, function() { + return next(null, txInfo); + }); + }); + }); +}; + + +// Simplified / faster Info version: No spent / outpoints info. +TransactionDb.prototype.fromIdInfoSimple = function(txid, cb) { + Rpc.getTxInfo(txid, true, function(err, info) { + if (err) return cb(err); + if (!info) return cb(); + return cb(err, info); + }); +}; + +TransactionDb.prototype.fromIdWithInfo = function(txid, cb) { + var self = this; + + self._getInfo(txid, function(err, info) { + if (err) return cb(err); + if (!info) return cb(); + return cb(err, { + txid: txid, + info: info + }); + }); +}; + +// Gets address info from an outpoint +TransactionDb.prototype.fromTxIdN = function(txid, n, cb) { + var self = this; + var k = OUTS_PREFIX + txid + '-' + n; + + db.get(k, function(err, val) { + var ret; + + if (!val || (err && err.notFound)) { + err = null; + ret = { + unconfirmedInput: 1 + }; + } else { + var a = val.split(':'); + ret = { + addr: a[0], + valueSat: parseInt(a[1]) + }; + } + + // spent? + var k = SPENT_PREFIX + txid + '-' + n + '-'; + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + self._addSpentInfo(ret, k[3], k[4], data.value); + }) + .on('error', function(error) { + return cb(error); + }) + .on('end', function() { + return cb(null, ret); + }); + }); +}; + + +TransactionDb.prototype.deleteCacheForAddress = function(addr, cb) { + var k = ADDR_PREFIX + addr + '-'; + var dbScript = []; + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var v = data.value.split(':'); + dbScript.push({ + type: 'put', + key: data.key, + value: v[0] + }); + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function() { + db.batch(dbScript, cb); + }); +}; + +TransactionDb.prototype.cacheConfirmations = function(txouts, cb) { + var self = this; + + var dbScript = []; + for (var i = 0; i < txouts.length; i++) { + var txout = txouts[i]; + + //everything already cached? + if (txout.spentIsConfirmedCached) + continue; + + var infoToCache = []; + if (txout.confirmations >= self.safeConfirmations) { + + if (txout.spentConfirmations >= self.safeConfirmations) { + // if spent, we overwrite scriptPubKey cache (not needed anymore) + // First 1 = txout.isConfirmedCached (must be equal to 1 at this point) + infoToCache = [1, 1, txout.spentTxId, txout.spentIndex, txout.spentTs]; + } else { + if (!txout.isConfirmedCached) { + infoToCache.push(1); + txout.confirmedWillBeCached = 1; + } + } + //console.log('[TransactionDb.js.352:infoToCache:]',infoToCache); //TODO + if (infoToCache.length) { + + infoToCache.unshift(txout.value_sat); + dbScript.push({ + type: 'put', + key: txout.key, + value: infoToCache.join(':') + }); + } + } + } + + //console.log('[TransactionDb.js.339:dbScript:]',dbScript); //TODO + db.batch(dbScript, cb); +}; + + +TransactionDb.prototype.cacheScriptPubKey = function(txouts, cb) { + // console.log('[TransactionDb.js.381:cacheScriptPubKey:]'); //TODO + var self = this; + var dbScript = []; + for (var ii in txouts) { + var txout = txouts[ii]; + //everything already cached? + if (txout.scriptPubKeyCached || txout.spentTxId) { + continue; + } + + if (txout.scriptPubKey) { + var infoToCache = [txout.value_sat, (txout.isConfirmedCached || txout.confirmedWillBeCached) ? 1 : 0, txout.scriptPubKey]; + dbScript.push({ + type: 'put', + key: txout.key, + value: infoToCache.join(':') + }); + } + } + db.batch(dbScript, cb); +}; + + + + +TransactionDb.prototype._parseAddrData = function(k, data, ignoreCache) { + var v = data.value.split(':'); + // console.log('[TransactionDb.js.375]',data.key,data.value); + var item = { + key: data.key, + ts: END_OF_WORLD_TS - parseInt(k[2]), + txid: k[3], + index: parseInt(k[4]), + value_sat: parseInt(v[0]) + }; + + if (ignoreCache) + return item; + + // Cache: + // v[1]== isConfirmedCached + // v[2]=== '1' -> is SpendCached -> [4]=spendTxId [5]=spentIndex [6]=spendTs + // v[2]!== '1' -> is ScriptPubkey -> [[2] = scriptPubkey + if (v[1] === '1') { + item.isConfirmed = 1; + item.isConfirmedCached = 1; + // console.log('[TransactionDb.js.356] CACHE HIT CONF:', item.key); + // Sent, confirmed + if (v[2] === '1') { + // console.log('[TransactionDb.js.356] CACHE HIT SPENT:', item.key); + item.spentIsConfirmed = 1; + item.spentIsConfirmedCached = 1; + item.spentTxId = v[3]; + item.spentIndex = parseInt(v[4]); + item.spentTs = parseInt(v[5]); + } + // Scriptpubkey cached + else if (v[2]) { + item.scriptPubKey = v[2]; + item.scriptPubKeyCached = 1; + // console.log('[TransactionDb.js.356] CACHE HIT SCRIPTPUBKEY:', item.key, v, item.scriptPubKey); + } + } + return item; +}; + +TransactionDb.prototype.fromAddr = function(addr, opts, cb) { + opts = opts || {}; + var self = this; + var k = ADDR_PREFIX + addr + '-'; + var ret = []; + var unique = {}; + + db.createReadStream({ + start: k, + end: k + '~', + limit: opts.txLimit > 0 ? opts.txLimit : -1 // -1 means not limit + }) + .on('data', function(data) { + var k = data.key.split('-'); + var index = k[3] + k[4]; + if (!unique[index]) { + unique[index] = 1; + ret.push(self._parseAddrData(k, data, opts.ignoreCache)); + } + }) + .on('error', cb) + .on('end', function() { + async.eachLimit(ret.filter(function(x) { + return !x.spentIsConfirmed; + }), CONCURRENCY, function(o, e_c) { + var k = SPENT_PREFIX + o.txid + '-' + o.index + '-'; + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + self._addSpentInfo(o, k[3], k[4], data.value); + }) + .on('error', e_c) + .on('end', e_c); + }, + function(err) { + return cb(err, ret); + }); + }); +}; + +TransactionDb.prototype._fromBuffer = function(buf) { + var buf2 = buffertools.reverse(buf); + return parseInt(buf2.toString('hex'), 16); +}; + +TransactionDb.prototype.getStandardizedTx = function(tx, time) { + var self = this; + tx.txid = bitcoreUtil.formatHashFull(tx.getHash()); + var ti = 0; + + tx.vin = tx.ins.map(function(txin) { + var ret = { + n: ti++ + }; + if (tx.isCoinBase()) { + ret.isCoinBase = true; + } else if (tx.isCoinStake()) { + ret.isCoinStake = true; + } else { + ret.txid = buffertools.reverse(new Buffer(txin.getOutpointHash())).toString('hex'); + ret.vout = txin.getOutpointIndex(); + } + return ret; + }); + + var to = 0; + tx.vout = tx.outs.map(function(txout) { + var val; + if (txout.s) { + var s = new Script(txout.s); + var addrs = new Address.fromScriptPubKey(s, config.network); + // support only for p2pubkey p2pubkeyhash and p2sh + if (addrs && addrs.length === 1) { + val = { + addresses: [addrs[0].toString()] + }; + } + } + return { + valueSat: self._fromBuffer(txout.v), + scriptPubKey: val, + n: to++ + }; + }); + tx.time = time; + return tx; +}; + + +TransactionDb.prototype.fillScriptPubKey = function(txouts, cb) { + var self = this; + // Complete utxo info + async.eachLimit(txouts, CONCURRENCY, function(txout, a_c) { + self.fromIdInfoSimple(txout.txid, function(err, info) { + if (!info || !info.vout) return a_c(err); + + txout.scriptPubKey = info.vout[txout.index].scriptPubKey.hex; + return a_c(); + }); + }, function() { + self.cacheScriptPubKey(txouts, cb); + }); +}; + +TransactionDb.prototype.removeFromTxId = function(txid, cb) { + async.series([ + + function(c) { + db.createReadStream({ + start: OUTS_PREFIX + txid + '-', + end: OUTS_PREFIX + txid + '~' + }).pipe( + db.createWriteStream({ + type: 'del' + }) + ).on('close', c); + }, + function(c) { + db.createReadStream({ + start: SPENT_PREFIX + txid + '-', + end: SPENT_PREFIX + txid + '~' + }) + .pipe( + db.createWriteStream({ + type: 'del' + }) + ).on('close', c); + } + ], + function(err) { + cb(err); + }); + +}; + + +// relatedAddrs is an optional hash, to collect related addresses in the transaction +TransactionDb.prototype._addScript = function(tx, relatedAddrs) { + var dbScript = []; + var ts = tx.time; + var txid = tx.txid || tx.hash; + // var u=require('util'); + // console.log('[TransactionDb.js.518]', u.inspect(tx,{depth:10})); //TODO + // Input Outpoints (mark them as spent) + var ii; + for (ii = 0; ii < tx.vin.length; ii++) { + var i = tx.vin[ii]; + if (i.txid) { + var k = SPENT_PREFIX + i.txid + '-' + i.vout + '-' + txid + '-' + i.n; + dbScript.push({ + type: 'put', + key: k, + value: ts || 0 + }); + } + } + + for (ii = 0; ii < tx.vout.length; ii++) { + var o = tx.vout[ii]; + // TODO : not supported=> standard multisig + if (o.scriptPubKey && o.scriptPubKey.addresses && o.scriptPubKey.addresses[0] && !o.scriptPubKey.addresses[1]) { + var addr = o.scriptPubKey.addresses[0]; + var sat = o.valueSat || ((o.value || 0) * util.COIN).toFixed(0); + + if (relatedAddrs) relatedAddrs[addr] = 1; + var k = OUTS_PREFIX + txid + '-' + o.n; + var tsr = END_OF_WORLD_TS - ts; + dbScript.push({ + type: 'put', + key: k, + value: addr + ':' + sat + }, { + type: 'put', + key: ADDR_PREFIX + addr + '-' + tsr + '-' + txid + '-' + o.n, + value: sat + }); + } + } + return dbScript; +}; + +// adds an unconfirmed TX +TransactionDb.prototype.add = function(tx, cb) { + var relatedAddrs = {}; + var dbScript = this._addScript(tx, relatedAddrs); + db.batch(dbScript, function(err) { + return cb(err, relatedAddrs); + }); +}; + +TransactionDb.prototype._addManyFromObjs = function(txs, next) { + var dbScript = []; + for(var i = 0; i < txs.length; i++) { + var s = this._addScript(txs[i]); + dbScript = dbScript.concat(s); + } + db.batch(dbScript, next); +}; + +TransactionDb.prototype._addManyFromHashes = function(txs, next) { + var self = this; + var dbScript = []; + async.eachLimit(txs, CONCURRENCY, function(tx, each_cb) { + if (tx === genesisTXID) + return each_cb(); + + Rpc.getTxInfo(tx, function(err, inInfo) { + if (!inInfo) return each_cb(err); + dbScript = dbScript.concat(self._addScript(inInfo)); + return each_cb(); + }); + }, + function(err) { + if (err) return next(err); + db.batch(dbScript, next); + }); +}; + + +TransactionDb.prototype.addMany = function(txs, next) { + if (!txs) return next(); + + var fn = (typeof txs[0] === 'string') ? + this._addManyFromHashes : this._addManyFromObjs; + + return fn.apply(this, [txs, next]); +}; + + +TransactionDb.prototype.getPoolInfo = function(txid, cb) { + var self = this; + + Rpc.getTxInfo(txid, function(err, txInfo) { + if (err) return cb(false); + var ret; + + if (txInfo && txInfo.isCoinBase) + ret = self.poolMatch.match(new Buffer(txInfo.vin[0].coinbase, 'hex')); + + return cb(ret); + }); +}; + + +TransactionDb.prototype.checkVersion02 = function(cb) { + var k = 'txa-'; + var isV2 = 1; + db.createReadStream({ + start: k, + end: k + '~', + limit: 1 + }) + .on('data', function(data) { + isV2 = 0; + }) + .on('end', function() { + return cb(isV2); + }); +}; + +TransactionDb.prototype.migrateV02 = function(cb) { + var k = 'txa-'; + var dbScript = []; + var c = 0; + var c2 = 0; + var N = 50000; + db.createReadStream({ + start: k, + end: k + '~' + }) + .on('data', function(data) { + var k = data.key.split('-'); + var v = data.value.split(':'); + dbScript.push({ + type: 'put', + key: ADDR_PREFIX + k[1] + '-' + (END_OF_WORLD_TS - parseInt(v[1])) + '-' + k[2] + '-' + k[3], + value: v[0] + }); + if (c++ > N) { + console.log('\t%dM txs outs processed', ((c2 += N) / 1e6).toFixed(3)); //TODO + db.batch(dbScript, function() { + c = 0; + dbScript = []; + }); + } + }) + .on('error', function(err) { + return cb(err); + }) + .on('end', function() { + return cb(); + }); +}; + + + +module.exports = require('soop')(TransactionDb); diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..2368092 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,13 @@ +var winston = require('winston'); +var config = require('../config/config'); + +var logger = new winston.Logger({ + transports: [ + new winston.transports.Console({ + level: 'error' + }), + ] +}); +logger.transports.console.level = config.loggerLevel; + +module.exports.logger = logger; diff --git a/package.json b/package.json new file mode 100644 index 0000000..3e4af56 --- /dev/null +++ b/package.json @@ -0,0 +1,112 @@ +{ + "name": "reddsight-api", + "description": "An open-source Reddcoin blockchain API. The Reddsight API provides you with a convenient, powerful and simple way to query and broadcast data on the bitcoin network and build your own services with it.", + "version": "0.2.12", + "author": { + "name": "Larry Ren", + "email": "ren@reddcoin.com" + }, + "repository": "git://github.com/reddcoin-project/reddsight-api.git", + "contributors": [ + { + "name": "Andrew Groff", + "email": "andrew@reddcoin.com" + }, + { + "name": "Mike Croteau", + "email": "mike@reddcoin.com" + }, + { + "name": "Ryan X Charles", + "email": "ryan@bitpay.com" + }, + { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + }, + { + "name": "Manuel Araoz", + "email": "manuelaraoz@gmail.com" + }, + { + "name": "Mario Colque", + "email": "colquemario@gmail.com" + }, + { + "name": "Gustavo Cortez", + "email": "cmgustavo83@gmail.com" + }, + { + "name": "Juan Ignacio Sosa Lopez", + "email": "bechilandia@gmail.com" + } + ], + "bugs": { + "url": "https://github.com/reddcoin-project/reddsight-api/issues" + }, + "homepage": "https://github.com/reddcoin-project/reddsight-api", + "license": "MIT", + "keywords": [ + "reddcoin", + "reddcore", + "reddsight", + "reddsight api", + "reddcoin api", + "insight", + "insight api", + "blockchain", + "bitcoin api", + "blockchain api", + "json", + "bitcore" + ], + "engines": { + "node": "*" + }, + "bin": "insight.js", + "scripts": { + "start": "node node_modules/grunt-cli/bin/grunt" + }, + "dependencies": { + "async": "*", + "base58-native": "0.1.2", + "bignum": "*", + "bitauth": "^0.1.1", + "reddcore": "git://github.com/reddcoin-project/reddcore.git", + "bufferput": "git://github.com/bitpay/node-bufferput.git", + "buffertools": "*", + "commander": "^2.3.0", + "connect-ratelimit": "git://github.com/dharmafly/connect-ratelimit.git#0550eff209c54f35078f46445000797fa942ab97", + "cron": "^1.0.4", + "express": "~3.4.7", + "glob": "*", + "leveldown": "~0.10.0", + "levelup": "~0.19.0", + "lodash": "^2.4.1", + "microtime": "^0.6.0", + "mkdirp": "^0.5.0", + "moment": "~2.5.0", + "nodemailer": "^1.3.0", + "preconditions": "^1.0.7", + "socket.io": "1.0.6", + "socket.io-client": "1.0.6", + "soop": "=0.1.5", + "winston": "*", + "xmlhttprequest": "~1.6.0" + }, + "devDependencies": { + "chai": "*", + "grunt": "~0.4.2", + "grunt-cli": "~0.1.11", + "grunt-concurrent": "~0.4.2", + "grunt-contrib-jshint": "~0.8.0", + "grunt-contrib-watch": "~0.5.3", + "grunt-env": "~0.4.1", + "grunt-markdown": "~0.5.0", + "grunt-mocha-test": "~0.8.1", + "grunt-nodemon": "~0.2.0", + "memdown": "^0.10.2", + "should": "^2.1.1", + "sinon": "^1.10.3" + } +} diff --git a/plugins/cleaner.js b/plugins/cleaner.js new file mode 100644 index 0000000..9323f65 --- /dev/null +++ b/plugins/cleaner.js @@ -0,0 +1,25 @@ +var mdb = require('../lib/MessageDb').default(); +var logger = require('../lib/logger').logger; +var preconditions = require('preconditions').singleton(); +var microtime = require('microtime'); +var cron = require('cron'); +var CronJob = cron.CronJob; + + +module.exports.init = function(config) { + var cronTime = config.cronTime || '0 * * * *'; + logger.info('Using cleaner plugin with cronTime ' + cronTime); + var onTick = function() { + var limit = microtime.now() - 1000 * 1000 * config.threshold; + mdb.removeUpTo(limit, function(err, n) { + if (err) logger.error(err); + else logger.info('Ran cleaner task, removed ' + n); + }); + }; + var job = new CronJob({ + cronTime: cronTime, + onTick: onTick + }); + onTick(); + job.start(); +}; diff --git a/plugins/config-cleaner.js b/plugins/config-cleaner.js new file mode 100644 index 0000000..fc77818 --- /dev/null +++ b/plugins/config-cleaner.js @@ -0,0 +1,6 @@ +module.exports = { + + cronTime: '0 * * * *', // run each hour + threshold: 30*24*60*60, // 30 days, in seconds + +}; diff --git a/plugins/config-credentialstore.js b/plugins/config-credentialstore.js new file mode 100644 index 0000000..7be35b6 --- /dev/null +++ b/plugins/config-credentialstore.js @@ -0,0 +1,2 @@ +module.exports = { +}; diff --git a/plugins/config-emailstore.js b/plugins/config-emailstore.js new file mode 100644 index 0000000..645024d --- /dev/null +++ b/plugins/config-emailstore.js @@ -0,0 +1,9 @@ +module.exports = { + email: { + service: 'Gmail', + auth: { + user: '', + pass: '' + } + } +}; diff --git a/plugins/config-mailbox.js b/plugins/config-mailbox.js new file mode 100644 index 0000000..f4d6253 --- /dev/null +++ b/plugins/config-mailbox.js @@ -0,0 +1,3 @@ +module.exports = { + +}; diff --git a/plugins/config-monitor.js b/plugins/config-monitor.js new file mode 100644 index 0000000..1958a96 --- /dev/null +++ b/plugins/config-monitor.js @@ -0,0 +1,3 @@ +module.exports = { + cronTime: '* * * * *', // run each minute +}; diff --git a/plugins/config-ratelimiter.js b/plugins/config-ratelimiter.js new file mode 100644 index 0000000..f4d6253 --- /dev/null +++ b/plugins/config-ratelimiter.js @@ -0,0 +1,3 @@ +module.exports = { + +}; diff --git a/plugins/credentialstore.js b/plugins/credentialstore.js new file mode 100644 index 0000000..5deff52 --- /dev/null +++ b/plugins/credentialstore.js @@ -0,0 +1,160 @@ +/** + * Credentials storage service + * + * Allows users to store encrypted data on the server, useful to store the user's credentials. + * + * Steps for the user would be: + * + * 1. Choose an username + * 2. Choose a password + * 3. Create a strong key for encryption using PBKDF2 or scrypt with the username and password + * 4. Use that key to AES-CRT encrypt the private key + * 5. Take the double SHA256 hash of "salt"+"username"+"password" and use that as a secret + * 6. Send a POST request to resource /credentials with the params: + * username=johndoe + * secret=2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892 + * record=YjU1MTI2YTM5ZjliMTE3MGEzMmU2ZjYxZTRhNjk0YzQ1MjM1ZTVhYzExYzA1ZWNkNmZm + * NjM5NWRlNmExMTE4NzIzYzYyYWMwODU1MTdkNWMyNjRiZTVmNmJjYTMxMGQyYmFiNjc4YzdiODV + * lZjg5YWIxYzQ4YjJmY2VkYWJjMDQ2NDYzODhkODFiYTU1NjZmMzgwYzhiODdiMzlmYjQ5ZTc1Nz + * FjYzQzYjk1YTEyYWU1OGMxYmQ3OGFhOTZmNGMz + * + * To retrieve data: + * + * 1. Recover the secret from the double sha256 of the salt, username, and password + * 2. Send a GET request to resource /credentials/username?secret=...... + * 3. Decrypt the data received + */ +(function() { + +'use strict'; + +var logger = require('../lib/logger').logger, + levelup = require('levelup'), + querystring = require('querystring'); + +var storePlugin = {}; + +/** + * Constant enum with the errors that the application may return + */ +var errors = { + MISSING_PARAMETER: { + code: 400, + message: 'Missing required parameter' + }, + INVALID_REQUEST: { + code: 400, + message: 'Invalid request parameter' + }, + NOT_FOUND: { + code: 404, + message: 'Credentials were not found' + } +}; + +var NAMESPACE = 'credentials-store-'; +var MAX_ALLOWED_STORAGE = 1024 /* no more than 1 kb */; + +/** + * Initializes the plugin + * + * @param {Express} expressApp + * @param {Object} config + */ +storePlugin.init = function(expressApp, config) { + var globalConfig = require('../config/config'); + logger.info('Using credentialstore plugin'); + + var path = globalConfig.leveldb + '/credentialstore' + (globalConfig.name ? ('-' + globalConfig.name) : ''); + storePlugin.db = config.db || globalConfig.db || levelup(path); + + expressApp.post(globalConfig.apiPrefix + '/credentials', storePlugin.post); + expressApp.get(globalConfig.apiPrefix + '/credentials/:username', storePlugin.get); +}; + +/** + * Helper function that ends a requests showing the user an error. The response body will be a JSON + * encoded object with only one property with key "error" and value error.message, one of + * the parameters of the function + * + * @param {Object} error - The error that caused the request to be terminated + * @param {number} error.code - the HTTP code to return + * @param {string} error.message - the message to send in the body + * @param {Express.Response} response - the express.js response. the methods status, json, and end + * will be called, terminating the request. + */ +var returnError = function(error, response) { + response.status(error.code).json({error: error.message}).end(); +}; + +/** + * Store a record in the database. The underlying database is merely a levelup instance (a key + * value store) that uses the username concatenated with the secret as a key to store the record. + * The request is expected to contain the parameters: + * * username + * * secret + * * record + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +storePlugin.post = function(request, response) { + + var queryData = ''; + + request.on('data', function(data) { + queryData += data; + if (queryData.length > MAX_ALLOWED_STORAGE) { + queryData = ''; + response.writeHead(413, {'Content-Type': 'text/plain'}).end(); + request.connection.destroy(); + } + }).on('end', function() { + var params = querystring.parse(queryData); + var username = params.username; + var secret = params.secret; + var record = params.record; + if (!username || !secret || !record) { + return returnError(errors.MISSING_PARAMETER, response); + } + + storePlugin.db.put(NAMESPACE + username + secret, record, function (err) { + if (err) { + return returnError({code: 500, message: err}, response); + } + response.json({success: true}).end(); + }); + }); +}; + +/** + * Retrieve a record from the database. + * + * The request is expected to contain the parameters: + * * username + * * secret + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +storePlugin.get = function(request, response) { + var username = request.param('username'); + var secret = request.param('secret'); + if (!username || !secret) { + return returnError(errors.MISSING_PARAMETER, response); + } + + storePlugin.db.get(NAMESPACE + username + secret, function (err, value) { + if (err) { + if (err.notFound) { + return returnError(errors.NOT_FOUND, response); + } + return returnError({code: 500, message: err}, response); + } + response.send(value).end(); + }); +}; + +module.exports = storePlugin; + +})(); diff --git a/plugins/emailTemplates/copay.html b/plugins/emailTemplates/copay.html new file mode 100644 index 0000000..3639b0d --- /dev/null +++ b/plugins/emailTemplates/copay.html @@ -0,0 +1,190 @@ + + + + + + <%= title%> + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + +

Hi, <%= email %>

+ +

You are now using Insight to store an encrypted Copay backup. This is a free +service that we are providing that you can turn off from Copay's preferences.

+ +

In order to prevent abuse, we need you to confirm that this email is valid and +that you requested this backup. Please follow this link if you agree on +backing up your Copay profile within our servers:

+ +

+ + Confirm your email (click here). +

+

If the above link doesn't work, head to: <%= confirm_url %>

+ +

We would also like you to know that:

+
    + +
  • We only store information encrypted by your Copay app. We can't retrieve your private keys, help you remember the password, or collect any information about your wallet.
  • + +
  • In case that one of our servers is compromised, intruders may be able to brute-force their way into your private keys unless you use a strong password.
  • + +
  • Our code is open source and can be audited here: +
      +
    • https://github.com/bitpay/insight
    • +
    • https://github.com/bitpay/copay
    • +
    +
  • +
+ +

Thanks!

+ +

The Copay Team

+ +
+ + + diff --git a/plugins/emailTemplates/copay.plain b/plugins/emailTemplates/copay.plain new file mode 100644 index 0000000..f6ee162 --- /dev/null +++ b/plugins/emailTemplates/copay.plain @@ -0,0 +1,27 @@ +Hi, <%= email %> + +You are now using Insight to store an encrypted Copay backup. This is a free +service that we are providing that you can turn off from Copay's preferences. + +In order to prevent abuse, we need you to confirm that this email is valid and +that you requested this backup. Please follow this link if you agree on +backing up your Copay profile within our servers: + + <%= confirm_url %> + +We would also like you to take note of these: + + * We only store information encrypted by your Copay app. We can't retrieve + your private keys, help you remember the password, or collect any information + about your wallet. + + * In case of a service compromise, intruders may be able to brute-force their + way into your private keys. Please use a strong password to avoid this. + + * Our code is open source and can be audited here: + https://github.com/bitpay/insight + https://github.com/bitpay/copay + +Thanks! + +The Copay Team diff --git a/plugins/emailstore.js b/plugins/emailstore.js new file mode 100644 index 0000000..de470b3 --- /dev/null +++ b/plugins/emailstore.js @@ -0,0 +1,608 @@ +/** + * Email-credentials-storage service + * + * Allows users to store encrypted data on the server, useful to store the user's credentials. + * + * Triggers an email to the user's provided email account. Note that the service may decide to + * remove information associated with unconfirmed email addresses! + * + * Steps for the user would be: + * + * 1. Select an email to use + * 2. Choose a password + * 3. Create a strong key for encryption using PBKDF2 or scrypt with the email and password + * 4. Use that key to AES-CRT encrypt the private key + * 5. Take the double SHA256 hash of "salt"+"email"+"password" and use that as a secret + * 6. Send a POST request to resource /email/register with the params: + * email=johndoe@email.com + * secret=2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892 + * record=YjU1MTI2YTM5ZjliMTE3MGEzMmU2ZjYxZTRhNjk0YzQ1MjM1ZTVhYzExYzA1ZWNkNmZm + * NjM5NWRlNmExMTE4NzIzYzYyYWMwODU1MTdkNWMyNjRiZTVmNmJjYTMxMGQyYmFiNjc4YzdiODV + * lZjg5YWIxYzQ4YjJmY2VkYWJjMDQ2NDYzODhkODFiYTU1NjZmMzgwYzhiODdiMzlmYjQ5ZTc1Nz + * FjYzQzYjk1YTEyYWU1OGMxYmQ3OGFhOTZmNGMz + * + * To verify an email: + * + * 1. Check the email sent by the insight server + * 2. Click on the link provided, or take the verification secret to make a request + * 3. The request done can be a POST or GET request to /email/validate with the params: + * email=johndoe@email.com + * verification_code=M5NWRlNmExMTE4NzIzYzYyYWMwODU1MT + * + * To retrieve data: + * + * 1. Recover the secret from the double sha256 of the salt, email, and password + * 2. Send a GET request to resource /email/retrieve?secret=...... + * 3. Decrypt the data received + */ +(function () { + +'use strict'; + +var _ = require('lodash'); +var async = require('async'); +var bitcore = require('bitcore'); +var crypto = require('crypto'); +var fs = require('fs'); +var levelup = require('levelup'); +var nodemailer = require('nodemailer'); +var querystring = require('querystring'); + +var logger = require('../lib/logger').logger; +var globalConfig = require('../config/config'); + +var emailPlugin = {}; + +/** + * Constant enum with the errors that the application may return + */ +emailPlugin.errors = { + MISSING_PARAMETER: { + code: 400, + message: 'Missing required parameter' + }, + INVALID_REQUEST: { + code: 400, + message: 'Invalid request parameter' + }, + NOT_FOUND: { + code: 404, + message: 'Credentials were not found' + }, + INTERNAL_ERROR: { + code: 500, + message: 'Unable to save to database' + }, + EMAIL_TAKEN: { + code: 409, + message: 'That email is already registered' + }, + INVALID_CODE: { + code: 403, + message: 'The provided code is invalid' + } +}; + +var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-'; +var STORED_VALUE = 'emailstore-'; +var PENDING = 'pending-'; +var VALIDATED = 'validated-'; + +var SEPARATOR = '#'; +var MAX_ALLOWED_STORAGE = 1024 * 100 /* no more than 100 kb */; + +var valueKey = function(email, key) { + return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex'); +}; + +var pendingKey = function(email) { + return PENDING + email; +}; + +var validatedKey = function(email) { + return VALIDATED + bitcore.util.twoSha256(email).toString('hex'); +}; + +var emailToPassphrase = function(email) { + return EMAIL_TO_PASSPHRASE + bitcore.util.twoSha256(email).toString('hex'); +}; + +/** + * Initializes the plugin + * + * @param {Object} config + */ +emailPlugin.init = function (config) { + logger.info('Using emailstore plugin'); + + var path = globalConfig.leveldb + '/emailstore' + (globalConfig.name ? ('-' + globalConfig.name) : ''); + emailPlugin.db = config.db || globalConfig.db || levelup(path); + + emailPlugin.email = config.emailTransport || nodemailer.createTransport(config.email); + + emailPlugin.textTemplate = config.textTemplate || 'copay.plain'; + emailPlugin.htmlTemplate = config.htmlTemplate || 'copay.html'; + + emailPlugin.crypto = config.crypto || crypto; + + emailPlugin.confirmUrl = ( + process.env.INSIGHT_EMAIL_CONFIRM_HOST + || config.confirmUrl + || 'https://insight.bitpay.com' + ) + globalConfig.apiPrefix + '/email/validate'; + + emailPlugin.redirectUrl = ( + config.redirectUrl + || 'https://copay.io/in/app?confirmed=true' + ); +}; + +/** + * Helper function that ends a requests showing the user an error. The response body will be a JSON + * encoded object with only one property with key "error" and value error.message, one of + * the parameters of the function + * + * @param {Object} error - The error that caused the request to be terminated + * @param {number} error.code - the HTTP code to return + * @param {string} error.message - the message to send in the body + * @param {Express.Response} response - the express.js response. the methods status, json, and end + * will be called, terminating the request. + */ +emailPlugin.returnError = function (error, response) { + response.status(error.code).json({error: error.message}).end(); +}; + +/** + * Helper that sends a verification email. + * + * @param {string} email - the user's email + * @param {string} secret - the verification secret + */ +emailPlugin.sendVerificationEmail = function (email, secret) { + var confirmUrl = emailPlugin.makeConfirmUrl(email, secret); + async.series([ + function(callback) { + emailPlugin.makeEmailBody({ + email: email, + confirm_url: confirmUrl + }, callback); + }, + function(callback) { + emailPlugin.makeEmailHTMLBody({ + email: email, + confirm_url: confirmUrl, + title: 'Your wallet backup needs confirmation' + }, callback); + } + ], function(err, results) { + var emailBody = results[0]; + var emailBodyHTML = results[1]; + var mailOptions = { + from: 'copay@copay.io', + to: email, + subject: '[Copay] Your wallet backup needs confirmation', + text: emailBody, + html: emailBodyHTML + }; + + // send mail with defined transport object + emailPlugin.email.sendMail(mailOptions, function (err, info) { + if (err) { + logger.error('An error occurred when trying to send email to ' + email, err); + } else { + logger.info('Message sent: ', info ? info : ''); + } + }); + }); +}; + +emailPlugin.makeConfirmUrl = function(email, secret) { + return emailPlugin.confirmUrl + ( + '?email=' + encodeURIComponent(email) + '&verification_code='+secret + ); +}; + +/** + * Returns a function that reads an underscore template and uses the `opts` param + * to build an email body + */ +var applyTemplate = function(templateFilename) { + return function(opts, callback) { + fs.readFile(__dirname + '/emailTemplates/' + emailPlugin[templateFilename], + function(err, template) { + return callback(err, _.template(template, opts)); + } + ); + }; +}; + +emailPlugin.makeEmailBody = applyTemplate('textTemplate'); +emailPlugin.makeEmailHTMLBody = applyTemplate('htmlTemplate'); + +/** + * @param {string} email + * @param {Function(err, boolean)} callback + */ +emailPlugin.exists = function(email, callback) { + emailPlugin.db.get(emailToPassphrase(email), function(err, value) { + if (err && err.notFound) { + return callback(null, false); + } else if (err) { + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(null, true); + }); +}; + +/** + * @param {string} email + * @param {string} passphrase + * @param {Function(err, boolean)} callback + */ +emailPlugin.checkPassphrase = function(email, passphrase, callback) { + emailPlugin.db.get(emailToPassphrase(email), function(err, retrievedPassphrase) { + if (err) { + if (err.notFound) { + return callback(emailPlugin.errors.INVALID_CODE); + } + logger.error('error checking passphrase', email, err); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(err, passphrase === retrievedPassphrase); + }); +}; + +/** + * @param {string} email + * @param {string} passphrase + * @param {Function(err)} callback + */ +emailPlugin.savePassphrase = function(email, passphrase, callback) { + emailPlugin.db.put(emailToPassphrase(email), passphrase, function(err) { + if (err) { + logger.error('error saving passphrase', err); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(null); + }); +}; + +/** + * @param {string} email + * @param {string} key + * @param {string} record + * @param {Function(err)} callback + */ +emailPlugin.saveEncryptedData = function(email, key, record, callback) { + emailPlugin.db.put(valueKey(email, key), record, function(err) { + if (err) { + logger.error('error saving encrypted data', email, key, record, err); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(); + }); +}; + +emailPlugin.createVerificationSecretAndSendEmail = function (email, callback) { + emailPlugin.createVerificationSecret(email, function(err, secret) { + if (err) { + logger.error('error saving verification secret', email, secret, err); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + if (secret) { + emailPlugin.sendVerificationEmail(email, secret); + } + callback(); + }); +}; + +/** + * Creates and stores a verification secret in the database. + * + * @param {string} email - the user's email + * @param {Function} callback - will be called with params (err, secret) + */ +emailPlugin.createVerificationSecret = function (email, callback) { + emailPlugin.db.get(pendingKey(email), function(err, value) { + if (err && err.notFound) { + var secret = emailPlugin.crypto.randomBytes(16).toString('hex'); + emailPlugin.db.put(pendingKey(email), secret, function (err) { + if (err) { + logger.error('error saving pending data:', email, secret); + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(null, secret); + }); + } else { + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + }); +}; + +/** + * @param {string} email + * @param {Function(err)} callback + */ +emailPlugin.retrieveByEmailAndKey = function(email, key, callback) { + emailPlugin.db.get(valueKey(email, key), function(error, value) { + if (error) { + if (error.notFound) { + return callback(emailPlugin.errors.NOT_FOUND); + } + return callback(emailPlugin.errors.INTERNAL_ERROR); + } + return callback(null, value); + }); +}; + +emailPlugin.retrieveDataByEmailAndPassphrase = function(email, key, passphrase, callback) { + emailPlugin.checkPassphrase(email, passphrase, function(err, matches) { + if (err) { + return callback(err); + } + if (matches) { + return emailPlugin.retrieveByEmailAndKey(email, key, callback); + } else { + return callback(emailPlugin.errors.INVALID_CODE); + } + }); +}; + +/** + * Store a record in the database. The underlying database is merely a levelup instance (a key + * value store) that uses the email concatenated with the secret as a key to store the record. + * The request is expected to contain the parameters: + * * email + * * secret + * * record + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +emailPlugin.post = function (request, response) { + + var queryData = ''; + var credentials = emailPlugin.getCredentialsFromRequest(request); + if (credentials.code) { + return emailPlugin.returnError(credentials, response); + } + var email = credentials.email; + var passphrase = credentials.passphrase; + + request.on('data', function (data) { + queryData += data; + if (queryData.length > MAX_ALLOWED_STORAGE) { + queryData = ''; + response.writeHead(413, {'Content-Type': 'text/plain'}).end(); + request.connection.destroy(); + } + }).on('end', function () { + var params = querystring.parse(queryData); + var key = params.key; + var record = params.record; + if (!email || !passphrase || !record || !key) { + return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response); + } + + emailPlugin.processPost(request, response, email, key, passphrase, record); + }); +}; + +emailPlugin.processPost = function(request, response, email, key, passphrase, record) { + async.series([ + /** + * Try to fetch this user's email. If it exists, check the secret is the same. + */ + function (callback) { + emailPlugin.exists(email, function(err, exists) { + if (err) { + return callback(err); + } else if (exists) { + emailPlugin.checkPassphrase(email, passphrase, function(err, match) { + if (err) { + return callback(err); + } + if (match) { + return callback(); + } else { + return callback(emailPlugin.errors.EMAIL_TAKEN); + } + }); + } else { + emailPlugin.savePassphrase(email, passphrase, function(err) { + if (err) { + return callback(err); + } + return callback(); + }); + } + }); + }, + /** + * Save the encrypted private key in the storage. + */ + function (callback) { + emailPlugin.saveEncryptedData(email, key, record, function(err) { + if (err) { + return callback(err); + } + return callback(); + }); + }, + /** + * Create and store the verification secret. If successful, send a verification email. + */ + function (callback) { + emailPlugin.createVerificationSecretAndSendEmail(email, function (err) { + if (err) { + callback({code: 500, message: err}); + } else { + callback(); + } + }); + } + ], function (err) { + if (err) { + emailPlugin.returnError(err, response); + } else { + response.json({success: true}).end(); + } + } + ); +}; + +/** + * Retrieve a record from the database (deprecated) + * + * The request is expected to contain the parameters: + * * email + * * secret + * * key + * + * @deprecated + * @param {Express.Request} request + * @param {Express.Response} response + */ +emailPlugin.get = function (request, response) { + var email = request.param('email'); + var key = request.param('key'); + var secret = request.param('secret'); + if (!secret) { + return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response); + } + + emailPlugin.retrieveDataByEmailAndPassphrase(email, key, secret, function (err, value) { + if (err) { + return emailPlugin.returnError(err, response); + } + response.send(value).end(); + }); +}; + +emailPlugin.getCredentialsFromRequest = function(request) { + if (!request.header('authorization')) { + return emailPlugin.errors.INVALID_REQUEST; + } + var authHeader = new Buffer(request.header('authorization'), 'base64').toString('utf8'); + var splitIndex = authHeader.indexOf(':'); + if (splitIndex === -1) { + return emailPlugin.errors.INVALID_REQUEST; + } + var email = authHeader.substr(0, splitIndex); + var passphrase = authHeader.substr(splitIndex + 1); + + return {email: email, passphrase: passphrase}; +}; + +/** + * Retrieve a record from the database + */ +emailPlugin.retrieve = function (request, response) { + var credentialsResult = emailPlugin.getCredentialsFromRequest(request); + if (_.contains(emailPlugin.errors, credentialsResult)) { + return emailPlugin.returnError(credentialsResult); + } + var email = credentialsResult.email; + var passphrase = credentialsResult.passphrase; + + var key = request.param('key'); + if (!passphrase || !email || !key) { + return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response); + } + + emailPlugin.retrieveDataByEmailAndPassphrase(email, key, passphrase, function (err, value) { + if (err) { + return emailPlugin.returnError(err, response); + } + response.send(value).end(); + }); +}; + +/** + * Marks an email as validated + * + * The two expected params are: + * * email + * * verification_code + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +emailPlugin.validate = function (request, response) { + var email = request.param('email'); + var secret = request.param('verification_code'); + if (!email || !secret) { + return emailPlugin.returnError(emailPlugin.errors.MISSING_PARAMETER, response); + } + + emailPlugin.db.get(pendingKey(email), function (err, value) { + if (err) { + if (err.notFound) { + return emailPlugin.returnError(emailPlugin.errors.NOT_FOUND, response); + } + return emailPlugin.returnError({code: 500, message: err}, response); + } else if (value !== secret) { + return emailPlugin.returnError(emailPlugin.errors.INVALID_CODE, response); + } else { + emailPlugin.db.put(validatedKey(email), true, function (err, value) { + if (err) { + return emailPlugin.returnError({code: 500, message: err}, response); + } else { + emailPlugin.db.remove(validatedKey(email), function (err, value) { + if (err) { + return emailPlugin.returnError({code: 500, message: err}, response); + } else { + response.redirect(emailPlugin.redirectUrl); + } + }); + } + }); + } + }); +}; + +/** + * Changes an user's passphrase + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +emailPlugin.changePassphrase = function (request, response) { + var credentialsResult = emailPlugin.getCredentialsFromRequest(request); + if (_.contains(emailPlugin.errors, credentialsResult)) { + return emailPlugin.returnError(credentialsResult); + } + var email = credentialsResult.email; + var passphrase = credentialsResult.passphrase; + + var queryData = ''; + request.on('data', function (data) { + queryData += data; + if (queryData.length > MAX_ALLOWED_STORAGE) { + queryData = ''; + response.writeHead(413, {'Content-Type': 'text/plain'}).end(); + request.connection.destroy(); + } + }).on('end', function () { + var params = querystring.parse(queryData); + var newPassphrase = params.passphrase; + if (!email || !passphrase || !newPassphrase) { + return emailPlugin.returnError(emailPlugin.errors.INVALID_REQUEST, response); + } + emailPlugin.checkPassphrase(email, passphrase, function (error) { + if (error) { + return emailPlugin.returnError(error, response); + } + emailPlugin.savePassphrase(email, newPassphrase, function (error) { + if (error) { + return emailPlugin.returnError(error, response); + } + return response.json({success: true}).end(); + }); + }); + }); +}; + +module.exports = emailPlugin; + +})(); diff --git a/plugins/mailbox.js b/plugins/mailbox.js new file mode 100644 index 0000000..57b9cbf --- /dev/null +++ b/plugins/mailbox.js @@ -0,0 +1,63 @@ +var microtime = require('microtime'); +var mdb = require('../lib/MessageDb').default(); +var logger = require('../lib/logger').logger; +var preconditions = require('preconditions').singleton(); + +var io; +module.exports.init = function(ext_io, config) { + logger.info('Using mailbox plugin'); + preconditions.checkArgument(ext_io); + io = ext_io; + io.sockets.on('connection', function(socket) { + // when it requests sync, send him all pending messages + socket.on('sync', function(ts) { + logger.verbose('Sync requested by ' + socket.id); + logger.debug(' from timestamp ' + ts); + var rooms = socket.rooms; + if (rooms.length !== 2) { + socket.emit('insight-error', 'Must subscribe with public key before syncing'); + return; + } + var to = rooms[1]; + var upper_ts = Math.round(microtime.now()); + logger.debug(' to timestamp ' + upper_ts); + mdb.getMessages(to, ts, upper_ts, function(err, messages) { + if (err) { + throw new Error('Couldn\'t get messages on sync request: ' + err); + } + logger.verbose('\tFound ' + messages.length + ' message' + (messages.length !== 1 ? 's' : '')); + + if (messages.length) { + for (var i = 0; i < messages.length; i++) { + broadcastMessage(messages[i], socket); + } + } else { + socket.emit('no messages'); + } + }); + }); + + // when it sends a message, add it to db + socket.on('message', function(m) { + logger.debug('Message sent from ' + m.pubkey + ' to ' + m.to); + mdb.addMessage(m, function(err) { + if (err) { + throw new Error('Couldn\'t add message to database: ' + err); + } + }); + }); + + }); + + mdb.on('message', broadcastMessage); + +}; + + + +var broadcastMessage = module.exports.broadcastMessage = function(message, socket) { + preconditions.checkState(io); + var s = socket || io.sockets.in(message.to); + logger.debug('sending message to ' + message.to); + s.emit('message', message); +} diff --git a/plugins/monitor.js b/plugins/monitor.js new file mode 100644 index 0000000..e5d0ac3 --- /dev/null +++ b/plugins/monitor.js @@ -0,0 +1,26 @@ +var mdb = require('../lib/MessageDb').default(); +var logger = require('../lib/logger').logger; +var preconditions = require('preconditions').singleton(); +var microtime = require('microtime'); +var cron = require('cron'); +var CronJob = cron.CronJob; + + +module.exports.init = function(config) { + var cronTime = config.cronTime || '0 * * * *'; + logger.info('Using monitor plugin with cronTime ' + cronTime); + var onTick = function() { + mdb.getAll(function(err, messages) { + if (err) logger.error(err); + else { + logger.info('Message db size = ' + messages.length); + } + }); + }; + var job = new CronJob({ + cronTime: cronTime, + onTick: onTick + }); + onTick(); + job.start(); +}; diff --git a/plugins/publicInfo/config.js b/plugins/publicInfo/config.js new file mode 100644 index 0000000..7be35b6 --- /dev/null +++ b/plugins/publicInfo/config.js @@ -0,0 +1,2 @@ +module.exports = { +}; diff --git a/plugins/publicInfo/publicInfo.js b/plugins/publicInfo/publicInfo.js new file mode 100644 index 0000000..ddafb03 --- /dev/null +++ b/plugins/publicInfo/publicInfo.js @@ -0,0 +1,144 @@ +/** + * Module to allow Copay users to publish public information about themselves + * + * It uses BitAuth to verify the authenticity of the request. + * + */ +(function() { + +'use strict'; + +var logger = require('../../lib/logger').logger, + levelup = require('levelup'), + bitauth = require('bitauth'), + globalConfig = require('../../config/config'), + querystring = require('querystring'); + +var publicInfo = {}; + +/** + * Constant enum with the errors that the application may return + */ +var errors = { + MISSING_PARAMETER: { + code: 400, + message: 'Missing required parameter' + }, + UNAUTHENTICATED: { + code: 401, + message: 'SIN validation error' + }, + NOT_FOUND: { + code: 404, + message: 'There\'s no record of public information for the public key requested' + } +}; + +var NAMESPACE = 'public-info-'; +var MAX_ALLOWED_STORAGE = 64 * 1024 /* no more than 64 kb of data is allowed to be stored */; + +/** + * Initializes the plugin + * + * @param {Express} expressApp + * @param {Object} config + */ +publicInfo.init = function(expressApp, config) { + logger.info('Using publicInfo plugin'); + + var path = globalConfig.leveldb + '/publicinfo' + (globalConfig.name ? ('-' + globalConfig.name) : ''); + publicInfo.db = config.db || globalConfig.db || levelup(path); + + expressApp.post(globalConfig.apiPrefix + '/public', publicInfo.post); + expressApp.get(globalConfig.apiPrefix + '/public/:sin', publicInfo.get); +}; + +/** + * Helper function that ends a requests showing the user an error. The response body will be a JSON + * encoded object with only one property with key "error" and value error.message, one of + * the parameters of the function + * + * @param {Object} error - The error that caused the request to be terminated + * @param {number} error.code - the HTTP code to return + * @param {string} error.message - the message to send in the body + * @param {Express.Response} response - the express.js response. the methods status, json, and end + * will be called, terminating the request. + */ +var returnError = function(error, response) { + response.status(error.code).json({error: error.message}).end(); +}; + +/** + * Store a record in the database. The underlying database is merely a levelup instance (a key + * value store) that uses the SIN to store the body of the message. + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +publicInfo.post = function(request, response) { + + var record = ''; + request.on('data', function(data) { + record += data; + if (record.length > MAX_ALLOWED_STORAGE) { + record = ''; + response.writeHead(413, {'Content-Type': 'text/plain'}).end(); + request.connection.destroy(); + } + }).on('end', function() { + var fullUrl = request.protocol + '://' + request.get('host') + request.url; + var data = fullUrl + record; + + bitauth.verifySignature(data, request.headers['x-identity'], request.headers['x-signature'], + function(err, result) { + if(err || !result) { + return returnError(errors.UNAUTHENTICATED, response); + } + + // Get the SIN from the public key + var sin = bitauth.getSinFromPublicKey(request.headers['x-identity']); + if (!sin) { + return returnError(errors.UNAUTHENTICATED, response); + } + publicInfo.db.put(NAMESPACE + sin, record, function (err) { + if (err) { + return returnError({code: 500, message: err}, response); + } + response.json({success: true}).end(); + if (request.testCallback) { + request.testCallback(); + } + }); + } + ); + }); +}; + +/** + * Retrieve a record from the database. + * + * The request is expected to contain the parameter "sin" + * + * @param {Express.Request} request + * @param {Express.Response} response + */ +publicInfo.get = function(request, response) { + var sin = request.param('sin'); + if (!sin) { + return returnError(errors.MISSING_PARAMETER, response); + } + + publicInfo.db.get(NAMESPACE + sin, function (err, value) { + if (err) { + if (err.notFound) { + return returnError(errors.NOT_FOUND, response); + } + return returnError({code: 500, message: err}, response); + } + response.send(value).end(); + }); +}; + +module.exports = publicInfo; + +})(); diff --git a/plugins/ratelimiter.js b/plugins/ratelimiter.js new file mode 100644 index 0000000..6c3b2b7 --- /dev/null +++ b/plugins/ratelimiter.js @@ -0,0 +1,35 @@ +var logger = require('../lib/logger').logger; +var preconditions = require('preconditions').singleton(); + +var limiter = require('connect-ratelimit'); +var ONE_HOUR = 60 * 60 * 1000; + +module.exports.init = function(app, config) { + preconditions.checkArgument(app); + logger.info('Using ratelimiter plugin'); + + config = config || {}; + config.whitelistRPH = config.whitelistRPH || 500000; + config.normalRPH = config.normalRPH || 10000; + config.blacklistRPH = config.blacklistRPH || 0; + + app.use(limiter({ + whitelist: [], + end: true, + blacklist: [], // 'example.com' + categories: { + whitelist: { + totalRequests: config.whitelistRPH, + every: ONE_HOUR + }, + blacklist: { + totalRequests: config.blacklistRPH, + every: ONE_HOUR + }, + normal: { + totalRequests: config.normalRPH, + every: ONE_HOUR + } + } + })); +}; diff --git a/test/integration/01-transactionouts.js b/test/integration/01-transactionouts.js new file mode 100644 index 0000000..732c3c8 --- /dev/null +++ b/test/integration/01-transactionouts.js @@ -0,0 +1,174 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + + + +var should = require('chai'); +var assert = require('assert'), + fs = require('fs'), + util = require('util'), + TransactionDb = require('../../lib/TransactionDb').default(); + +var txItemsValid = JSON.parse(fs.readFileSync('test/integration/txitems.json')); +var txDb; + +describe('TransactionDb fromIdWithInfo', function(){ + + before(function(c) { + txDb = TransactionDb; + return c(); + }); + + + var txid = '7e621eeb02874ab039a8566fd36f4591e65eca65313875221842c53de6907d6c'; + it('tx info ' + txid, function(done) { + txDb.fromIdWithInfo(txid, function(err, tx) { + + if (err) done(err); + assert.equal(tx.txid, txid); + assert(!tx.info.isCoinBase); + + for(var i=0; i<20; i++) { + assert(parseFloat(tx.info.vin[i].value) === parseFloat(50), 'input '+i); + } + + + tx.info.vin[0].addr.should.equal('msGKGCy2i8wbKS5Fo1LbWUTJnf1GoFFG59'); + tx.info.vin[1].addr.should.equal('mfye7oHsdrHbydtj4coPXCasKad2eYSv5P'); + done(); + }); + }); + + it('tx info', function(done) { + var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; + txDb.fromIdWithInfo(txid, function(err, tx) { + if (err) done(err); + assert.equal(tx.txid, txid); + assert(!tx.info.isCoinBase); + done(); + }); + }); + + it('should pool tx\'s info from bitcoind', function(done) { + var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; + txDb.fromIdWithInfo(txid, function(err, tx) { + if (err) done(err); + assert.equal(tx.info.txid, txid); + assert.equal(tx.info.blockhash, '000000000185678d3d7ecc9962c96418174431f93fe20bf216d5565272423f74'); + assert.equal(tx.info.valueOut, 1.66174); + assert.equal(tx.info.fees, 0.0005 ); + assert.equal(tx.info.size, 226 ); + assert(!tx.info.isCoinBase); + done(); + }); + }); + + var txid1 = '2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399'; + it('test a coinbase TX ' + txid1, function(done) { + txDb.fromIdWithInfo(txid1, function(err, tx) { + if (err) done(err); + assert(tx.info.isCoinBase); + assert.equal(tx.info.txid, txid1); + assert(!tx.info.feeds); + done(); + }); + }); + var txid22 = '666'; + it('test invalid TX ' + txid22, function(done) { + txDb.fromIdWithInfo(txid22, function(err, tx) { + if (err && err.message.match(/must.be.hexadecimal/)) { + return done(); + } + else { + return done(err); + } + }); + }); + + var txid23 = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca227'; + it('test unexisting TX ' + txid23, function(done) { + + txDb.fromIdWithInfo(txid23, function(err, tx) { + assert(!err); + assert(!tx); + return done(); + }); + }); + + + + var txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608'; + it('create TX on the fly ' + txid2, function(done) { + txDb.fromIdWithInfo(txid2, function(err, tx) { + if (err) return done(err); + assert.equal(tx.info.txid, txid2); + done(); + }); + }); + + txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608'; + it('test a broken TX ' + txid2, function(done) { + txDb.fromIdWithInfo(txid2, function(err, tx) { + if (err) return done(err); + assert.equal(tx.info.txid, txid2); + assert.equal(tx.info.vin[0].addr, 'n1JagbRWBDi6VMvG7HfZmXX74dB9eiHJzU'); + done(); + }); + }); +}); + +describe('TransactionDb Outs', function(){ + + before(function(c) { + txDb = TransactionDb; + return c(); + }); + + txItemsValid.forEach( function(v) { + if (v.disabled) return; + it('test a processing tx ' + v.txid, function(done) { + this.timeout(60000); + + // Remove first + txDb.removeFromTxId(v.txid, function() { + + txDb.fromTxId( v.txid, function(err, readItems) { + assert.equal(readItems.length,0); + + var unmatch=[]; + txDb.addMany([v.txid], function(err) { + if (err) return done(err); + + txDb.fromTxId( v.txid, function(err, readItems) { + + v.items.forEach(function(validItem){ + unmatch[validItem.addr] =1; + }); + assert.equal(readItems.length,v.items.length); + + v.items.forEach(function(validItem){ + var readItem = readItems.shift(); + + assert.equal(readItem.addr,validItem.addr); + assert.equal(readItem.value_sat,validItem.value_sat); + assert.equal(readItem.index,validItem.index); + assert.equal(readItem.spendIndex, null); + assert.equal(readItem.spendTxIdBuf, null); + delete unmatch[validItem.addr]; + }); + + var valid = util.inspect(v.items, { depth: null }); + assert(!Object.keys(unmatch).length,'\n\tUnmatchs:' + Object.keys(unmatch) + "\n\n" +valid + '\nvs.\n' + readItems); + return done(); + + }); + }); + }); + }); + }); + }); +}); + + diff --git a/test/integration/02-transactionouts.js b/test/integration/02-transactionouts.js new file mode 100644 index 0000000..78a363f --- /dev/null +++ b/test/integration/02-transactionouts.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + + + +var + assert = require('assert'), + fs = require('fs'), + util = require('util'), + async = require('async'), + config = require('../../config/config'), + TransactionDb = require('../../lib/TransactionDb').default(); + +var spentValid = JSON.parse(fs.readFileSync('test/integration/spent.json')); + +var txDb; + +describe('TransactionDb Expenses', function(){ + + before(function(c) { + txDb = TransactionDb; + + // lets spend! + async.each(Object.keys(spentValid), + function(txid,c_out) { + async.each(spentValid[txid], + function(i,c_in) { + txDb.addMany([i.txid], function(err) { + return c_in(); + }); + }, + function(err) { + return c_out(); + } + ); + }, + function(err) { + return c(); + } + ); + }); + + Object.keys(spentValid).forEach( function(txid) { + it('test result of spending tx ' + txid, function(done) { + var s = spentValid[txid]; + var c=0; + txDb.fromTxId( txid, function(err, readItems) { + s.forEach( function(v) { + assert.equal(readItems[c].spentTxId,v.txid); + assert.equal(readItems[c].spentIndex,v.n); + c++; + }); + done(); + }); + }); + }); +}); diff --git a/test/integration/99-sync.js.descructive-test b/test/integration/99-sync.js.descructive-test new file mode 100644 index 0000000..4a9c605 --- /dev/null +++ b/test/integration/99-sync.js.descructive-test @@ -0,0 +1,366 @@ +#!/usr/bin/env node +'use strict'; +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var should = require('chai'); + +var + assert = require('assert'), + async = require('async'), + HistoricSync = require('../../lib/HistoricSync'); + +var s; +var b = [ + '00000000c4cbd75af741f3a2b2ff72d9ed4d83a048462c1efe331be31ccf006b', //0 B#16 + '00000000fe198cce4c8abf9dca0fee1182cb130df966cc428ad2a230df8da743', //1 + '000000008d55c3e978639f70af1d2bf1fe6f09cb3143e104405a599215c89a48', //2 + '000000009b3bca4909f38313f2746120129cce4a699a1f552390955da470c5a9', //3 + '00000000ede57f31cc598dc241d129ccb4d8168ef112afbdc870dc60a85f5dd3', //4 B#20 + '000000001a29aff8154de6230a4909a3a971c52ae590006e00fddff33f9e3773', //5 B#21 +]; +var t = [ + 'd08582d3711f75d085c618874fb0d049ae09d5ec95ec6f5abd289f4b54712c54', // TX from B#16 + '1729001087e0cebea8d14de1653d5cf59628d9746bc1ae65f776f1cbaff7ebad', //1 + 'cf53d7ccd83a099acfbc319ee10c1e3b10e3d42ba675b569fdd6b69cb8d2db4e', //2 + '73a4988adf462b6540cfa59097804174b298cfa439f73c1a072c2c6fbdbe57c7', //3 + 'd45f9da73619799e9d7bd03cc290e70875ea4cbad56b8bffa15135fbbb3df9ea', //4 Tx from B20 +]; + +var initialTest = function(cb) { + async.each([2,3,4], function(i,c) { + s.sync.bDb.getPrev(b[i], function(err, p) { + assert.equal(p,b[i-1]); + return c(); + }); + }, function() { + async.each([0,1,2,3,4], function(i,c) { + s.sync.bDb.has(b[i], function(err, p) { + assert(p); + return c(); + }); + }, function() { + async.each([0,1,2,3], function(i,c) { + s.sync.bDb.getNext(b[i], function(err, p) { + assert.equal(p,b[i+1]); + return c(); + }); + }, function() { + async.each([0,1,2,3], function(i,c) { + s.sync.bDb.getHeight(b[i], function(err, h) { + assert.equal(h,16+i); + return c(); + }); + }, function() { + async.each([0,1,2,3], function(i,c) { + s.sync.bDb.getDepth(b[i], function(err, d) { + assert.equal(d,4-i); + return c(); + }); + }, cb); + }); + }); + }); + }); +}; + + + +/* + * TEST CASES + * + * b0 height = 16; + * + * Blocks: 0-1-2-3-4 + * case 1) + * 0-1-2-3-4 + * \ + * C1* (height should be 19) + * + * case 2) + * 0-1-2---3-4 + * \ \ + * C1 C2* + * + * case 2b) + * 0-1-2---3-4 + * \ \ + * C1 C2-C2b(TX=C1.TX)* + * case 2c) + * 0-1-2---3-4 + * \ \ + * C1 C2-C2b(TX=C1.TX) + * \ + * C2c(TX=C2.TX)* + * + */ + +var checkTxs = function(txs,heights){ + var i=0; + txs.forEach(function(tx){ + var v=heights[i++]; + it('tx height:'+tx+' #'+v,function(done){ + s.sync.bDb.getBlockForTx(tx, function(err,hash,height) { + assert(!err,err); + if (v>=0) { + assert(hash); + height.should.equal(v); + } else { + assert(!hash); + assert(!height); + } + + return done(); + }); + }); + }); +}; + +//heights is optional +var checkBlocks = function(hashes,heights){ + var i=0; + hashes.forEach(function(hash){ + var v = heights[i++]; + it('block height:'+hash+' #'+v,function(done){ + s.sync.bDb.getHeight(hash, function(err,height) { + assert(!err,err); + height.should.equal(v); + return done(); + }); + }); + }); +}; + + +describe('Sync Reorgs', function(){ + var opts = { + forceRPC: true, + stopAt: b[5], + }; + + before(function(done) { + s = new HistoricSync(); + s.sync.destroy(done); + }); + + it('simple RPC forward syncing', function(done) { + s.start(opts, function(err) { + if (err) return done(err); + initialTest(done); + }); + }); + + var case1 = { + hash: '0000000042d3db6c529dd8f1b085367cb6d907b534f5b1a5dfdd3a34a3459886', + tx: [ '2dc27c8d4b98f5e2ed009cfc164ac4990f18e03c4d5b71e780f9c8b7b2c5f151' ], + time: 1296690099, + previousblockhash: b[2], + }; + + + describe('reorg, case 1', function() { + checkTxs([t[0], t[1], t[2], t[3], t[4]],[16,17,18,19,20]); + checkBlocks([b[0], b[1], b[2], b[3], b[4]],[16,17,18,19,20]); + it('trigger reorg case 1', function(done1){ + s.sync.storeTipBlock(case1, function(err) { + assert(!err, 'shouldnt return error' + err); + done1(); + }); + }); + checkTxs([t[0], t[1], t[2], t[3], t[4],case1.tx],[16,17,18,-1,-1,19]); + checkBlocks([b[0], b[1], b[2],b[3],b[4],case1.hash],[16,17,18,-1,-1,19]); + + it('reorg, case 1 (repeat)', function(done) { + s.sync.storeTipBlock(case1, function(err) { + assert(!err, 'shouldnt return error' + err); + return done(); + }); + }); + }); + + var case2 = { + hash: '00000000c262f9428bb84407780bb0bd008b74941d651111ab2500cf649fa45d', + tx: [ '3fa6fce216e91c9dc9a6267168e9d8bfb4ae57aec0d26590442cfec6e8233682' ], + time: 1296690099, + previousblockhash: b[3], + }; + + +// * case 2) +// * 0-1-2---3-4 +// * \ \ +// * C1 C2* + + + describe('reorg, case 2', function() { + checkTxs([t[0], t[1], t[2], t[3], t[4],case1.tx[0]],[16,17,18,-1,-1,19]); + checkBlocks([b[0], b[1], b[2],b[3],b[4],case1.hash],[16,17,18,-1,-1,19]); + it('trigger reorg case 2', function(done1){ + s.sync.storeTipBlock(case2, function(err) { + assert(!err, 'shouldnt return error' + err); + return done1(); + }); + }); + checkBlocks([b[0], b[1], b[2],b[3],b[4],case2.hash],[16,17,18,19,-1,20]); + checkTxs([t[0], t[1], t[2],t[3],t[4],case2.tx[0]],[16,17,18,19,-1,20]); + it('next from block 2', function(done1){ + s.sync.bDb.getNext(b[2], function(err, val) { + assert(!err); + assert.equal(val,b[3]); + return done1(); + }); + }); + }); + + +// * case 2b) +// * 0-1-2---3-4 +// * \ \ +// * C1 C2-C2b(TX=C1.TX)* + + + var case2b = { + hash: '0000000022bb34bc09f8d8d0ae26b86c87f8483e54b9c3addfe6f30b12bc656a', + tx: case1.tx, + time: 1296690099, + previousblockhash: case2.hash, + }; + + describe('reorg, case 2', function() { + it('reorg case 2b', function(done1){ + s.sync.storeTipBlock(case2b, function(err) { + assert(!err, 'shouldnt return error' + err); + return done1(); + }); + }); + checkBlocks([b[0], b[1], b[2],b[3],b[4],case2.hash, case2b.hash, case1.hash],[16,17,18,19,-1,20, 21, -1]); + checkTxs([t[0], t[1], t[2],t[3],t[4],case2.tx[0], case1.tx],[16,17,18,19,-1,20, 21]); + }); + + var case2c = { + hash: '0000000000000000000000000000000000000000000000000000000000000004', + tx: case2.tx, + time: 1296690099, + previousblockhash: case1.hash, + }; + +// * case 2c) +// * 0-1-2---3-4 +// * \ \ +// * C1 C2-C2b(TX=C1.TX) +// * \ +// * C2c(TX=C2.TX)* + + describe('reorg, case 2c', function() { + it('trigger reorg case 2c', function(done1){ + s.sync.storeTipBlock(case2c, function(err) { + assert(!err, 'shouldnt return error' + err); + return done1(); + }); + }); + checkBlocks([b[0], b[1], b[2], + b[3],b[4],case2.hash, case2b.hash, + case1.hash, case2c.hash], + [16,17,18, + -1,-1,-1, -1, + 19, 20 + ]); + checkTxs( + [t[0], t[1], t[2], + t[3],t[4], case2.tx[0], + case1.tx, case2c.tx[0]], + [16,17,18, + -1,-1, 20, + 19, 20]); + }); + + +// * case 3) +// * 0-1-2---3-4 +// * \ \ +// * C1 C2-C2b(TX=C1.TX) +// * \ +// * C2c(TX=C2.TX) +// +// (orphan)-C3* +// -> returns error + + var case3 = { + hash: '0000000000000000000000000000000000000000000000000000000000000005', + tx: case2.tx, + time: 1296690099, + previousblockhash: '666', + }; + + describe('reorg, case 3)', function() { + it('case 3). Should return an error', function(done1){ + s.sync.storeTipBlock(case3, function(err) { + assert(err, 'should return error' + err); + return done1(); + }); + }); + //shoudnt change anything + checkBlocks([b[0], b[1], b[2], + b[3],b[4],case2.hash, case2b.hash, + case1.hash, case2c.hash], + [16,17,18, + -1,-1,-1, -1, + 19, 20 + ]); + checkTxs( + [t[0], t[1], t[2], + t[3],t[4], case2.tx[0], + case1.tx, case2c.tx[0]], + [16,17,18, + -1,-1, 20, + 19, 20]); + }); + + var p2p = { + hash: '0000000000000000000000000000000000000000000000000000000000000006', + tx: ['f6c2901f39fd07f2f2e503183d76f73ecc1aee9ac9216fde58e867bc29ce674e'], + time: 1296690099, + previousblockhash: '111', + }; + + describe('p2p sync. no reorgs', function() { + it('Should return an error', function(done1){ + s.sync.storeTipBlock(p2p, false, function(err) { + assert(!err, 'shouldnt return error' + err); + return done1(); + }); + it('Block should be stored', function(done1){ + s.sync.bDb.has(p2p.hash, function(err,is) { + assert(!err); + assert(is); + return done1(); + }); + }); + //shoudnt change anything + checkBlocks([b[0], b[1], b[2], + b[3],b[4],case2.hash, case2b.hash, + case1.hash, case2c.hash, + p2p.hash], + [16,17,18, + -1,-1,-1, -1, + 19, 20, + -1 + ]); + }); + it('next Block should be stored', function(done1){ + s.sync.bDb.getNext(p2p.hash, function(err,v) { + assert(!err); + assert.equal(v,p2p.nextblockhash); + return done1(); + }); + }); + it('next Block should be stored', function(done1){ + s.sync.bDb.getNext(p2p.previousblockhash, function(err,v) { + assert(!err); + assert.equal(v,p2p.hash); + return done1(); + }); + }); + }); +}); + + diff --git a/test/integration/addr.js b/test/integration/addr.js new file mode 100644 index 0000000..70d80fd --- /dev/null +++ b/test/integration/addr.js @@ -0,0 +1,146 @@ +#!/usr/bin/env node + +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var assert = require('assert'), + fs = require('fs'), + Address = require('../../app/models/Address'), + TransactionDb = require('../../lib/TransactionDb').default(), + addrValid = JSON.parse(fs.readFileSync('test/integration/addr.json')), + utxoValid = JSON.parse(fs.readFileSync('test/integration/utxo.json')); + +var should = require('chai'); + +var txDb; +describe('Address balances', function() { + this.timeout(5000); + + before(function(c) { + txDb = TransactionDb; + + var l =addrValid.length; + var i =0; + addrValid.forEach(function(v) { + TransactionDb.deleteCacheForAddress(v.addr, function() { + if (++i===l) return c(); + }); + }); + }); + + addrValid.forEach(function(v) { + if (v.disabled) { + console.log(v.addr + ' => disabled in JSON'); + } else { + it('Address info for: ' + v.addr, function(done) { + var a = new Address(v.addr, txDb); + a.update(function(err) { + if (err) done(err); + v.addr.should.equal(a.addrStr); + + if (v.unconfirmedTxApperances) + a.unconfirmedTxApperances.should.equal(v.unconfirmedTxApperances || 0, 'unconfirmedTxApperances'); + if (v.unconfirmedBalanceSat) + a.unconfirmedBalanceSat.should.equal(v.unconfirmedBalanceSat || 0, 'unconfirmedBalanceSat'); + if (v.txApperances) + a.txApperances.should.equal(v.txApperances, 'txApperances'); + + if (v.totalReceived) a.totalReceived.should.equal(v.totalReceived,'totalReceived'); + if (v.totalSent) assert.equal(v.totalSent, a.totalSent, 'send: ' + a.totalSent); + + if (v.balance) assert.equal(v.balance, a.balance, 'balance: ' + a.balance); + + if (v.transactions) { + + v.transactions.forEach(function(tx) { + a.transactions.should.include(tx); + }); + } + done(); + }); + }); + + it('Address info (cache) for: ' + v.addr, function(done) { + var a = new Address(v.addr, txDb); + a.update(function(err) { + if (err) done(err); + v.addr.should.equal(a.addrStr); + a.unconfirmedTxApperances.should.equal(v.unconfirmedTxApperances || 0, 'unconfirmedTxApperances'); + a.unconfirmedBalanceSat.should.equal(v.unconfirmedBalanceSat || 0, 'unconfirmedBalanceSat'); + if (v.txApperances) + a.txApperances.should.equal(v.txApperances, 'txApperances'); + + if (v.totalReceived) a.totalReceived.should.equal(v.totalReceived,'totalReceived'); + if (v.totalSent) assert.equal(v.totalSent, a.totalSent, 'send: ' + a.totalSent); + if (v.balance) assert.equal(v.balance, a.balance, 'balance: ' + a.balance); + done(); + },{txLimit:0}); + }); + } + }); + +}); + +//tested against https://api.biteasy.com/testnet/v1/addresses/2N1pLkosf6o8Ciqs573iwwgVpuFS6NbNKx5/unspent-outputs?per_page=40 +describe('Address unspent', function() { + + before(function(c) { + txDb = TransactionDb; + var l = utxoValid.length; + var d=0; + + utxoValid.forEach(function(v) { + //console.log('Deleting cache for', v.addr); //TODO + txDb.deleteCacheForAddress(v.addr,function(){ + if (d++ == l-1) return c(); + }); + }); + }); + + + utxoValid.forEach(function(v) { + if (v.disabled) { + console.log(v.addr + ' => disabled in JSON'); + } else { + it('Address unspent for: ' + v.addr, function(done) { + this.timeout(2000); + var a = new Address(v.addr, txDb); + a.update(function(err) { + if (err) done(err); + assert.equal(v.addr, a.addrStr); + if (v.length) a.unspent.length.should.equal(v.length, 'Unspent count'); + if (v.tx0id) { + var x=a.unspent.filter(function(x){ + return x.txid === v.tx0id; + }); + assert(x,'found output'); + x.length.should.equal(1,'found output'); + x[0].scriptPubKey.should.equal(v.tx0scriptPubKey,'scriptPubKey'); + x[0].amount.should.equal(v.tx0amount,'amount'); + } + done(); + }, {onlyUnspent:1}); + }); + it('Address unspent (cached) for: ' + v.addr, function(done) { + this.timeout(2000); + var a = new Address(v.addr, txDb); + a.update(function(err) { + if (err) done(err); + assert.equal(v.addr, a.addrStr); + if (v.length) a.unspent.length.should.equal(v.length, 'Unspent count'); + if (v.tx0id) { + var x=a.unspent.filter(function(x){ + return x.txid === v.tx0id; + }); + assert(x,'found output'); + x.length.should.equal(1,'found output'); + x[0].scriptPubKey.should.equal(v.tx0scriptPubKey,'scriptPubKey'); + x[0].amount.should.equal(v.tx0amount,'amount'); + } + done(); + }, {onlyUnspent:1}); + }); + } + }); +}); diff --git a/test/integration/addr.json b/test/integration/addr.json new file mode 100644 index 0000000..c973ce6 --- /dev/null +++ b/test/integration/addr.json @@ -0,0 +1,138 @@ +[ + { + "addr": "mm8CDNJnk8PtQPEtTns2ah5nmxN63ENHtY", + "balance": 1.4, + "txApperances": 3 + }, + + { + "addr": "mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS", + "balance": 0, + "totalReceived": 50, + "totalSent": 50.0, + "txApperances": 2 + }, + { + "addr": "muyg1K5WsHkfMVCkUXU2y7Xp5ZD6RGzCeH", + "balance": 0.38571339, + "totalReceived": 0.38571339, + "totalSent": 0, + "txApperances": 1 + }, + { + "addr": "mhPEfAmeKVwT7arwMYbhwnL2TfwuWbP4r4", + "totalReceived": 1069, + "txApperances": 13, + "balance": 1065, + "totalSent": 4 + }, + { + "addr": "n47CfqnKWdNwqY1UWxTmNJAqYutFxdH3zY", + "balance": 0, + "totalReceived":26.4245, + "totalSent": 26.4245, + "txApperances": 4 + }, + { + "addr": "mzSyyXgofoBxpr6gYcU3cV345G8hJpixRd", + "balance": 0, + "totalReceived":3.9775, + "totalSent": 3.9775, + "txApperances": 2 + }, + { + "addr": "mgqvRGJMwR9JU5VhJ3x9uX9MTkzTsmmDgQ", + "txApperances": 27, + "balance": 0, + "totalReceived": 54.81284116 + }, + { + "disabled": 1, + "addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29", + "balance": 1363.14677867, + "totalReceived": 1363.14677867, + "totalSent": 0, + "txApperances": 7947, + "unconfirmedTxApperances": 5, + "unconfirmedBalanceSat": 149174913 + }, + { + "addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H", + "txApperances": 7166, + "balance": 6513, + "totalReceived": 357130.17644359, + "totalSent": 350617.17644359 + }, + { + "addr": "mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM", + "txApperances": 2, + "balance": 0, + "totalReceived": 0.01979459, + "totalSent": 0, + "transactions": [ "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5" ] + }, + { + "addr": "mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5", + "balance": 5524.61806422, + "totalReceived": 12157.65075053, + "totalSent": 6633.03268631, + "txApperances": 443, + "transactions": [ + "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5", + "f6e80d4fd1a2377406856c67d0cee5ac7e5120993ff97e617ca9aac33b4c6b1e", + "bc27f31caae86750b126d9b09e969362b85b7c15f41421387d682064544bf7e7", + "2cd6a1cb26880276fbc9851396f1bd8081cb2b9107ff6921e8fd65ed2df3df79", + "8bea41f573bccb7b648bc0b1bbfeba8a96da05b1d819ff4a33d39fbcd334ecfd", + "cb0d55c37acc57f759255193673e13858b5ab3d8fdfa7ee8b25f9964bdaa11e3", + "7b007aeace2299d27b6bb6c24d0a8040d6a87e4c2601216c34d226462b75f915", + "a9f40fbaecd2b28a05405e28b95566d7b3bd8ac38a2853debd72517f2994c6fc", + "4123255b7678e37c168b9e929927760bc5d9363b0c78ec61a7b4a78b2a07adab", + "cb3760529c2684c32047a2fddf0e2534c9241e5d72011aac4a8982e0c7b46df3", + "e8d00d8cc744381233dbc95e2d657345084dfb6df785b81285183f4c89b678d4", + "7a748364255c5b64979d9d3da35ea0fbef0114e0d7f96fccd5bea76f6d19f06b", + "d0b7e087040f67ef9bd9f21ccf53d1b5410400351d949cabf127caf28a6e7add", + "209f97873265652b83922921148cad92d7e048c6822e4864e984753e04181470", + "3a4af7755d3061ecced2f3707c2623534104f08aa73a52ca243d7ddecf5fe86d", + "4a4b5c8d464a77814ed35a37e2a28e821d467a803761427c057f67823309b725", + "d85f5265618fb694c3ea3ca6f73eba93df8a644bc1c7286cec2fbc2fbf7d895e", + "0d2c778ed9976b52792c941cac126bda37d3b1453082022d5e36ac401be3b249", + "daf03d666047ca0b5340b4a0027f8562b7c5bac87dca3727093b5393176a541a", + "a0dc03a870e589ea51e3d3a8aed0d34f4f1ae6844acad26dae48fe523b26e764", + "3df1a50e2e5d8525f04bd21a66bad824364a975449fa24fd5c2537d0f713919b", + "7bc26c1f3b4ab5ca57677593d28d13bff468a658f4d5efc379c1612554cf668e", + "ded4cbc9c52fd5599b6a93f89a79cde9aeb5a7f8f56732bb67ae9554325b3666", + "91224a219196a3f6e6f40ad2137b13fe54109e57aaed7527ea34aa903e6b8313", + "ee899a182bbb75e98ef14d83489e631dd66a8c5059dc8255692dd8ca9efba01f", + "0a61590c7548bd4f6a0df1575b268057e5e3e295a44eaeeb1dfbd01332c585ed", + "d56c22950ad2924f404b5b0baa6e49b0df1aaf09d1947842aed9d0178958eb9d", + "c6b5368c5a256141894972fbd02377b3894aa0df7c35fab5e0eca90de064fdc1", + "158e1f9c3f8ec44e88052cadef74e8eb99fbad5697d0b005ba48c933f7d96816", + "7f6191c0f4e3040901ef0d5d6e76af4f16423061ca1347524c86205e35d904d9", + "2c2e20f976b98a0ca76c57eca3653699b60c1bd9503cc9cc2fb755164a679a26", + "59bc81733ff0eaf2b106a70a655e22d2cdeff80ada27b937693993bf0c22e9ea", + "7da38b66fb5e8582c8be85abecfd744a6de89e738dd5f3aaa0270b218ec424eb", + "393d51119cdfbf0a308c0bbde2d4c63546c0961022bad1503c4bbaed0638c837", + "4518868741817ae6757fd98de27693b51fad100e89e5206b9bbf798aeebb804c", + "c58bce14de1e3016504babd8bbe8175207d75074134a2548a71743fa3e56c58d", + "6e69ec4a97515a8fd424f123a5fc1fdfd3c3adcd741292cbc09c09a2cc433bea", + "0e15f2498362050e5ceb6157d0fbf820fdcaf936e447207d433ee7701d7b99c2", + "a3789e113041db907a1217ddb5c3aaf0eff905cc3d913e68d977e1ab4d19acea", + "80b460922faf0ad1e8b8a55533654c9a9f3039bfff0fff2bcf8536b8adf95939" + ] + }, + { + "addr": "mtA6woo1wjCeu1dLkWgpSD3tRnRfrHt3FL", + "balance": 349.845, + "totalReceived": 349.845, + "totalSent": 0, + "txApperances": 13, + "transactions": [ + "794eafc0ad68a3576034eb137d7d20d3bdf1777ecf27e0e20e96e1adcfc66659", + "0130721f29f50b773858c3c9081678bdddebcd18078c5fa2436d979b54ed5cef", + "fb1024947b48d90255aedd3f0f1df3673a7e98d06346bb2ac89b116aa19c5db4" + ] + } + +] + + diff --git a/test/integration/addrCache.js b/test/integration/addrCache.js new file mode 100644 index 0000000..815e76e --- /dev/null +++ b/test/integration/addrCache.js @@ -0,0 +1,199 @@ +#!/usr/bin/env node + +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var assert = require('assert'), + fs = require('fs'), + Address = require('../../app/models/Address'), + TransactionDb = require('../../lib/TransactionDb').default(), + addrValid = JSON.parse(fs.readFileSync('test/integration/addr.json')), + utxoValid = JSON.parse(fs.readFileSync('test/integration/utxo.json')); + +var should = require('chai'); + +var txDb; + + +describe('Address cache ', function() { + this.timeout(5000); + + before(function(c) { + txDb = TransactionDb; + txDb.deleteCacheForAddress('muAt5RRqDarPFCe6qDXGZc54xJjXYUyepG',function(){ + txDb.deleteCacheForAddress('mt2AzeCorSf7yFckj19HFiXJgh9aNyc4h3',function(){ + txDb.deleteCacheForAddress('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk',c); + }); + + }); + }); + + + + it('cache case 1 w/o cache', function(done) { + var a = new Address('muAt5RRqDarPFCe6qDXGZc54xJjXYUyepG', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0, 'balance'); + a.totalReceived.should.equal(19175, 'totalReceived'); + a.txApperances.should.equal(2, 'txApperances'); + return done(); + }); + }); + it('cache case 1 w cache', function(done) { + var a = new Address('muAt5RRqDarPFCe6qDXGZc54xJjXYUyepG', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0, 'balance'); + a.totalReceived.should.equal(19175, 'totalReceived'); + a.txApperances.should.equal(2, 'txApperances'); + return done(); + }); + }); + + + + it('cache case 2 w/o cache', function(done) { + var a = new Address('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0.23, 'balance'); + a.totalReceived.should.equal(0.23, 'totalReceived'); + a.txApperances.should.equal(1, 'txApperances'); + return done(); + }); + }); + + it('cache case 2 w cache', function(done) { + var a = new Address('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0.23, 'balance'); + a.totalReceived.should.equal(0.23, 'totalReceived'); + a.txApperances.should.equal(1, 'txApperances'); + return done(); + }); + }); + + it('cache case 2 unspent wo cache', function(done) { + txDb.deleteCacheForAddress('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk',function() { + var a = new Address('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk', txDb); + a.update(function(err) { + if (err) done(err); + a.unspent.length.should.equal(1); + a.unspent[0].scriptPubKey.should.equal('a914a1d5be9f72224b5e83d00d7f5b9b674d456c573f87'); + a.unspent[0].confirmations.should.be.above(15000); + a.unspent[0].confirmationsFromCache.should.equal(false); + a.update(function(err) { + a.balance.should.equal(0.23, 'balance'); + a.totalReceived.should.equal(0.23, 'totalReceived'); + a.txApperances.should.equal(1, 'txApperances'); + return done(); + }); + }, {onlyUnspent:1}); + }); + }); + + it('cache case 2 unspent w cache', function(done) { + + var a = new Address('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk', txDb); + a.update(function(err) { + if (err) done(err); + a.unspent.length.should.equal(1); + a.unspent[0].confirmationsFromCache.should.equal(true); + a.unspent[0].confirmations.should.equal(6); + a.unspent[0].scriptPubKey.should.equal('a914a1d5be9f72224b5e83d00d7f5b9b674d456c573f87'); + a.update(function(err) { + a.balance.should.equal(0.23, 'balance'); + a.totalReceived.should.equal(0.23, 'totalReceived'); + a.txApperances.should.equal(1, 'txApperances'); + return done(); + }); + + }, {onlyUnspent:1}); + }); + + + + it('cache case 3 w/o cache', function(done) { + var a = new Address('mt2AzeCorSf7yFckj19HFiXJgh9aNyc4h3', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0, 'balance'); + a.totalReceived.should.equal(1376000, 'totalReceived'); + a.txApperances.should.equal(8003, 'txApperances'); + return done(); + }); + },1); + it('cache case 4 w cache', function(done) { + var a = new Address('mt2AzeCorSf7yFckj19HFiXJgh9aNyc4h3', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0, 'balance'); + a.totalReceived.should.equal(1376000, 'totalReceived'); + a.txApperances.should.equal(8003, 'txApperances'); + return done(); + },{txLimit:0}); + }); + it('cache case 4 w ignore cache', function(done) { + var a = new Address('mt2AzeCorSf7yFckj19HFiXJgh9aNyc4h3', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0, 'balance'); + a.totalReceived.should.equal(1376000, 'totalReceived'); + a.txApperances.should.equal(8003, 'txApperances'); + return done(); + },{txLimit:0, ignoreCache:1}); + }); + + it('cache case 5 unspent w cache', function(done) { + var a = new Address('2NBuTjjZrURxLaMyPUu2sJwNrtpt7GtPX2p', txDb); + a.update(function(err) { + if (err) done(err); + a.unspent.length.should.equal(1); + a.unspent[0].confirmationsFromCache.should.equal(true); + a.unspent[0].confirmations.should.equal(6); + return done(); + }, {onlyUnspent:1}); + }); + + it('cache fix broken cases', function(done) { + txDb._db.put('txa2-2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk-9998599199253-16c0287dbea7e323431caff7f7e490da6de66530717f86f8dae9549b3355301a-0', '23000000:1399232338:0:a914a1d5be9f72224b5e83d00d7f5b9b674d456c573f87', function(){ + var a = new Address('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk', txDb); + a.update(function(err) { + if (err) done(err); + a.balance.should.equal(0.23, 'balance'); + a.totalReceived.should.equal(0.23, 'totalReceived'); + a.txApperances.should.equal(1, 'txApperances'); + a.transactions.length.should.equal(1); + a.transactions[0].should.equal('16c0287dbea7e323431caff7f7e490da6de66530717f86f8dae9549b3355301a'); + return done(); + }); + }); + }); + it('cache fix broken cases 2)', function(done) { + txDb._db.put('txa2-2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk-9998599199253-16c0287dbea7e323431caff7f7e490da6de66530717f86f8dae9549b3355301a-0', '23000000:1399232338:0:a914a1d5be9f72224b5e83d00d7f5b9b674d456c573f87', function(){ + var a = new Address('2N7zvqQTUYFfhYvFs1NEzureMLvhwk5FSsk', txDb); + a.update(function(err) { + if (err) done(err); + a.unspent.length.should.equal(1); + a.unspent[0].confirmationsFromCache.should.equal(false); + a.unspent[0].confirmations.should.above(6); + a.unspent[0].scriptPubKey.should.equal('a914a1d5be9f72224b5e83d00d7f5b9b674d456c573f87'); + a.update(function(err) { + if (err) done(err); + a.unspent.length.should.equal(1); + a.unspent[0].confirmationsFromCache.should.equal(true); + a.unspent[0].confirmations.should.equal(6); + a.unspent[0].scriptPubKey.should.equal('a914a1d5be9f72224b5e83d00d7f5b9b674d456c573f87'); + return done(); + }, {onlyUnspent:1}); + }, {onlyUnspent:1}); + }); + }); + + +}); + + diff --git a/test/integration/block.js b/test/integration/block.js new file mode 100644 index 0000000..0d6e5e4 --- /dev/null +++ b/test/integration/block.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + + +var TESTING_BLOCK = '000000000185678d3d7ecc9962c96418174431f93fe20bf216d5565272423f74'; + +var +assert = require('assert'), + // config = require('../../config/config'), + BlockDb = require('../../lib/BlockDb').default(); + +var bDb; + +describe('BlockDb fromHashWithInfo', function() { + + before(function(c) { + bDb = BlockDb; + return c(); + }); + + it('should poll block\'s info from bitcoind', function(done) { + bDb.fromHashWithInfo(TESTING_BLOCK, function(err, b2) { + if (err) done(err); + assert.equal(b2.hash, TESTING_BLOCK, 'hash'); + assert.equal(b2.info.hash, TESTING_BLOCK, 'info.hash'); + assert.equal(b2.info.height, 71619); + assert.equal(b2.info.nonce, 3960980741); + assert.equal(b2.info.bits, '1c018c14'); + assert.equal(b2.info.merkleroot, '9a326cb524aa2e5bc926b8c1f6de5b01257929ee02158054b55aae93a55ec9dd'); + assert.equal(b2.info.nextblockhash, '000000000121941b3b10d76fbe67b35993df91eb3398e9153e140b4f6213cb84'); + done(); + }); + }); + it('return true in has', function(done) { + bDb.has(TESTING_BLOCK, function(err, has) { + assert.equal(has, true); + done(); + }); + }); +}); diff --git a/test/integration/blockExtractor.js b/test/integration/blockExtractor.js new file mode 100644 index 0000000..a42ccd4 --- /dev/null +++ b/test/integration/blockExtractor.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + + + +var assert = require('assert'), + config = require('../../config/config'), + BlockExtractor = require('../../lib/BlockExtractor'), + networks = require('reddcore/networks'), + util = require('reddcore/util/util'); + +var should = require('chai'); +//var txItemsValid = JSON.parse(fs.readFileSync('test/model/txitems.json')); + +describe('BlockExtractor', function(){ + + var be = new BlockExtractor(config.bitcoind.dataDir, config.network); + + var network = config.network === 'testnet' ? networks.testnet: networks.livenet; + + it('should glob block files ', function(done) { + assert(be.files.length>0); + done(); + }); + + var lastTs; + + it('should read genesis block ', function(done) { + be.getNextBlock(function(err,b) { + assert(!err); + var genesisHashReversed = new Buffer(32); + network.genesisBlock.hash.copy(genesisHashReversed); + var genesis = util.formatHashFull(network.genesisBlock.hash); + + assert.equal(util.formatHashFull(b.hash),genesis); + assert.equal(b.nounce,network.genesisBlock.nounce); + assert.equal(b.timestamp,network.genesisBlock.timestamp); + assert.equal(b.merkle_root.toString('hex'),network.genesisBlock.merkle_root.toString('hex')); + + lastTs = b.timestamp; + done(); + }); + }); + + it('should read next '+config.network+' block ', function(done) { + be.getNextBlock(function(err,b) { + assert(!err); + // 2nd block of testnet3 + util.formatHashFull(b.hash).should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'); + assert(b.timestamp > lastTs, 'timestamp > genesis_ts'); + done(); + }); + }); + + it.skip('should read 100000 blocks with no error ', function(done) { + + var i=0; + while(i++<100000) { + be.getNextBlock(function(err,b) { + assert(!err,err); + assert(lastTs < b.timestamp, 'genesisTS < b.timestamp: ' + lastTs + '<' + b.timestamp + ":" + i); + if(i % 1000 === 1) process.stdout.write('.'); + if(i === 100000) done(); + }); + } + }); + + + +}); + + + diff --git a/test/integration/blocklist.js b/test/integration/blocklist.js new file mode 100644 index 0000000..19b104a --- /dev/null +++ b/test/integration/blocklist.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var TESTING_BLOCK0 = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206'; +var TESTING_BLOCK1 = '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943'; +var START_TS = 1; +var END_TS = '1296688928~'; // 2/2/2011 23:23PM + +var assert = require('assert'), + BlockDb = require('../../lib/BlockDb').default(); + +var bDb; + +describe('BlockDb getBlocksByDate', function(){ + + + before(function(c) { + bDb = BlockDb; + return c(); + }); + + it('Get Hash by Date', function(done) { + + bDb.getBlocksByDate(START_TS, END_TS, 2, function(err, list) { + if (err) done(err); + assert(list, 'returns list'); + assert.equal(list.length,2, 'list has 2 items'); + assert.equal(list[1].hash, TESTING_BLOCK0); + assert.equal(list[0].hash, TESTING_BLOCK1); + done(); + }); + }); +}); + diff --git a/test/integration/messages.js b/test/integration/messages.js new file mode 100644 index 0000000..d51ccba --- /dev/null +++ b/test/integration/messages.js @@ -0,0 +1,90 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var assert = require('assert'), + config = require('../../config/config'), + messages = require('../../app/controllers/messages'), + correctMessage = 'test2', + correctAddress, + correctSignature; + +if(config.network === 'livenet') { + correctAddress = '16Q7eRty2LrpAWvP3VTtaXXCMZj2v4xm57', + correctSignature = 'HERpcxkyOezkBPPwvUUAaxYXR/9X/8eyVjp8WKGYl7Aw8' + + 'pMsiMXDWXf8G1t/SOUEWy94I+KA/SrBKYs2LfIHA0Q='; +} else { + correctAddress = 'mhtJo5nZLcreM5Arrf8EDABpCevp2MfmCW', + correctSignature = 'G/y2UhjZ4qBPLQGmOhl/4p/EIwTHIO1iq95kPxDk9RjYr' + + '1JKL6dsCSuhXat7VLTGwAM3PdgRh/jwGxi6x6dNeSE='; +} + +function createMockReq(body) { + // create a simplified mock of express' request object, suitable for the + // needs of test cases in this file + return { + body: body, + param: function(name) { + return this.body[name]; + } + }; +} + +describe('messages.verify', function() { + + it('should return true with correct message', function(done) { + var mockReq = createMockReq({ + address: correctAddress, + signature: correctSignature, + message: correctMessage + }); + var mockRes = { + json: function(data) { + assert.deepEqual(data, { + result: true, + }); + done(); + } + }; + messages.verify(mockReq, mockRes); + }); + + it('should return false with incorrect message', function(done) { + var mockReq = createMockReq({ + address: correctAddress, + signature: correctSignature, + message: 'NOPE' + }); + var mockRes = { + json: function(data) { + assert.deepEqual(data, { + result: false, + }); + done(); + } + }; + + messages.verify(mockReq, mockRes); + }); + + it('should return error with incorrect parameters', function(done) { + var mockReq = createMockReq({ + address: correctAddress, + message: correctMessage + }); + var mockRes = { + status: function(code) { + assert.equal(code, 400); + return this; + }, + send: function(data) { + assert.ok(data.match(/^Missing parameters/), + "Match not found, got '" + data + "' instead") + done(); + } + }; + messages.verify(mockReq, mockRes); + }); + +}); diff --git a/test/integration/nodecheck.js b/test/integration/nodecheck.js new file mode 100644 index 0000000..2e2973c --- /dev/null +++ b/test/integration/nodecheck.js @@ -0,0 +1,17 @@ +'use strict'; + +var BlockDb = require('../../lib/BlockDb').default(); +var height_needed = 180000; +var bDb = BlockDb; + +var expect = require('chai').expect; + +describe('Node check', function() { + it('should contain block ' + height_needed, function(done) { + bDb.blockIndex(height_needed, function(err, b) { + expect(err).to.equal(null); + expect(b).to.not.equal(null); + done(); + }); + }); +}); diff --git a/test/integration/spent.json b/test/integration/spent.json new file mode 100644 index 0000000..351661b --- /dev/null +++ b/test/integration/spent.json @@ -0,0 +1,32 @@ +{ + "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237": [ + { + "txid": "bcd8da8ee847da377f8aaca92502c05e5f914c6a2452753146013b0e642a25a0", + "n": 0 + }, + { + "txid": "deb7bddc67e936ae49b97a97885d29e60afc6f6784f6d871f2904614a67250f5", + "n": 0 + } + ], + "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee": [ + { + "txid": "c0c46d6be0183f52c88afe2d649800ecdaa7594ee390c77bafbd06322e6c823d", + "n": 11 + }, + { + "txid": "d60e980419c5a8abd629fdea5032d561678b62e23b3fdba62b42f410c5a29560", + "n": 1 + } + ], + "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b": [ + { + "txid": "aa21822f1a69bc54e5a4ab60b25c09503702a821379fd2dfbb696b8ada4ce5b9", + "n": 0 + }, + { + "txid": "a33bd24a47ab6f23758ed09e05716f809614f2e280e5a05a317ec6d839e81225", + "n": 1 + } + ] +} diff --git a/test/integration/status.js b/test/integration/status.js new file mode 100644 index 0000000..d912fa0 --- /dev/null +++ b/test/integration/status.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var assert = require('assert'), + Status = require('../../app/models/Status'); + +describe('Status', function(){ + + it('getInfo', function(done) { + var d = new Status(); + + d.getInfo(function(err) { + if (err) done(err); + assert.equal('number', typeof d.info.difficulty); + done(); + }); + }); + + it('getDifficulty', function(done) { + var d = new Status(); + + d.getDifficulty(function(err) { + if (err) done(err); + assert.equal('number', typeof d.difficulty); + done(); + }); + }); + + it('getLastBlockHash', function(done) { + var d = new Status(); + + d.getLastBlockHash(function(err) { + if (err) done(err); + assert.equal('string', typeof d.lastblockhash); + done(); + }); + }); + + +}); + diff --git a/test/integration/txitems.json b/test/integration/txitems.json new file mode 100644 index 0000000..283e873 --- /dev/null +++ b/test/integration/txitems.json @@ -0,0 +1,62 @@ +[ + { + "disabled": 1, + "txid": "75c5ffe6dc2eb0f6bd011a08c041ef115380ccd637d859b379506a0dca4c26fc" + }, + { + "txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237", + "toRm": [ + "txs-86a03cac7d87f596008c6d5a8d3fd8b88842932ea6f0337673eda16f6b472f7f-0", + "txs-bcd8da8ee847da377f8aaca92502c05e5f914c6a2452753146013b0e642a25a0-0" + ], + "items": [ + { + "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", + "value_sat": 134574000, + "index": 0 + }, + { + "addr": "n28wb1cRGxPtfmsenYKFfsvnZ6kRapx3jF", + "value_sat": 31600000, + "index": 1 + } + ] + }, + { + "txid": "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee", + "toRm": [ + "txs-01621403689cb4a95699a3dbae029d7031c5667678ef14e2054793954fb27917-0" + ], + "items": [ + { + "addr": "mhfQJUSissP6nLM5pz6DxHfctukrrLct2T", + "value_sat": 19300000, + "index": 0 + }, + { + "addr": "mzcDhbL877ES3MGftWnc3EuTSXs3WXDDML", + "value_sat": 21440667, + "index": 1 + } + ] + }, + { + "txid": "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b", + "toRm": [ + "txs-2d7b680fb06e4d7eeb65ca49ac7522276586e0090b7fe662fc708129429c5e6a-0" + ], + "items": [ + { + "addr": "mhqyL1nDQDo1WLH9qH8sjRjx2WwrnmAaXE", + "value_sat": 1327746, + "index": 0 + }, + { + "addr": "mkGrySSnxcqRbtPCisApj3zXCQVmUUWbf1", + "value_sat": 1049948, + "index": 1 + } + ] + } + +] diff --git a/test/integration/utxo.json b/test/integration/utxo.json new file mode 100644 index 0000000..f45dfb6 --- /dev/null +++ b/test/integration/utxo.json @@ -0,0 +1,16 @@ +[ + { + "addr": "muyg1K5WsHkfMVCkUXU2y7Xp5ZD6RGzCeH", + "length": 1, + "tx0id": "eeabc70063d3f266e190e8735bc4599c811d3a79d138da1364e88502069b029c", + "tx0scriptPubKey": "76a9149e9f6515c70db535abdbbc983c7d8d1bff6c20cd88ac", + "tx0amount": 0.38571339 + }, + { + "addr": "2N1pLkosf6o8Ciqs573iwwgVpuFS6NbNKx5", + "length": 13, + "tx0id": "b9cc61b55814a0f972788e9025db1013157f83716e08239026a156efe892a05c", + "tx0scriptPubKey": "a9145e0461e38796367580305e3615fc1b70e4c3307687", + "tx0amount": 0.001 + } +] diff --git a/test/lib/PeerSync.js b/test/lib/PeerSync.js new file mode 100644 index 0000000..9c29b11 --- /dev/null +++ b/test/lib/PeerSync.js @@ -0,0 +1,49 @@ +'use strict'; +var chai = require('chai'), + expect = chai.expect, + sinon = require('sinon'); + +var PeerSync = require('../../lib/PeerSync.js'); +describe('PeerSync', function() { + var ps; + + beforeEach(function(done) { + ps = new PeerSync(); + done(); + }); + afterEach(function() { + ps.close(); + }); + + + describe('#handleInv()', function() { + var inv_info = { + message: { + invs: [] + }, + conn: { + sendGetData: sinon.spy() + } + }; + it('should return with no errors', function() { + expect(function() { + ps.handleInv(inv_info); + }).not.to.throw(Error); + }); + it('should call sendGetData', function() { + ps.handleInv(inv_info); + expect(inv_info.conn.sendGetData.calledTwice).to.be.ok; + }); + }); + + describe('#run()', function() { + it('should setup peerman', function() { + var startSpy = sinon.spy(ps.peerman, 'start'); + var onSpy = sinon.spy(ps.peerman, 'on'); + ps.run(); + + expect(startSpy.called).to.be.ok; + expect(onSpy.called).to.be.ok; + }); + }); +}); diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..a9caeb4 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,5 @@ +--require should +-R spec +--ui bdd +--recursive + diff --git a/test/test.CredentialStore.js b/test/test.CredentialStore.js new file mode 100644 index 0000000..608453d --- /dev/null +++ b/test/test.CredentialStore.js @@ -0,0 +1,101 @@ +'use strict'; + +var chai = require('chai'); +var assert = require('assert'); +var sinon = require('sinon'); +var should = chai.should; +var expect = chai.expect; + +describe('credentialstore test', function() { + + var globalConfig = require('../config/config'); + var leveldb_stub = sinon.stub(); + leveldb_stub.post = sinon.stub(); + leveldb_stub.get = sinon.stub(); + var plugin = require('../plugins/credentialstore'); + var express_mock = null; + var request = null; + var response = null; + + beforeEach(function() { + + express_mock = sinon.stub(); + express_mock.post = sinon.stub(); + express_mock.get = sinon.stub(); + + plugin.init(express_mock, {db: leveldb_stub}); + + request = sinon.stub(); + request.on = sinon.stub(); + request.param = sinon.stub(); + response = sinon.stub(); + response.send = sinon.stub(); + response.status = sinon.stub(); + response.json = sinon.stub(); + response.end = sinon.stub(); + }); + + it('initializes correctly', function() { + assert(plugin.db === leveldb_stub); + assert(express_mock.post.calledWith( + globalConfig.apiPrefix + '/credentials', plugin.post + )); + assert(express_mock.get.calledWith( + globalConfig.apiPrefix + '/credentials/:username', plugin.get + )); + }); + + it('writes a message correctly', function() { + + var data = 'username=1&secret=2&record=3'; + request.on.onFirstCall().callsArgWith(1, data); + request.on.onFirstCall().returnsThis(); + request.on.onSecondCall().callsArg(1); + leveldb_stub.put = sinon.stub(); + + leveldb_stub.put.onFirstCall().callsArg(2); + response.json.returnsThis(); + + plugin.post(request, response); + + assert(leveldb_stub.put.firstCall.args[0] === 'credentials-store-12'); + assert(leveldb_stub.put.firstCall.args[1] === '3'); + assert(response.json.calledWith({success: true})); + }); + + it('retrieves a message correctly', function() { + + request.param.onFirstCall().returns('username'); + request.param.onSecondCall().returns('secret'); + + var returnValue = '!@#$%'; + leveldb_stub.get.onFirstCall().callsArgWith(1, null, returnValue); + response.send.returnsThis(); + + plugin.get(request, response); + + assert(leveldb_stub.get.firstCall.args[0] === 'credentials-store-usernamesecret'); + assert(response.send.calledWith(returnValue)); + assert(response.end.calledOnce); + }); + + it('fails with messages that are too long', function() { + + response.writeHead = sinon.stub(); + request.connection = {}; + request.connection.destroy = sinon.stub(); + var data = 'blob'; + for (var i = 0; i < 2048; i++) { + data += '----'; + } + request.on.onFirstCall().callsArgWith(1, data); + request.on.onFirstCall().returnsThis(); + response.writeHead.returnsThis(); + + plugin.post(request, response); + + assert(response.writeHead.calledWith(413, {'Content-Type': 'text/plain'})); + assert(response.end.calledOnce); + assert(request.connection.destroy.calledOnce); + }); +}); diff --git a/test/test.EmailStore.js b/test/test.EmailStore.js new file mode 100644 index 0000000..608e649 --- /dev/null +++ b/test/test.EmailStore.js @@ -0,0 +1,428 @@ +'use strict'; + +var chai = require('chai'); +var assert = require('assert'); +var sinon = require('sinon'); +var crypto = require('crypto'); +var bitcore = require('bitcore'); +var logger = require('../lib/logger').logger; +var should = chai.should; +var expect = chai.expect; + +logger.transports.console.level = 'non'; + +describe('emailstore test', function() { + + var globalConfig = require('../config/config'); + + // Mock components of plugin + var leveldb_stub = sinon.stub(); + leveldb_stub.put = sinon.stub(); + leveldb_stub.get = sinon.stub(); + leveldb_stub.remove = sinon.stub(); + var email_stub = sinon.stub(); + email_stub.sendMail = sinon.stub(); + + var cryptoMock = { + randomBytes: sinon.stub() + }; + + var plugin = require('../plugins/emailstore'); + var express_mock = null; + var request = null; + var response = null; + + beforeEach(function() { + + plugin.init({ + db: leveldb_stub, + emailTransport: email_stub, + crypto: cryptoMock + }); + + request = sinon.stub(); + request.on = sinon.stub(); + request.param = sinon.stub(); + response = sinon.stub(); + response.send = sinon.stub(); + response.status = sinon.stub(); + response.json = sinon.stub(); + response.end = sinon.stub(); + response.redirect = sinon.stub(); + }); + + it('initializes correctly', function() { + assert(plugin.db === leveldb_stub); + }); + + describe('database queries', function() { + + describe('exists', function() { + var fakeEmail = 'fake@email.com'; + var fakeEmailKey = 'email-to-passphrase-' + bitcore.util.twoSha256(fakeEmail).toString('hex'); + + beforeEach(function() { + leveldb_stub.get.reset(); + }); + + it('validates that an email is already registered', function(done) { + leveldb_stub.get.onFirstCall().callsArg(1); + + plugin.exists(fakeEmail, function(err, exists) { + leveldb_stub.get.firstCall.args[0].should.equal(fakeEmailKey); + exists.should.equal(true); + done(); + }); + }); + + it('returns false when an email doesn\'t exist', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, {notFound: true}); + + plugin.exists(fakeEmail, function(err, exists) { + leveldb_stub.get.firstCall.args[0].should.equal(fakeEmailKey); + exists.should.equal(false); + done(); + }); + }); + + it('returns an internal error if database query couldn\'t be made', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, 'error'); + plugin.exists(fakeEmail, function(err, exists) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + }); + + describe('passphrase', function() { + var fakeEmail = 'fake@email.com'; + var fakePassphrase = 'secretPassphrase123'; + + beforeEach(function() { + leveldb_stub.get.reset(); + leveldb_stub.put.reset(); + }); + + it('returns true if passphrase matches', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, null, fakePassphrase); + + plugin.checkPassphrase(fakeEmail, fakePassphrase, function(err, result) { + result.should.equal(true); + done(); + }); + }); + + it('returns false if passphrsase doesn\'t match', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, null, 'invalid passphrase'); + + plugin.checkPassphrase(fakeEmail, fakePassphrase, function(err, result) { + result.should.equal(false); + done(); + }); + }); + + it('returns an internal error if database query couldn\'t be made', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, 'error'); + + plugin.checkPassphrase(fakeEmail, fakePassphrase, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + + it('stores passphrase correctly', function(done) { + leveldb_stub.put.onFirstCall().callsArg(2); + + plugin.savePassphrase(fakeEmail, fakePassphrase, function(err) { + expect(err).to.equal(null); + done(); + }); + }); + + it('doesn\'t store the email in the key', function(done) { + leveldb_stub.put.onFirstCall().callsArg(2); + + plugin.savePassphrase(fakeEmail, fakePassphrase, function(err) { + leveldb_stub.put.firstCall.args[0].should.not.contain(fakeEmail); + done(); + }); + }); + + it('returns internal error on database error', function(done) { + leveldb_stub.put.onFirstCall().callsArgWith(2, 'error'); + + plugin.savePassphrase(fakeEmail, fakePassphrase, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + }); + + describe('saving encrypted data', function() { + var fakeEmail = 'fake@email.com'; + var fakeKey = 'nameForData'; + var fakeRecord = 'fakeRecord'; + var expectedKey = 'emailstore-' + + bitcore.util.twoSha256(fakeEmail + '#' + fakeKey).toString('hex'); + + beforeEach(function() { + leveldb_stub.get.reset(); + leveldb_stub.put.reset(); + }); + + it('saves data under the expected key', function(done) { + leveldb_stub.put.onFirstCall().callsArgWith(2); + + plugin.saveEncryptedData(fakeEmail, fakeKey, fakeRecord, function(err) { + leveldb_stub.put.firstCall.args[0].should.equal(expectedKey); + done(); + }); + }); + + it('fails with INTERNAL_ERROR on database error', function(done) { + leveldb_stub.put.onFirstCall().callsArgWith(2, 'error'); + + plugin.saveEncryptedData(fakeEmail, fakeKey, fakeRecord, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + }); + + describe('creating verification secret', function() { + var sendVerificationEmail = sinon.stub(plugin, 'sendVerificationEmail'); + var fakeEmail = 'fake@email.com'; + var fakeRandom = 'fakerandom'; + var randomBytes = {toString: function() { return fakeRandom; }}; + + beforeEach(function() { + leveldb_stub.get.reset(); + leveldb_stub.put.reset(); + + sendVerificationEmail.reset(); + cryptoMock.randomBytes = sinon.stub(); + cryptoMock.randomBytes.onFirstCall().returns(randomBytes); + }); + + var setupLevelDb = function() { + leveldb_stub.get.onFirstCall().callsArgWith(1, {notFound: true}); + leveldb_stub.put.onFirstCall().callsArg(2); + }; + + it('saves data under the expected key', function(done) { + setupLevelDb(); + + plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) { + leveldb_stub.put.firstCall.args[1].should.equal(fakeRandom); + done(); + }); + }); + it('calls the function to verify the email', function(done) { + setupLevelDb(); + + plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) { + sendVerificationEmail.calledOnce; + done(); + }); + }); + it('returns internal error on put database error', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, {notFound: true}); + leveldb_stub.put.onFirstCall().callsArgWith(2, 'error'); + plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + it('returns internal error on get database error', function(done) { + leveldb_stub.get.onFirstCall().callsArgWith(1, 'error'); + plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) { + err.should.equal(plugin.errors.INTERNAL_ERROR); + done(); + }); + }); + + after(function() { + plugin.sendVerificationEmail.restore(); + }); + }); + }); + + describe('on registration', function() { + + var emailParam = 'email'; + var secretParam = 'secret'; + var keyParam = 'key'; + var recordParam = 'record'; + beforeEach(function() { + var data = ('email=' + emailParam + '&secret=' + secretParam + + '&record=' + recordParam + '&key=' + keyParam); + request.on.onFirstCall().callsArgWith(1, data); + request.on.onFirstCall().returnsThis(); + request.on.onSecondCall().callsArg(1); + response.json.returnsThis(); + }); + + it('should allow new registrations', function() { + plugin.getCredentialsFromRequest = sinon.mock(); + plugin.getCredentialsFromRequest.onFirstCall().returns({ + email: emailParam, + passphrase: secretParam + }); + plugin.exists = sinon.stub(); + plugin.exists.onFirstCall().callsArgWith(1, null, false); + plugin.savePassphrase = sinon.stub(); + plugin.savePassphrase.onFirstCall().callsArg(2); + plugin.saveEncryptedData = sinon.stub(); + plugin.saveEncryptedData.onFirstCall().callsArg(3); + plugin.createVerificationSecretAndSendEmail = sinon.stub(); + plugin.createVerificationSecretAndSendEmail.onFirstCall().callsArg(1); + response.send.onFirstCall().returnsThis(); + + plugin.post(request, response); + + assert(plugin.exists.firstCall.args[0] === emailParam); + assert(plugin.savePassphrase.firstCall.args[0] === emailParam); + assert(plugin.savePassphrase.firstCall.args[1] === secretParam); + assert(plugin.saveEncryptedData.firstCall.args[0] === emailParam); + assert(plugin.saveEncryptedData.firstCall.args[1] === keyParam); + assert(plugin.saveEncryptedData.firstCall.args[2] === recordParam); + assert(plugin.createVerificationSecretAndSendEmail.firstCall.args[0] === emailParam); + }); + + it('should allow to overwrite data', function() { + plugin.getCredentialsFromRequest = sinon.mock(); + plugin.getCredentialsFromRequest.onFirstCall().returns({ + email: emailParam, + passphrase: secretParam + }); + plugin.exists = sinon.stub(); + plugin.exists.onFirstCall().callsArgWith(1, null, true); + plugin.checkPassphrase = sinon.stub(); + plugin.checkPassphrase.onFirstCall().callsArgWith(2, null, true); + plugin.saveEncryptedData = sinon.stub(); + plugin.saveEncryptedData.onFirstCall().callsArg(3); + plugin.createVerificationSecretAndSendEmail = sinon.stub(); + plugin.createVerificationSecretAndSendEmail.onFirstCall().callsArg(1); + response.send.onFirstCall().returnsThis(); + + plugin.post(request, response); + + assert(plugin.exists.firstCall.args[0] === emailParam); + assert(plugin.checkPassphrase.firstCall.args[0] === emailParam); + assert(plugin.checkPassphrase.firstCall.args[1] === secretParam); + assert(plugin.saveEncryptedData.firstCall.args[0] === emailParam); + assert(plugin.saveEncryptedData.firstCall.args[1] === keyParam); + assert(plugin.saveEncryptedData.firstCall.args[2] === recordParam); + assert(plugin.createVerificationSecretAndSendEmail.firstCall.args[0] === emailParam); + }); + }); + + describe('when validating email', function() { + + var email = '1'; + var secret = '2'; + beforeEach(function() { + + request.param.onFirstCall().returns(email); + request.param.onSecondCall().returns(secret); + leveldb_stub.put = sinon.stub(); + leveldb_stub.get = sinon.stub(); + leveldb_stub.remove = sinon.stub(); + leveldb_stub.put.onFirstCall().callsArg(2); + leveldb_stub.remove.onFirstCall().callsArg(1); + response.json.returnsThis(); + }); + + it('should validate correctly an email if the secret matches', function() { + leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret); + response.redirect = sinon.stub(); + + plugin.validate(request, response); + + assert(response.redirect.firstCall.calledWith(plugin.redirectUrl)); + }); + + it('should fail to validate an email if the secret doesn\'t match', function() { + var invalid = '3'; + leveldb_stub.get.onFirstCall().callsArgWith(1, null, invalid); + response.status.returnsThis(); + response.json.returnsThis(); + + plugin.validate(request, response); + + assert(response.status.firstCall.calledWith(plugin.errors.INVALID_CODE.code)); + assert(response.json.firstCall.calledWith({error: 'The provided code is invalid'})); + assert(response.end.calledOnce); + }); + }); + + describe('when retrieving data', function() { + + it('should validate the secret and return the data', function() { + request.param.onFirstCall().returns('email'); + request.param.onSecondCall().returns('key'); + request.param.onThirdCall().returns('secret'); + plugin.retrieveDataByEmailAndPassphrase = sinon.stub(); + plugin.retrieveDataByEmailAndPassphrase.onFirstCall().callsArgWith(3, null, 'encrypted'); + response.send.onFirstCall().returnsThis(); + + plugin.get(request, response); + + assert(request.param.firstCall.args[0] === 'email'); + assert(request.param.secondCall.args[0] === 'key'); + assert(request.param.thirdCall.args[0] === 'secret'); + assert(plugin.retrieveDataByEmailAndPassphrase.firstCall.args[0] === 'email'); + assert(plugin.retrieveDataByEmailAndPassphrase.firstCall.args[1] === 'key'); + assert(plugin.retrieveDataByEmailAndPassphrase.firstCall.args[2] === 'secret'); + assert(response.send.firstCall.args[0] === 'encrypted'); + assert(response.end.calledOnce); + }); + }); + + describe('changing the user password', function() { + + var originalCredentials = plugin.getCredentialsFromRequest; + + beforeEach(function() { + plugin.getCredentialsFromRequest = sinon.mock(); + plugin.getCredentialsFromRequest.onFirstCall().returns({ + email: 'email', + passphrase: 'passphrase' + }); + request.on = sinon.stub(); + request.on.onFirstCall().callsArgWith(1, 'newPassphrase=newPassphrase'); + request.on.onFirstCall().returns(request); + request.on.onSecondCall().callsArg(1); + response.status.onFirstCall().returnsThis(); + plugin.checkPassphrase = sinon.stub(); + plugin.savePassphrase = sinon.stub(); + }); + + it('should validate the previous passphrase', function() { + response.status.onFirstCall().returnsThis(); + response.json.onFirstCall().returnsThis(); + plugin.checkPassphrase.onFirstCall().callsArgWith(2, 'error'); + + plugin.changePassphrase(request, response); + + assert(response.status.calledOnce); + assert(response.json.calledOnce); + assert(response.end.calledOnce); + }); + + it('should change the passphrase', function() { + response.json.onFirstCall().returnsThis(); + plugin.checkPassphrase.onFirstCall().callsArgWith(2, null); + plugin.savePassphrase.onFirstCall().callsArgWith(2, null); + + plugin.changePassphrase(request, response); + assert(response.json.calledOnce); + assert(response.end.calledOnce); + }); + + after(function() { + plugin.getCredentialsFromRequest = originalCredentials; + }); + }); +}); + diff --git a/test/test.MessageDb.js b/test/test.MessageDb.js new file mode 100644 index 0000000..c68c261 --- /dev/null +++ b/test/test.MessageDb.js @@ -0,0 +1,133 @@ +'use strict'; + +var chai = require('chai'); +var should = chai.should; +var expect = chai.expect; + +var levelup = require('levelup'); +var memdown = require('memdown'); +var microtime = require('microtime'); +var MessageDb = require('../lib/MessageDb'); +var bitcore = require('reddcore'); +var SIN = bitcore.SIN; +var Key = bitcore.Key; +var AuthMessage = bitcore.AuthMessage; + +describe('MessageDb', function() { + var opts = { + name: 'test-MessageDb', + db: levelup({ + db: memdown, + sync: true, + valueEncoding: 'json' + }) + }; + it('should be able to create instance', function() { + var mdb = new MessageDb(opts); + expect(mdb).to.exist; + }); + it('should be able to create default instance', function() { + var mdb = MessageDb.default(); + expect(mdb).to.exist; + }); + var fpk = Key.generateSync(); + var tpk = Key.generateSync(); + var from = fpk.public.toString('hex'); + var to = tpk.public.toString('hex'); + var messageData = { + a: 1, + b: 2 + }; + var messageData2 = {}; + var messageData3 = ['a', 'b']; + var message = AuthMessage.encode(to, fpk, messageData); + var message2 = AuthMessage.encode(to, fpk, messageData2); + var message3 = AuthMessage.encode(to, fpk, messageData3); + it('should be able to add and read a message', function(done) { + var mdb = new MessageDb(opts); + var lower_ts = microtime.now(); + mdb.addMessage(message, function(err) { + expect(err).to.not.exist; + var upper_ts = microtime.now(); + mdb.getMessages(to, lower_ts, upper_ts, function(err, messages) { + expect(err).to.not.exist; + messages.length.should.equal(1); + messages[0].ts.should.be.below(upper_ts); + messages[0].ts.should.be.above(lower_ts); + var m = AuthMessage.decode(tpk, messages[0]).payload; + m.a.should.equal(1); + m.b.should.equal(2); + done(); + }); + }); + }); + var sharedMDB; + it('should be able to add many messages and read some', function(done) { + var mdb = new MessageDb(opts); + sharedMDB = mdb; + var lower_ts = microtime.now(); + mdb.addMessage(message, function(err) { + expect(err).to.not.exist; + mdb.addMessage(message2, function(err) { + expect(err).to.not.exist; + var upper_ts = microtime.now(); + setTimeout(function() { + mdb.addMessage(message3, function(err) { + expect(err).to.not.exist; + mdb.getMessages(to, lower_ts, upper_ts, function(err, messages) { + expect(err).to.not.exist; + messages.length.should.equal(2); + messages[0].ts.should.be.below(upper_ts); + messages[0].ts.should.be.above(lower_ts); + var m0 = AuthMessage.decode(tpk, messages[0]).payload; + JSON.stringify(m0).should.equal('{"a":1,"b":2}'); + messages[1].ts.should.be.below(upper_ts); + messages[1].ts.should.be.above(lower_ts); + var m1 = AuthMessage.decode(tpk, messages[1]).payload; + JSON.stringify(m1).should.equal('{}'); + done(); + }); + }); + }, 10); + }); + }); + }); + it('should be able to add many messages and read all', function(done) { + var mdb = sharedMDB; + mdb.getMessages(to, null, null, function(err, messages) { + expect(err).to.not.exist; + messages.length.should.equal(4); + var m0 = AuthMessage.decode(tpk, messages[0]).payload; + JSON.stringify(m0).should.equal('{"a":1,"b":2}'); + var m1 = AuthMessage.decode(tpk, messages[1]).payload; + JSON.stringify(m1).should.equal('{"a":1,"b":2}'); + var m2 = AuthMessage.decode(tpk, messages[2]).payload; + JSON.stringify(m2).should.equal('{}'); + var m3 = AuthMessage.decode(tpk, messages[3]).payload; + JSON.stringify(m3).should.equal('["a","b"]'); + done(); + }); + }); + it('should be able #removeUpTo', function(done) { + var mdb = sharedMDB; + var upper_ts = microtime.now(); + mdb.addMessage(message, function(err) { + expect(err).to.not.exist; + mdb.removeUpTo(upper_ts, function(err, n) { + expect(err).to.not.exist; + n.should.equal(4); + mdb.getAll(function(error, all) { + expect(error).to.not.exist; + all.length.should.equal(1); + done(); + }); + + }); + }); + }); + it('should be able to close instance', function() { + var mdb = new MessageDb(opts); + mdb.close(); + expect(mdb).to.exist; + }); +}); diff --git a/test/test.PublicProfile.js b/test/test.PublicProfile.js new file mode 100644 index 0000000..5444b2e --- /dev/null +++ b/test/test.PublicProfile.js @@ -0,0 +1,113 @@ +'use strict'; + +var chai = require('chai'); +var assert = require('assert'); +var sinon = require('sinon'); +var should = chai.should; +var expect = chai.expect; +var bitauth = require('bitauth'); + +describe('public profile test', function() { + + var globalConfig = require('../config/config'); + var leveldb_stub = sinon.stub(); + leveldb_stub.put = sinon.stub(); + leveldb_stub.get = sinon.stub(); + var plugin = require('../plugins/publicInfo/publicInfo.js'); + var express_mock = null; + var request = null; + var response = null; + + beforeEach(function() { + + express_mock = sinon.stub(); + express_mock.post = sinon.stub(); + express_mock.get = sinon.stub(); + + plugin.init(express_mock, {db: leveldb_stub}); + + request = sinon.stub(); + request.on = sinon.stub(); + request.param = sinon.stub(); + response = sinon.stub(); + response.send = sinon.stub(); + response.status = sinon.stub(); + response.json = sinon.stub(); + response.end = sinon.stub(); + }); + + it('initializes correctly', function() { + assert(plugin.db === leveldb_stub); + assert(express_mock.post.calledWith( + globalConfig.apiPrefix + '/public', plugin.post + )); + assert(express_mock.get.calledWith( + globalConfig.apiPrefix + '/public/:sin', plugin.get + )); + }); + + it('writes a message correctly', function(done) { + + var privateKey = bitauth.generateSin(); + var protocol = 'https'; + var dataToSign = protocol + '://hosturlSTUFF'; + var signature = bitauth.sign(dataToSign, privateKey.priv); + request.get = function() { return 'host'; }; + request.protocol = protocol; + request.url = 'url'; + request.headers = { + 'x-identity': privateKey.pub, + 'x-signature': signature + }; + request.on.onFirstCall().callsArgWith(1, 'STUFF'); + request.on.onFirstCall().returnsThis(); + request.on.onSecondCall().callsArg(1); + + leveldb_stub.put.onFirstCall().callsArg(2); + response.status.returns(response); + response.json.returns(response); + + request.testCallback = function() { + assert(leveldb_stub.put.firstCall.args[0] === 'public-info-' + privateKey.sin); + assert(leveldb_stub.put.firstCall.args[1] === 'STUFF'); + assert(response.json.calledOnce); + assert(response.end.calledOnce); + done(); + }; + + plugin.post(request, response); + }); + + it('fails if the signature is invalid', function() { + var data = 'uecord3'; + request.get = function() { return ''; }; + request.headers = {}; + request.on.onFirstCall().callsArgWith(1, data); + request.on.onFirstCall().returnsThis(); + request.on.onSecondCall().callsArg(1); + leveldb_stub.put = sinon.stub(); + + leveldb_stub.put.onFirstCall().callsArg(2); + response.json.returnsThis(); + response.status.returnsThis(); + + plugin.post(request, response); + + assert(response.end.calledOnce); + }); + + it('retrieves a message correctly', function() { + + request.param.onFirstCall().returns('SIN'); + + var returnValue = '!@#$%'; + leveldb_stub.get.onFirstCall().callsArgWith(1, null, returnValue); + response.send.returnsThis(); + + plugin.get(request, response); + + assert(leveldb_stub.get.firstCall.args[0] === 'public-info-SIN'); + assert(response.send.calledWith(returnValue)); + assert(response.end.calledOnce); + }); +}); diff --git a/test/test.socket-server.js b/test/test.socket-server.js new file mode 100644 index 0000000..97546fb --- /dev/null +++ b/test/test.socket-server.js @@ -0,0 +1,30 @@ +'use strict'; + +var chai = require('chai'); +var should = chai.should; +var expect = chai.expect; +var sinon = require('sinon'); + +var socket = require('../app/controllers/socket'); +var bitcore = require('reddcore'); +var EventEmitter = require('events').EventEmitter; + +describe('socket server', function() { + it('should be able to call init with no args', function() { + socket.init.should.not.throw(); + }); + it('should register socket handlers', function() { + var io = { + sockets: new EventEmitter(), + } + socket.init(io); + + var mockSocket = {}; + mockSocket.on = sinon.spy(); + io.sockets.emit('connection', mockSocket); + mockSocket.on.calledWith('subscribe'); + mockSocket.on.calledWith('sync'); + mockSocket.on.calledWith('message'); + }); + +}); diff --git a/util/p2p.js b/util/p2p.js new file mode 100755 index 0000000..0d1d885 --- /dev/null +++ b/util/p2p.js @@ -0,0 +1,18 @@ +#! /usr/bin/env node +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var PeerSync = require('../lib/PeerSync'); + +var PROGRAM_VERSION = '0.1'; +var program = require('commander'); + +program + .version(PROGRAM_VERSION) + .parse(process.argv); + +var ps = new PeerSync(); +ps.run(); + + diff --git a/util/sync.js b/util/sync.js new file mode 100755 index 0000000..2bb11cd --- /dev/null +++ b/util/sync.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + + +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var SYNC_VERSION = '0.1'; +var program = require('commander'); +var HistoricSync = require('../lib/HistoricSync'); +var async = require('async'); + +program + .version(SYNC_VERSION) + .option('-D --destroy', 'Remove current DB (and start from there)', 0) + .option('-S --startfile', 'Number of file from bitcoind to start(default=0)') + .option('-R --rpc', 'Force sync with RPC') + .option('--start [hash]', 'StartAt block') + .option('--stop [hash]', 'StopAt block') + .option('-v --verbose', 'Verbose 0/1', 0) + .parse(process.argv); + +var historicSync = new HistoricSync({ + shouldBroadcastSync: true, +}); + + +async.series([ + function(cb) { + if (!program.destroy) return cb(); + console.log('Deleting Sync DB...'); + historicSync.sync.destroy(cb); + }, + function(cb) { + var opts= { + forceStartFile: program.startfile, + forceRPC: program.rpc, + startAt: program.start, + stopAt: program.stop, + }; + console.log('[options]',opts); //TODO + historicSync.start(opts,cb); + }, + ], + function(err) { + historicSync.close(); + if (err) console.log('CRITICAL ERROR: ', historicSync.info()); +}); + diff --git a/util/upgradeV0.2js b/util/upgradeV0.2js new file mode 100755 index 0000000..abdf99d --- /dev/null +++ b/util/upgradeV0.2js @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +'use strict'; + +var HistoricSync = require('../lib/HistoricSync'); +var async = require('async'); + + +var historicSync = new HistoricSync({ shouldBroadcastSync: false }); +var txDb=historicSync.sync.txDb; +var bDb=historicSync.sync.bDb; + +var height = 0; +var hash = historicSync.genesis; +var tipHash; + +async.series([ + function(c){ + txDb.checkVersion02(function(isV2){ + var err; + if(isV2) err='Already in v0.2!'; + return c(err); + }); + }, + function(c){ + console.log('[1/3] Migrating txs ... (this will take some minutes...)'); //TODO + txDb.migrateV02(c); + }, + function(c){ + var script=[]; + async.whilst( + function() { + return hash; + }, + function (w_cb) { + script=script.concat(bDb._setHeightScript(hash,height)); + bDb.getNext(hash,function(err,val){ + if (err) return w_cb(err); + tipHash = hash; + hash = val; + if (hash) height++; + if (!(height%1000) || !hash) { + console.log('[2/3] migrating blocks \t%d blocks processed', height); + bDb._runScript(script, function(err) { + script=[]; + return w_cb(err); + }); + } + else return w_cb(); + }); + }, c); + }, + function(c){ + console.log('[3/3] Migrating txs... (this will take some minutes...)'); //TODO + bDb.migrateV02(c); + }, + function(c){ + bDb.setTip(tipHash, height, c); + }, + function(c){ + bDb.migrateV02cleanup(c); + }, + ],function(err){ + if (err) + console.log('## '+err); + else + console.log('Finished OK.'); +});