Merge pull request #352 from pnagurny/v3/remove-files

Remove files
This commit is contained in:
Braydon Fuller 2015-09-02 11:59:33 -04:00
commit adacd21e57
107 changed files with 21 additions and 52875 deletions

View File

@ -1,94 +0,0 @@
'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']);
};

179
README.md
View File

@ -1,184 +1,23 @@
# Insight API
# *insight API*
A Bitcoin blockchain REST and web socket API service for [Bitcore Node](https://github.com/bitpay/bitcore-node).
*insight API* is an open-source bitcoin blockchain REST
and websocket API. Insight API runs in NodeJS and uses LevelDB for storage.
This is a backend-only service. If you're looking for the web frontend application,
take a look at https://github.com/bitpay/insight.
*Insight API* allows to develop bitcoin-related applications (such as wallets) that
require certain information from the blockchain that bitcoind does not provide.
A blockchain explorer front-end has been developed on top of *Insight API*. It can
be downloaded at [Github Insight Repository](https://github.com/bitpay/insight).
## Warning
Insight file sync does not work with **bitcoind** v0.10
In order to use Insigtht you must set the environment variable INSIGHT_FORCE_RPC_SYNC = 1
We are working on `bitcore-node` to replace Insight-api. Check `bitcore-node` on [github](https://github.com/bitpay/bitcore-node).
This is a backend-only service. If you're looking for the web frontend application, take a look at https://github.com/bitpay/insight.
## Prerequisites
* **bitcoind** - Download and Install [Bitcoin](http://bitcoin.org/en/download)
- [Bitcore Node 0.2.x](https://github.com/bitpay/bitcore-node)
*insight API* needs a *trusted* bitcoind node to run. *insight API* will connect to the node
through the RPC API, bitcoin peer-to-peer protocol, and will even read its raw block .dat files for syncing.
Configure bitcoind to listen to RPC calls and set `txindex` to true.
The easiest way to do this is by copying `./etc/bitcoind/bitcoin.conf` to your
bitcoin data directory (usually `~/.bitcoin` on Linux, `%appdata%\Bitcoin\` on Windows,
or `~/Library/Application Support/Bitcoin` on Mac OS X).
bitcoind must be running and must have finished downloading the blockchain **before** running *insight 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 Insight API, clone the main repository:
$ git clone https://github.com/bitpay/insight-api && cd insight-api
Install dependencies:
$ npm install
Run the main application:
$ node insight.js
Then open a browser and go to:
http://localhost:3001
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:3001/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 bitcoind host
BITCOIND_PORT # RPC bitcoind Port
BITCOIND_P2P_HOST # P2P bitcoind Host (will default to BITCOIND_HOST, if specified)
BITCOIND_P2P_PORT # P2P bitcoind Port
BITCOIND_USER # RPC username
BITCOIND_PASS # RPC password
BITCOIND_DATADIR # bitcoind datadir. 'testnet3' 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 insight's internal DB. (defaults to $HOME/.insight)
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_CURRENCYRATES # if "true" will enable a plugin to obtain historic conversion rates for various currencies
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
ENABLE_EMAILSTORE # if "true" will enable a plugin to store data with a validated email address
INSIGHT_EMAIL_CONFIRM_HOST # Only meanfull if ENABLE_EMAILSTORE is enable. Hostname for the confirm URLs. E.g: 'https://insight.bitpay.com'
```
Make sure that bitcoind 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 *insight* 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 bitcoind server to update addresses and balances. *insight-api* needs exactly one trusted bitcoind node to run. This node must have finished downloading the blockchain before running *insight-api*.
While *insight* 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 bitcoind'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, `~/.bitcoin` on Linux). In case a
non-standard location is used, it needs to be defined (see the Configuration section).
As of June 2014, using `.dat` files the sync process takes 9 hrs.
for livenet and 30 mins. for testnet.
While synchronizing the blockchain, *insight-api* listens for new blocks and
transactions relayed by the bitcoind node. Those are also stored on *insight-api*'s database.
In case *insight-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 bitcoind is shutdown, *insight-api* needs to be stopped and restarted
once bitcoind 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, particulary -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 *insight-api* normally will trigger
the historic sync automatically.
### DB storage requirement
To store the blockchain and address related information, *insight-api* uses LevelDB.
Two DBs are created: txs and blocks. By default these are stored on
``~/.insight/``
Please note that some older versions of Insight-API store that on `<insight's root>/db`.
This can be changed at config/config.js. As of June 2014, storing the livenet blockchain takes ~35GB of disk space (2GB for the testnet).
## Development
To run insight locally for development with grunt:
```$ NODE_ENV=development grunt```
To run the tests
```$ grunt test```
Contributions and suggestions are welcome at [insight-api github repository](https://github.com/bitpay/insight-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, insight provides a REST API at `/api`, but this prefix is configurable from the var `apiPrefix` in the `config.js` file.
The end-points are:
**Note:** You can use an existing Bitcoin data directory, however `txindex` needs to be set to true in `bitcoin.conf`.
## API HTTP Endpoints
### Block
```
/api/block/[:hash]
/api/block/00000000a967199a2fad0877433c93df785a8d8ce062e5f9b451cd1397bdbf62
```
### Block index
Get block hash by height
```
@ -198,11 +37,13 @@ which is the hash of the Genesis block (0 height)
/api/raw/[:rawid]
/api/raw/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c
```
### Address
```
/api/addr/[:addr][?noTxList=1&noCache=1]
/api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1
```
### Address Properties
```
/api/addr/[:addr]/balance
@ -211,6 +52,7 @@ which is the hash of the Genesis block (0 height)
/api/addr/[:addr]/unconfirmedBalance
```
The response contains the value in Satoshis.
### Unspent Outputs
```
/api/addr/[:addr]/utxo[?noCache=1]
@ -320,7 +162,6 @@ Sample output:
Note: if pagination params are not specified, the result is an array of transactions.
### Transaction broadcasting
POST method:
```

View File

@ -1,284 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash');
var Address = require('../models/Address');
var common = require('./common');
var async = require('async');
var MAX_BATCH_SIZE = 100;
var RPC_CONCURRENCY = 5;
var tDb = require('../../lib/TransactionDb').default();
var checkSync = function(req, res) {
if (req.historicSync) {
var i = req.historicSync.info()
if (i.status !== 'finished') {
common.notReady(req, res, i.syncPercentage);
return false;
}
}
return true;
};
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 addrs param:' + e.message,
code: 1
}, res, next);
return null;
}
return as;
};
exports.show = function(req, res, next) {
if (!checkSync(req, res)) return;
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) {
if (!checkSync(req, res)) return;
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) {
if (!checkSync(req, res)) return;
var as = getAddrs(req, res, next);
if (as) {
var utxos = [];
async.eachLimit(as, RPC_CONCURRENCY, 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.multitxs = function(req, res, next) {
if (!checkSync(req, res)) return;
function processTxs(txs, from, to, cb) {
txs = _.uniq(_.flatten(txs), 'txid');
var nbTxs = txs.length;
if (_.isUndefined(from) && _.isUndefined(to)) {
from = 0;
to = MAX_BATCH_SIZE;
}
if (!_.isUndefined(from) && _.isUndefined(to))
to = from + MAX_BATCH_SIZE;
if (!_.isUndefined(from) && !_.isUndefined(to) && to - from > MAX_BATCH_SIZE)
to = from + MAX_BATCH_SIZE;
if (from < 0) from = 0;
if (to < 0) to = 0;
if (from > nbTxs) from = nbTxs;
if (to > nbTxs) to = nbTxs;
txs.sort(function(a, b) {
var b = (b.firstSeenTs || b.ts)+ b.txid;
var a = (a.firstSeenTs || a.ts)+ a.txid;
if (a > b) return -1;
if (a < b) return 1;
return 0;
});
txs = txs.slice(from, to);
var txIndex = {};
_.each(txs, function(tx) {
txIndex[tx.txid] = tx;
});
async.eachLimit(txs, RPC_CONCURRENCY, function(tx2, callback) {
tDb.fromIdWithInfo(tx2.txid, function(err, tx) {
if (err) {
console.log(err);
return common.handleErrors(err, res);
}
if (tx && tx.info) {
if (tx2.firstSeenTs)
tx.info.firstSeenTs = tx2.firstSeenTs;
txIndex[tx.txid].info = tx.info;
} else {
// TX no longer available
txIndex[tx2.txid].info = {
txid: tx2.txid,
possibleDoubleSpend: true,
firstSeenTs: tx2.firstSeenTs,
};
}
callback();
});
}, function(err) {
if (err) return cb(err);
// It could be that a txid is stored at an address but it is
// no longer at bitcoind (for example a double spend)
var transactions = _.compact(_.pluck(txs, 'info'));
transactions = {
totalItems: nbTxs,
from: +from,
to: +to,
items: transactions,
};
return cb(null, transactions);
});
};
var from = req.param('from');
var to = req.param('to');
var as = getAddrs(req, res, next);
if (as) {
var txs = [];
async.eachLimit(as, RPC_CONCURRENCY, function(a, callback) {
a.update(function(err) {
if (err) callback(err);
txs.push(a.transactions);
callback();
}, {
ignoreCache: req.param('noCache'),
includeTxInfo: true,
});
}, function(err) { // finished callback
if (err) return common.handleErrors(err, res);
processTxs(txs, from, to, function(err, transactions) {
if (err) return common.handleErrors(err, res);
res.jsonp(transactions);
});
});
}
};
exports.balance = function(req, res, next) {
if (!checkSync(req, res)) return;
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) {
if (!checkSync(req, res)) return;
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) {
if (!checkSync(req, res)) return;
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) {
if (!checkSync(req, res)) return;
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')
});
};

View File

@ -1,173 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var common = require('./common');
var async = require('async');
var bdb = require('../../lib/BlockDb').default();
var tdb = require('../../lib/TransactionDb').default();
/**
* 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,
}
});
});
}
});
};

View File

@ -1,60 +0,0 @@
'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 }
});
}
};

View File

@ -1,26 +0,0 @@
'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');
}
else {
var version = _getVersion();
res.send('insight API v' + version);
}
};
exports.version = function(req, res) {
var version = _getVersion();
res.json({
version: version
});
};

View File

@ -1,27 +0,0 @@
'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});
});
};

View File

@ -1,80 +0,0 @@
'use strict';
// server-side socket behaviour
var ios = null; // io is already taken in express
var util = require('bitcore').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);
try {
t.vout = tx.vout.map(function(o) {
var r = {};
r[o.scriptPubKey.addresses] = o.valueSat;
return r;
});
} catch (e) {};
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);
};

View File

@ -1,55 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var Status = require('../models/Status'),
common = require('./common');
/**
* Status
*/
exports.show = function(req, res) {
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 'getDifficulty':
statusObject.getDifficulty(returnJsonp);
break;
case 'getTxOutSetInfo':
statusObject.getTxOutSetInfo(returnJsonp);
break;
case 'getLastBlockHash':
statusObject.getLastBlockHash(returnJsonp);
break;
case 'getBestBlockHash':
statusObject.getBestBlockHash(returnJsonp);
break;
case 'getInfo':
default:
statusObject.getInfo(returnJsonp);
}
};
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);
}
};

View File

@ -1,197 +0,0 @@
'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 imports = require('soop').imports();
var bitcore = require('bitcore');
var RpcClient = bitcore.RpcClient;
var config = require('../../config/config');
var bitcoreRpc = imports.bitcoreRpc || new RpcClient(config.bitcoind);
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});
});
};
exports.rawTransaction = function (req, res, next, txid) {
bitcoreRpc.getRawTransaction(txid, function (err, transaction) {
if (err || !transaction)
return common.handleErrors(err, res);
else {
req.rawTransaction = { 'rawtx': transaction.result };
return next();
}
});
};
/**
* 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);
bdb.fillVinConfirmations(tx.info, function(err) {
if (err)
return common.handleErrors(err, res);
req.transaction = tx.info;
return next();
});
});
};
/**
* Show transaction
*/
exports.show = function(req, res) {
if (req.transaction) {
res.jsonp(req.transaction);
}
};
/**
* Show raw transaction
*/
exports.showRaw = function(req, res) {
if (req.rawTransaction) {
res.jsonp(req.rawTransaction);
}
};
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: []
});
}
};

View File

@ -1,21 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash');
var Utils = require('../models/Utils');
var common = require('./common');
exports.estimateFee = function(req, res) {
var args = req.query.nbBlocks || '2';
var nbBlocks = args.split(',');
var utilsObject = new Utils();
utilsObject.estimateFee(nbBlocks, function(err, fees) {
if (err) return common.handleErrors(err, res);
res.jsonp(fees);
});
};

View File

@ -1,216 +0,0 @@
'use strict';
var imports = require('soop').imports();
var _ = require('lodash');
var async = require('async');
var bitcore = require('bitcore');
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, includeInfo) {
function addTx(data) {
if (!txList) return;
if (includeInfo) {
txList.push(data);
} else {
txList.push(data.txid);
}
};
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;
addTx({
txid: txItem.txid,
ts: txItem.ts,
firstSeenTs: txItem.firstSeenTs,
});
}
// Spent tx
if (txItem.spentTxId && !seen[txItem.spentTxId]) {
addTx({
txid: txItem.spentTxId,
ts: txItem.spentTs
});
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)
// .includeTxInfo
//
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() {
//_.filter will filterout unspend without scriptPubkey
//(probably from double spends)
self.unspent = _.filter(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,
};
}), 'scriptPubKey');;
return next();
});
} else {
txOut.forEach(function(txItem) {
self._addTxItem(txItem, txList, opts.includeTxInfo);
});
if (txList)
self.transactions = txList;
return next();
}
});
});
});
};
module.exports = require('soop')(Address);

View File

@ -1,105 +0,0 @@
'use strict';
//var imports = require('soop').imports();
var async = require('async');
var bitcore = require('bitcore');
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);

View File

@ -1,26 +0,0 @@
'use strict';
//var imports = require('soop').imports();
var _ = require('lodash');
var async = require('async');
var bitcore = require('bitcore');
var RpcClient = bitcore.RpcClient;
var config = require('../../config/config');
var rpc = new RpcClient(config.bitcoind);
function Utils() {}
Utils.prototype.estimateFee = function(nbBlocks, cb) {
var that = this;
async.map([].concat(nbBlocks), function(n, next) {
rpc.estimateFee(+n, function(err, info) {
return next(err, [n, info.result]);
});
}, function(err, result) {
if (err) return cb(err);
return cb(null, _.zipObject(result));
});
};
module.exports = require('soop')(Utils);

View File

@ -1,19 +0,0 @@
'use strict';
exports.notReady = function (err, res, p) {
res.status(503).send('Server not yet ready. Sync Percentage:' + p);
};
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');
}
};

View File

@ -1,122 +0,0 @@
'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() + '/.insight');
if (process.env.INSIGHT_NETWORK === 'livenet') {
env = 'livenet';
db = home;
port = '3000';
b_port = '8332';
p2p_port = '8333';
} else {
env = 'testnet';
db = home + '/testnet';
port = '3001';
b_port = '18332';
p2p_port = '18333';
}
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 forceRPCsync = process.env.INSIGHT_FORCE_RPC_SYNC;
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%\\Bitcoin\\';
if (isMac) dataDir = process.env.HOME + '/Library/Application Support/Bitcoin/';
if (isLinux) dataDir = process.env.HOME + '/.bitcoin/';
}
dataDir += network === 'testnet' ? 'testnet3' : '';
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 enableRatelimiter = process.env.ENABLE_RATELIMITER === 'true';
var enableEmailstore = process.env.ENABLE_EMAILSTORE === 'true';
var loggerLevel = process.env.LOGGER_LEVEL || 'info';
var enableHTTPS = process.env.ENABLE_HTTPS === 'true';
var enableCurrencyRates = process.env.ENABLE_CURRENCYRATES === 'true';
if (!fs.existsSync(db)) {
mkdirp.sync(db);
}
module.exports = {
enableRatelimiter: enableRatelimiter,
ratelimiter: require('../plugins/config-ratelimiter.js'),
enableEmailstore: enableEmailstore,
enableCurrencyRates: enableCurrencyRates,
currencyrates: require('../plugins/config-currencyrates'),
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,
forceRPCsync: forceRPCsync,
};

View File

@ -1,72 +0,0 @@
'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, setHistoric);
app.use(config.apiPrefix, setPeer);
app.use(require('morgan')(':remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" '));
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) {
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 404 since no middleware responded
app.use(function(req, res) {
res.status(404).jsonp({
status: 404,
url: req.originalUrl,
error: 'Not found'
});
});
};

View File

@ -1,14 +0,0 @@
'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, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization');
res.setHeader('Access-Control-Expose-Headers', 'X-Email-Needs-Validation,X-Quota-Per-Item,X-Quota-Items-Limit,X-RateLimit-Limit,X-RateLimit-Remaining');
next();
});
};

View File

@ -1,85 +0,0 @@
'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);
// Raw Routes
app.get(apiPrefix + '/rawtx/:txid', transactions.showRaw);
app.param('txid', transactions.rawTransaction);
// 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);
app.get(apiPrefix + '/addrs/:addrs/txs', addresses.multitxs);
app.post(apiPrefix + '/addrs/txs', addresses.multitxs);
// 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);
// Utils route
var utils = require('../app/controllers/utils');
app.get(apiPrefix + '/utils/estimatefee', utils.estimateFee);
// 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.get(apiPrefix + '/email/retrieve', emailPlugin.retrieve);
}
// Currency rates plugin
if (config.enableCurrencyRates) {
var currencyRatesPlugin = require('../plugins/currencyrates');
app.get(apiPrefix + '/rates/:code', currencyRatesPlugin.getRate);
}
// 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);
};

View File

@ -1,25 +0,0 @@
#!/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);
});

View File

@ -1,26 +0,0 @@
#!/usr/bin/env node
'use strict';
var levelup = require('levelup');
var dbPath = process.argv[2];
var s = process.argv[3];
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 () {
});

View File

@ -1,21 +0,0 @@
#!usr/bin/env node
var email = process.argv[2];
if (!email) {
console.log('\tdeleteWholeProfile.js <email>');
process.exit(-1);
}
console.log('\t Deleting email:', email, process.env.INSIGHT_NETWORK);
var p = require('../plugins/emailstore');
p.init({});
p.deleteWholeProfile(email, function(err) {
if (err)
console.log('[err:]', err);
else
console.log('done');
});

View File

@ -1,41 +0,0 @@
#!/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();
});
});

View File

@ -1,33 +0,0 @@
#!/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" {} +

View File

@ -1,25 +0,0 @@
#!/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}));
})

View File

@ -1,21 +0,0 @@
#!/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}));
});

View File

@ -1,31 +0,0 @@
#!/usr/bin/env node
'use strict';
var util = require('util');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var RpcClient = require('../node_modules/bitcore/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} ));
});

View File

@ -1,34 +0,0 @@
#!/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
});
}

View File

@ -1,30 +0,0 @@
#!/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 () {
});

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
#!/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('bitcore/networks'),
util = require('bitcore/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
});
}

View File

@ -1,70 +0,0 @@
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

View File

@ -1,20 +0,0 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var RpcClient = require('../node_modules/bitcore/RpcClient').class();
var config = require('../config/config');
var 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);
});

View File

@ -1,17 +0,0 @@
#!/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
});

View File

@ -1,10 +0,0 @@
rpcuser=user
rpcpassword=pass
server=1
txindex=1
# Allow connections outsite localhost?
rpcallowip=192.168.1.*
rpcport=8332

View File

@ -1,10 +0,0 @@
rpcuser=user
rpcpassword=pass
server=1
txindex=1
# Allow connections outsite localhost?
rpcallowip=192.168.1.*
rpcport=18332
testnet=3

View File

@ -1,235 +0,0 @@
[
{
"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"
]
},
{
"poolName":"BTCChina Pool",
"url":"https://pool.btcchina.com/",
"searchStrings":[
"BTCChina Pool",
"btcchina.com"
]
}
]

View File

@ -1,14 +0,0 @@
-----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-----

View File

@ -1,15 +0,0 @@
-----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-----

File diff suppressed because one or more lines are too long

View File

@ -1,122 +0,0 @@
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: 13px Helvetica, Arial;
}
form {
background: #000;
padding: 3px;
position: fixed;
bottom: 0;
width: 100%;
}
form input {
border: 0;
padding: 10px;
width: 40%;
margin-right: .5%;
}
form button {
width: 9%;
background: rgb(130, 224, 255);
border: none;
padding: 10px;
}
#messages {
list-style-type: none;
margin: 0;
padding: 0;
}
#messages li {
padding: 5px 10px;
}
#messages li:nth-child(odd) {
background: #eee;
}
</style>
</head>
<body>
<h3 id="pubkey">Generating public key...</h3>
<ul id="messages"></ul>
<form action="">
<input id="other" placeholder="other user's key" autocomplete="off" />
<input id="m" placeholder="Text message..." autocomplete="off" />
<button>Send</button>
</form>
<script src="http://localhost:3001/socket.io/socket.io.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script src="./bitcore.js"></script>
<script>
var fixedPK = prompt('Enter your private key or leave empty to generate one', 'a52e54d90c1ff4846e6f164e56405094cc96ecc0ea7732831e24418204c8ab55');
$(document).ready(function()
{
// load dependencies
var socket = io('http://localhost:3001');
var bitcore = require('bitcore');
var util = bitcore.util;
var Key = bitcore.Key;
var AuthMessage = bitcore.AuthMessage;
var Buffer = bitcore.Buffer;
// generate new identity
var pk = Key.generateSync();
if (fixedPK) {
pk.private = new Buffer(fixedPK, 'hex');
pk.regenerateSync();
}
console.log(pk.private.toString('hex'));
var pubkey = pk.public.toString('hex');
$('#pubkey').text('Your key: '+pubkey);
// show message
var show = function(from, text) {
$('#messages').append($('<li>').text(from+': ' + text));
};
// send chat handler
$('form').submit(function(e)
{
e.preventDefault();
var text = $('#m').val()
if (text.length === 0) {
return;
}
var otherPubkey = $('#other').val();
var data = AuthMessage.encode(otherPubkey, pk, text);
socket.emit('message', data);
show('You', text);
$('#m').val('');
return;
});
// receive chat handler
socket.emit('subscribe', pubkey);
var ts = undefined; // timestamp to sync, undefined syncs all
socket.emit('sync', ts);
socket.on('message', function(msg)
{
try {
var data = AuthMessage.decode(pk, msg);
} catch(e) {
alert(e);
}
show(msg.pubkey, data.payload);
});
});
</script>
</body>
</html>

View File

@ -1,34 +0,0 @@
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);
});

View File

@ -1,153 +0,0 @@
#!/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' ? '* (/testnet3 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.enableEmailstore) {
require('./plugins/emailstore').init(config.emailstore);
}
if (config.enableCurrencyRates) {
require('./plugins/currencyrates').init(config.currencyrates);
}
// express settings
require('./config/express')(expressApp, historicSync, peerSync);
require('./config/routes')(expressApp);
//Start the app by listening on <port>
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;

View File

@ -1,519 +0,0 @@
'use strict';
var imports = require('soop').imports();
var TIMESTAMP_PREFIX = 'bts-'; // bts-<ts> => <hash>
var PREV_PREFIX = 'bpr-'; // bpr-<hash> => <prev_hash>
var NEXT_PREFIX = 'bne-'; // bne-<hash> => <next_hash>
var MAIN_PREFIX = 'bma-'; // bma-<hash> => <height> (0 is unconnected)
var TIP = 'bti-'; // bti = <hash>:<height> 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-<txid> = <block>
var MAX_OPEN_FILES = 500;
var CONCURRENCY = 5;
var DFLT_REQUIRED_CONFIRMATIONS = 1;
/**
* Module dependencies.
*/
var levelup = require('levelup'),
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 ii in txs) {
dbScript.push({
type: 'del',
key: IN_BLK_PREFIX + txs[ii],
});
}
return dbScript;
};
BlockDb.prototype._addTxsScript = function(txs, hash, height) {
var dbScript = [];
for (var ii in txs) {
dbScript.push({
type: 'put',
key: IN_BLK_PREFIX + txs[ii],
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 ? true : false;
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._fillConfirmationsOneVin = function(o, chainHeight, cb) {
var self = this;
self.getBlockForTx(o.txid, function(err, hash, height) {
if (err) return cb(err);
o.isConfirmed = false;
o.confirmations = 0;
if (height >= 0) {
o.isConfirmed = chainHeight >= height;
o.confirmations = chainHeight - height + 1;
}
o.unconfirmedInput = ! o.isConfirmed;
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);
});
};
BlockDb.prototype.fillVinConfirmations = function(tx, cb) {
var self = this;
this.getTip(function(err, hash, height) {
var vin = tx.vin;
if (!vin) return cb();
async.eachLimit(vin, CONCURRENCY, function(v, e_c) {
self._fillConfirmationsOneVin(v, 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);

View File

@ -1,147 +0,0 @@
'use strict';
var bitcore = require('bitcore'),
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;
var mb = parseInt(size/1024/1024);
console.log('Reading Blockfile %s [%d MB]',
fname, mb);
if(mb > 1023)
throw new Error('CRITICAL ERROR: file size greater than 1023MB, use cat blk*.dat > bootstrap.dat to create new '
+ 'dat files @128MB. (https://github.com/bitpay/insight-api/issues/35)');
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);

View File

@ -1,466 +0,0 @@
'use strict';
var imports = require('soop').imports();
var util = require('util');
var async = require('async');
var bitcore = require('bitcore');
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('bitcore');
var RpcClient = bitcore.RpcClient;
this.rpc = new RpcClient(config.bitcoind);
this.getBitcoinCoreVersion(function(bitcoinVersion) {
if (bitcoinVersion > 100000 && !config.forceRPCsync) {
info('-------------------------------------------------------');
info('- Bitcoin Core version >0.10 only works with RPC sync -');
info('- Set the env variable INSIGHT_FORCE_RPC_SYNC = 1 -');
info('-------------------------------------------------------');
process.exit(1);
} else {
info('Bitcoin Core version ', bitcoinVersion);
info('Using RPC sync ');
}
});
this.sync = new Sync(opts);
this.height = 0;
}
HistoricSync.prototype.getBitcoinCoreVersion = function(cb) {
var self = this;
self.rpc.getInfo(function(err, info) {
if (err) {
error('ERROR ', err);
process.exit(-1);
};
return cb(info.result.version);
});
};
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,
};
var isCoinBase = 1;
block.tx = b.txs.map(function(tx) {
var ret = self.sync.txDb.getStandardizedTx(tx, b.timestamp, isCoinBase);
isCoinBase = 0;
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);
var limit = self.network == 'livenet' ? 6 : 100;
if (d > limit) {
error('Previous Tip block 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 (config.forceRPCsync) return next();
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);

View File

@ -1,182 +0,0 @@
'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('bitcore');
var AuthMessage = bitcore.AuthMessage;
var preconditions = require('preconditions').singleton();
var MESSAGE_PREFIX = 'msg-'; // msg-<recieving_pubkey>-<ts> => <message>
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;
try {
key = messageKey(m.to);
} catch (e) {
cb(new Error('Bad message'));
return;
};
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 = [],
opts;
lower_ts = lower_ts || 1;
try {
opts = {
start: messageKey(to, lower_ts),
end: messageKey(to, upper_ts),
// limit: limit, TODO
reverse: false,
};
} catch (e) {
cb(new Error('Bad message range'));
return;
};
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);

View File

@ -1,150 +0,0 @@
'use strict';
var fs = require('fs');
var bitcore = require('bitcore');
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);

View File

@ -1,32 +0,0 @@
'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);

View File

@ -1,119 +0,0 @@
'use strict';
var imports = require('soop').imports();
var bitcore = require('bitcore'),
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 dont 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;
});
// Outputs => add total
var valueOutSat = 0;
info.vout.forEach( function(o) {
o.value = o.value.toFixed(8);
valueOutSat += o.value * bitcore.util.COIN;
});
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) / 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);

View File

@ -1,300 +0,0 @@
'use strict';
var imports = require('soop').imports();
var config = imports.config || require('../config/config');
var bitcore = require('bitcore');
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);

View File

@ -1,758 +0,0 @@
'use strict';
var imports = require('soop').imports();
// to show tx outs
var OUTS_PREFIX = 'txo-'; //txo-<txid>-<n> => [addr, btc_sat]
var SPENT_PREFIX = 'txs-'; //txs-<txid(out)>-<n(out)>-<txid(in)>-<n(in)> = ts
// to sum up addr balance (only outs, spents are gotten later)
var ADDR_PREFIX = 'txa2-'; //txa-<addr>-<tsr>-<txid>-<n>
// tsr = 1e13-js_timestamp
// => + btc_sat [:isConfirmed:[scriptPubKey|isSpendConfirmed:SpentTxid:SpentVout:SpentTs]
// |balance:txApperances
// TODO: use bitcore networks module
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
var CONCURRENCY = 5;
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('bitcore'),
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('bitcore/util/EncodedData', {
base58: base58
});
var versionedData = require('soop').load('bitcore/util/VersionedData', {
parent: encodedData
});
var Address = require('soop').load('bitcore/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 (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 scalated
}
i.addr = ret.addr;
i.valueSat = ret.valueSat;
i.value = ret.valueSat / util.COIN;
valueIn += i.valueSat;
if (ret.multipleSpentAttempt || !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 ii in txouts) {
var txout = txouts[ii];
//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] = self._parseAddrData(k, data, opts.ignoreCache);
ret.push(unique[index]);
} else {
// add first seen
unique[index].firstSeenTs = END_OF_WORLD_TS - parseInt(k[2]);
}
})
.on('error', cb)
.on('end', function() {
// This is a bad idea. ADDR_PREFIX ONLY indexes tx in outputs
// it is still necesarry for search for inputs
// if (opts.dontFillSpent) {
// return cb(null, ret)
//}
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, isCoinBase) {
var self = this;
tx.txid = bitcoreUtil.formatHashFull(tx.getHash());
var ti = 0;
tx.vin = tx.ins.map(function(txin) {
var ret = {
n: ti++
};
if (isCoinBase) {
ret.isCoinBase = 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)
for (var ii in tx.vin) {
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 (var ii in tx.vout) {
var o = tx.vout[ii];
if (o.scriptPubKey && o.scriptPubKey.addresses &&
o.scriptPubKey.addresses[0] && !o.scriptPubKey.addresses[1] // TODO : not supported=> standard multisig
) {
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 unconfimed 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 ii in txs) {
var s = this._addScript(txs[ii]);
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);

View File

@ -1,13 +0,0 @@
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;

View File

@ -21,6 +21,9 @@
}, {
"name": "Ivan Socolsky",
"email": "jungans@gmail.com"
}, {
"name": "Patrick Nagurny",
"email": "patrick@bitpay.com"
}],
"bugs": {
"url": "https://github.com/bitpay/insight-api/issues"
@ -39,50 +42,18 @@
"engines": {
"node": "*"
},
"bin": "insight.js",
"scripts": {
"start": "node insight.js"
"test": "NODE_ENV=test mocha -R spec --recursive"
},
"bitcoreNode": "bitcore-node",
"bitcoreNode": "lib",
"dependencies": {
"async": "*",
"base58-native": "0.1.2",
"bignum": "*",
"morgan": "*",
"bitcore": "git://github.com/bitpay/bitcore.git#51c53b16ced6bcaf4b78329955d6814579fe4ee9",
"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",
"preconditions": "^1.0.7",
"request": "^2.48.0",
"socket.io": "1.0.6",
"socket.io-client": "1.0.6",
"soop": "=0.1.5",
"winston": "*",
"xmlhttprequest": "~1.6.0"
"bitcore": "^0.13.2",
"lodash": "^2.4.1"
},
"devDependencies": {
"mocha": "~1.16.2",
"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"
}

View File

@ -1,2 +0,0 @@
module.exports = {
};

View File

@ -1,4 +0,0 @@
module.exports = {
fetchIntervalInMinutes: 60,
defaultSource: 'BitPay',
};

View File

@ -1,3 +0,0 @@
module.exports = {
cronTime: '* * * * *', // run each minute
};

View File

@ -1,3 +0,0 @@
module.exports = {
};

View File

@ -1,15 +0,0 @@
var _ = require('lodash');
module.exports.id = 'BitPay';
module.exports.url = 'https://bitpay.com/api/rates/';
module.exports.parseFn = function(raw) {
var rates = _.compact(_.map(raw, function(d) {
if (!d.code || !d.rate) return null;
return {
code: d.code,
rate: d.rate,
};
}));
return rates;
};

View File

@ -1,11 +0,0 @@
var _ = require('lodash');
module.exports.id = 'Bitstamp';
module.exports.url = 'https://www.bitstamp.net/api/ticker/';
module.exports.parseFn = function(raw) {
return [{
code: 'USD',
rate: parseFloat(raw.last)
}];
};

View File

@ -1,199 +0,0 @@
(function() {
'use strict';
var _ = require('lodash');
var async = require('async');
var levelup = require('levelup');
var request = require('request');
var preconditions = require('preconditions').singleton();
var logger = require('../lib/logger').logger;
var globalConfig = require('../config/config');
var currencyRatesPlugin = {};
function getCurrentTs() {
return Math.floor(new Date() / 1000);
};
function getKey(sourceId, code, ts) {
var key = sourceId + '-' + code.toUpperCase();
if (ts) {
key += '-' + ts;
}
return key;
};
function returnError(error, res) {
res.status(error.code).json({
error: error.message,
}).end();
};
currencyRatesPlugin.init = function(config) {
logger.info('Using currencyrates plugin');
config = config || {};
var path = globalConfig.leveldb + '/currencyRates' + (globalConfig.name ? ('-' + globalConfig.name) : '');
currencyRatesPlugin.db = config.db || globalConfig.db || levelup(path);
if (_.isArray(config.sources)) {
currencyRatesPlugin.sources = config.sources;
} else {
currencyRatesPlugin.sources = [
require('./currencyRates/bitpay'),
require('./currencyRates/bitstamp'),
];
}
currencyRatesPlugin.request = config.request || request;
currencyRatesPlugin.defaultSource = config.defaultSource || globalConfig.defaultSource;
currencyRatesPlugin.initialized = true;
var interval = config.fetchIntervalInMinutes || globalConfig.fetchIntervalInMinutes;
if (interval) {
currencyRatesPlugin._fetch();
setInterval(function() {
currencyRatesPlugin._fetch();
}, interval * 60 * 1000);
}
};
currencyRatesPlugin._retrieve = function(source, cb) {
logger.debug('Fetching data for ' + source.id);
currencyRatesPlugin.request.get({
url: source.url,
json: true
}, function(err, res, body) {
if (err || !body) {
logger.warn('Error fetching data for ' + source.id, err);
return cb(err);
}
logger.debug('Data for ' + source.id + ' fetched successfully');
if (!source.parseFn) {
return cb('No parse function for source ' + source.id);
}
var rates = source.parseFn(body);
return cb(null, rates);
});
};
currencyRatesPlugin._store = function(source, rates, cb) {
logger.debug('Storing data for ' + source.id);
var ts = getCurrentTs();
var ops = _.map(rates, function(r) {
return {
type: 'put',
key: getKey(source.id, r.code, ts),
value: r.rate,
};
});
currencyRatesPlugin.db.batch(ops, function(err) {
if (err) {
logger.warn('Error storing data for ' + source.id, err);
return cb(err);
}
logger.debug('Data for ' + source.id + ' stored successfully');
return cb();
});
};
currencyRatesPlugin._dump = function(opts) {
var all = [];
currencyRatesPlugin.db.readStream(opts)
.on('data', console.log);
};
currencyRatesPlugin._fetch = function(cb) {
cb = cb || function() {};
preconditions.shouldNotBeFalsey(currencyRatesPlugin.initialized);
async.each(currencyRatesPlugin.sources, function(source, cb) {
currencyRatesPlugin._retrieve(source, function(err, res) {
if (err) {
logger.warn(err);
return cb();
}
currencyRatesPlugin._store(source, res, function(err, res) {
return cb();
});
});
}, function(err) {
return cb(err);
});
};
currencyRatesPlugin._getOneRate = function(sourceId, code, ts, cb) {
var result = null;
currencyRatesPlugin.db.createValueStream({
lte: getKey(sourceId, code, ts),
gte: getKey(sourceId, code) + '!',
reverse: true,
limit: 1,
})
.on('data', function(data) {
var num = parseFloat(data);
result = _.isNumber(num) && !_.isNaN(num) ? num : null;
})
.on('error', function(err) {
return cb(err);
})
.on('end', function() {
return cb(null, result);
});
};
currencyRatesPlugin._getRate = function(sourceId, code, ts, cb) {
preconditions.shouldNotBeFalsey(currencyRatesPlugin.initialized);
preconditions.shouldNotBeEmpty(code);
preconditions.shouldBeFunction(cb);
ts = ts || getCurrentTs();
if (!_.isArray(ts)) {
return currencyRatesPlugin._getOneRate(sourceId, code, ts, function(err, rate) {
if (err) return cb(err);
return cb(null, {
rate: rate
});
});
}
async.map(ts, function(ts, cb) {
currencyRatesPlugin._getOneRate(sourceId, code, ts, function(err, rate) {
if (err) return cb(err);
return cb(null, {
ts: parseInt(ts),
rate: rate
});
});
}, function(err, res) {
if (err) return cb(err);
return cb(null, res);
});
};
currencyRatesPlugin.getRate = function(req, res) {
var source = req.param('source') || currencyRatesPlugin.defaultSource;
var ts = req.param('ts');
if (_.isString(ts) && ts.indexOf(',') !== -1) {
ts = ts.split(',');
}
currencyRatesPlugin._getRate(source, req.param('code'), ts, function(err, result) {
if (err) returnError({
code: 500,
message: err,
});
res.json(result);
});
};
module.exports = currencyRatesPlugin;
})();

View File

@ -1,190 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><%= title%></title>
<style type="text/css">
/* Based on The MailChimp Reset INLINE: Yes. */
/* Client-specific Styles */
#outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
/* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
.ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
/* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
#backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
/* End reset */
/* Some sensible defaults for images
Bring inline: Yes. */
img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
a img {border:none;}
.image_fix {display:block;}
/* Yahoo paragraph fix
Bring inline: Yes. */
p {margin: 1em 0;}
/* Hotmail header color reset
Bring inline: Yes. */
h1, h2, h3, h4, h5, h6 {color: black !important;}
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: blue !important;}
h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {
color: red !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
}
h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
color: purple !important; /* Preferably not the same color as the normal header link color. There is limited support for psuedo classes in email clients, this was added just for good measure. */
}
/* Outlook 07, 10 Padding issue fix
Bring inline: No.*/
table td {border-collapse: collapse;}
/* Remove spacing around Outlook 07, 10 tables
Bring inline: Yes */
table { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
/* Styling your links has become much simpler with the new Yahoo. In fact, it falls in line with the main credo of styling in email and make sure to bring your styles inline. Your link colors will be uniform across clients when brought inline.
Bring inline: Yes. */
a {color: orange;}
/***************************************************
****************************************************
MOBILE TARGETING
****************************************************
***************************************************/
@media only screen and (max-device-width: 480px) {
/* Part one of controlling phone number linking for mobile. */
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: blue; /* or whatever your want */
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: orange !important;
pointer-events: auto;
cursor: default;
}
}
/* More Specific Targeting */
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
/* You guessed it, ipad (tablets, smaller screens, etc) */
/* repeating for the ipad */
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: blue; /* or whatever your want */
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: orange !important;
pointer-events: auto;
cursor: default;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
/* Put your iPhone 4g styles in here */
}
/* Android targeting */
@media only screen and (-webkit-device-pixel-ratio:.75){
/* Put CSS for low density (ldpi) Android layouts in here */
}
@media only screen and (-webkit-device-pixel-ratio:1){
/* Put CSS for medium density (mdpi) Android layouts in here */
}
@media only screen and (-webkit-device-pixel-ratio:1.5){
/* Put CSS for high density (hdpi) Android layouts in here */
}
/* end Android targeting */
</style>
<!-- Targeting Windows Mobile -->
<!--[if IEMobile 7]>
<style type="text/css">
</style>
<![endif]-->
<!-- ***********************************************
****************************************************
END MOBILE TARGETING
****************************************************
************************************************ -->
<!--[if gte mso 9]>
<style>
/* Target Outlook 2007 and 2010 */
</style>
<![endif]-->
</head>
<body>
<!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
<table cellpadding="0" cellspacing="0" border="0" id="backgroundTable">
<tr>
<td valign="top">
<!-- Tables are the most common way to format your email consistently. Set your table widths inside cells and in most cases reset cellpadding, cellspacing, and border to zero. Use nested tables as a way to space effectively in your message. -->
<table cellpadding="0" cellspacing="0" border="0" align="center">
<tr>
<td width="200" valign="top"></td>
<td width="200" valign="top"></td>
<td width="200" valign="top"></td>
</tr>
</table>
<!-- End example table -->
<p>Hi, <%= email %></p>
<p>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.</p>
<p>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:</p>
<p>
<!-- Yahoo Link color fix updated: Simply bring your link styling inline. -->
<a href="<%= confirm_url %>" target ="_blank" title="Confirm your email" style="color: orange; text-decoration: none;">Confirm your email (click here).</a>
</p>
<p style="font-size: small">If the above link doesn't work, head to: <%= confirm_url %></p>
<p>We would also like you to know that:</p>
<ul>
<li>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. </li>
<li>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.</li>
<li> Our code is open source and can be audited here:
<ul>
<li> https://github.com/bitpay/insight </li>
<li> https://github.com/bitpay/copay </li>
</ul>
</li>
</ul>
<p>Thanks!</p>
<p> The Copay Team </p>
</td>
</tr>
</table>
<!-- End of wrapper table -->
</body>
</html>

View File

@ -1,27 +0,0 @@
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

View File

@ -1,288 +0,0 @@
/**
* GIST: https://gist.github.com/eordano/3e80ee3383554e94a08e
*/
(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 querystring = require('querystring');
var moment = require('moment');
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: 'Email already confirmed or credentials 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'
},
OVER_QUOTA: {
code: 406,
message: 'User quota exceeded',
},
ERROR_SENDING_EMAIL: {
code: 501,
message: 'Could not send verification email',
},
};
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
var STORED_VALUE = 'emailstore-';
var ITEMS_COUNT = 'itemscount-';
var PENDING = 'pending-';
var VALIDATED = 'validated-';
var SEPARATOR = '#';
var UNCONFIRMED_PER_ITEM_QUOTA = 1024 * 150; /* 150 kb */
var CONFIRMED_PER_ITEM_QUOTA = 1024 * 300; /* 300 kb */
var UNCONFIRMED_ITEMS_LIMIT = 6;
var CONFIRMED_ITEMS_LIMIT = 11;
var POST_LIMIT = 1024 * 300 /* Max POST 300 kb */ ;
var valueKey = function(email, key) {
return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex');
};
var countKey = function(email) {
return ITEMS_COUNT + bitcore.util.twoSha256(email).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');
config = config || {};
var path = globalConfig.leveldb + '/emailstore' + (globalConfig.name ? ('-' + globalConfig.name) : '');
emailPlugin.db = config.db || globalConfig.db || levelup(path);
emailPlugin.crypto = config.crypto || crypto;
};
/**
* 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 <tt>error.message</tt>, 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();
};
/**
* @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();
});
};
/**
* @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.getCredentialsFromRequest = function(request) {
var auth = request.header('authorization');
if (!auth) {
return emailPlugin.errors.INVALID_REQUEST;
}
var authHeader = new Buffer(auth, '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
};
};
/**
* @param {string} email
* @param {Function(err, boolean)} callback
*/
emailPlugin.isConfirmed = function(email, callback) {
emailPlugin.db.get(validatedKey(email), function(err, isConfirmed) {
if (err && err.notFound) {
return callback(null, false);
} else if (err) {
return callback(emailPlugin.errors.INTERNAL_ERROR);
}
return callback(null, !!isConfirmed);
});
};
emailPlugin.authorizeRequest = function(request, withKey, callback) {
var credentialsResult = emailPlugin.getCredentialsFromRequest(request);
if (_.contains(emailPlugin.errors, credentialsResult)) {
return callback(credentialsResult);
}
var email = credentialsResult.email;
var passphrase = credentialsResult.passphrase;
var key;
if (withKey) {
key = request.param('key');
}
if (!passphrase || !email || (withKey && !key)) {
return callback(emailPlugin.errors.MISSING_PARAMETER);
}
emailPlugin.checkPassphrase(email, passphrase, function(err, matches) {
if (err) {
return callback(err);
}
if (!matches) {
return callback(emailPlugin.errors.INVALID_CODE);
}
return callback(null, email, key);
});
};
emailPlugin.authorizeRequestWithoutKey = function(request, callback) {
emailPlugin.authorizeRequest(request, false, callback);
};
emailPlugin.authorizeRequestWithKey = function(request, callback) {
emailPlugin.authorizeRequest(request, true, callback);
};
/**
* Retrieve a record from the database
*/
emailPlugin.retrieve = function(request, response) {
emailPlugin.authorizeRequestWithKey(request, function(err, email, key) {
if (err)
return emailPlugin.returnError(err, response);
emailPlugin.retrieveByEmailAndKey(email, key, function(err, value) {
if (err)
return emailPlugin.returnError(err, response);
response.send(value).end();
});
});
};
module.exports = emailPlugin;
})();

View File

@ -1,26 +0,0 @@
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();
};

View File

@ -1,2 +0,0 @@
module.exports = {
};

View File

@ -1,144 +0,0 @@
/**
* 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 <tt>error.message</tt>, 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;
})();

View File

@ -1,35 +0,0 @@
var logger = require('../lib/logger').logger;
var preconditions = require('preconditions').singleton();
var limiter = require('connect-ratelimit');
var THREE_HOURS = 3* 60 * 60 * 1000;
module.exports.init = function(app, config) {
preconditions.checkArgument(app);
logger.info('Using ratelimiter plugin');
config = config || {};
config.whitelistRPH = config.whitelistRPH || 3*60*60*10;
config.normalRPH = config.normalRPH || 3*60*60;
config.blacklistRPH = config.blacklistRPH || 0;
app.use(limiter({
whitelist: [],
end: true,
blacklist: [], // 'example.com'
categories: {
whitelist: {
totalRequests: config.whitelistRPH,
every: THREE_HOURS
},
blacklist: {
totalRequests: config.blacklistRPH,
every: THREE_HOURS
},
normal: {
totalRequests: config.normalRPH,
every: THREE_HOURS
}
}
}));
};

View File

@ -1,7 +1,7 @@
'use strict';
var sinon = require('sinon');
var should = require('should');
var AddressController = require('../../bitcore-node/addresses');
var AddressController = require('../lib/addresses');
var _ = require('lodash');
var bitcore = require('bitcore');

View File

@ -2,7 +2,7 @@
var should = require('should');
var sinon = require('sinon');
var BlockController = require('../../bitcore-node/blocks');
var BlockController = require('../lib/blocks');
var bitcore = require('bitcore');
var _ = require('lodash');

View File

@ -1,174 +0,0 @@
#!/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();
});
});
});
});
});
});
});

View File

@ -1,59 +0,0 @@
#!/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();
});
});
});
});

View File

@ -1,366 +0,0 @@
#!/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();
});
});
});
});

View File

@ -1,146 +0,0 @@
#!/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});
});
}
});
});

View File

@ -1,138 +0,0 @@
[
{
"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"
]
}
]

View File

@ -1,199 +0,0 @@
#!/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});
});
});
});

View File

@ -1,43 +0,0 @@
#!/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();
});
});
});

View File

@ -1,75 +0,0 @@
#!/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('bitcore/networks'),
util = require('bitcore/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();
});
}
});
});

View File

@ -1,36 +0,0 @@
#!/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();
});
});
});

View File

@ -1,90 +0,0 @@
#!/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);
});
});

View File

@ -1,17 +0,0 @@
'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();
});
});
});

View File

@ -1,32 +0,0 @@
{
"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
}
]
}

View File

@ -1,43 +0,0 @@
#!/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();
});
});
});

View File

@ -1,62 +0,0 @@
[
{
"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
}
]
}
]

View File

@ -1,69 +0,0 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var _ = require('lodash');
var async = require('async');
var assert = require('assert');
var fs = require('fs');
var Address = require('../../app/models/Address');
var addresses = require('../../app/controllers/addresses');
var TransactionDb = require('../../lib/TransactionDb').default();
var fixture = JSON.parse(fs.readFileSync('test/integration/txs.json'));
var should = require('chai');
var sinon = require('sinon');
var txDb;
describe('Transactions for multiple addresses', function() {
this.timeout(5000);
var req, res;
before(function(c) {
txDb = TransactionDb;
var i = 0;
_.each(_.flatten(_.pluck(fixture, 'addrs')), function(addr) {
TransactionDb.deleteCacheForAddress(addr, function() {
if (++i === fixture.length) return c();
});
});
});
beforeEach(function(c) {
req = {};
res = {};
return c();
});
describe('Transactions from multiple addresses', function () {
_.each(fixture, function (f) {
it(f.test, function (done) {
var checkResult = function(txs) {
var paginated = !_.isUndefined(f.from) || !_.isUndefined(f.to);
txs.should.exist;
if (paginated) {
txs.totalItems.should.equal(f.totalTransactions);
txs.items.length.should.equal(f.returnedTransactions);
if (f.transactions) {
JSON.stringify(_.pluck(txs.items, 'txid')).should.equal(JSON.stringify(f.transactions));
}
} else {
txs.should.be.instanceof(Array);
txs.length.should.equal(f.returnedTransactions);
}
done();
};
res.jsonp = checkResult;
req.param = sinon.stub();
req.param.withArgs('addrs').returns(f.addrs.join(','));
req.param.withArgs('from').returns(f.from);
req.param.withArgs('to').returns(f.to);
addresses.multitxs(req, res);
});
});
});
});

View File

@ -1,32 +0,0 @@
[{
"test": "should return non-paginated txs for single address",
"addrs": ["mtA6woo1wjCeu1dLkWgpSD3tRnRfrHt3FL"],
"totalTransactions": 13,
"returnedTransactions": 13
},{
"test": "should return paginated txs for single address",
"addrs": ["mtA6woo1wjCeu1dLkWgpSD3tRnRfrHt3FL"],
"totalTransactions": 13,
"from": 8,
"to": 12,
"returnedTransactions": 4
},{
"test": "should return non-paginated txs for multiple addresses",
"addrs": ["mqtYvjsUg59XpngBAYjFF51Xauc28a6Ckd","myDZmGN9euuRhM1mniSaXhQjNdqFmUzUkj"],
"totalTransactions": 4,
"returnedTransactions": 4
},{
"test": "should return paginated txs for multiple addresses",
"addrs": ["mqtYvjsUg59XpngBAYjFF51Xauc28a6Ckd","myDZmGN9euuRhM1mniSaXhQjNdqFmUzUkj", "n2NbTWCuUyTvpA2YKTpVD4SmCouFLcm8rS"],
"totalTransactions": 6,
"from": 2,
"to": 6,
"returnedTransactions": 4,
"transactions": [
"3e81723d069b12983b2ef694c9782d32fca26cc978de744acbc32c3d3496e915",
"b701b0680bc2b6f8b58f13d035a754486ecefd2438e3495cd8f395ab7f5e7ba9",
"80fb4fd2a6e5e795a4f30aebeda49424e7ed4d3630b128efb946aa964e0bc9c0",
"855e881892d7cd4a8fa81dbd8f6e9d142d02bffc10ffbe5428036ee55c3e3e0f"
]
}
]

View File

@ -1,16 +0,0 @@
[
{
"addr": "muyg1K5WsHkfMVCkUXU2y7Xp5ZD6RGzCeH",
"length": 1,
"tx0id": "eeabc70063d3f266e190e8735bc4599c811d3a79d138da1364e88502069b029c",
"tx0scriptPubKey": "76a9149e9f6515c70db535abdbbc983c7d8d1bff6c20cd88ac",
"tx0amount": 0.38571339
},
{
"addr": "2N1pLkosf6o8Ciqs573iwwgVpuFS6NbNKx5",
"length": 13,
"tx0id": "b9cc61b55814a0f972788e9025db1013157f83716e08239026a156efe892a05c",
"tx0scriptPubKey": "a9145e0461e38796367580305e3615fc1b70e4c3307687",
"tx0amount": 0.001
}
]

View File

@ -1,49 +0,0 @@
'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;
});
});
});

View File

@ -1,5 +0,0 @@
--require should
-R spec
--ui bdd
--recursive

View File

@ -1,101 +0,0 @@
'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);
});
});

View File

@ -1,265 +0,0 @@
'use strict';
var chai = require('chai');
var assert = require('assert');
var sinon = require('sinon');
var should = chai.should;
var expect = chai.expect;
var levelup = require('levelup');
var memdown = require('memdown');
var logger = require('../lib/logger').logger;
logger.transports.console.level = 'non';
var rates = require('../plugins/currencyrates');
var db;
describe('Rates service', function() {
beforeEach(function() {
db = levelup(memdown);
});
describe('#getRate', function() {
beforeEach(function() {
rates.init({
db: db,
});
});
it('should get rate with exact ts', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45
}, ]);
rates._getRate('bitpay', 'USD', 10, function(err, res) {
expect(err).to.not.exist;
res.rate.should.equal(123.45);
done();
});
});
it('should get rate with approximate ts', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45,
}, {
type: 'put',
key: 'bitpay-USD-20',
value: 200.00,
}]);
rates._getRate('bitpay', 'USD', 25, function(err, res) {
res.rate.should.equal(200.00);
done();
});
});
it('should return null when no rate found', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-20',
value: 123.45,
}, {
type: 'put',
key: 'bitpay-USD-30',
value: 200.00,
}]);
rates._getRate('bitpay', 'USD', 10, function(err, res) {
expect(res.rate).to.be.null;
done();
});
});
it('should get rate from specified source', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45,
}, {
type: 'put',
key: 'bitstamp-USD-10',
value: 200.00,
}]);
rates._getRate('bitpay', 'USD', 12, function(err, res) {
res.rate.should.equal(123.45);
done();
});
});
it('should get rate for specified currency', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 123.45,
}, {
type: 'put',
key: 'bitpay-EUR-10',
value: 200.00,
}]);
rates._getRate('bitpay', 'EUR', 12, function(err, res) {
res.rate.should.equal(200.00);
done();
});
});
it('should get multiple rates', function(done) {
db.batch([{
type: 'put',
key: 'bitpay-USD-10',
value: 100.00,
}, {
type: 'put',
key: 'bitpay-USD-20',
value: 200.00,
}, {
type: 'put',
key: 'bitstamp-USD-30',
value: 300.00,
}, {
type: 'put',
key: 'bitpay-USD-30',
value: 400.00,
}]);
rates._getRate('bitpay', 'USD', [10, 20, 35], function(err, res) {
expect(err).to.not.exist;
res.length.should.equal(3);
res[0].ts.should.equal(10);
res[1].ts.should.equal(20);
res[2].ts.should.equal(35);
res[0].rate.should.equal(100.00);
res[1].rate.should.equal(200.00);
res[2].rate.should.equal(400.00);
done();
});
});
});
describe('#fetch', function() {
it('should fetch from all sources', function(done) {
var sources = [];
sources.push({
id: 'id1',
url: 'http://dummy1',
parseFn: function(raw) {
return raw;
},
});
sources.push({
id: 'id2',
url: 'http://dummy2',
parseFn: function(raw) {
return raw;
},
});
var ds1 = [{
code: 'USD',
rate: 123.45,
}, {
code: 'EUR',
rate: 200.00,
}];
var ds2 = [{
code: 'USD',
rate: 126.39,
}];
var request = sinon.stub();
request.get = sinon.stub();
request.get.withArgs({
url: 'http://dummy1',
json: true
}).yields(null, null, ds1);
request.get.withArgs({
url: 'http://dummy2',
json: true
}).yields(null, null, ds2);
rates.init({
db: db,
sources: sources,
request: request,
});
var clock = sinon.useFakeTimers(1400000000 * 1000);
rates._fetch(function(err, res) {
clock.restore();
expect(err).to.not.exist;
var result = [];
db.readStream()
.on('data', function(data) {
result.push(data);
})
.on('close', function() {
result.length.should.equal(3);
result[0].key.should.equal('id1-EUR-1400000000');
result[1].key.should.equal('id1-USD-1400000000');
result[2].key.should.equal('id2-USD-1400000000');
parseFloat(result[0].value).should.equal(200.00);
parseFloat(result[1].value).should.equal(123.45);
parseFloat(result[2].value).should.equal(126.39);
done();
});
});
});
it('should not stop when failing to fetch source', function(done) {
var sources = [];
sources.push({
id: 'id1',
url: 'http://dummy1',
parseFn: function(raw) {
return raw;
},
});
sources.push({
id: 'id2',
url: 'http://dummy2',
parseFn: function(raw) {
return raw;
},
});
var ds2 = [{
code: 'USD',
rate: 126.39,
}];
var request = sinon.stub();
request.get = sinon.stub();
request.get.withArgs({
url: 'http://dummy1',
json: true
}).yields('dummy error', null, null);
request.get.withArgs({
url: 'http://dummy2',
json: true
}).yields(null, null, ds2);
rates.init({
db: db,
sources: sources,
request: request,
});
var clock = sinon.useFakeTimers(1400000000 * 1000);
rates._fetch(function(err, res) {
clock.restore();
expect(err).to.not.exist;
var result = [];
db.readStream()
.on('data', function(data) {
result.push(data);
})
.on('close', function() {
result.length.should.equal(1);
result[0].key.should.equal('id2-USD-1400000000');
parseFloat(result[0].value).should.equal(126.39);
done();
});
});
});
});
});

View File

@ -1,683 +0,0 @@
'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;
var moment = require('moment');
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.del = sinon.stub();
var email_stub = sinon.stub();
email_stub.sendMail = sinon.stub().callsArg(1);
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().returns({
json: function() {
return {
end: function() {
}
}
}
});
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 fakeEmail = 'fake@email.com';
var fakeRandom = 'fakerandom';
var randomBytes = {
toString: function() {
return fakeRandom;
}
};
beforeEach(function() {
leveldb_stub.get.reset();
leveldb_stub.put.reset();
plugin.email.sendMail.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();
var clock = sinon.useFakeTimers();
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
var arg = JSON.parse(leveldb_stub.put.firstCall.args[1]);
arg.secret.should.equal(fakeRandom);
arg.created.should.equal(moment().unix());
clock.restore();
done();
});
});
it('sends verification email', function(done) {
setupLevelDb();
plugin.createVerificationSecretAndSendEmail(fakeEmail, function(err) {
plugin.email.sendMail.calledOnce.should.be.true;
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();
});
});
});
});
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() {
var originalCredentials = plugin.getCredentialsFromRequest;
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.isConfirmed = sinon.stub();
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
plugin.checkSizeQuota = sinon.stub();
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
plugin.checkAndUpdateItemQuota = sinon.stub();
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
plugin.saveEncryptedData = sinon.stub();
plugin.saveEncryptedData.onFirstCall().callsArg(3);
plugin.createVerificationSecretAndSendEmail = sinon.stub();
plugin.createVerificationSecretAndSendEmail.onFirstCall().callsArg(1);
response.send.onFirstCall().returnsThis();
plugin.save(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);
plugin.getCredentialsFromRequest = originalCredentials;
});
it('should allow to overwrite data', function() {
var originalCredentials = plugin.getCredentialsFromRequest;
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.isConfirmed = sinon.stub();
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
plugin.checkSizeQuota = sinon.stub();
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
plugin.checkAndUpdateItemQuota = sinon.stub();
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
plugin.saveEncryptedData = sinon.stub();
plugin.saveEncryptedData.onFirstCall().callsArg(3);
plugin.createVerificationSecretAndSendEmail = sinon.stub();
response.send.onFirstCall().returnsThis();
plugin.save(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);
plugin.createVerificationSecretAndSendEmail.called.should.be.false;
plugin.getCredentialsFromRequest = originalCredentials;
});
it('should delete profile on error sending verification email', function() {
var originalCredentials = plugin.getCredentialsFromRequest;
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.isConfirmed = sinon.stub();
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
plugin.checkSizeQuota = sinon.stub();
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
plugin.checkAndUpdateItemQuota = sinon.stub();
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
plugin.saveEncryptedData = sinon.stub();
plugin.saveEncryptedData.onFirstCall().callsArg(3);
plugin.createVerificationSecretAndSendEmail = sinon.stub();
plugin.createVerificationSecretAndSendEmail.onFirstCall().callsArgWith(1, 'error');
var deleteWholeProfile = sinon.stub(plugin, 'deleteWholeProfile');
deleteWholeProfile.onFirstCall().callsArg(1);
response.send.onFirstCall().returnsThis();
plugin.save(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);
assert(deleteWholeProfile.firstCall.args[0] === emailParam);
plugin.getCredentialsFromRequest = originalCredentials;
});
after(function () {
plugin.deleteWholeProfile.restore();
});
});
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.put.onFirstCall().callsArg(2);
leveldb_stub.del.onFirstCall().callsArg(1);
response.json.returnsThis();
});
it('should validate correctly an email if the secret matches (secret only)', function() {
leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret);
leveldb_stub.del = sinon.stub().yields(null);
response.redirect = sinon.stub();
plugin.validate(request, response);
assert(response.redirect.firstCall.calledWith(plugin.redirectUrl));
});
it('should validate correctly an email if the secret matches (secret + creation date)', function() {
leveldb_stub.get.onFirstCall().callsArgWith(1, null, JSON.stringify({
secret: secret,
created: moment().unix(),
}));
leveldb_stub.del = sinon.stub().yields(null);
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('resend validation email', function () {
var email = 'fake@email.com';
var secret = '123';
beforeEach(function() {
leveldb_stub.get.reset();
request.param.onFirstCall().returns(email);
response.json.returnsThis();
response.redirect = sinon.stub();
});
it('should resend validation email when pending', function () {
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1, null, email);
leveldb_stub.get.onFirstCall().callsArgWith(1, null, JSON.stringify({ secret: secret, created: new Date() }));
plugin.sendVerificationEmail = sinon.spy();
plugin.resendEmail(request, response);
plugin.sendVerificationEmail.calledOnce.should.be.true;
plugin.sendVerificationEmail.calledWith(email, secret).should.be.true;
});
it('should resend validation email when pending (old style secret)', function () {
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1, null, email);
leveldb_stub.get.onFirstCall().callsArgWith(1, null, secret);
plugin.sendVerificationEmail = sinon.spy();
plugin.resendEmail(request, response);
plugin.sendVerificationEmail.calledOnce.should.be.true;
plugin.sendVerificationEmail.calledWith(email, secret).should.be.true;
});
it('should not resend when email is no longer pending', function () {
plugin.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1, null, email);
leveldb_stub.get.onFirstCall().callsArgWith(1, { notFound: true });
plugin.sendVerificationEmail = sinon.spy();
plugin.resendEmail(request, response);
plugin.sendVerificationEmail.should.not.be.called;
});
});
describe('removing items', function() {
var fakeEmail = 'fake@email.com';
var fakeKey = 'nameForData';
beforeEach(function() {
leveldb_stub.del = sinon.stub();
});
it('deletes a stored element (key)', function(done) {
leveldb_stub.del.onFirstCall().callsArg(1);
plugin.checkAndUpdateItemCounter = sinon.stub();
plugin.checkAndUpdateItemCounter.onFirstCall().callsArg(3);
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
expect(err).to.be.undefined;
done();
});
});
it('returns NOT FOUND if trying to delete a stored element by key', function(done) {
leveldb_stub.del.onFirstCall().callsArgWith(1, {notFound: true});
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
err.should.equal(plugin.errors.NOT_FOUND);
done();
});
});
it('returns INTERNAL_ERROR if an unexpected error ocurrs', function(done) {
leveldb_stub.del.onFirstCall().callsArgWith(1, {unexpected: true});
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
it('can delete a whole profile (validation data and passphrase)', function(done) {
leveldb_stub.del.callsArg(1);
plugin.deleteWholeProfile(fakeEmail, function(err) {
expect(err).to.be.undefined;
leveldb_stub.del.callCount.should.equal(3);
done();
});
});
it('dismisses not found errors', function(done) {
leveldb_stub.del.callsArg(1);
leveldb_stub.del.onSecondCall().callsArgWith(1, {notFound: true});
plugin.deleteWholeProfile(fakeEmail, function(err) {
expect(err).to.be.undefined;
done();
});
});
it('returns internal error if something goes awry', function(done) {
leveldb_stub.del.callsArg(1);
leveldb_stub.del.onSecondCall().callsArgWith(1, {unexpected: true});
plugin.deleteWholeProfile(fakeEmail, function(err) {
err.should.equal(plugin.errors.INTERNAL_ERROR);
done();
});
});
});
describe('when retrieving data', function() {
it('should validate the secret and return the data', function() {
request.param.onFirstCall().returns('key');
plugin.authorizeRequestWithKey = sinon.stub().callsArgWith(1,null, 'email','key');
plugin.retrieveByEmailAndKey = sinon.stub().yields(null, 'encrypted');
response.send.onFirstCall().returnsThis();
plugin.addValidationHeader = sinon.stub().callsArg(2);
plugin.addValidationAndQuotaHeader = sinon.stub().callsArg(2);
plugin.retrieve(request, response);
response.send.calledOnce.should.equal(true);
assert(plugin.retrieveByEmailAndKey.firstCall.args[0] === 'email');
assert(plugin.retrieveByEmailAndKey.firstCall.args[1] === 'key');
assert(response.send.firstCall.args[0] === 'encrypted');
assert(response.end.calledOnce);
});
});
describe('authorizing requests', function() {
var originalCredentials;
beforeEach(function() {
originalCredentials = plugin.getCredentialsFromRequest;
plugin.getCredentialsFromRequest = sinon.mock();
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
passphrase: 'pass'
});
request.param.onFirstCall().returns('key');
request.on = sinon.stub();
request.on.onFirstCall().callsArgWith(1, 'newPassphrase=newPassphrase');
request.on.onFirstCall().returns(request);
request.on.onSecondCall().callsArg(1);
plugin.checkPassphrase = sinon.stub().callsArgWith(2,null, true);
});
it('should authorize a request', function(done){
plugin.authorizeRequest(request, false, function(err, email, key) {
expect(err).to.be.null;
expect(key).to.be.undefined;
email.should.be.equal('email');
done();
});
});
it('should authorize a request with key', function(done){
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
passphrase: 'pass',
});
plugin.authorizeRequest(request, true, function(err, email, key) {
expect(err).to.be.null;
email.should.be.equal('email');
key.should.be.equal('key');
done();
});
});
it('should not authorize a request when param are missing', function(done){
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
});
plugin.authorizeRequest(request, false, function(err, email, key) {
expect(err).not.to.be.null;
expect(key).to.be.undefined;
expect(email).to.be.undefined;
done();
});
});
it('should not authorize a request when param are missing (case2)', function(done){
plugin.getCredentialsFromRequest.onFirstCall().returns({
passphrase: 'pass'
});
plugin.authorizeRequest(request, false, function(err, email, key) {
expect(err).not.to.be.null;
expect(key).to.be.undefined;
expect(email).to.be.undefined;
done();
});
});
it('should not authorize a request when param are missing (case3)', function(done){
request.param.onFirstCall().returns(undefined);
plugin.getCredentialsFromRequest.onFirstCall().returns({
email: 'email',
passphrase: 'pass'
});
plugin.authorizeRequest(request, true, function(err, email, key) {
expect(err).not.to.be.null;
expect(key).to.be.undefined;
expect(email).to.be.undefined;
done();
});
});
after(function() {
plugin.getCredentialsFromRequest = originalCredentials;
});
});
describe('changing the user password', function() {
beforeEach(function() {
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.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1,'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.authorizeRequestWithoutKey = sinon.stub().callsArgWith(1,null, 'email');
plugin.checkPassphrase.onFirstCall().callsArgWith(2, null);
plugin.savePassphrase.onFirstCall().callsArgWith(2, null);
plugin.changePassphrase(request, response);
assert(response.json.calledOnce);
assert(response.end.calledOnce);
});
});
});

View File

@ -1,133 +0,0 @@
'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('bitcore');
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;
});
});

Some files were not shown because too many files have changed in this diff Show More