commit
a0e80b2229
5
.gitignore
vendored
5
.gitignore
vendored
@ -24,7 +24,6 @@ report
|
|||||||
*~
|
*~
|
||||||
.idea
|
.idea
|
||||||
.project
|
.project
|
||||||
peerdb.json
|
|
||||||
|
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
.nodemonignore
|
.nodemonignore
|
||||||
@ -38,6 +37,10 @@ db/blocks/*
|
|||||||
db/blocks
|
db/blocks
|
||||||
db/testnet/blocks/*
|
db/testnet/blocks/*
|
||||||
db/testnet/blocks
|
db/testnet/blocks
|
||||||
|
db/*
|
||||||
|
db-test/
|
||||||
|
|
||||||
README.html
|
README.html
|
||||||
public
|
public
|
||||||
|
|
||||||
|
blocks
|
||||||
|
|||||||
@ -23,7 +23,7 @@ Blocks.setNode = function(aNode) {
|
|||||||
* Finds a block by its hash
|
* Finds a block by its hash
|
||||||
*/
|
*/
|
||||||
Blocks.blockHashParam = function(req, res, next, blockHash) {
|
Blocks.blockHashParam = function(req, res, next, blockHash) {
|
||||||
node.getBlock(blockHash)
|
node.blockService.getBlock(blockHash)
|
||||||
.then(function(block) {
|
.then(function(block) {
|
||||||
req.block = block;
|
req.block = block;
|
||||||
})
|
})
|
||||||
@ -38,7 +38,7 @@ Blocks.blockHashParam = function(req, res, next, blockHash) {
|
|||||||
*/
|
*/
|
||||||
Blocks.heightParam = function(req, res, next, height) {
|
Blocks.heightParam = function(req, res, next, height) {
|
||||||
height = parseInt(height);
|
height = parseInt(height);
|
||||||
node.getBlock(height)
|
node.blockService.getBlockByHeight(height)
|
||||||
.then(function(block) {
|
.then(function(block) {
|
||||||
req.block = block;
|
req.block = block;
|
||||||
})
|
})
|
||||||
@ -83,7 +83,7 @@ Blocks.list = function(req, res) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Blocks.getLatest = function(req, res) {
|
Blocks.getLatest = function(req, res) {
|
||||||
node.getLatestBlock()
|
node.blockService.getLatest()
|
||||||
.then(function(block) {
|
.then(function(block) {
|
||||||
req.block = block;
|
req.block = block;
|
||||||
Blocks.get(req, res);
|
Blocks.get(req, res);
|
||||||
|
|||||||
@ -25,7 +25,7 @@ Transactions.setNode = function(aNode) {
|
|||||||
* Finds a transaction by its hash
|
* Finds a transaction by its hash
|
||||||
*/
|
*/
|
||||||
Transactions.txHashParam = function(req, res, next, txHash) {
|
Transactions.txHashParam = function(req, res, next, txHash) {
|
||||||
node.getTransaction(txHash)
|
node.transactionService.getTransaction(txHash)
|
||||||
.then(function(tx) {
|
.then(function(tx) {
|
||||||
req.tx = tx;
|
req.tx = tx;
|
||||||
})
|
})
|
||||||
|
|||||||
@ -11,6 +11,9 @@ describe('BitcoreHTTP', function() {
|
|||||||
|
|
||||||
// mocks
|
// mocks
|
||||||
var opts = {
|
var opts = {
|
||||||
|
BitcoreNode: {
|
||||||
|
database: {}
|
||||||
|
},
|
||||||
port: 1234
|
port: 1234
|
||||||
};
|
};
|
||||||
var nodeMock;
|
var nodeMock;
|
||||||
@ -23,7 +26,7 @@ describe('BitcoreHTTP', function() {
|
|||||||
should.exist(http);
|
should.exist(http);
|
||||||
});
|
});
|
||||||
it('from create', function() {
|
it('from create', function() {
|
||||||
var http = new BitcoreHTTP.create();
|
var http = new BitcoreHTTP.create(opts);
|
||||||
should.exist(http);
|
should.exist(http);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -25,26 +25,28 @@ describe('BitcoreHTTP v1 blocks routes', function() {
|
|||||||
return mockBlocks[hash];
|
return mockBlocks[hash];
|
||||||
};
|
};
|
||||||
var last3 = _.keys(mockBlocks).splice(-3).map(blockForHash);
|
var last3 = _.keys(mockBlocks).splice(-3).map(blockForHash);
|
||||||
var some2 = _.keys(mockBlocks).splice(2,2).map(blockForHash);
|
var some2 = _.keys(mockBlocks).splice(2, 2).map(blockForHash);
|
||||||
var nodeMock, app, agent;
|
var nodeMock, app, agent;
|
||||||
var blockList = _.values(mockBlocks);
|
var blockList = _.values(mockBlocks);
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
nodeMock = new EventEmitter();
|
nodeMock = new EventEmitter();
|
||||||
nodeMock.getBlock = function(blockHash) {
|
nodeMock.blockService = {};
|
||||||
var block;
|
nodeMock.blockService.resolveBlock = function(block, blockHash) {
|
||||||
if (typeof blockHash === 'number') {
|
|
||||||
var height = blockHash;
|
|
||||||
block = mockBlocks[_.keys(mockBlocks)[height - 100000]];
|
|
||||||
} else {
|
|
||||||
block = mockBlocks[blockHash];
|
|
||||||
}
|
|
||||||
if (_.isUndefined(block)) {
|
if (_.isUndefined(block)) {
|
||||||
return Promise.reject(new BitcoreNode.errors.Blocks.NotFound(blockHash));
|
return Promise.reject(new BitcoreNode.errors.Blocks.NotFound(blockHash));
|
||||||
}
|
}
|
||||||
return Promise.resolve(block);
|
return Promise.resolve(block);
|
||||||
|
};
|
||||||
|
nodeMock.blockService.getBlockByHeight = function(height) {
|
||||||
|
var block = mockBlocks[_.keys(mockBlocks)[height - 100000]];
|
||||||
|
return this.resolveBlock(block, height);
|
||||||
|
};
|
||||||
|
nodeMock.blockService.getBlock = function(blockHash) {
|
||||||
|
var block = mockBlocks[blockHash];
|
||||||
|
return this.resolveBlock(block, blockHash);
|
||||||
|
|
||||||
};
|
};
|
||||||
nodeMock.getLatestBlock = function() {
|
nodeMock.blockService.getLatest = function() {
|
||||||
return Promise.resolve(lastBlock);
|
return Promise.resolve(lastBlock);
|
||||||
};
|
};
|
||||||
nodeMock.listBlocks = function(from, to, offset, limit) {
|
nodeMock.listBlocks = function(from, to, offset, limit) {
|
||||||
|
|||||||
@ -23,7 +23,8 @@ describe('BitcoreHTTP v1 transactions routes', function() {
|
|||||||
var nodeMock, app, agent;
|
var nodeMock, app, agent;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
nodeMock = new EventEmitter();
|
nodeMock = new EventEmitter();
|
||||||
nodeMock.getTransaction = function(txHash) {
|
nodeMock.transactionService = {};
|
||||||
|
nodeMock.transactionService.getTransaction = function(txHash) {
|
||||||
var tx = mockTransactions[txHash];
|
var tx = mockTransactions[txHash];
|
||||||
if (_.isUndefined(tx)) {
|
if (_.isUndefined(tx)) {
|
||||||
return Promise.reject(new BitcoreNode.errors.Transactions.NotFound(txHash));
|
return Promise.reject(new BitcoreNode.errors.Transactions.NotFound(txHash));
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
BitcoreNode:
|
BitcoreNode:
|
||||||
|
LevelUp: ./db
|
||||||
|
network: livenet
|
||||||
NetworkMonitor:
|
NetworkMonitor:
|
||||||
network: livenet
|
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 8333
|
port: 8333
|
||||||
Reporter: simple # none, simple, matrix
|
Reporter: none # none, simple, matrix
|
||||||
LevelUp: ./db
|
BitcoreHTTP:
|
||||||
|
host: localhost
|
||||||
|
port: 8080
|
||||||
RPC:
|
RPC:
|
||||||
user: username
|
user: user
|
||||||
pass: password
|
pass: password
|
||||||
protocol: http
|
protocol: http
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
|
|||||||
@ -1,206 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = require('lodash');
|
|
||||||
var Address = require('../models/Address');
|
|
||||||
var common = require('./common');
|
|
||||||
var async = require('async');
|
|
||||||
|
|
||||||
var tDb = require('../../lib/TransactionDb').default();
|
|
||||||
|
|
||||||
var getAddr = function(req, res, next) {
|
|
||||||
var a;
|
|
||||||
try {
|
|
||||||
var addr = req.param('addr');
|
|
||||||
a = new Address(addr);
|
|
||||||
} catch (e) {
|
|
||||||
common.handleErrors({
|
|
||||||
message: 'Invalid address:' + e.message,
|
|
||||||
code: 1
|
|
||||||
}, res, next);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getAddrs = function(req, res, next) {
|
|
||||||
var as = [];
|
|
||||||
try {
|
|
||||||
var addrStrs = req.param('addrs');
|
|
||||||
var s = addrStrs.split(',');
|
|
||||||
if (s.length === 0) return as;
|
|
||||||
for (var i = 0; i < s.length; i++) {
|
|
||||||
var a = new Address(s[i]);
|
|
||||||
as.push(a);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
common.handleErrors({
|
|
||||||
message: 'Invalid address:' + e.message,
|
|
||||||
code: 1
|
|
||||||
}, res, next);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return as;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.show = function(req, res, next) {
|
|
||||||
var a = getAddr(req, res, next);
|
|
||||||
|
|
||||||
if (a) {
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
} else {
|
|
||||||
return res.jsonp(a.getObj());
|
|
||||||
}
|
|
||||||
}, {txLimit: req.query.noTxList?0:-1, ignoreCache: req.param('noCache')});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.utxo = function(req, res, next) {
|
|
||||||
var a = getAddr(req, res, next);
|
|
||||||
if (a) {
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err)
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
else {
|
|
||||||
return res.jsonp(a.unspent);
|
|
||||||
}
|
|
||||||
}, {onlyUnspent:1, ignoreCache: req.param('noCache')});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.multiutxo = function(req, res, next) {
|
|
||||||
var as = getAddrs(req, res, next);
|
|
||||||
if (as) {
|
|
||||||
var utxos = [];
|
|
||||||
async.each(as, function(a, callback) {
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err) callback(err);
|
|
||||||
utxos = utxos.concat(a.unspent);
|
|
||||||
callback();
|
|
||||||
}, {onlyUnspent:1, ignoreCache: req.param('noCache')});
|
|
||||||
}, function(err) { // finished callback
|
|
||||||
if (err) return common.handleErrors(err, res);
|
|
||||||
res.jsonp(utxos);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.multitxs = function(req, res, next) {
|
|
||||||
|
|
||||||
function processTxs(txs, from, to, cb) {
|
|
||||||
txs = _.uniq(_.flatten(txs), 'txid');
|
|
||||||
var nbTxs = txs.length;
|
|
||||||
var paginated = !_.isUndefined(from) || !_.isUndefined(to);
|
|
||||||
|
|
||||||
if (paginated) {
|
|
||||||
txs.sort(function(a, b) {
|
|
||||||
return (b.ts || b.ts) - (a.ts || a.ts);
|
|
||||||
});
|
|
||||||
var start = Math.max(from || 0, 0);
|
|
||||||
var end = Math.min(to || txs.length, txs.length);
|
|
||||||
txs = txs.slice(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
var txIndex = {};
|
|
||||||
_.each(txs, function (tx) { txIndex[tx.txid] = tx; });
|
|
||||||
|
|
||||||
async.each(txs, function (tx, callback) {
|
|
||||||
tDb.fromIdWithInfo(tx.txid, function(err, tx) {
|
|
||||||
if (err) console.log(err);
|
|
||||||
if (tx && tx.info) {
|
|
||||||
txIndex[tx.txid].info = tx.info;
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}, function (err) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
|
|
||||||
var transactions = _.pluck(txs, 'info');
|
|
||||||
if (paginated) {
|
|
||||||
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, 10, 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) {
|
|
||||||
var a = getAddr(req, res, next);
|
|
||||||
if (a)
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
} else {
|
|
||||||
return res.jsonp(a.balanceSat);
|
|
||||||
}
|
|
||||||
}, {ignoreCache: req.param('noCache')});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.totalReceived = function(req, res, next) {
|
|
||||||
var a = getAddr(req, res, next);
|
|
||||||
if (a)
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
} else {
|
|
||||||
return res.jsonp(a.totalReceivedSat);
|
|
||||||
}
|
|
||||||
}, {ignoreCache: req.param('noCache')});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.totalSent = function(req, res, next) {
|
|
||||||
var a = getAddr(req, res, next);
|
|
||||||
if (a)
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
} else {
|
|
||||||
return res.jsonp(a.totalSentSat);
|
|
||||||
}
|
|
||||||
}, {ignoreCache: req.param('noCache')});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.unconfirmedBalance = function(req, res, next) {
|
|
||||||
var a = getAddr(req, res, next);
|
|
||||||
if (a)
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
} else {
|
|
||||||
return res.jsonp(a.unconfirmedBalanceSat);
|
|
||||||
}
|
|
||||||
}, {ignoreCache: req.param('noCache')});
|
|
||||||
};
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
var common = require('./common'),
|
|
||||||
async = require('async'),
|
|
||||||
BlockDb = require('../../lib/BlockDb'),
|
|
||||||
TransactionDb = require('../../lib/TransactionDb');
|
|
||||||
|
|
||||||
var bdb = new BlockDb();
|
|
||||||
var tdb = new TransactionDb();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find block by hash ...
|
|
||||||
*/
|
|
||||||
exports.block = function(req, res, next, hash) {
|
|
||||||
bdb.fromHashWithInfo(hash, function(err, block) {
|
|
||||||
if (err || !block)
|
|
||||||
return common.handleErrors(err, res, next);
|
|
||||||
else {
|
|
||||||
tdb.getPoolInfo(block.info.tx[0], function(info) {
|
|
||||||
block.info.poolInfo = info;
|
|
||||||
req.block = block.info;
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show block
|
|
||||||
*/
|
|
||||||
exports.show = function(req, res) {
|
|
||||||
if (req.block) {
|
|
||||||
res.jsonp(req.block);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show block by Height
|
|
||||||
*/
|
|
||||||
exports.blockindex = function(req, res, next, height) {
|
|
||||||
bdb.blockIndex(height, function(err, hashStr) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(400).send('Bad Request'); // TODO
|
|
||||||
} else {
|
|
||||||
res.jsonp(hashStr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var getBlock = function(blockhash, cb) {
|
|
||||||
bdb.fromHashWithInfo(blockhash, function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
if (!block.info) {
|
|
||||||
console.log('Could not get %s from RPC. Orphan? Error?', blockhash); //TODO
|
|
||||||
// Probably orphan
|
|
||||||
block.info = {
|
|
||||||
hash: blockhash,
|
|
||||||
isOrphan: 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
tdb.getPoolInfo(block.info.tx[0], function(info) {
|
|
||||||
block.info.poolInfo = info;
|
|
||||||
return cb(err, block.info);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of blocks by date
|
|
||||||
*/
|
|
||||||
|
|
||||||
var DFLT_LIMIT=200;
|
|
||||||
// in testnet, this number is much bigger, we dont support
|
|
||||||
// exploring blocks by date.
|
|
||||||
|
|
||||||
exports.list = function(req, res) {
|
|
||||||
var isToday = false;
|
|
||||||
|
|
||||||
//helper to convert timestamps to yyyy-mm-dd format
|
|
||||||
var formatTimestamp = function(date) {
|
|
||||||
var yyyy = date.getUTCFullYear().toString();
|
|
||||||
var mm = (date.getUTCMonth() + 1).toString(); // getMonth() is zero-based
|
|
||||||
var dd = date.getUTCDate().toString();
|
|
||||||
|
|
||||||
return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]); //padding
|
|
||||||
};
|
|
||||||
|
|
||||||
var dateStr;
|
|
||||||
var todayStr = formatTimestamp(new Date());
|
|
||||||
|
|
||||||
if (req.query.blockDate) {
|
|
||||||
// TODO: Validate format yyyy-mm-dd
|
|
||||||
dateStr = req.query.blockDate;
|
|
||||||
isToday = dateStr === todayStr;
|
|
||||||
} else {
|
|
||||||
dateStr = todayStr;
|
|
||||||
isToday = true;
|
|
||||||
}
|
|
||||||
var gte = Math.round((new Date(dateStr)).getTime() / 1000);
|
|
||||||
|
|
||||||
//pagination
|
|
||||||
var lte = parseInt(req.query.startTimestamp) || gte + 86400;
|
|
||||||
var prev = formatTimestamp(new Date((gte - 86400) * 1000));
|
|
||||||
var next = lte ? formatTimestamp(new Date(lte * 1000)) :null;
|
|
||||||
var limit = parseInt(req.query.limit || DFLT_LIMIT) + 1;
|
|
||||||
var more;
|
|
||||||
|
|
||||||
bdb.getBlocksByDate(gte, lte, limit, function(err, blockList) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
res.status(500).send(err);
|
|
||||||
} else {
|
|
||||||
var l = blockList.length;
|
|
||||||
|
|
||||||
if (l===limit) {
|
|
||||||
more = true;
|
|
||||||
blockList.pop;
|
|
||||||
}
|
|
||||||
|
|
||||||
var moreTs=lte;
|
|
||||||
async.mapSeries(blockList,
|
|
||||||
function(b, cb) {
|
|
||||||
getBlock(b.hash, function(err, info) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
if (b.ts < moreTs) moreTs = b.ts;
|
|
||||||
return cb(err, {
|
|
||||||
height: info.height,
|
|
||||||
size: info.size,
|
|
||||||
hash: b.hash,
|
|
||||||
time: b.ts || info.time,
|
|
||||||
txlength: info.tx.length,
|
|
||||||
poolInfo: info.poolInfo
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, function(err, allblocks) {
|
|
||||||
|
|
||||||
// sort blocks by height
|
|
||||||
allblocks.sort(
|
|
||||||
function compare(a,b) {
|
|
||||||
if (a.height < b.height) return 1;
|
|
||||||
if (a.height > b.height) return -1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.jsonp({
|
|
||||||
blocks: allblocks,
|
|
||||||
length: allblocks.length,
|
|
||||||
pagination: {
|
|
||||||
next: next,
|
|
||||||
prev: prev,
|
|
||||||
currentTs: lte - 1,
|
|
||||||
current: dateStr,
|
|
||||||
isToday: isToday,
|
|
||||||
more: more,
|
|
||||||
moreTs: moreTs,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
|
|
||||||
exports.handleErrors = function (err, res) {
|
|
||||||
if (err) {
|
|
||||||
if (err.code) {
|
|
||||||
res.status(400).send(err.message + '. Code:' + err.code);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(503).send(err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(404).send('Not found');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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('bitcore-node API v' + version);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.version = function(req, res) {
|
|
||||||
var version = _getVersion();
|
|
||||||
res.json({
|
|
||||||
version: version
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Status = require('../models/Status'),
|
|
||||||
common = require('./common');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status
|
|
||||||
*/
|
|
||||||
exports.show = function(req, res) {
|
|
||||||
|
|
||||||
if (! req.query.q) {
|
|
||||||
res.status(400).send('Bad Request');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var option = req.query.q;
|
|
||||||
var statusObject = new Status();
|
|
||||||
|
|
||||||
var returnJsonp = function (err) {
|
|
||||||
if (err || ! statusObject)
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
else {
|
|
||||||
res.jsonp(statusObject);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
switch(option) {
|
|
||||||
case 'getInfo':
|
|
||||||
statusObject.getInfo(returnJsonp);
|
|
||||||
break;
|
|
||||||
case 'getDifficulty':
|
|
||||||
statusObject.getDifficulty(returnJsonp);
|
|
||||||
break;
|
|
||||||
case 'getTxOutSetInfo':
|
|
||||||
statusObject.getTxOutSetInfo(returnJsonp);
|
|
||||||
break;
|
|
||||||
case 'getLastBlockHash':
|
|
||||||
statusObject.getLastBlockHash(returnJsonp);
|
|
||||||
break;
|
|
||||||
case 'getBestBlockHash':
|
|
||||||
statusObject.getBestBlockHash(returnJsonp);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res.status(400).send('Bad Request');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.sync = function(req, res) {
|
|
||||||
if (req.historicSync)
|
|
||||||
res.jsonp(req.historicSync.info());
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.peer = function(req, res) {
|
|
||||||
if (req.peerSync) {
|
|
||||||
var info = req.peerSync.info();
|
|
||||||
res.jsonp(info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,166 +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 tDb = require('../../lib/TransactionDb').default();
|
|
||||||
var bdb = require('../../lib/BlockDb').default();
|
|
||||||
|
|
||||||
exports.send = function(req, res) {
|
|
||||||
Rpc.sendRawTransaction(req.body.rawtx, function(err, txid) {
|
|
||||||
if (err) {
|
|
||||||
var message;
|
|
||||||
if(err.code == -25) {
|
|
||||||
message = util.format(
|
|
||||||
'Generic error %s (code %s)',
|
|
||||||
err.message, err.code);
|
|
||||||
} else if(err.code == -26) {
|
|
||||||
message = util.format(
|
|
||||||
'Transaction rejected by network (code %s). Reason: %s',
|
|
||||||
err.code, err.message);
|
|
||||||
} else {
|
|
||||||
message = util.format('%s (code %s)', err.message, err.code);
|
|
||||||
}
|
|
||||||
return res.status(400).send(message);
|
|
||||||
}
|
|
||||||
res.json({'txid' : txid});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find transaction by hash ...
|
|
||||||
*/
|
|
||||||
exports.transaction = function(req, res, next, txid) {
|
|
||||||
|
|
||||||
tDb.fromIdWithInfo(txid, function(err, tx) {
|
|
||||||
if (err || ! tx)
|
|
||||||
return common.handleErrors(err, res);
|
|
||||||
else {
|
|
||||||
req.transaction = tx.info;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show transaction
|
|
||||||
*/
|
|
||||||
exports.show = function(req, res) {
|
|
||||||
|
|
||||||
if (req.transaction) {
|
|
||||||
res.jsonp(req.transaction);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var getTransaction = function(txid, cb) {
|
|
||||||
|
|
||||||
tDb.fromIdWithInfo(txid, function(err, tx) {
|
|
||||||
if (err) console.log(err);
|
|
||||||
|
|
||||||
if (!tx || !tx.info) {
|
|
||||||
console.log('[transactions.js.48]:: TXid %s not found in RPC. CHECK THIS.', txid);
|
|
||||||
return ({ txid: txid });
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(null, tx.info);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of transaction
|
|
||||||
*/
|
|
||||||
exports.list = function(req, res, next) {
|
|
||||||
var bId = req.query.block;
|
|
||||||
var addrStr = req.query.address;
|
|
||||||
var page = req.query.pageNum;
|
|
||||||
var pageLength = 10;
|
|
||||||
var pagesTotal = 1;
|
|
||||||
var txLength;
|
|
||||||
var txs;
|
|
||||||
|
|
||||||
if (bId) {
|
|
||||||
bdb.fromHashWithInfo(bId, function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
return res.status(500).send('Internal Server Error');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! block) {
|
|
||||||
return res.status(404).send('Not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
txLength = block.info.tx.length;
|
|
||||||
|
|
||||||
if (page) {
|
|
||||||
var spliceInit = page * pageLength;
|
|
||||||
txs = block.info.tx.splice(spliceInit, pageLength);
|
|
||||||
pagesTotal = Math.ceil(txLength / pageLength);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
txs = block.info.tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
async.mapSeries(txs, getTransaction, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(404).send('TX not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp({
|
|
||||||
pagesTotal: pagesTotal,
|
|
||||||
txs: results
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (addrStr) {
|
|
||||||
var a = new Address(addrStr);
|
|
||||||
|
|
||||||
a.update(function(err) {
|
|
||||||
if (err && !a.totalReceivedSat) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(404).send('Invalid address');
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
txLength = a.transactions.length;
|
|
||||||
|
|
||||||
if (page) {
|
|
||||||
var spliceInit = page * pageLength;
|
|
||||||
txs = a.transactions.splice(spliceInit, pageLength);
|
|
||||||
pagesTotal = Math.ceil(txLength / pageLength);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
txs = a.transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
async.mapSeries(txs, getTransaction, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(404).send('TX not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp({
|
|
||||||
pagesTotal: pagesTotal,
|
|
||||||
txs: results
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.jsonp({
|
|
||||||
txs: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,71 +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 + '/sync', setHistoric);
|
|
||||||
app.use(config.apiPrefix + '/peer', setPeer);
|
|
||||||
app.use(express.logger('dev'));
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(express.urlencoded());
|
|
||||||
app.use(express.methodOverride());
|
|
||||||
app.use(express.compress());
|
|
||||||
|
|
||||||
if (config.enableEmailstore) {
|
|
||||||
var allowCopayCrossDomain = function(req, res, next) {
|
|
||||||
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'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var imports = require('soop').imports();
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
self.unspent = txOut.map(function(x){
|
|
||||||
return {
|
|
||||||
address: self.addrStr,
|
|
||||||
txid: x.txid,
|
|
||||||
vout: x.index,
|
|
||||||
ts: x.ts,
|
|
||||||
scriptPubKey: x.scriptPubKey,
|
|
||||||
amount: x.value_sat / BitcoreUtil.COIN,
|
|
||||||
confirmations: x.isConfirmedCached ? (config.safeConfirmations) : x.confirmations,
|
|
||||||
confirmationsFromCache: !!x.isConfirmedCached,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
txOut.forEach(function(txItem){
|
|
||||||
self._addTxItem(txItem, txList, opts.includeTxInfo);
|
|
||||||
});
|
|
||||||
if (txList)
|
|
||||||
self.transactions = txList;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = require('soop')(Address);
|
|
||||||
|
|
||||||
@ -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);
|
|
||||||
@ -1,65 +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);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Currency
|
|
||||||
var currency = require('../app/controllers/currency');
|
|
||||||
app.get(apiPrefix + '/currency', currency.index);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
};
|
|
||||||
14
index.js
14
index.js
@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
var BitcoreNode = require('./lib/node');
|
var BitcoreNode = require('./lib/node');
|
||||||
var reporters = require('./lib/reporters');
|
var reporters = require('./lib/reporters');
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
Promise.longStackTraces();
|
||||||
|
|
||||||
|
BitcoreNode.errors = require('./lib/errors');
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
bitcore.Networks.defaultNetwork = bitcore.Networks.get(config.get('BitcoreNode').network);
|
||||||
|
|
||||||
var node = BitcoreNode.create(config.get('BitcoreNode'));
|
var node = BitcoreNode.create(config.get('BitcoreNode'));
|
||||||
node.start();
|
node.start();
|
||||||
node.on('error', function(err) {
|
node.on('error', function(err) {
|
||||||
@ -14,6 +21,10 @@ if (require.main === module) {
|
|||||||
console.log('Error: ', err);
|
console.log('Error: ', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
process.on('SIGINT', function() {
|
||||||
|
node.stop();
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
var reporterName = config.get('Reporter');
|
var reporterName = config.get('Reporter');
|
||||||
var reporter = reporters[reporterName];
|
var reporter = reporters[reporterName];
|
||||||
@ -24,7 +35,4 @@ if (require.main === module) {
|
|||||||
node.on('Transaction', reporter);
|
node.on('Transaction', reporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BitcoreNode.errors = require('./lib/errors');
|
|
||||||
|
|
||||||
module.exports = BitcoreNode;
|
module.exports = BitcoreNode;
|
||||||
|
|||||||
119
lib/Rpc.js
119
lib/Rpc.js
@ -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);
|
|
||||||
|
|
||||||
|
|
||||||
166
lib/blockchain.js
Normal file
166
lib/blockchain.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var $ = bitcore.util.preconditions;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
|
|
||||||
|
var NULL = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
|
||||||
|
function BlockChain() {
|
||||||
|
this.tip = NULL;
|
||||||
|
this.work = {};
|
||||||
|
this.work[NULL] = 0;
|
||||||
|
this.height = {};
|
||||||
|
this.height[NULL] = -1;
|
||||||
|
this.hashByHeight = { '-1': NULL };
|
||||||
|
this.next = {};
|
||||||
|
this.prev = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockChain.NULL = NULL;
|
||||||
|
|
||||||
|
BlockChain.fromObject = function(obj) {
|
||||||
|
var blockchain = new BlockChain();
|
||||||
|
blockchain.tip = obj.tip;
|
||||||
|
blockchain.work = obj.work;
|
||||||
|
blockchain.hashByHeight = obj.hashByHeight;
|
||||||
|
blockchain.height = obj.height;
|
||||||
|
blockchain.next = obj.next;
|
||||||
|
blockchain.prev = obj.prev;
|
||||||
|
return blockchain;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getWork = function(bits) {
|
||||||
|
var bytes = ((bits >>> 24) & 0xff) >>> 0;
|
||||||
|
return ((bits & 0xffffff) << (8 * (bytes - 3))) >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.addData = function(block) {
|
||||||
|
$.checkArgument(block instanceof bitcore.Block, 'Argument is not a Block instance');
|
||||||
|
|
||||||
|
var prevHash = bitcore.util.buffer.reverse(block.header.prevHash).toString('hex');
|
||||||
|
|
||||||
|
this.work[block.hash] = this.work[prevHash] + getWork(block.header.bits);
|
||||||
|
this.prev[block.hash] = prevHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.proposeNewBlock = function(block) {
|
||||||
|
$.checkArgument(block instanceof bitcore.Block, 'Argument is not a Block instance');
|
||||||
|
var prevHash = bitcore.util.buffer.reverse(block.header.prevHash).toString('hex');
|
||||||
|
|
||||||
|
if (_.isUndefined(this.work[prevHash])) {
|
||||||
|
throw new Error('No previous data to estimate work');
|
||||||
|
}
|
||||||
|
this.addData(block);
|
||||||
|
|
||||||
|
if (this.work[block.hash] > this.work[this.tip]) {
|
||||||
|
|
||||||
|
var toUnconfirm = [];
|
||||||
|
var toConfirm = [];
|
||||||
|
var commonAncestor;
|
||||||
|
|
||||||
|
var pointer = block.hash;
|
||||||
|
while (_.isUndefined(this.height[pointer])) {
|
||||||
|
toConfirm.push(pointer);
|
||||||
|
pointer = this.prev[pointer];
|
||||||
|
}
|
||||||
|
commonAncestor = pointer;
|
||||||
|
|
||||||
|
pointer = this.tip;
|
||||||
|
while (pointer !== commonAncestor) {
|
||||||
|
toUnconfirm.push(pointer);
|
||||||
|
pointer = this.prev[pointer];
|
||||||
|
}
|
||||||
|
|
||||||
|
toConfirm.reverse();
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
toUnconfirm.map(function(hash) {
|
||||||
|
self.unconfirm(hash);
|
||||||
|
});
|
||||||
|
toConfirm.map(function(hash) {
|
||||||
|
self.confirm(hash);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
unconfirmed: toUnconfirm,
|
||||||
|
confirmed: toConfirm
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
unconfirmed: [],
|
||||||
|
confirmed: []
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.confirm = function(hash) {
|
||||||
|
var prevHash = this.prev[hash];
|
||||||
|
$.checkState(prevHash === this.tip);
|
||||||
|
|
||||||
|
this.tip = hash;
|
||||||
|
var height = this.height[prevHash] + 1;
|
||||||
|
this.next[prevHash] = hash;
|
||||||
|
this.hashByHeight[height] = hash;
|
||||||
|
this.height[hash] = height;
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.unconfirm = function(hash) {
|
||||||
|
var prevHash = this.prev[hash];
|
||||||
|
$.checkState(hash === this.tip);
|
||||||
|
|
||||||
|
this.tip = prevHash;
|
||||||
|
var height = this.height[hash];
|
||||||
|
delete this.next[prevHash];
|
||||||
|
delete this.hashByHeight[height];
|
||||||
|
delete this.height[hash];
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.getBlockLocator = function() {
|
||||||
|
$.checkState(this.tip);
|
||||||
|
$.checkState(!_.isUndefined(this.height[this.tip]));
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
var currentHeight = this.height[this.tip];
|
||||||
|
var exponentialBackOff = 1;
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
if (currentHeight >= 0) {
|
||||||
|
result.push(this.hashByHeight[currentHeight--]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (currentHeight > 0) {
|
||||||
|
result.push(this.hashByHeight[currentHeight]);
|
||||||
|
currentHeight -= exponentialBackOff;
|
||||||
|
exponentialBackOff *= 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.hasData = function(hash) {
|
||||||
|
return !_.isUndefined(this.work[hash]);
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.prune = function() {
|
||||||
|
var self = this;
|
||||||
|
_.each(this.prev, function(key, value) {
|
||||||
|
if (!self.height[key]) {
|
||||||
|
delete this.prev[key];
|
||||||
|
delete this.work[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.toObject = function() {
|
||||||
|
return {
|
||||||
|
tip: this.tip,
|
||||||
|
work: this.work,
|
||||||
|
next: this.next,
|
||||||
|
hashByHeight: this.hashByHeight,
|
||||||
|
height: this.height,
|
||||||
|
prev: this.prev
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockChain.prototype.toJSON = function() {
|
||||||
|
return JSON.stringify(this.toObject());
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BlockChain;
|
||||||
13
lib/data/genesis.js
Normal file
13
lib/data/genesis.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
livenet: new Buffer('010000000000000000000000000000000000000000000000000000000000' +
|
||||||
|
'0000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a5132' +
|
||||||
|
'3a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c01010000000100000000' +
|
||||||
|
'00000000000000000000000000000000000000000000000000000000ffff' +
|
||||||
|
'ffff4d04ffff001d0104455468652054696d65732030332f4a616e2f3230' +
|
||||||
|
'3039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f' +
|
||||||
|
'6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01' +
|
||||||
|
'000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a6' +
|
||||||
|
'7962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b' +
|
||||||
|
'8d578a4c702b6bf11d5fac00000000', 'hex'),
|
||||||
|
testnet: new Buffer('')
|
||||||
|
}
|
||||||
@ -37,13 +37,26 @@ EventBus.prototype.process = function(e) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var eventsEmitted = processEvent(e)
|
|
||||||
|
var whenPreviousFinishes = Promise.resolve();
|
||||||
|
if (this.previous && !this.previous.isFulfilled()) {
|
||||||
|
//console.log('setting new task with other running, lets queue');
|
||||||
|
whenPreviousFinishes = this.previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = whenPreviousFinishes
|
||||||
|
.then(function() {
|
||||||
|
//console.log('ok, lets go with the new block');
|
||||||
|
return processEvent(e);
|
||||||
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
done.forEach(function(event) {
|
done.forEach(function(event) {
|
||||||
self.emit(event.name || event.constructor.name, event);
|
self.emit(event.name || event.constructor.name, event);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return eventsEmitted;
|
this.previous = current;
|
||||||
|
return current;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ var EventEmitter = require('eventemitter2').EventEmitter2;
|
|||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
var p2p = require('bitcore-p2p');
|
var p2p = require('bitcore-p2p');
|
||||||
var Peer = p2p.Peer;
|
var Peer = p2p.Peer;
|
||||||
var messages = new p2p.Messages();
|
var messages = new p2p.Messages();
|
||||||
@ -21,10 +22,13 @@ util.inherits(NetworkMonitor, EventEmitter);
|
|||||||
|
|
||||||
NetworkMonitor.create = function(eventBus, opts) {
|
NetworkMonitor.create = function(eventBus, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
var network = Networks.get(opts.network) || Networks.defaultNetwork;
|
|
||||||
var host = opts.host || 'localhost';
|
var host = opts.host || 'localhost';
|
||||||
var port = opts.port || Networks.defaultNetwork.port;
|
var port = opts.port || Networks.defaultNetwork.port;
|
||||||
var peer = new Peer(host, port, network);
|
var peer = new Peer({
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
network: Networks.defaultNetwork
|
||||||
|
});
|
||||||
return new NetworkMonitor(eventBus, peer);
|
return new NetworkMonitor(eventBus, peer);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,24 +39,56 @@ NetworkMonitor.prototype.setupPeer = function(peer) {
|
|||||||
self.emit('ready');
|
self.emit('ready');
|
||||||
});
|
});
|
||||||
peer.on('inv', function(m) {
|
peer.on('inv', function(m) {
|
||||||
|
self.emit('inv', m.inventory);
|
||||||
// TODO only ask for data if tx or block is unknown
|
// TODO only ask for data if tx or block is unknown
|
||||||
peer.sendMessage(messages.GetData(m.inventory));
|
peer.sendMessage(messages.GetData(m.inventory));
|
||||||
});
|
});
|
||||||
peer.on('tx', function(m) {
|
peer.on('tx', function(m) {
|
||||||
self.bus.process(m.transaction);
|
self.bus.process(m.transaction)
|
||||||
|
.catch(function(err) {
|
||||||
|
self.abort(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
peer.on('block', function(m) {
|
peer.on('block', function(m) {
|
||||||
self.bus.process(m.block);
|
self.bus.process(m.block)
|
||||||
|
.catch(function(err) {
|
||||||
|
self.abort(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
peer.on('error', function(err) {
|
peer.on('error', function(err) {
|
||||||
self.emit('error', err);
|
self.emit('error', err);
|
||||||
|
self.abort(err);
|
||||||
|
});
|
||||||
|
peer.on('disconnect', function() {
|
||||||
|
self.emit('disconnect');
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
NetworkMonitor.prototype.requestBlocks = function(locator) {
|
||||||
|
$.checkArgument(_.isArray(locator) &&
|
||||||
|
_.isUndefined(locator[0]) ||
|
||||||
|
_.isString(locator[0]), 'start must be a block hash string array');
|
||||||
|
this.peer.sendMessage(messages.GetBlocks({
|
||||||
|
starts: locator,
|
||||||
|
//stop: '000000002c05cc2e78923c34df87fd108b22221ac6076c18f3ade378a4d915e9' // TODO: remove this!!!
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
NetworkMonitor.prototype.start = function() {
|
NetworkMonitor.prototype.start = function() {
|
||||||
this.peer.connect();
|
this.peer.connect();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
NetworkMonitor.prototype.stop = function(reason) {
|
||||||
|
this.peer.disconnect();
|
||||||
|
console.log('Stopping network, reason:', reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
NetworkMonitor.prototype.abort = function(reason) {
|
||||||
|
this.peer.disconnect();
|
||||||
|
if (reason) {
|
||||||
|
throw reason;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = NetworkMonitor;
|
module.exports = NetworkMonitor;
|
||||||
|
|||||||
169
lib/node.js
169
lib/node.js
@ -4,36 +4,175 @@ var util = require('util');
|
|||||||
var EventEmitter = require('eventemitter2').EventEmitter2;
|
var EventEmitter = require('eventemitter2').EventEmitter2;
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
var _ = bitcore.deps._;
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
var RPC = require('bitcoind-rpc');
|
||||||
|
|
||||||
var NetworkMonitor = require('./networkmonitor');
|
var NetworkMonitor = require('./networkmonitor');
|
||||||
var EventBus = require('./eventbus');
|
var EventBus = require('./eventbus');
|
||||||
|
|
||||||
var BitcoreNode = function(bus, nm) {
|
var LevelUp = require('levelup');
|
||||||
$.checkArgument(bus);
|
var BlockService = require('./services/block');
|
||||||
$.checkArgument(nm);
|
var TransactionService = require('./services/transaction');
|
||||||
var self = this;
|
var AddressService = require('./services/address');
|
||||||
this.bus = bus;
|
|
||||||
this.nm = nm;
|
|
||||||
|
|
||||||
this.bus.onAny(function(value) {
|
var BlockChain = require('./blockchain');
|
||||||
self.emit(this.event, value);
|
var genesisBlocks = require('./data/genesis');
|
||||||
});
|
|
||||||
this.nm.on('error', function(err) {
|
var BitcoreNode = function(bus, networkMonitor, blockService, transactionService, addressService) {
|
||||||
self.emit('error', err);
|
$.checkArgument(bus, 'bus is required');
|
||||||
});
|
$.checkArgument(networkMonitor, 'networkMonitor is required');
|
||||||
|
$.checkArgument(blockService, 'blockService is required');
|
||||||
|
$.checkArgument(transactionService, 'transactionService is required');
|
||||||
|
$.checkArgument(addressService, 'addressService is required');
|
||||||
|
this.bus = bus;
|
||||||
|
this.networkMonitor = networkMonitor;
|
||||||
|
|
||||||
|
this.tip = null;
|
||||||
|
|
||||||
|
this.addressService = addressService;
|
||||||
|
this.transactionService = transactionService;
|
||||||
|
this.blockService = blockService;
|
||||||
|
|
||||||
|
this.blockCache = {};
|
||||||
|
this.inventory = {}; // blockHash -> bool (has data)
|
||||||
|
this.initialize();
|
||||||
};
|
};
|
||||||
util.inherits(BitcoreNode, EventEmitter);
|
util.inherits(BitcoreNode, EventEmitter);
|
||||||
|
|
||||||
BitcoreNode.create = function(opts) {
|
BitcoreNode.create = function(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
var bus = new EventBus();
|
var bus = new EventBus();
|
||||||
var nm = NetworkMonitor.create(bus, opts.NetworkMonitor);
|
|
||||||
return new BitcoreNode(bus, nm);
|
var networkMonitor = NetworkMonitor.create(bus, opts.NetworkMonitor);
|
||||||
|
|
||||||
|
var database = opts.database || Promise.promisifyAll(
|
||||||
|
new LevelUp(opts.LevelUp || './db')
|
||||||
|
);
|
||||||
|
var rpc = opts.rpc || Promise.promisifyAll(new RPC(opts.RPC));
|
||||||
|
|
||||||
|
var transactionService = opts.transactionService || new TransactionService({
|
||||||
|
rpc: rpc,
|
||||||
|
database: database
|
||||||
|
});
|
||||||
|
var blockService = opts.blockService || new BlockService({
|
||||||
|
rpc: rpc,
|
||||||
|
database: database,
|
||||||
|
transactionService: transactionService
|
||||||
|
});
|
||||||
|
var addressService = opts.addressService || new AddressService({
|
||||||
|
rpc: rpc,
|
||||||
|
database: database,
|
||||||
|
transactionService: transactionService,
|
||||||
|
blockService: blockService
|
||||||
|
});
|
||||||
|
return new BitcoreNode(bus, networkMonitor, blockService, transactionService, addressService);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
BitcoreNode.prototype.initialize = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
if (!self.blockchain) {
|
||||||
|
// not ready yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tipHash = self.blockchain.tip;
|
||||||
|
var block = self.blockCache[tipHash];
|
||||||
|
console.log('block', block.id, 'height', block.height);
|
||||||
|
}, 5 * 1000);
|
||||||
|
|
||||||
|
this.bus.register(bitcore.Block, function(block) {
|
||||||
|
|
||||||
|
var prevHash = bitcore.util.buffer.reverse(block.header.prevHash).toString('hex');
|
||||||
|
self.blockCache[block.hash] = block;
|
||||||
|
self.inventory[block.hash] = true;
|
||||||
|
if (!self.blockchain.hasData(prevHash)) {
|
||||||
|
self.requestFromTip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var blockchainChanges = self.blockchain.proposeNewBlock(block);
|
||||||
|
|
||||||
|
// Annotate block with extra data from the chain
|
||||||
|
block.height = self.blockchain.height[block.id];
|
||||||
|
block.work = self.blockchain.work[block.id];
|
||||||
|
|
||||||
|
return Promise.each(blockchainChanges.unconfirmed, function(hash) {
|
||||||
|
return self.blockService.unconfirm(self.blockCache[hash]);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return Promise.all(blockchainChanges.confirmed.map(function(hash) {
|
||||||
|
return self.blockService.confirm(self.blockCache[hash]);
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
var deleteHeight = block.height - 100;
|
||||||
|
if (deleteHeight > 0) {
|
||||||
|
var deleteHash = self.blockchain.hashByHeight[deleteHeight];
|
||||||
|
delete self.blockCache[deleteHash];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
// TODO: include this
|
||||||
|
if (false && _.size(self.inventory) && _.all(_.values(self.inventory))) {
|
||||||
|
self.inventory = {};
|
||||||
|
self.requestFromTip();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
self.stop(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bus.onAny(function(value) {
|
||||||
|
self.emit(this.event, value);
|
||||||
|
});
|
||||||
|
this.networkMonitor.on('error', function(err) {
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
this.networkMonitor.on('disconnect', function() {
|
||||||
|
console.log('network monitor disconnected');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
BitcoreNode.prototype.start = function() {
|
BitcoreNode.prototype.start = function() {
|
||||||
this.nm.start();
|
var self = this;
|
||||||
|
var genesis = bitcore.Block.fromBuffer(genesisBlocks[bitcore.Networks.defaultNetwork.name]);
|
||||||
|
|
||||||
|
this.blockService.getBlockchain().then(function(blockchain) {
|
||||||
|
if (!blockchain) {
|
||||||
|
console.log('nothing');
|
||||||
|
self.blockchain = new BlockChain();
|
||||||
|
self.bus.process(genesis);
|
||||||
|
} else {
|
||||||
|
self.blockchain = blockchain;
|
||||||
|
}
|
||||||
|
self.sync();
|
||||||
|
self.networkMonitor.start();
|
||||||
|
});
|
||||||
|
this.networkMonitor.on('stop', function() {
|
||||||
|
self.blockService.saveBlockchain(self.blockchain);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BitcoreNode.prototype.stop = function(reason) {
|
||||||
|
this.networkMonitor.abort(reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
BitcoreNode.prototype.requestFromTip = function() {
|
||||||
|
var locator = this.blockchain.getBlockLocator();
|
||||||
|
console.log('requesting blocks, locator size:', locator.length);
|
||||||
|
this.networkMonitor.requestBlocks(locator);
|
||||||
|
};
|
||||||
|
|
||||||
|
BitcoreNode.prototype.sync = function() {
|
||||||
|
var self = this;
|
||||||
|
this.networkMonitor.on('ready', function() {
|
||||||
|
self.requestFromTip();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = BitcoreNode;
|
module.exports = BitcoreNode;
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var TransactionService = require('./transaction');
|
var TransactionService = require('./transaction');
|
||||||
|
var RPC = require('bitcoind-rpc');
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
|
|
||||||
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var LevelUp = require('levelup');
|
var LevelUp = require('levelup');
|
||||||
var LevelLock = require('level-lock');
|
|
||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
var RPC = require('bitcoind-rpc');
|
var RPC = require('bitcoind-rpc');
|
||||||
var TransactionService = require('./transaction');
|
var TransactionService = require('./transaction');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
|
||||||
var BitcoreNode = require('../../');
|
var errors = require('../errors');
|
||||||
|
var BlockChain = require('../blockchain');
|
||||||
|
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var JSUtil = bitcore.util.js;
|
var JSUtil = bitcore.util.js;
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
|
|
||||||
var LOCK = 'lock-';
|
|
||||||
var NULLBLOCKHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
var NULLBLOCKHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
||||||
var GENESISPARENT = {
|
var GENESISPARENT = {
|
||||||
height: -1,
|
height: -1,
|
||||||
@ -36,22 +36,24 @@ var helper = function(index) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var Index = {
|
var Index = {
|
||||||
timestamp: 'bts-', // bts-<timestamp> -> hash for the block that was mined at this TS
|
timestamp: 'bts-', // bts-<timestamp> -> hash for the block that was mined at this TS
|
||||||
prev: 'prev-', // prev-<hash> -> parent hash
|
prev: 'prev-', // prev-<hash> -> parent hash
|
||||||
next: 'nxt-', // nxt-<hash> -> hash for the next block in the main chain that is a child
|
next: 'nxt-', // nxt-<hash> -> hash for the next block in the main chain that is a child
|
||||||
height: 'bh-', // bh-<hash> -> height (-1 means disconnected)
|
height: 'bh-', // bh-<hash> -> height (-1 means disconnected)
|
||||||
tip: 'tip' // tip -> { hash: hex, height: int }, the latest tip
|
tip: 'tip', // tip -> { hash: hex, height: int }, the latest tip
|
||||||
|
work: 'wk-' // wk-<hash> -> amount of work for block
|
||||||
};
|
};
|
||||||
_.extend(Index, {
|
_.extend(Index, {
|
||||||
getNextBlock: helper(Index.next),
|
getNextBlock: helper(Index.next),
|
||||||
getPreviousBlock: helper(Index.prev),
|
getPreviousBlock: helper(Index.prev),
|
||||||
getBlockHeight: helper(Index.height),
|
getBlockHeight: helper(Index.height),
|
||||||
|
getBlockWork: helper(Index.work),
|
||||||
getBlockByTs: function(block) {
|
getBlockByTs: function(block) {
|
||||||
return Index.timestamp + block.header.time;
|
return Index.timestamp + block.header.time;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function BlockService (opts) {
|
function BlockService(opts) {
|
||||||
opts = _.extend({}, opts);
|
opts = _.extend({}, opts);
|
||||||
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
|
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
|
||||||
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
|
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
|
||||||
@ -61,21 +63,6 @@ function BlockService (opts) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockService.prototype.writeLock = function() {
|
|
||||||
var self = this;
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
if (self.lock) {
|
|
||||||
return reject();
|
|
||||||
} else {
|
|
||||||
self.lock = true;
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
BlockService.prototype.unlock = function() {
|
|
||||||
this.lock = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms data as received from an RPC result structure for `getblock`,
|
* Transforms data as received from an RPC result structure for `getblock`,
|
||||||
@ -88,15 +75,13 @@ BlockService.prototype.unlock = function() {
|
|||||||
* @param {Number} blockData.time a 32 bit number with the timestamp when this block was created
|
* @param {Number} blockData.time a 32 bit number with the timestamp when this block was created
|
||||||
* @param {Number} blockData.nonce a 32 bit number with a random number
|
* @param {Number} blockData.nonce a 32 bit number with a random number
|
||||||
* @param {string} blockData.bits a 32 bit "varint" encoded number with the length of the block
|
* @param {string} blockData.bits a 32 bit "varint" encoded number with the length of the block
|
||||||
* @param {string} blockData.merkleRoot an hex string of length 64 with the hash of the block
|
* @param {string} blockData.merkleroot an hex string of length 64 with the hash of the block
|
||||||
* @param {Array} transactions an array of bitcore.Transaction objects, in the order that forms the
|
* @param {Array} transactions an array of bitcore.Transaction objects, in the order that forms the
|
||||||
* merkle root hash
|
* merkle root hash
|
||||||
* @return {bitcore.Block}
|
* @return {bitcore.Block}
|
||||||
*/
|
*/
|
||||||
BlockService.blockRPCtoBitcore = function(blockData, transactions) {
|
BlockService.blockRPCtoBitcore = function(blockData) {
|
||||||
$.checkArgument(_.all(transactions, function(transaction) {
|
$.checkArgument(blockData, 'blockData is required');
|
||||||
return transaction instanceof bitcore.Transaction;
|
|
||||||
}), 'All transactions must be instances of bitcore.Transaction');
|
|
||||||
var block = new bitcore.Block({
|
var block = new bitcore.Block({
|
||||||
header: new bitcore.BlockHeader({
|
header: new bitcore.BlockHeader({
|
||||||
version: blockData.version,
|
version: blockData.version,
|
||||||
@ -107,13 +92,13 @@ BlockService.blockRPCtoBitcore = function(blockData, transactions) {
|
|||||||
time: blockData.time,
|
time: blockData.time,
|
||||||
nonce: blockData.nonce,
|
nonce: blockData.nonce,
|
||||||
bits: new bitcore.deps.bnjs(
|
bits: new bitcore.deps.bnjs(
|
||||||
new bitcore.deps.Buffer(blockData.bits, 'hex')
|
new bitcore.deps.Buffer(blockData.bits, 'hex')
|
||||||
),
|
),
|
||||||
merkleRoot: bitcore.util.buffer.reverse(
|
merkleRoot: bitcore.util.buffer.reverse(
|
||||||
new bitcore.deps.Buffer(blockData.merkleroot, 'hex')
|
new bitcore.deps.Buffer(blockData.merkleroot, 'hex')
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
transactions: transactions
|
transactions: blockData.transactions
|
||||||
});
|
});
|
||||||
block.height = blockData.height;
|
block.height = blockData.height;
|
||||||
return block;
|
return block;
|
||||||
@ -126,8 +111,7 @@ BlockService.blockRPCtoBitcore = function(blockData, transactions) {
|
|||||||
* @return {Promise} a promise that will always be rejected
|
* @return {Promise} a promise that will always be rejected
|
||||||
*/
|
*/
|
||||||
var blockNotFound = function(err) {
|
var blockNotFound = function(err) {
|
||||||
console.log(err, err.stack);
|
throw new errors.Blocks.NotFound(err);
|
||||||
return Promise.reject(new BitcoreNode.errors.Blocks.NotFound());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,7 +120,7 @@ var blockNotFound = function(err) {
|
|||||||
* @param {string} blockHash the hash of the block to be fetched
|
* @param {string} blockHash the hash of the block to be fetched
|
||||||
* @return {Promise<Block>}
|
* @return {Promise<Block>}
|
||||||
*/
|
*/
|
||||||
BlockService.prototype.getBlock = function(blockHash) {
|
BlockService.prototype.getBlock = function(blockHash, opts) {
|
||||||
$.checkArgument(
|
$.checkArgument(
|
||||||
JSUtil.isHexa(blockHash) || bitcore.util.buffer.isBuffer(blockHash),
|
JSUtil.isHexa(blockHash) || bitcore.util.buffer.isBuffer(blockHash),
|
||||||
'Block hash must be a buffer or hexa'
|
'Block hash must be a buffer or hexa'
|
||||||
@ -144,27 +128,33 @@ BlockService.prototype.getBlock = function(blockHash) {
|
|||||||
if (bitcore.util.buffer.isBuffer(blockHash)) {
|
if (bitcore.util.buffer.isBuffer(blockHash)) {
|
||||||
blockHash = bitcore.util.buffer.reverse(blockHash).toString('hex');
|
blockHash = bitcore.util.buffer.reverse(blockHash).toString('hex');
|
||||||
}
|
}
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
var blockData;
|
var blockData;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
return Promise.try(function() {
|
return Promise.try(function() {
|
||||||
|
return self.rpc.getBlockAsync(blockHash);
|
||||||
|
})
|
||||||
|
.catch(blockNotFound)
|
||||||
|
.then(function(block) {
|
||||||
|
|
||||||
return self.rpc.getBlockAsync(blockHash);
|
blockData = block.result;
|
||||||
|
|
||||||
}).then(function(block) {
|
if (opts.withoutTransactions) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
blockData = block.result;
|
return Promise.all(blockData.tx.map(function(txId) {
|
||||||
return Promise.all(blockData.tx.map(function(txId) {
|
return self.transactionService.getTransaction(txId);
|
||||||
return self.transactionService.getTransaction(txId);
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
}).then(function(transactions) {
|
}).then(function(transactions) {
|
||||||
|
|
||||||
blockData.transactions = transactions;
|
blockData.transactions = transactions;
|
||||||
return BlockService.blockRPCtoBitcore(blockData);
|
return BlockService.blockRPCtoBitcore(blockData);
|
||||||
|
|
||||||
}).catch(blockNotFound);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,13 +170,16 @@ BlockService.prototype.getBlockByHeight = function(height) {
|
|||||||
|
|
||||||
return Promise.try(function() {
|
return Promise.try(function() {
|
||||||
|
|
||||||
return self.rpc.getBlockHash(height);
|
return self.rpc.getBlockHashAsync(height);
|
||||||
|
|
||||||
}).then(function(blockHash) {
|
})
|
||||||
|
.catch(blockNotFound)
|
||||||
|
.then(function(result) {
|
||||||
|
|
||||||
return self.getBlock(blockHash);
|
var blockHash = result.result;
|
||||||
|
return self.getBlock(blockHash);
|
||||||
|
|
||||||
}).catch(blockNotFound);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -206,96 +199,133 @@ BlockService.prototype.getLatest = function() {
|
|||||||
|
|
||||||
return self.getBlock(blockHash);
|
return self.getBlock(blockHash);
|
||||||
|
|
||||||
}).catch(blockNotFound);
|
}).catch(LevelUp.errors.NotFoundError, function() {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a block from the network
|
||||||
|
*
|
||||||
|
* @param {bitcore.Block} block
|
||||||
|
* @return a list of events back to the event bus
|
||||||
|
*/
|
||||||
|
BlockService.prototype.onBlock = function(block) {
|
||||||
|
var events = [];
|
||||||
|
return this.save(block)
|
||||||
|
.then(function(block) {
|
||||||
|
console.log('block', block.id, 'saved with height', block.height);
|
||||||
|
block.transactions.forEach(function(tx) {
|
||||||
|
events.push(tx);
|
||||||
|
});
|
||||||
|
return events;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a block as the current tip of the blockchain
|
* Set a block as the current tip of the blockchain
|
||||||
*
|
*
|
||||||
* @param {bitcore.Block} block
|
* @param {bitcore.Block} block
|
||||||
|
* @param {Array=} ops
|
||||||
* @return {Promise<Block>} a promise of the same block, for chaining
|
* @return {Promise<Block>} a promise of the same block, for chaining
|
||||||
*/
|
*/
|
||||||
BlockService.prototype._confirmBlock = function(block) {
|
BlockService.prototype.confirm = function(block, ops) {
|
||||||
$.checkArgument(block instanceof bitcore.Block);
|
$.checkArgument(block instanceof bitcore.Block);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var ops = [];
|
ops = ops || [];
|
||||||
|
|
||||||
return this.writeLock().then(function() {
|
//console.log(0);
|
||||||
|
return Promise.try(function() {
|
||||||
|
//console.log(1);
|
||||||
|
self._setNextBlock(ops, block.header.prevHash, block);
|
||||||
|
|
||||||
return self._setNextBlock(ops, block.header.prevHash, block);
|
//console.log(3);
|
||||||
|
self._setBlockHeight(ops, block);
|
||||||
|
|
||||||
}).then(function() {
|
//console.log(3);
|
||||||
|
self._setBlockWork(ops, block);
|
||||||
|
|
||||||
if (block.header.prevHash.toString('hex') !== NULLBLOCKHASH) {
|
//console.log(4);
|
||||||
return self.getBlock(block.header.prevHash);
|
self._setBlockByTs(ops, block);
|
||||||
} else {
|
|
||||||
return GENESISPARENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).then(function(parent) {
|
self._setTip(ops, block);
|
||||||
|
|
||||||
return self._setBlockHeight(ops, block, parent.height + 1);
|
//console.log(5);
|
||||||
|
return Promise.all(block.transactions.map(function(transaction) {
|
||||||
|
return self.transactionService._confirmTransaction(ops, block, transaction);
|
||||||
|
}));
|
||||||
|
|
||||||
}).then(function() {
|
})
|
||||||
|
.then(function() {
|
||||||
return self._setBlockByTs(ops, block);
|
//console.log(6);
|
||||||
|
return self.database.batchAsync(ops);
|
||||||
}).then(function() {
|
})
|
||||||
|
.then(function() {
|
||||||
return Promise.all(block.transactions.map(function(transaction) {
|
//console.log(7);
|
||||||
return self.transactionService._confirmTransaction(ops, block, transaction);
|
return block;
|
||||||
}));
|
});
|
||||||
|
|
||||||
}).then(function() {
|
|
||||||
|
|
||||||
return self.database.batchAsync(ops)
|
|
||||||
|
|
||||||
}).then(function() {
|
|
||||||
return self.unlock();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockService.prototype._setNextBlock = function(ops, prevBlockHash, block) {
|
BlockService.prototype._setNextBlock = function(ops, prevBlockHash, block) {
|
||||||
if (bitcore.util.buffer.isBuffer(prevBlockHash)) {
|
if (bitcore.util.buffer.isBuffer(prevBlockHash)) {
|
||||||
prevBlockHash = bitcore.util.buffer.reverse(prevBlockHash).toString('hex');
|
prevBlockHash = bitcore.util.buffer.reverse(prevBlockHash).toString('hex');
|
||||||
}
|
}
|
||||||
return Promise.try(function() {
|
ops.push({
|
||||||
ops.push({
|
type: 'put',
|
||||||
type: 'put',
|
key: Index.getNextBlock(prevBlockHash),
|
||||||
key: Index.getNextBlock(prevBlockHash),
|
value: block.hash
|
||||||
value: block.hash
|
});
|
||||||
});
|
ops.push({
|
||||||
ops.push({
|
type: 'put',
|
||||||
type: 'put',
|
key: Index.getPreviousBlock(block.hash),
|
||||||
key: Index.getPreviousBlock(block.hash),
|
value: prevBlockHash
|
||||||
value: prevBlockHash.toString('hex')
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockService.prototype._setBlockHeight = function(ops, block, height) {
|
BlockService.prototype._setBlockHeight = function(ops, block) {
|
||||||
return Promise.try(function() {
|
ops.push({
|
||||||
ops.push({
|
type: 'put',
|
||||||
type: 'put',
|
key: Index.getBlockHeight(block),
|
||||||
key: Index.getBlockHeight(block),
|
value: block.height
|
||||||
value: height
|
});
|
||||||
});
|
};
|
||||||
return ops;
|
|
||||||
|
BlockService.prototype._setTip = function(ops, block) {
|
||||||
|
ops.push({
|
||||||
|
type: 'put',
|
||||||
|
key: Index.tip,
|
||||||
|
value: block.hash
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockService.prototype._setBlockWork = function(ops, block) {
|
||||||
|
ops.push({
|
||||||
|
type: 'put',
|
||||||
|
key: Index.getBlockWork(block),
|
||||||
|
value: block.work
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockService.prototype._setBlockByTs = function(ops, block) {
|
BlockService.prototype._setBlockByTs = function(ops, block) {
|
||||||
|
|
||||||
|
// TODO: uncomment this
|
||||||
|
/*
|
||||||
var self = this;
|
var self = this;
|
||||||
var key = Index.timestamp + block.time;
|
var key = Index.timestamp + block.header.time;
|
||||||
|
console.log('key', key);
|
||||||
|
|
||||||
return Promise.try(function() {
|
return Promise.try(function() {
|
||||||
|
|
||||||
|
console.log('a');
|
||||||
return self.database.getAsync(key);
|
return self.database.getAsync(key);
|
||||||
|
|
||||||
}).then(function(result) {
|
})
|
||||||
|
.then(function(result) {
|
||||||
|
|
||||||
|
console.log('b');
|
||||||
if (result === block.hash) {
|
if (result === block.hash) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
@ -303,7 +333,10 @@ BlockService.prototype._setBlockByTs = function(ops, block) {
|
|||||||
throw new Error('Found blocks that have same timestamp');
|
throw new Error('Found blocks that have same timestamp');
|
||||||
}
|
}
|
||||||
|
|
||||||
}).error(function(err) {
|
})
|
||||||
|
.error(function(err) {
|
||||||
|
console.log('err', err);
|
||||||
|
|
||||||
// TODO: Check if err is not found
|
// TODO: Check if err is not found
|
||||||
return ops.push({
|
return ops.push({
|
||||||
type: 'put',
|
type: 'put',
|
||||||
@ -311,6 +344,64 @@ BlockService.prototype._setBlockByTs = function(ops, block) {
|
|||||||
value: block.hash
|
value: block.hash
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unconfirm a block
|
||||||
|
*
|
||||||
|
* @param {bitcore.Block} block
|
||||||
|
* @param {Array=} ops
|
||||||
|
* @return {Promise<Block>} a promise of the same block, for chaining
|
||||||
|
*/
|
||||||
|
BlockService.prototype.unconfirm = function(block, ops) {
|
||||||
|
|
||||||
|
ops = ops || [];
|
||||||
|
|
||||||
|
return Promise.try(function() {
|
||||||
|
|
||||||
|
self._removeNextBlock(ops, block.header.prevHash, block);
|
||||||
|
|
||||||
|
self._unsetBlockHeight(ops, block, block.height);
|
||||||
|
|
||||||
|
self._dropBlockByTs(ops, block);
|
||||||
|
|
||||||
|
return Promise.all(block.transactions.map(function(transaction) {
|
||||||
|
return self.transactionService._unconfirmTransaction(ops, block, transaction);
|
||||||
|
}));
|
||||||
|
|
||||||
|
}).then(function() {
|
||||||
|
|
||||||
|
return self.database.batchAsync(ops);
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockService.prototype._removeNextBlock = function(ops, prevHash, block) {
|
||||||
|
|
||||||
|
if (bitcore.util.buffer.isBuffer(prevBlockHash)) {
|
||||||
|
prevBlockHash = bitcore.util.buffer.reverse(prevBlockHash).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
ops.push({
|
||||||
|
type: 'del',
|
||||||
|
key: Index.getNextBlock(prevBlockHash)
|
||||||
|
});
|
||||||
|
ops.push({
|
||||||
|
type: 'del',
|
||||||
|
key: Index.getPreviousBlock(block.hash)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockService.prototype._unsetBlockHeight = function(ops, block, height) {
|
||||||
|
ops.push({
|
||||||
|
type: 'del',
|
||||||
|
key: Index.getBlockHeight(block)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockService.prototype._dropBlockByTs = function(ops, block) {
|
||||||
|
// TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -346,4 +437,57 @@ BlockService.prototype.getBlockForTransaction = function(transaction) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BlockService.prototype.getBlockchain = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var blockchain = new BlockChain();
|
||||||
|
|
||||||
|
var fetchBlock = function(blockHash) {
|
||||||
|
return Promise.all([
|
||||||
|
self.database.getAsync(Index.getPreviousBlock(blockHash)).then(function(prevHash) {
|
||||||
|
blockchain.prev[blockHash] = prevHash;
|
||||||
|
blockchain.next[prevHash] = blockHash;
|
||||||
|
}),
|
||||||
|
self.database.getAsync(Index.getBlockHeight(blockHash)).then(function(height) {
|
||||||
|
blockchain.height[blockHash] = +height;
|
||||||
|
blockchain.hashByHeight[height] = blockHash;
|
||||||
|
}),
|
||||||
|
self.database.getAsync(Index.getBlockWork(blockHash)).then(function(work) {
|
||||||
|
blockchain.work[blockHash] = work;
|
||||||
|
})
|
||||||
|
]).then(function() {
|
||||||
|
return blockHash;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var fetchUnlessGenesis = function(blockHash) {
|
||||||
|
return fetchBlock(blockHash).then(function() {
|
||||||
|
if (blockchain.prev[blockHash] === BlockChain.NULL) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return fetchUnlessGenesis(blockchain.prev[blockHash]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.database.getAsync(Index.tip)
|
||||||
|
.catch(function(err) {
|
||||||
|
if (err.notFound) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then(function(tip) {
|
||||||
|
if (!tip) {
|
||||||
|
console.log('No tip found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Tip is', tip);
|
||||||
|
blockchain.tip = tip;
|
||||||
|
return fetchUnlessGenesis(tip).then(function() {
|
||||||
|
return blockchain;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = BlockService;
|
module.exports = BlockService;
|
||||||
|
|||||||
@ -179,6 +179,7 @@ TransactionService.prototype._getAddressForInput = function(input) {
|
|||||||
hash, bitcore.Networks.defaultNetwork, bitcore.Address.PayToPublicKeyHash
|
hash, bitcore.Networks.defaultNetwork, bitcore.Address.PayToPublicKeyHash
|
||||||
);
|
);
|
||||||
} else if (script.isPublicKeyIn()) {
|
} else if (script.isPublicKeyIn()) {
|
||||||
|
/*
|
||||||
return self.getTransaction(input.prevTxId.toString('hex')).then(function(transaction) {
|
return self.getTransaction(input.prevTxId.toString('hex')).then(function(transaction) {
|
||||||
var outputScript = transaction.outputs[input.outputIndex].script;
|
var outputScript = transaction.outputs[input.outputIndex].script;
|
||||||
if (outputScript.isPublicKeyOut()) {
|
if (outputScript.isPublicKeyOut()) {
|
||||||
@ -189,6 +190,7 @@ TransactionService.prototype._getAddressForInput = function(input) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
} else {
|
} else {
|
||||||
return new bitcore.Script(script.chunks[script.chunks.length - 1]).toAddress();
|
return new bitcore.Script(script.chunks[script.chunks.length - 1]).toAddress();
|
||||||
}
|
}
|
||||||
@ -208,4 +210,18 @@ TransactionService.prototype._confirmTransaction = function(ops, block, transact
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TransactionService.prototype._unconfirmTransaction = function(ops, block, transaction) {
|
||||||
|
var self = this;
|
||||||
|
ops.push({
|
||||||
|
type: 'del',
|
||||||
|
key: Index.getBlockForTransaction(transaction),
|
||||||
|
value: block.id
|
||||||
|
});
|
||||||
|
return Promise.all(
|
||||||
|
_.map(transaction.outputs, self._unconfirmOutput(ops, block, transaction))
|
||||||
|
.concat(
|
||||||
|
_.map(transaction.inputs, self._unconfirmInput(ops, block, transaction))
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = TransactionService;
|
module.exports = TransactionService;
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
"async": "0.9.0",
|
"async": "0.9.0",
|
||||||
"bitcoind-rpc": "^0.2.1",
|
"bitcoind-rpc": "^0.2.1",
|
||||||
"bitcore": "bitpay/bitcore",
|
"bitcore": "bitpay/bitcore",
|
||||||
"bitcore-p2p": "bitpay/bitcore-p2p",
|
"bitcore-p2p": "^0.11.0",
|
||||||
"bluebird": "^2.9.12",
|
"bluebird": "^2.9.12",
|
||||||
"body-parser": "^1.12.0",
|
"body-parser": "^1.12.0",
|
||||||
"bufferput": "bitpay/node-bufferput",
|
"bufferput": "bitpay/node-bufferput",
|
||||||
@ -59,8 +59,9 @@
|
|||||||
"express": "4.11.1",
|
"express": "4.11.1",
|
||||||
"glob": "*",
|
"glob": "*",
|
||||||
"js-yaml": "^3.2.7",
|
"js-yaml": "^3.2.7",
|
||||||
"level-lock": "^1.0.1",
|
"leveldown": "~1.0.0",
|
||||||
"levelup": "~0.19.0",
|
"levelup": "^0.19.0",
|
||||||
|
"memdown": "^1.0.0",
|
||||||
"moment": "~2.5.0",
|
"moment": "~2.5.0",
|
||||||
"morgan": "^1.5.1",
|
"morgan": "^1.5.1",
|
||||||
"request": "^2.48.0",
|
"request": "^2.48.0",
|
||||||
|
|||||||
@ -38,6 +38,9 @@ describe('NetworkMonitor', function() {
|
|||||||
block: mockBlock
|
block: mockBlock
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
peerMock.disconnect = function() {
|
||||||
|
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('instantiates correctly from constructor', function() {
|
it('instantiates correctly from constructor', function() {
|
||||||
@ -62,7 +65,10 @@ describe('NetworkMonitor', function() {
|
|||||||
|
|
||||||
it('broadcasts errors in underlying peer', function(cb) {
|
it('broadcasts errors in underlying peer', function(cb) {
|
||||||
var nm = new NetworkMonitor(busMock, peerMock);
|
var nm = new NetworkMonitor(busMock, peerMock);
|
||||||
nm.on('error', cb);
|
nm.on('error', function() {
|
||||||
|
console.log('under');
|
||||||
|
cb();
|
||||||
|
});
|
||||||
nm.start();
|
nm.start();
|
||||||
peerMock.emit('error');
|
peerMock.emit('error');
|
||||||
});
|
});
|
||||||
|
|||||||
28
test/node.js
28
test/node.js
@ -13,36 +13,50 @@ Promise.longStackTraces();
|
|||||||
describe('BitcoreNode', function() {
|
describe('BitcoreNode', function() {
|
||||||
|
|
||||||
// mocks
|
// mocks
|
||||||
var busMock, nmMock;
|
var node, busMock, nmMock, bsMock, tsMock, asMock, chainMock;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
busMock = new EventBus();
|
busMock = new EventBus();
|
||||||
nmMock = new EventEmitter();
|
nmMock = new EventEmitter();
|
||||||
nmMock.start = function() {};
|
nmMock.start = function() {};
|
||||||
|
chainMock = {};
|
||||||
|
bsMock = {};
|
||||||
|
bsMock.getBlockchain = function() {
|
||||||
|
return Promise.resolve(chainMock);
|
||||||
|
};
|
||||||
|
tsMock = {};
|
||||||
|
asMock = {};
|
||||||
|
node = new BitcoreNode(busMock, nmMock, bsMock, tsMock, asMock);
|
||||||
});
|
});
|
||||||
describe('instantiates', function() {
|
describe('instantiates', function() {
|
||||||
it('from constructor', function() {
|
it('from constructor', function() {
|
||||||
var node = new BitcoreNode(busMock, nmMock);
|
var n = new BitcoreNode(busMock, nmMock, bsMock, tsMock, asMock);
|
||||||
should.exist(node);
|
should.exist(n);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('from create', function() {
|
it('from create', function() {
|
||||||
var node = BitcoreNode.create();
|
var dbMock = {};
|
||||||
|
var rpcMock = {};
|
||||||
|
var opts = {
|
||||||
|
database: dbMock,
|
||||||
|
rpc: rpcMock,
|
||||||
|
blockService: bsMock,
|
||||||
|
transactionService: tsMock
|
||||||
|
};
|
||||||
|
var node = BitcoreNode.create(opts);
|
||||||
should.exist(node);
|
should.exist(node);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('starts', function() {
|
it('starts', function() {
|
||||||
var node = new BitcoreNode(busMock, nmMock);
|
node.start();
|
||||||
node.start.bind(node).should.not.throw();
|
node.start.bind(node).should.not.throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('broadcasts errors from network monitor', function(cb) {
|
it('broadcasts errors from network monitor', function(cb) {
|
||||||
var node = new BitcoreNode(busMock, nmMock);
|
|
||||||
node.on('error', cb);
|
node.on('error', cb);
|
||||||
nmMock.emit('error');
|
nmMock.emit('error');
|
||||||
});
|
});
|
||||||
it('exposes all events from the event bus', function(cb) {
|
it('exposes all events from the event bus', function(cb) {
|
||||||
var node = new BitcoreNode(busMock, nmMock);
|
|
||||||
node.on('foo', cb);
|
node.on('foo', cb);
|
||||||
busMock.emit('foo');
|
busMock.emit('foo');
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,7 +28,7 @@ describe('BlockService', function() {
|
|||||||
describe('getBlock', function() {
|
describe('getBlock', function() {
|
||||||
|
|
||||||
var mockRpc, transactionMock, database, blockService;
|
var mockRpc, transactionMock, database, blockService;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
database = sinon.mock();
|
database = sinon.mock();
|
||||||
mockRpc = sinon.mock();
|
mockRpc = sinon.mock();
|
||||||
@ -43,7 +43,7 @@ describe('BlockService', function() {
|
|||||||
height: 2,
|
height: 2,
|
||||||
version: 1,
|
version: 1,
|
||||||
merkleroot: '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5',
|
merkleroot: '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5',
|
||||||
tx: [ '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5' ],
|
tx: ['9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5'],
|
||||||
time: 1231469744,
|
time: 1231469744,
|
||||||
nonce: 1639830024,
|
nonce: 1639830024,
|
||||||
bits: '1d00ffff',
|
bits: '1d00ffff',
|
||||||
@ -63,7 +63,7 @@ describe('BlockService', function() {
|
|||||||
transactionService: transactionMock,
|
transactionService: transactionMock,
|
||||||
database: database
|
database: database
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retrieves correctly a block, uses RPC', function(callback) {
|
it('retrieves correctly a block, uses RPC', function(callback) {
|
||||||
|
|
||||||
@ -87,10 +87,19 @@ describe('BlockService', function() {
|
|||||||
return arg();
|
return arg();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var work = 1000;
|
||||||
|
var work169 = 169;
|
||||||
|
var work170 = 170;
|
||||||
var genesisBlock = require('../data/genesis');
|
var genesisBlock = require('../data/genesis');
|
||||||
|
genesisBlock.work = work;
|
||||||
|
genesisBlock.height = 1;
|
||||||
var block169 = require('../data/169');
|
var block169 = require('../data/169');
|
||||||
|
block169.work = work169;
|
||||||
|
block169.height = 169;
|
||||||
var block170 = require('../data/170');
|
var block170 = require('../data/170');
|
||||||
|
block170.work = work170;
|
||||||
|
block170.height = 170;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
database = sinon.mock();
|
database = sinon.mock();
|
||||||
mockRpc = sinon.mock();
|
mockRpc = sinon.mock();
|
||||||
@ -102,64 +111,66 @@ describe('BlockService', function() {
|
|||||||
database: database
|
database: database
|
||||||
});
|
});
|
||||||
blockService.writeLock = sinon.mock();
|
blockService.writeLock = sinon.mock();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes the expected calls when confirming the genesis block', function(callback) {
|
it('makes the expected calls when confirming the genesis block', function(callback) {
|
||||||
database.batchAsync = function(ops) {
|
database.batchAsync = function(ops) {
|
||||||
ops.should.deep.equal([
|
var expectedOps = [{
|
||||||
{ type: 'put',
|
type: 'put',
|
||||||
key: 'nxt-0000000000000000000000000000000000000000000000000000000000000000',
|
key: 'nxt-0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
value: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f' },
|
value: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
||||||
{ type: 'put',
|
}, {
|
||||||
key: 'prev-000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
type: 'put',
|
||||||
value: '0000000000000000000000000000000000000000000000000000000000000000' },
|
key: 'prev-000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||||
{ type: 'put',
|
value: '0000000000000000000000000000000000000000000000000000000000000000'
|
||||||
key: 'bh-000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
}, {
|
||||||
value: 0 },
|
type: 'put',
|
||||||
{ type: 'put',
|
key: 'bh-000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||||
key: 'bts-1231006505',
|
value: 0
|
||||||
value: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f' }
|
}, {
|
||||||
]);
|
type: 'put',
|
||||||
return thenCaller;
|
key: 'wk-000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||||
};
|
value: work
|
||||||
blockService.unlock = callback;
|
}, {
|
||||||
blockService.writeLock.onFirstCall().returns(thenCaller);
|
type: 'put',
|
||||||
blockService.getBlock = sinon.mock();
|
key: 'tip',
|
||||||
database.getAsync = function() {
|
value: genesisBlock.id
|
||||||
return Promise.reject({notFound: true});
|
}];
|
||||||
|
ops.should.deep.equal(expectedOps);
|
||||||
|
return callback();
|
||||||
};
|
};
|
||||||
transactionMock._confirmTransaction = sinon.mock();
|
transactionMock._confirmTransaction = sinon.mock();
|
||||||
blockService._confirmBlock(genesisBlock);
|
blockService.confirm(genesisBlock);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes the expected calls when confirming the block #170', function(callback) {
|
it('makes the expected calls when confirming the block #170', function(callback) {
|
||||||
database.batchAsync = function(ops) {
|
database.batchAsync = function(ops) {
|
||||||
ops.should.deep.equal([
|
ops.should.deep.equal([{
|
||||||
{ type: 'put',
|
type: 'put',
|
||||||
key: 'nxt-000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55',
|
key: 'nxt-000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55',
|
||||||
value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee' },
|
value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee'
|
||||||
{ type: 'put',
|
}, {
|
||||||
key: 'prev-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee',
|
type: 'put',
|
||||||
value: '000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55' },
|
key: 'prev-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee',
|
||||||
{ type: 'put',
|
value: '000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55'
|
||||||
key: 'bh-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee',
|
}, {
|
||||||
value: 170 },
|
type: 'put',
|
||||||
{ type: 'put',
|
key: 'bh-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee',
|
||||||
key: 'bts-1231731025',
|
value: 170
|
||||||
value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee' }
|
}, {
|
||||||
]);
|
type: 'put',
|
||||||
return thenCaller;
|
key: 'wk-00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee',
|
||||||
|
value: work170
|
||||||
|
}, {
|
||||||
|
type: 'put',
|
||||||
|
key: 'tip',
|
||||||
|
value: block170.id
|
||||||
|
}]);
|
||||||
|
return callback();
|
||||||
};
|
};
|
||||||
blockService.unlock = callback;
|
|
||||||
blockService.writeLock.onFirstCall().returns(thenCaller);
|
blockService.writeLock.onFirstCall().returns(thenCaller);
|
||||||
blockService.getBlock = function() {
|
|
||||||
return Promise.resolve(block169);
|
|
||||||
};
|
|
||||||
database.getAsync = function() {
|
|
||||||
return Promise.reject({notFound: true});
|
|
||||||
};
|
|
||||||
transactionMock._confirmTransaction = sinon.spy();
|
transactionMock._confirmTransaction = sinon.spy();
|
||||||
blockService._confirmBlock(block170);
|
blockService.confirm(block170);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -147,7 +147,9 @@ describe('TransactionService', function() {
|
|||||||
sequenceNumber: 4294967295,
|
sequenceNumber: 4294967295,
|
||||||
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901',
|
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901',
|
||||||
heightConfirmed: 170 } },
|
heightConfirmed: 170 } },
|
||||||
{ type: 'put',
|
]);
|
||||||
|
/* TODO: This should work if address spent is accepted for public key. Add test for P2PKH if not accepted
|
||||||
|
* { type: 'put',
|
||||||
key: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
|
key: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
|
||||||
value:
|
value:
|
||||||
{ heightSpent: 170,
|
{ heightSpent: 170,
|
||||||
@ -156,7 +158,7 @@ describe('TransactionService', function() {
|
|||||||
spendInput: { prevTxId: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9',
|
spendInput: { prevTxId: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9',
|
||||||
outputIndex: 0,
|
outputIndex: 0,
|
||||||
sequenceNumber: 4294967295,
|
sequenceNumber: 4294967295,
|
||||||
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901' }}}]);
|
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901' }}}]);*/
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user