commit
80141ab144
@ -1,18 +0,0 @@
|
||||
BitcoreHTTP:
|
||||
port: 8080
|
||||
logging: true
|
||||
BitcoreNode:
|
||||
LevelUp: ./db
|
||||
network: livenet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 8333
|
||||
Reporter: none # none, simple, matrix
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http #http, https
|
||||
#rejectUnauthorized: false
|
||||
#disableAgent: true
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
@ -1,16 +0,0 @@
|
||||
BitcoreHTTP:
|
||||
port: 8080
|
||||
logging: true
|
||||
BitcoreNode:
|
||||
LevelUp: ./db
|
||||
network: livenet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 8333
|
||||
Reporter: none # none, simple, matrix
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
@ -1,16 +0,0 @@
|
||||
BitcoreHTTP:
|
||||
port: 8080
|
||||
logging: true
|
||||
BitcoreNode:
|
||||
LevelUp: ./testnet-db
|
||||
network: testnet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 18333
|
||||
Reporter: none # none, simple, matrix
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 18332
|
||||
@ -5,8 +5,6 @@ var _ = bitcore.deps._;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var Address = bitcore.Address;
|
||||
|
||||
var BitcoreNode = require('../../');
|
||||
|
||||
var Addresses = {};
|
||||
|
||||
var node;
|
||||
@ -23,7 +21,7 @@ Addresses.setNode = function(aNode) {
|
||||
* Finds an address' info by it's string representation
|
||||
*/
|
||||
Addresses.addressParam = function(req, res, next, address) {
|
||||
if (!Address.isValid(address)) {
|
||||
if (!Address.isValid(address, bitcore.Networks.defaultNetwork)) {
|
||||
res.status(422);
|
||||
res.send('/v1/addresses/ parameter must be a valid bitcoin address');
|
||||
return;
|
||||
@ -76,7 +74,7 @@ Addresses.utxos = function(req, res) {
|
||||
$.checkState(_.all(req.addresses, function(addr) {
|
||||
return addr instanceof Address;
|
||||
}));
|
||||
node.getUTXOs(req.addresses)
|
||||
node.addressService.getUnspent(req.addresses)
|
||||
.then(function(utxos) {
|
||||
res.send(utxos);
|
||||
});
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var _ = bitcore.deps._;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var Block = bitcore.Block;
|
||||
|
||||
var BitcoreNode = require('../../');
|
||||
var errors = require('../../lib/errors');
|
||||
|
||||
var Blocks = {};
|
||||
|
||||
@ -28,7 +27,7 @@ Blocks.blockHashParam = function(req, res, next, blockHash) {
|
||||
req.block = block;
|
||||
})
|
||||
.then(next)
|
||||
.catch(BitcoreNode.errors.Blocks.NotFound, function() {
|
||||
.catch(errors.Blocks.NotFound, function() {
|
||||
res.status(404).send('Block with id ' + blockHash + ' not found');
|
||||
})
|
||||
.catch(function() {
|
||||
@ -46,7 +45,7 @@ Blocks.heightParam = function(req, res, next, height) {
|
||||
req.block = block;
|
||||
})
|
||||
.then(next)
|
||||
.catch(BitcoreNode.errors.Blocks.NotFound, function() {
|
||||
.catch(errors.Blocks.NotFound, function() {
|
||||
res.status(404).send('Block with height ' + height + ' not found');
|
||||
})
|
||||
.catch(function() {
|
||||
|
||||
@ -7,7 +7,7 @@ var _ = bitcore.deps._;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var Transaction = bitcore.Transaction;
|
||||
|
||||
var BitcoreNode = require('../../');
|
||||
var errors = require('../../lib/errors');
|
||||
|
||||
var Transactions = {};
|
||||
|
||||
@ -30,7 +30,7 @@ Transactions.txHashParam = function(req, res, next, txHash) {
|
||||
req.tx = tx;
|
||||
})
|
||||
.then(next)
|
||||
.catch(BitcoreNode.errors.Transactions.NotFound, function() {
|
||||
.catch(errors.Transactions.NotFound, function() {
|
||||
res.status(404).send('Transaction with id ' + txHash + ' not found');
|
||||
})
|
||||
.catch(function() {
|
||||
@ -81,24 +81,12 @@ Transactions.send = function(req, res) {
|
||||
.then(function() {
|
||||
res.send('Transaction broadcasted successfully');
|
||||
})
|
||||
.catch(BitcoreNode.errors.Transactions.CantBroadcast, function(err) {
|
||||
.catch(errors.Transactions.CantBroadcast, function(err) {
|
||||
res.status(422).send(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a list of transactions given certain request options
|
||||
*/
|
||||
Transactions.list = function(req, res) {
|
||||
var opts = {};
|
||||
opts.address = req.address;
|
||||
node.listTransactions(opts)
|
||||
.then(function(transactions) {
|
||||
res.send(transactions);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var buildIOHelper = function(name) {
|
||||
$.checkArgument(name === 'inputs' || name === 'outputs');
|
||||
@ -110,11 +98,11 @@ var buildIOHelper = function(name) {
|
||||
' for ' + req.tx.id + ' not found, it only has ' + req.tx[name].length + ' ' + name + '.');
|
||||
return;
|
||||
}
|
||||
res.send(req.tx[name][req.index].toJSON());
|
||||
res.send(req.tx[name][req.index].toObject());
|
||||
return;
|
||||
}
|
||||
res.send(req.tx[name].map(function(x) {
|
||||
return x.toJSON();
|
||||
return x.toObject();
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
15
api/index.js
15
api/index.js
@ -1,19 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
var BitcoreHTTP = require('./lib/http');
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
if (require.main === module) {
|
||||
var config = require('config');
|
||||
var network = config.get('BitcoreHTTP.BitcoreNode').network;
|
||||
console.log('Starting bitcore-node-http', network, 'network');
|
||||
bitcore.Networks.defaultNetwork = bitcore.Networks.get(network);
|
||||
var http = BitcoreHTTP.create(config.get('BitcoreHTTP'));
|
||||
http.start()
|
||||
.catch(function(err) {
|
||||
http.stop();
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = BitcoreHTTP;
|
||||
|
||||
@ -44,6 +44,8 @@ BitcoreHTTP.prototype.setupExpress = function() {
|
||||
app.use(morgan('dev'));
|
||||
}
|
||||
|
||||
app.set('json spaces', 2);
|
||||
|
||||
// install routes
|
||||
app.use('/', routes(this.node));
|
||||
|
||||
|
||||
@ -44,7 +44,6 @@ function initRouter(node) {
|
||||
|
||||
// Address routes
|
||||
router.get('/addresses/:address', Addresses.get);
|
||||
router.get('/addresses/:address/transactions', Transactions.list);
|
||||
router.get('/addresses/:addresses/utxos', Addresses.utxos);
|
||||
|
||||
// error routes
|
||||
|
||||
@ -11,17 +11,11 @@ var _ = bitcore.deps._;
|
||||
|
||||
|
||||
var mockAddresses = require('../data/addresses');
|
||||
var mockTransactions = require('../data/transactions');
|
||||
|
||||
describe('BitcoreHTTP v1 addresses routes', function() {
|
||||
|
||||
// mocks
|
||||
var transactionList = _.values(mockTransactions);
|
||||
var nodeMock, agent;
|
||||
var txs_for_addr = function(addr) {
|
||||
var amount = mockAddresses[addr].summary.transactions.length;
|
||||
return transactionList.slice(0, amount);
|
||||
};
|
||||
var utxos_for_addrs = function(addrs) {
|
||||
return _.reduce(addrs, function(utxos, addr) {
|
||||
return utxos.concat(mockAddresses[addr].utxos);
|
||||
@ -53,10 +47,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
||||
nodeMock.addressService.getSummary = function(address) {
|
||||
return Promise.resolve(mockAddresses[address.toString()].summary);
|
||||
};
|
||||
nodeMock.listTransactions = function(opts) {
|
||||
return Promise.resolve(txs_for_addr(opts.address));
|
||||
};
|
||||
nodeMock.getUTXOs = function(addresses) {
|
||||
nodeMock.addressService.getUnspent = function(addresses) {
|
||||
return Promise.resolve(utxos_for_addrs(addresses));
|
||||
};
|
||||
agent = require('../app')(nodeMock);
|
||||
@ -77,19 +68,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
||||
it('works with valid address ' + addr, function(cb) {
|
||||
agent.get('/v1/addresses/' + addr)
|
||||
.expect(200)
|
||||
.expect(JSON.stringify(info.summary), cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('/addresses/:address/transactions', function() {
|
||||
it('fails with invalid address', function(cb) {
|
||||
failsWithInvalidAddress(agent, '/v1/addresses/1BpbpfLdY7oBS9gK7aDXgvMgr1DpvNH3B2/transactions', cb);
|
||||
});
|
||||
_.keys(mockAddresses).forEach(function(addr) {
|
||||
it('works with valid address ' + addr, function(cb) {
|
||||
agent.get('/v1/addresses/' + addr + '/transactions')
|
||||
.expect(200)
|
||||
.expect(JSON.stringify(txs_for_addr(addr)), cb);
|
||||
.expect(info.summary, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -104,7 +83,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
||||
it('works with valid address ' + addr, function(cb) {
|
||||
agent.get('/v1/addresses/' + addr + '/utxos')
|
||||
.expect(200)
|
||||
.expect(JSON.stringify(utxos_for_addrs([addr])), cb);
|
||||
.expect(utxos_for_addrs([addr]), cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -118,7 +97,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
||||
var path = '/v1/addresses/' + list + '/utxos';
|
||||
agent.get(path)
|
||||
.expect(200)
|
||||
.expect(JSON.stringify(utxos_for_addrs(addresses)), cb);
|
||||
.expect(utxos_for_addrs(addresses), cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -120,7 +120,7 @@ describe('BitcoreHTTP v1 blocks routes', function() {
|
||||
it('works with valid blockHash ...' + hash.substring(hash.length - 8), function(cb) {
|
||||
agent.get('/v1/blocks/' + hash)
|
||||
.expect(200)
|
||||
.expect(block.toJSON(), cb);
|
||||
.expect(block.toObject(), cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -138,7 +138,7 @@ describe('BitcoreHTTP v1 blocks routes', function() {
|
||||
it('works with valid height', function(cb) {
|
||||
agent.get('/v1/blocks/100000')
|
||||
.expect(200)
|
||||
.expect(b1.toJSON(), cb);
|
||||
.expect(b1.toObject(), cb);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ describe('BitcoreHTTP v1 transactions routes', function() {
|
||||
it('works with valid txHash ...' + hash.substring(hash.length - 8), function(cb) {
|
||||
agent.get('/v1/transactions/' + hash)
|
||||
.expect(200)
|
||||
.expect(mockTransactions[hash].toJSON(), cb);
|
||||
.expect(mockTransactions[hash].toObject(), cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -112,7 +112,7 @@ describe('BitcoreHTTP v1 transactions routes', function() {
|
||||
agent.get('/v1/transactions/' + hash + '/' + name + '/')
|
||||
.expect(200)
|
||||
.expect(tx[name].map(function(x) {
|
||||
return x.toJSON();
|
||||
return x.toObject();
|
||||
}), cb);
|
||||
});
|
||||
var canGetSpecificInput = function(i) {
|
||||
@ -120,7 +120,7 @@ describe('BitcoreHTTP v1 transactions routes', function() {
|
||||
return function(cb) {
|
||||
agent.get('/v1/transactions/' + hash + '/' + name + '/' + i)
|
||||
.expect(200)
|
||||
.expect(x.toJSON(), cb);
|
||||
.expect(x.toObject(), cb);
|
||||
};
|
||||
};
|
||||
for (var i = 0; i < tx[name].length; i++) {
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
BitcoreNode:
|
||||
LevelUp: ./db
|
||||
network: livenet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 8333
|
||||
Reporter: none # none, simple, matrix
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http # http, https
|
||||
#rejectUnauthorized: false
|
||||
#disableAgent: true
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
BitcoreHTTP:
|
||||
port: 8080
|
||||
logging: true
|
||||
BitcoreNode:
|
||||
LevelUp: ./db
|
||||
network: livenet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 8333
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http #http, https
|
||||
#rejectUnauthorized: false
|
||||
#disableAgent: true
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
BitcoreNode:
|
||||
LevelUp: ./db
|
||||
network: livenet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 8333
|
||||
Reporter: none # none, simple, matrix
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
BitcoreHTTP:
|
||||
port: 8080
|
||||
logging: true
|
||||
BitcoreNode:
|
||||
LevelUp: ./db
|
||||
network: livenet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 8333
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
BitcoreNode:
|
||||
LevelUp: ./testnet-db
|
||||
network: testnet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 18333
|
||||
Reporter: none # none, simple, matrix
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 18332
|
||||
BitcoreHTTP:
|
||||
port: 18080
|
||||
logging: true
|
||||
BitcoreNode:
|
||||
LevelUp: ./testnet-db
|
||||
network: testnet
|
||||
NetworkMonitor:
|
||||
host: localhost
|
||||
port: 18333
|
||||
RPC:
|
||||
user: user
|
||||
pass: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 18332
|
||||
|
||||
31
index.js
31
index.js
@ -1,21 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var BitcoreNode = require('./lib/node');
|
||||
var reporters = require('./lib/reporters');
|
||||
var BitcoreHTTP = require('./api/lib/http');
|
||||
var bitcore = require('bitcore');
|
||||
var Promise = require('bluebird');
|
||||
Promise.longStackTraces();
|
||||
|
||||
BitcoreNode.errors = require('./lib/errors');
|
||||
|
||||
if (require.main === module) {
|
||||
var config = require('config');
|
||||
var network = config.get('BitcoreNode').network;
|
||||
console.log('Starting bitcore-node', network, 'network');
|
||||
var nodeConfig = config.get('BitcoreHTTP.BitcoreNode');
|
||||
var httpConfig = config.get('BitcoreHTTP');
|
||||
var network = nodeConfig.network;
|
||||
console.log('Starting bitcore-node-http', network, 'network');
|
||||
bitcore.Networks.defaultNetwork = bitcore.Networks.get(network);
|
||||
|
||||
var node = BitcoreNode.create(config.get('BitcoreNode'));
|
||||
node.start();
|
||||
var node = BitcoreNode.create(nodeConfig);
|
||||
node.on('error', function(err) {
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
console.log('Connection to bitcoind failed');
|
||||
@ -23,18 +22,12 @@ if (require.main === module) {
|
||||
console.log('Error: ', err);
|
||||
}
|
||||
});
|
||||
process.on('SIGINT', function() {
|
||||
node.stop();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
var reporterName = config.get('BitcoreNode.Reporter');
|
||||
var reporter = reporters[reporterName];
|
||||
if (!reporter) {
|
||||
throw new Error('Unrecognized network reporter: ' + reporterName +
|
||||
'. Available: ' + Object.keys(reporters));
|
||||
}
|
||||
node.on('Transaction', reporter);
|
||||
var http = new BitcoreHTTP(node, httpConfig);
|
||||
http.start()
|
||||
.catch(function(err) {
|
||||
http.stop();
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = BitcoreNode;
|
||||
|
||||
@ -79,8 +79,6 @@ BitcoreNode.prototype.initialize = function() {
|
||||
var prevHeight = 0;
|
||||
var statTimer = 5 * 1000;
|
||||
this.interval = setInterval(function() {
|
||||
console.log('MB used:', process.memoryUsage().heapTotal / 1024 / 1024,
|
||||
100 * self.getSyncProgress() + '% synced');
|
||||
if (!self.blockchain) {
|
||||
// not ready yet
|
||||
console.log('No blockchain yet');
|
||||
@ -94,7 +92,8 @@ BitcoreNode.prototype.initialize = function() {
|
||||
}
|
||||
var delta = block.height - prevHeight;
|
||||
prevHeight = block.height;
|
||||
console.log(block.id, block.height, 'vel', delta * 1000 / statTimer, 'b/s');
|
||||
console.log(block.id, block.height, 'vel', delta * 1000 / statTimer, 'b/s',
|
||||
100 * self.getSyncProgress() + '% synced');
|
||||
}, statTimer);
|
||||
|
||||
this.bus.register(bitcore.Block, function(block) {
|
||||
@ -223,4 +222,6 @@ BitcoreNode.prototype.sync = function() {
|
||||
});
|
||||
};
|
||||
|
||||
var errors = require('./errors');
|
||||
BitcoreNode.errors = errors;
|
||||
module.exports = BitcoreNode;
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
var Promise = require('bluebird');
|
||||
var bitcore = require('bitcore');
|
||||
var TransactionService = require('./transaction');
|
||||
var RPC = require('bitcoind-rpc');
|
||||
var _ = bitcore.deps._;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
||||
var LASTTXHASH = bitcore.util.buffer.fill(bitcore.util.buffer.emptyBuffer(32), -1).toString('hex');
|
||||
@ -14,8 +14,8 @@ function AddressService(opts) {
|
||||
opts = _.extend({}, opts);
|
||||
this.transactionService = opts.transactionService;
|
||||
this.blockService = opts.blockService;
|
||||
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
|
||||
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
|
||||
this.database = opts.database;
|
||||
this.rpc = opts.rpc;
|
||||
}
|
||||
|
||||
AddressService.prototype.getSummary = function(address, confirmations) {
|
||||
@ -56,6 +56,7 @@ AddressService.processOutput = function(data) {
|
||||
|
||||
var retrieveOutputs = function(indexFunction, processElement) {
|
||||
return function(address) {
|
||||
$.checkArgument(address, 'address required');
|
||||
var results = [];
|
||||
var self = this;
|
||||
|
||||
@ -81,11 +82,44 @@ AddressService.prototype.getAllOutputs = retrieveOutputs(
|
||||
|
||||
AddressService.prototype.getSpent = retrieveOutputs(
|
||||
TransactionService.Index.getSpentOutputsForAddress,
|
||||
function(element) {
|
||||
return JSON.parse(element.value);
|
||||
function(e) {
|
||||
return AddressService.processOutput(e);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
AddressService.prototype.getUnspent = function(addrs) {
|
||||
|
||||
$.checkArgument(addrs, 'addresses required');
|
||||
$.checkArgument(_.isArray(addrs), 'addresses is array required');
|
||||
|
||||
var self = this;
|
||||
return Promise.all(addrs.map(function(addr) {
|
||||
return self.getUnspentForAddress(addr);
|
||||
}))
|
||||
.then(function(results) {
|
||||
return _.flatten(results);
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
AddressService.prototype.getUnspentForAddress = function(addr) {
|
||||
$.checkArgument(addr, 'address required');
|
||||
var all, spent;
|
||||
var self = this;
|
||||
return this.getAllOutputs(addr)
|
||||
.then(function(s) {
|
||||
all = s;
|
||||
return self.getSpent(addr);
|
||||
})
|
||||
.then(function(s) {
|
||||
spent = s;
|
||||
return _.filter(all, function(out) {
|
||||
return !_.contains(spent, out);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs, spent, confirmations) {
|
||||
|
||||
var result = {};
|
||||
|
||||
@ -206,8 +206,6 @@ BlockService.prototype.listBlocks = function(from, to, offset, limit) {
|
||||
var start = from + offset;
|
||||
var end = Math.min(to, start + limit);
|
||||
var blocks = [];
|
||||
//console.log(from, to, offset, limit);
|
||||
//console.log(start, end);
|
||||
// TODO: optimize: precompute heights and fetch all blocks in parallel?
|
||||
var fetchBlock = function(height) {
|
||||
if (height >= end) {
|
||||
@ -284,37 +282,27 @@ BlockService.prototype.confirm = function(block, ops) {
|
||||
|
||||
ops = ops || [];
|
||||
|
||||
//console.log(0);
|
||||
return Promise.try(function() {
|
||||
//console.log(0.5);
|
||||
self._setHeader(ops, block);
|
||||
//console.log(1);
|
||||
self._setNextBlock(ops, block.header.prevHash, block);
|
||||
|
||||
//console.log(3);
|
||||
self._setBlockHeight(ops, block);
|
||||
|
||||
//console.log(3);
|
||||
self._setBlockWork(ops, block);
|
||||
|
||||
//console.log(4);
|
||||
self._setBlockByTs(ops, block);
|
||||
|
||||
//console.log(4.1);
|
||||
self._setTip(ops, block);
|
||||
|
||||
//console.log(5);
|
||||
return Promise.all(block.transactions.map(function(transaction) {
|
||||
return self.transactionService._confirmTransaction(ops, block, transaction);
|
||||
}));
|
||||
|
||||
})
|
||||
.then(function() {
|
||||
//console.log(6);
|
||||
return self.database.batchAsync(ops);
|
||||
})
|
||||
.then(function() {
|
||||
//console.log(7);
|
||||
return block;
|
||||
});
|
||||
};
|
||||
@ -373,17 +361,14 @@ BlockService.prototype._setBlockByTs = function(ops, block) {
|
||||
/*
|
||||
var self = this;
|
||||
var key = Index.timestamp + block.header.time;
|
||||
console.log('key', key);
|
||||
|
||||
return Promise.try(function() {
|
||||
|
||||
console.log('a');
|
||||
return self.database.getAsync(key);
|
||||
|
||||
})
|
||||
.then(function(result) {
|
||||
|
||||
console.log('b');
|
||||
if (result === block.hash) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
@ -501,7 +486,6 @@ BlockService.prototype.getBlockchain = function() {
|
||||
var fetchHeader = function(blockHash) {
|
||||
if (blockHash === BlockChain.NULL) {
|
||||
console.log('All headers fetched, total =', headers.length);
|
||||
console.log(process.memoryUsage().heapTotal / 1024 / 1024, 'mb used');
|
||||
return;
|
||||
}
|
||||
var headerKey = Index.getBlockHeader(blockHash);
|
||||
|
||||
@ -20,12 +20,11 @@ var LevelUp = require('levelup');
|
||||
var Promise = require('bluebird');
|
||||
var bitcore = require('bitcore');
|
||||
var config = require('config');
|
||||
var BitcoreNode = require('../../');
|
||||
var errors = require('../errors');
|
||||
|
||||
var _ = bitcore.deps._;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
||||
var GENESISTX = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
||||
|
||||
var helper = function(name) {
|
||||
@ -87,7 +86,7 @@ TransactionService.Index = Index;
|
||||
|
||||
var txNotFound = function(error) {
|
||||
if (error.message === 'No information available about transaction') {
|
||||
throw new BitcoreNode.errors.Transactions.NotFound();
|
||||
throw new errors.Transactions.NotFound();
|
||||
}
|
||||
throw error;
|
||||
};
|
||||
|
||||
@ -1,300 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var imports = require('soop').imports();
|
||||
|
||||
var config = imports.config || require('../config/config');
|
||||
var bitcore = require('bitcore');
|
||||
var networks = bitcore.networks;
|
||||
var async = require('async');
|
||||
|
||||
var logger = require('./logger').logger;
|
||||
var d = logger.log;
|
||||
var info = logger.info;
|
||||
|
||||
|
||||
|
||||
var syncId = 0;
|
||||
|
||||
function Sync(opts) {
|
||||
this.id = syncId++;
|
||||
this.opts = opts || {};
|
||||
this.bDb = require('./BlockDb').default();
|
||||
this.txDb = require('./TransactionDb').default();
|
||||
this.network = config.network === 'testnet' ? networks.testnet : networks.livenet;
|
||||
this.cachedLastHash = null;
|
||||
}
|
||||
|
||||
Sync.prototype.close = function(cb) {
|
||||
var self = this;
|
||||
self.txDb.close(function() {
|
||||
self.bDb.close(cb);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Sync.prototype.destroy = function(next) {
|
||||
var self = this;
|
||||
async.series([
|
||||
|
||||
function(b) {
|
||||
self.bDb.drop(b);
|
||||
},
|
||||
function(b) {
|
||||
self.txDb.drop(b);
|
||||
},
|
||||
], next);
|
||||
};
|
||||
|
||||
/*
|
||||
* Arrives a NEW block, which is the new TIP
|
||||
*
|
||||
* Case 0) Simple case
|
||||
* A-B-C-D-E(TIP)-NEW
|
||||
*
|
||||
* Case 1)
|
||||
* A-B-C-D-E(TIP)
|
||||
* \
|
||||
* NEW
|
||||
*
|
||||
* 1) Declare D-E orphans (and possible invalidate TXs on them)
|
||||
*
|
||||
* Case 2)
|
||||
* A-B-C-D-E(TIP)
|
||||
* \
|
||||
* F-G-NEW
|
||||
* 1) Set F-G as connected (mark TXs as valid)
|
||||
* 2) Set new heights in F-G-NEW
|
||||
* 3) Declare D-E orphans (and possible invalidate TXs on them)
|
||||
*
|
||||
*
|
||||
* Case 3)
|
||||
*
|
||||
* A-B-C-D-E(TIP) ... NEW
|
||||
*
|
||||
* NEW is ignored (if allowReorgs is false)
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
Sync.prototype.storeTipBlock = function(b, allowReorgs, cb) {
|
||||
|
||||
if (typeof allowReorgs === 'function') {
|
||||
cb = allowReorgs;
|
||||
allowReorgs = true;
|
||||
}
|
||||
if (!b) return cb();
|
||||
var self = this;
|
||||
|
||||
if ( self.storingBlock ) {
|
||||
logger.debug('Storing a block already. Delaying storeTipBlock with:' +
|
||||
b.hash);
|
||||
return setTimeout( function() {
|
||||
logger.debug('Retrying storeTipBlock with: ' + b.hash);
|
||||
self.storeTipBlock(b,allowReorgs,cb);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
self.storingBlock=1;
|
||||
var oldTip, oldNext, oldHeight, needReorg = false, height = -1;
|
||||
var newPrev = b.previousblockhash;
|
||||
async.series([
|
||||
|
||||
// This seems unnecesary.
|
||||
// function(c) {
|
||||
// // TODO? remove this check?
|
||||
// self.bDb.has(b.hash, function(err, val) {
|
||||
// return c(err ||
|
||||
// (val ? new Error('WARN: Ignoring already existing block:' + b.hash) : null));
|
||||
// });
|
||||
// },
|
||||
function(c) {
|
||||
if (!allowReorgs || newPrev === self.cachedLastHash) return c();
|
||||
self.bDb.has(newPrev, function(err, val) {
|
||||
// Genesis? no problem
|
||||
if (!val && newPrev.match(/^0+$/)) return c();
|
||||
return c(err ||
|
||||
(!val ? new Error('NEED_SYNC Ignoring block with non existing prev:' + b.hash) : null));
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
if (!allowReorgs) return c();
|
||||
self.bDb.getTip(function(err, hash, h) {
|
||||
oldTip = hash;
|
||||
oldHeight = hash ? (h || 0) : -1
|
||||
if (oldTip && newPrev !== oldTip) {
|
||||
needReorg = true;
|
||||
logger.debug('REORG Triggered, tip mismatch');
|
||||
}
|
||||
return c();
|
||||
});
|
||||
},
|
||||
|
||||
function(c) {
|
||||
if (!needReorg) return c();
|
||||
self.bDb.getNext(newPrev, function(err, val) {
|
||||
if (err) return c(err);
|
||||
oldNext = val;
|
||||
return c();
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
if (!allowReorgs) return c();
|
||||
if (needReorg) {
|
||||
info('NEW TIP: %s NEED REORG (old tip: %s #%d)', b.hash, oldTip, oldHeight);
|
||||
self.processReorg(oldTip, oldNext, newPrev, oldHeight, function(err, h) {
|
||||
if (err) throw err;
|
||||
|
||||
height = h;
|
||||
return c();
|
||||
});
|
||||
}
|
||||
else {
|
||||
height = oldHeight + 1;
|
||||
return c();
|
||||
}
|
||||
},
|
||||
function(c) {
|
||||
self.cachedLastHash = b.hash; // just for speed up.
|
||||
self.bDb.add(b, height, c);
|
||||
},
|
||||
function(c) {
|
||||
if (!allowReorgs) return c();
|
||||
self.bDb.setTip(b.hash, height, function(err) {
|
||||
return c(err);
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
self.bDb.setNext(newPrev, b.hash, function(err) {
|
||||
return c(err);
|
||||
});
|
||||
}
|
||||
|
||||
],
|
||||
function(err) {
|
||||
if (err && err.toString().match(/WARN/)) {
|
||||
err = null;
|
||||
}
|
||||
self.storingBlock=0;
|
||||
return cb(err, height);
|
||||
});
|
||||
};
|
||||
|
||||
Sync.prototype.processReorg = function(oldTip, oldNext, newPrev, oldHeight, cb) {
|
||||
var self = this;
|
||||
|
||||
var orphanizeFrom, newHeight;
|
||||
|
||||
async.series([
|
||||
|
||||
function(c) {
|
||||
self.bDb.getHeight(newPrev, function(err, height) {
|
||||
if (!height) {
|
||||
// Case 3 + allowReorgs = true
|
||||
return c(new Error('Could not found block:' + newPrev));
|
||||
}
|
||||
if (height<0) return c();
|
||||
|
||||
newHeight = height + 1;
|
||||
info('Reorg Case 1) OldNext: %s NewHeight: %d', oldNext, newHeight);
|
||||
orphanizeFrom = oldNext;
|
||||
return c(err);
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
if (orphanizeFrom) return c();
|
||||
|
||||
info('Reorg Case 2)');
|
||||
self.setBranchConnectedBackwards(newPrev, function(err, yHash, newYHashNext, height) {
|
||||
if (err) return c(err);
|
||||
newHeight = height;
|
||||
self.bDb.getNext(yHash, function(err, yHashNext) {
|
||||
// Connect the new branch, and orphanize the old one.
|
||||
orphanizeFrom = yHashNext;
|
||||
self.bDb.setNext(yHash, newYHashNext, function(err) {
|
||||
return c(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
if (!orphanizeFrom) return c();
|
||||
self._setBranchOrphan(orphanizeFrom, function(err) {
|
||||
return c(err);
|
||||
});
|
||||
},
|
||||
],
|
||||
function(err) {
|
||||
return cb(err, newHeight);
|
||||
});
|
||||
};
|
||||
|
||||
Sync.prototype._setBranchOrphan = function(fromHash, cb) {
|
||||
var self = this,
|
||||
hashInterator = fromHash;
|
||||
|
||||
async.whilst(
|
||||
function() {
|
||||
return hashInterator;
|
||||
},
|
||||
function(c) {
|
||||
self.bDb.setBlockNotMain(hashInterator, function(err) {
|
||||
if (err) return cb(err);
|
||||
self.bDb.getNext(hashInterator, function(err, val) {
|
||||
hashInterator = val;
|
||||
return c(err);
|
||||
});
|
||||
});
|
||||
}, cb);
|
||||
};
|
||||
|
||||
Sync.prototype.setBranchConnectedBackwards = function(fromHash, cb) {
|
||||
//console.log('[Sync.js.219:setBranchConnectedBackwards:]',fromHash); //TODO
|
||||
var self = this,
|
||||
hashInterator = fromHash,
|
||||
lastHash = fromHash,
|
||||
yHeight,
|
||||
branch = [];
|
||||
|
||||
async.doWhilst(
|
||||
function(c) {
|
||||
branch.unshift(hashInterator);
|
||||
|
||||
self.bDb.getPrev(hashInterator, function(err, val) {
|
||||
if (err) return c(err);
|
||||
lastHash = hashInterator;
|
||||
hashInterator = val;
|
||||
self.bDb.getHeight(hashInterator, function(err, height) {
|
||||
yHeight = height;
|
||||
return c();
|
||||
});
|
||||
});
|
||||
},
|
||||
function() {
|
||||
return hashInterator && yHeight<=0;
|
||||
},
|
||||
function() {
|
||||
info('\tFound yBlock: %s #%d', hashInterator, yHeight);
|
||||
var heightIter = yHeight + 1;
|
||||
var hashIter;
|
||||
async.whilst(
|
||||
function() {
|
||||
hashIter = branch.shift();
|
||||
return hashIter;
|
||||
},
|
||||
function(c) {
|
||||
self.bDb.setBlockMain(hashIter, heightIter++, c);
|
||||
},
|
||||
function(err) {
|
||||
return cb(err, hashInterator, lastHash, heightIter);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//Store unconfirmed TXs
|
||||
Sync.prototype.storeTx = function(tx, cb) {
|
||||
this.txDb.add(tx, cb);
|
||||
};
|
||||
|
||||
|
||||
module.exports = require('soop')(Sync);
|
||||
@ -1,142 +0,0 @@
|
||||
'use strict';
|
||||
var bitcore = require('bitcore'),
|
||||
Block = bitcore.Block,
|
||||
networks = bitcore.networks,
|
||||
Parser = bitcore.BinaryParser,
|
||||
fs = require('fs'),
|
||||
Buffer = bitcore.Buffer,
|
||||
glob = require('glob'),
|
||||
async = require('async');
|
||||
|
||||
function BlockExtractor(dataDir, network) {
|
||||
var path = dataDir + '/blocks/blk*.dat';
|
||||
|
||||
this.dataDir = dataDir;
|
||||
this.files = glob.sync(path);
|
||||
this.nfiles = this.files.length;
|
||||
|
||||
if (this.nfiles === 0)
|
||||
throw new Error('Could not find block files at: ' + path);
|
||||
|
||||
this.currentFileIndex = 0;
|
||||
this.isCurrentRead = false;
|
||||
this.currentBuffer = null;
|
||||
this.currentParser = null;
|
||||
this.network = network === 'testnet' ? networks.testnet: networks.livenet;
|
||||
this.magic = this.network.magic.toString('hex');
|
||||
}
|
||||
|
||||
BlockExtractor.prototype.currentFile = function() {
|
||||
return this.files[this.currentFileIndex];
|
||||
};
|
||||
|
||||
|
||||
BlockExtractor.prototype.nextFile = function() {
|
||||
if (this.currentFileIndex < 0) return false;
|
||||
|
||||
var ret = true;
|
||||
|
||||
this.isCurrentRead = false;
|
||||
this.currentBuffer = null;
|
||||
this.currentParser = null;
|
||||
|
||||
if (this.currentFileIndex < this.nfiles - 1) {
|
||||
this.currentFileIndex++;
|
||||
}
|
||||
else {
|
||||
this.currentFileIndex=-1;
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
BlockExtractor.prototype.readCurrentFileSync = function() {
|
||||
if (this.currentFileIndex < 0 || this.isCurrentRead) return;
|
||||
|
||||
this.isCurrentRead = true;
|
||||
|
||||
var fname = this.currentFile();
|
||||
if (!fname) return;
|
||||
|
||||
|
||||
var stats = fs.statSync(fname);
|
||||
|
||||
var size = stats.size;
|
||||
|
||||
console.log('Reading Blockfile %s [%d MB]',
|
||||
fname, parseInt(size/1024/1024));
|
||||
|
||||
var fd = fs.openSync(fname, 'r');
|
||||
|
||||
var buffer = new Buffer(size);
|
||||
|
||||
fs.readSync(fd, buffer, 0, size, 0);
|
||||
|
||||
this.currentBuffer = buffer;
|
||||
this.currentParser = new Parser(buffer);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
BlockExtractor.prototype._getMagic = function() {
|
||||
if (!this.currentParser)
|
||||
return null;
|
||||
|
||||
var byte0 = this.currentParser ? this.currentParser.buffer(1).toString('hex') : null;
|
||||
|
||||
|
||||
|
||||
// Grab 3 bytes from block without removing them
|
||||
var p = this.currentParser.pos;
|
||||
var bytes123 = this.currentParser.subject.toString('hex',p,p+3);
|
||||
var magic = byte0 + bytes123;
|
||||
|
||||
if (magic !=='00000000' && magic !== this.magic) {
|
||||
if(this.errorCount++ > 4)
|
||||
throw new Error('CRITICAL ERROR: Magic number mismatch: ' +
|
||||
magic + '!=' + this.magic);
|
||||
magic=null;
|
||||
}
|
||||
|
||||
if (magic==='00000000')
|
||||
magic =null;
|
||||
|
||||
return magic;
|
||||
};
|
||||
|
||||
BlockExtractor.prototype.getNextBlock = function(cb) {
|
||||
var b;
|
||||
var magic;
|
||||
var isFinished = 0;
|
||||
|
||||
while(!magic && !isFinished) {
|
||||
this.readCurrentFileSync();
|
||||
magic= this._getMagic();
|
||||
|
||||
if (!this.currentParser || this.currentParser.eof() ) {
|
||||
|
||||
if (this.nextFile()) {
|
||||
console.log('Moving forward to file:' + this.currentFile() );
|
||||
magic = null;
|
||||
} else {
|
||||
console.log('Finished all files');
|
||||
isFinished = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isFinished)
|
||||
return cb();
|
||||
|
||||
// Remove 3 bytes from magic and spacer
|
||||
this.currentParser.buffer(3+4);
|
||||
|
||||
b = new Block();
|
||||
b.parse(this.currentParser);
|
||||
b.getHash();
|
||||
this.errorCount=0;
|
||||
return cb(null,b);
|
||||
};
|
||||
|
||||
module.exports = require('soop')(BlockExtractor);
|
||||
|
||||
@ -1,435 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var util = require('util');
|
||||
var async = require('async');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var networks = bitcore.networks;
|
||||
var config = imports.config || require('../config/config');
|
||||
var Sync = require('./Sync');
|
||||
var sockets = require('../app/controllers/socket.js');
|
||||
var BlockExtractor = require('./BlockExtractor.js');
|
||||
var buffertools = require('buffertools');
|
||||
var bitcoreUtil = bitcore.util;
|
||||
var logger = require('./logger').logger;
|
||||
var info = logger.info;
|
||||
var error = logger.error;
|
||||
var PERCENTAGE_TO_START_FROM_RPC = 0.96;
|
||||
|
||||
var BAD_GEN_ERROR = 'Bad genesis block. Network mismatch between bitcore-node and bitcoind? bitcore-node is configured for:';
|
||||
var BAD_GEN_ERROR_DB = 'Bad genesis block. Network mismatch between bitcore-node and levelDB? bitcore-node is configured for:';
|
||||
|
||||
function HistoricSync(opts) {
|
||||
opts = opts || {};
|
||||
this.shouldBroadcast = opts.shouldBroadcastSync;
|
||||
|
||||
this.network = config.network === 'testnet' ? networks.testnet: networks.livenet;
|
||||
|
||||
var genesisHashReversed = new Buffer(32);
|
||||
this.network.genesisBlock.hash.copy(genesisHashReversed);
|
||||
buffertools.reverse(genesisHashReversed);
|
||||
this.genesis = genesisHashReversed.toString('hex');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var RpcClient = bitcore.RpcClient;
|
||||
|
||||
this.rpc = new RpcClient(config.bitcoind);
|
||||
this.sync = new Sync(opts);
|
||||
this.height =0;
|
||||
}
|
||||
|
||||
HistoricSync.prototype.showProgress = function() {
|
||||
var self = this;
|
||||
|
||||
if ( self.status ==='syncing' &&
|
||||
( self.height ) % self.step !== 1) return;
|
||||
|
||||
if (self.error)
|
||||
error(self.error);
|
||||
|
||||
else {
|
||||
self.updatePercentage();
|
||||
info(util.format('status: [%d%%]', self.syncPercentage));
|
||||
}
|
||||
if (self.shouldBroadcast) {
|
||||
sockets.broadcastSyncInfo(self.info());
|
||||
}
|
||||
//
|
||||
// if (self.syncPercentage > 10) {
|
||||
// process.exit(-1);
|
||||
// }
|
||||
};
|
||||
|
||||
|
||||
HistoricSync.prototype.setError = function(err) {
|
||||
var self = this;
|
||||
self.error = err.message?err.message:err.toString();
|
||||
self.status='error';
|
||||
self.showProgress();
|
||||
return err;
|
||||
};
|
||||
|
||||
|
||||
|
||||
HistoricSync.prototype.close = function() {
|
||||
this.sync.close();
|
||||
};
|
||||
|
||||
|
||||
HistoricSync.prototype.info = function() {
|
||||
this.updatePercentage();
|
||||
return {
|
||||
status: this.status,
|
||||
blockChainHeight: this.blockChainHeight,
|
||||
syncPercentage: this.syncPercentage,
|
||||
height: this.height,
|
||||
syncTipHash: this.sync.tip,
|
||||
error: this.error,
|
||||
type: this.type,
|
||||
startTs: this.startTs,
|
||||
endTs: this.endTs,
|
||||
};
|
||||
};
|
||||
|
||||
HistoricSync.prototype.updatePercentage = function() {
|
||||
var r = this.height / this.blockChainHeight;
|
||||
this.syncPercentage = parseFloat(100 * r).toFixed(3);
|
||||
if (this.syncPercentage > 100) this.syncPercentage = 100;
|
||||
};
|
||||
|
||||
HistoricSync.prototype.getBlockFromRPC = function(cb) {
|
||||
var self = this;
|
||||
|
||||
if (!self.currentRpcHash) return cb();
|
||||
|
||||
var blockInfo;
|
||||
self.rpc.getBlock(self.currentRpcHash, function(err, ret) {
|
||||
if (err) return cb(err);
|
||||
if (ret) {
|
||||
blockInfo = ret.result;
|
||||
// this is to match block retreived from file
|
||||
if (blockInfo.hash === self.genesis)
|
||||
blockInfo.previousblockhash =
|
||||
self.network.genesisBlock.prev_hash.toString('hex');
|
||||
|
||||
self.currentRpcHash = blockInfo.nextblockhash;
|
||||
}
|
||||
else {
|
||||
blockInfo = null;
|
||||
}
|
||||
return cb(null, blockInfo);
|
||||
});
|
||||
};
|
||||
|
||||
HistoricSync.prototype.getStandardizedBlock = function(b) {
|
||||
var self = this;
|
||||
|
||||
var block = {
|
||||
hash: bitcoreUtil.formatHashFull(b.getHash()),
|
||||
previousblockhash: bitcoreUtil.formatHashFull(b.prev_hash),
|
||||
time: b.timestamp,
|
||||
};
|
||||
var isCoinBase = 1;
|
||||
block.tx = b.txs.map(function(tx){
|
||||
var ret = self.sync.txDb.getStandardizedTx(tx, b.timestamp, isCoinBase);
|
||||
isCoinBase=0;
|
||||
return ret;
|
||||
});
|
||||
return block;
|
||||
};
|
||||
|
||||
HistoricSync.prototype.getBlockFromFile = function(cb) {
|
||||
var self = this;
|
||||
|
||||
var blockInfo;
|
||||
|
||||
//get Info
|
||||
self.blockExtractor.getNextBlock(function(err, b) {
|
||||
if (err || ! b) return cb(err);
|
||||
blockInfo = self.getStandardizedBlock(b);
|
||||
self.sync.bDb.setLastFileIndex(self.blockExtractor.currentFileIndex, function(err) {
|
||||
return cb(err,blockInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
HistoricSync.prototype.updateBlockChainHeight = function(cb) {
|
||||
var self = this;
|
||||
|
||||
self.rpc.getBlockCount(function(err, res) {
|
||||
self.blockChainHeight = res.result;
|
||||
return cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
HistoricSync.prototype.checkNetworkSettings = function(next) {
|
||||
var self = this;
|
||||
|
||||
self.hasGenesis = false;
|
||||
|
||||
// check network config
|
||||
self.rpc.getBlockHash(0, function(err, res){
|
||||
if (!err && ( res && res.result !== self.genesis)) {
|
||||
err = new Error(BAD_GEN_ERROR + config.network);
|
||||
}
|
||||
if (err) return next(err);
|
||||
self.sync.bDb.has(self.genesis, function(err, b) {
|
||||
if (!err && ( res && res.result !== self.genesis)) {
|
||||
err = new Error(BAD_GEN_ERROR_DB + config.network);
|
||||
}
|
||||
self.hasGenesis = b?true:false;
|
||||
return next(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
HistoricSync.prototype.updateStartBlock = function(opts, next) {
|
||||
var self = this;
|
||||
|
||||
self.startBlock = self.genesis;
|
||||
|
||||
if (opts.startAt) {
|
||||
self.sync.bDb.fromHashWithInfo(opts.startAt, function(err, bi) {
|
||||
var blockInfo = bi ? bi.info : {};
|
||||
if (blockInfo.height) {
|
||||
self.startBlock = opts.startAt;
|
||||
self.height = blockInfo.height;
|
||||
info('Resuming sync from block: %s #%d',opts.startAt, self.height);
|
||||
return next(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.sync.bDb.getTip(function(err,tip, height) {
|
||||
if (!tip) return next();
|
||||
|
||||
var blockInfo;
|
||||
var oldtip;
|
||||
|
||||
//check that the tip is still on the mainchain
|
||||
async.doWhilst(
|
||||
function(cb) {
|
||||
self.sync.bDb.fromHashWithInfo(tip, function(err, bi) {
|
||||
blockInfo = bi ? bi.info : {};
|
||||
if (oldtip)
|
||||
self.sync.bDb.setBlockNotMain(oldtip, cb);
|
||||
else
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
if (err) return next(err);
|
||||
var ret = false;
|
||||
|
||||
var d = Math.abs(height-blockInfo.height);
|
||||
if (d>6) {
|
||||
error('Previous Tip block tip height differs by %d. Please delete and resync (-D)',d);
|
||||
process.exit(1);
|
||||
}
|
||||
if ( self.blockChainHeight === blockInfo.height ||
|
||||
blockInfo.confirmations > 0) {
|
||||
ret = false;
|
||||
}
|
||||
else {
|
||||
oldtip = tip;
|
||||
if (!tip)
|
||||
throw new Error('Previous blockchain tip was not found on bitcoind. Please reset bitcore-node DB. Tip was:'+tip)
|
||||
tip = blockInfo.previousblockhash;
|
||||
info('Previous TIP is now orphan. Back to:' + tip);
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
function(err) {
|
||||
self.startBlock = tip;
|
||||
self.height = height;
|
||||
info('Resuming sync from block: %s #%d',tip,height);
|
||||
return next(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
HistoricSync.prototype.prepareFileSync = function(opts, next) {
|
||||
var self = this;
|
||||
|
||||
if ( opts.forceRPC || !config.bitcoind.dataDir ||
|
||||
self.height > self.blockChainHeight * PERCENTAGE_TO_START_FROM_RPC) return next();
|
||||
|
||||
|
||||
try {
|
||||
self.blockExtractor = new BlockExtractor(config.bitcoind.dataDir, config.network);
|
||||
} catch (e) {
|
||||
info(e.message + '. Disabling file sync.');
|
||||
return next();
|
||||
}
|
||||
|
||||
self.getFn = self.getBlockFromFile;
|
||||
self.allowReorgs = true;
|
||||
self.sync.bDb.getLastFileIndex(function(err, idx) {
|
||||
|
||||
if (opts.forceStartFile)
|
||||
self.blockExtractor.currentFileIndex = opts.forceStartFile;
|
||||
else if (idx) self.blockExtractor.currentFileIndex = idx;
|
||||
|
||||
var h = self.genesis;
|
||||
|
||||
info('Seeking file to:' + self.startBlock);
|
||||
//forward till startBlock
|
||||
async.whilst(
|
||||
function() {
|
||||
return h !== self.startBlock;
|
||||
},
|
||||
function (w_cb) {
|
||||
self.getBlockFromFile(function(err,b) {
|
||||
if (!b) return w_cb('Could not find block ' + self.startBlock);
|
||||
h=b.hash;
|
||||
setImmediate(function(){
|
||||
return w_cb(err);
|
||||
});
|
||||
});
|
||||
}, function(err){
|
||||
console.log('\tFOUND Starting Block!');
|
||||
|
||||
// TODO SET HEIGHT
|
||||
return next(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//NOP
|
||||
HistoricSync.prototype.prepareRpcSync = function(opts, next) {
|
||||
var self = this;
|
||||
|
||||
if (self.blockExtractor) return next();
|
||||
self.getFn = self.getBlockFromRPC;
|
||||
self.allowReorgs = true;
|
||||
self.currentRpcHash = self.startBlock;
|
||||
return next();
|
||||
};
|
||||
|
||||
HistoricSync.prototype.showSyncStartMessage = function() {
|
||||
var self = this;
|
||||
|
||||
info('Got ' + self.height +
|
||||
' blocks in current DB, out of ' + self.blockChainHeight + ' block at bitcoind');
|
||||
|
||||
if (self.blockExtractor) {
|
||||
info('bitcoind dataDir configured...importing blocks from .dat files');
|
||||
info('First file index: ' + self.blockExtractor.currentFileIndex);
|
||||
}
|
||||
else {
|
||||
info('syncing from RPC (slow)');
|
||||
}
|
||||
|
||||
info('Starting from: ', self.startBlock);
|
||||
self.showProgress();
|
||||
};
|
||||
|
||||
|
||||
HistoricSync.prototype.setupSyncStatus = function() {
|
||||
var self = this;
|
||||
|
||||
var step = parseInt( (self.blockChainHeight - self.height) / 1000);
|
||||
if (step < 10) step = 10;
|
||||
|
||||
self.step = step;
|
||||
self.type = self.blockExtractor?'from .dat Files':'from RPC calls';
|
||||
self.status = 'syncing';
|
||||
self.startTs = Date.now();
|
||||
self.endTs = null;
|
||||
this.error = null;
|
||||
this.syncPercentage = 0;
|
||||
};
|
||||
|
||||
HistoricSync.prototype.checkDBVersion = function(cb) {
|
||||
this.sync.txDb.checkVersion02(function(isOk){
|
||||
if (!isOk) {
|
||||
console.log('\n#############################\n\n ## bitcore-node API DB is older that v0.2. Please resync using:\n $ util/sync.js -D\n More information at bitcore-node API\'s Readme.md');
|
||||
process.exit(1);
|
||||
}
|
||||
// Add more test here in future changes.
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
HistoricSync.prototype.prepareToSync = function(opts, next) {
|
||||
var self = this;
|
||||
|
||||
self.status = 'starting';
|
||||
async.series([
|
||||
function(s_c) {
|
||||
self.checkDBVersion(s_c);
|
||||
},
|
||||
function(s_c) {
|
||||
self.checkNetworkSettings(s_c);
|
||||
},
|
||||
function(s_c) {
|
||||
self.updateBlockChainHeight(s_c);
|
||||
},
|
||||
function(s_c) {
|
||||
self.updateStartBlock(opts,s_c);
|
||||
},
|
||||
function(s_c) {
|
||||
self.prepareFileSync(opts, s_c);
|
||||
},
|
||||
function(s_c) {
|
||||
self.prepareRpcSync(opts, s_c);
|
||||
},
|
||||
],
|
||||
function(err) {
|
||||
if (err) return(self.setError(err));
|
||||
|
||||
self.showSyncStartMessage();
|
||||
self.setupSyncStatus();
|
||||
return next();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
HistoricSync.prototype.start = function(opts, next) {
|
||||
var self = this;
|
||||
|
||||
if (self.status==='starting' || self.status==='syncing') {
|
||||
error('## Wont start to sync while status is %s', self.status);
|
||||
return next();
|
||||
}
|
||||
|
||||
self.prepareToSync(opts, function(err) {
|
||||
if (err) return next(self.setError(err));
|
||||
|
||||
async.whilst(
|
||||
function() {
|
||||
self.showProgress();
|
||||
return self.status === 'syncing';
|
||||
},
|
||||
function (w_cb) {
|
||||
self.getFn(function(err,blockInfo) {
|
||||
if (err) return w_cb(self.setError(err));
|
||||
|
||||
if (blockInfo && blockInfo.hash && (!opts.stopAt || opts.stopAt !== blockInfo.hash)) {
|
||||
self.sync.storeTipBlock(blockInfo, self.allowReorgs, function(err, height) {
|
||||
if (err) return w_cb(self.setError(err));
|
||||
if (height>=0) self.height=height;
|
||||
setImmediate(function(){
|
||||
return w_cb(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.endTs = Date.now();
|
||||
self.status = 'finished';
|
||||
var info = self.info();
|
||||
logger.debug('Done Syncing blockchain', info.type, 'to height', info.height);
|
||||
return w_cb(err);
|
||||
}
|
||||
});
|
||||
}, next);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = require('soop')(HistoricSync);
|
||||
@ -1,150 +0,0 @@
|
||||
'use strict';
|
||||
var fs = require('fs');
|
||||
var bitcore = require('bitcore');
|
||||
var bitcoreUtil = bitcore.util;
|
||||
var Sync = require('./Sync');
|
||||
var Peer = bitcore.Peer;
|
||||
var PeerManager = bitcore.PeerManager;
|
||||
var config = require('../config/config');
|
||||
var networks = bitcore.networks;
|
||||
var sockets = require('../app/controllers/socket.js');
|
||||
|
||||
var peerdb_fn = 'peerdb.json';
|
||||
|
||||
function PeerSync(opts) {
|
||||
opts = opts|| {};
|
||||
this.shouldBroadcast = opts.shouldBroadcast;
|
||||
this.connected = false;
|
||||
this.peerdb = undefined;
|
||||
this.allowReorgs = false;
|
||||
var pmConfig = {
|
||||
network: config.network
|
||||
};
|
||||
this.peerman = new PeerManager(pmConfig);
|
||||
this.load_peers();
|
||||
this.sync = new Sync(opts);
|
||||
this.verbose = opts.verbose || false;
|
||||
}
|
||||
|
||||
PeerSync.prototype.log = function() {
|
||||
if (this.verbose) console.log(arguments);
|
||||
};
|
||||
|
||||
PeerSync.prototype.load_peers = function() {
|
||||
this.peerdb = [{
|
||||
ipv4: config.bitcoind.p2pHost,
|
||||
port: config.bitcoind.p2pPort
|
||||
}];
|
||||
|
||||
fs.writeFileSync(peerdb_fn, JSON.stringify(this.peerdb));
|
||||
};
|
||||
|
||||
PeerSync.prototype.info = function() {
|
||||
return {
|
||||
connected: this.connected,
|
||||
host: this.peerdb[0].ipv4,
|
||||
port: this.peerdb[0].port
|
||||
};
|
||||
};
|
||||
|
||||
PeerSync.prototype.handleInv = function(info) {
|
||||
var invs = info.message.invs;
|
||||
info.conn.sendGetData(invs);
|
||||
};
|
||||
|
||||
PeerSync.prototype._broadcastAddr = function(txid, addrs) {
|
||||
if (addrs) {
|
||||
for(var ii in addrs){
|
||||
sockets.broadcastAddressTx(txid, ii);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
PeerSync.prototype.handleTx = function(info) {
|
||||
var self =this;
|
||||
var tx = this.sync.txDb.getStandardizedTx(info.message.tx);
|
||||
self.log('[p2p_sync] Handle tx: ' + tx.txid);
|
||||
tx.time = tx.time || Math.round(new Date().getTime() / 1000);
|
||||
|
||||
this.sync.storeTx(tx, function(err, relatedAddrs) {
|
||||
if (err) {
|
||||
self.log('[p2p_sync] Error in handle TX: ' + JSON.stringify(err));
|
||||
}
|
||||
else if (self.shouldBroadcast) {
|
||||
sockets.broadcastTx(tx);
|
||||
self._broadcastAddr(tx.txid, relatedAddrs);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
PeerSync.prototype.handleBlock = function(info) {
|
||||
var self = this;
|
||||
var block = info.message.block;
|
||||
var blockHash = bitcoreUtil.formatHashFull(block.calcHash());
|
||||
self.log('[p2p_sync] Handle block: %s (allowReorgs: %s)', blockHash, self.allowReorgs);
|
||||
|
||||
var tx_hashes = block.txs.map(function(tx) {
|
||||
return bitcoreUtil.formatHashFull(tx.hash);
|
||||
});
|
||||
|
||||
self.sync.storeTipBlock({
|
||||
'hash': blockHash,
|
||||
'tx': tx_hashes,
|
||||
'previousblockhash': bitcoreUtil.formatHashFull(block.prev_hash),
|
||||
}, self.allowReorgs, function(err, height) {
|
||||
if (err && err.message.match(/NEED_SYNC/) && self.historicSync) {
|
||||
self.log('[p2p_sync] Orphan block received. Triggering sync');
|
||||
self.historicSync.start({forceRPC:1}, function(){
|
||||
self.log('[p2p_sync] Done resync.');
|
||||
});
|
||||
}
|
||||
else if (err) {
|
||||
self.log('[p2p_sync] Error in handle Block: ', err);
|
||||
}
|
||||
else {
|
||||
if (self.shouldBroadcast) {
|
||||
sockets.broadcastBlock(blockHash);
|
||||
// broadcasting address here is a bad idea. listening to new block
|
||||
// should be enoght
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
PeerSync.prototype.handleConnected = function(data) {
|
||||
var peerman = data.pm;
|
||||
var peers_n = peerman.peers.length;
|
||||
this.log('[p2p_sync] Connected to ' + peers_n + ' peer' + (peers_n !== 1 ? 's' : ''));
|
||||
};
|
||||
|
||||
PeerSync.prototype.run = function() {
|
||||
var self = this;
|
||||
|
||||
this.peerdb.forEach(function(datum) {
|
||||
var peer = new Peer(datum.ipv4, datum.port);
|
||||
self.peerman.addPeer(peer);
|
||||
});
|
||||
|
||||
this.peerman.on('connection', function(conn) {
|
||||
self.connected = true;
|
||||
conn.on('inv', self.handleInv.bind(self));
|
||||
conn.on('block', self.handleBlock.bind(self));
|
||||
conn.on('tx', self.handleTx.bind(self));
|
||||
});
|
||||
this.peerman.on('connect', self.handleConnected.bind(self));
|
||||
|
||||
this.peerman.on('netDisconnected', function() {
|
||||
self.connected = false;
|
||||
});
|
||||
|
||||
this.peerman.start();
|
||||
};
|
||||
|
||||
PeerSync.prototype.close = function() {
|
||||
this.sync.close();
|
||||
};
|
||||
|
||||
|
||||
module.exports = require('soop')(PeerSync);
|
||||
@ -6,7 +6,6 @@ var events = require('events');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var AddressService = require('../../lib/services/address');
|
||||
|
||||
@ -43,7 +42,9 @@ describe('AddressService', function() {
|
||||
beforeEach(initialize);
|
||||
|
||||
it('calls internal functions as expected', function(done) {
|
||||
service.blockService = { getLatest: sinon.mock() };
|
||||
service.blockService = {
|
||||
getLatest: sinon.mock()
|
||||
};
|
||||
service.getAllOutputs = sinon.mock();
|
||||
service.getSpent = sinon.mock();
|
||||
service.buildAddressSummary = sinon.mock();
|
||||
@ -105,17 +106,16 @@ describe('AddressService', function() {
|
||||
AddressService.processOutput = sinon.stub(AddressService, 'processOutput');
|
||||
AddressService.processOutput.onFirstCall().returns('processed');
|
||||
|
||||
var element = {key: 'key', value: 'value'};
|
||||
var element = {
|
||||
key: 'key',
|
||||
value: 'value'
|
||||
};
|
||||
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||
service.getAllOutputs(address).then(function(arg) {
|
||||
service.database.createReadStream.firstCall.args[0].should.deep.equal(
|
||||
{
|
||||
gte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||
lte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||
}
|
||||
);
|
||||
service.database.createReadStream.firstCall.args[0].should.deep.equal({
|
||||
gte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||
lte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||
});
|
||||
AddressService.processOutput.firstCall.args[0].should.equal(element);
|
||||
AddressService.processOutput.reset();
|
||||
arg[0].should.equal('processed');
|
||||
@ -131,20 +131,22 @@ describe('AddressService', function() {
|
||||
var dataCall = new events.EventEmitter();
|
||||
service.database.createReadStream.onFirstCall().returns(dataCall);
|
||||
|
||||
var element = {key: 'key', value: JSON.stringify({a: 'b'})};
|
||||
var element = {
|
||||
key: 'key',
|
||||
value: JSON.stringify({
|
||||
a: 'b'
|
||||
})
|
||||
};
|
||||
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||
service.getSpent(address).then(function(arg) {
|
||||
service.database.createReadStream.firstCall.args[0].should.deep.equal(
|
||||
{
|
||||
gte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||
lte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||
}
|
||||
);
|
||||
arg[0].should.deep.equal({a: 'b'});
|
||||
done();
|
||||
});
|
||||
service.getSpent(address)
|
||||
.then(function(arg) {
|
||||
service.database.createReadStream.firstCall.args[0].should.deep.equal({
|
||||
gte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||
lte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||
});
|
||||
arg[0].should.deep.equal('processed');
|
||||
done();
|
||||
});
|
||||
|
||||
dataCall.emit('data', element);
|
||||
dataCall.emit('end');
|
||||
@ -158,107 +160,203 @@ describe('AddressService', function() {
|
||||
var tip = {
|
||||
height: 10
|
||||
};
|
||||
var allOutputs = [
|
||||
{
|
||||
var allOutputs = [{
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 1
|
||||
}];
|
||||
|
||||
it('calculates balance correctly for confirmed balance', function() {
|
||||
var allOutputs = [{
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 1
|
||||
}
|
||||
];
|
||||
|
||||
it('calculates balance correctly for confirmed balance', function() {
|
||||
var allOutputs = [ { satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 } ];
|
||||
}];
|
||||
var spendOutputs = [];
|
||||
|
||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||
address: address.toString(),
|
||||
transactions: ['A'],
|
||||
confirmed: { balance: 10, sent: 0, received: 10 },
|
||||
unconfirmed: { balance: 10, sent: 0, received: 10 }
|
||||
confirmed: {
|
||||
balance: 10,
|
||||
sent: 0,
|
||||
received: 10
|
||||
},
|
||||
unconfirmed: {
|
||||
balance: 10,
|
||||
sent: 0,
|
||||
received: 10
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('calculates balance correctly for unconfirmed balance', function() {
|
||||
var allOutputs = [
|
||||
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
|
||||
];
|
||||
var spendOutputs = [ ];
|
||||
var allOutputs = [{
|
||||
satoshis: 20,
|
||||
txId: 'B',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 10
|
||||
}];
|
||||
var spendOutputs = [];
|
||||
|
||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||
address: address.toString(),
|
||||
transactions: ['B'],
|
||||
confirmed: { balance: 0, sent: 0, received: 0 },
|
||||
unconfirmed: { balance: 20, sent: 0, received: 20 }
|
||||
confirmed: {
|
||||
balance: 0,
|
||||
sent: 0,
|
||||
received: 0
|
||||
},
|
||||
unconfirmed: {
|
||||
balance: 20,
|
||||
sent: 0,
|
||||
received: 20
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('works with multiple transactions', function() {
|
||||
var allOutputs = [
|
||||
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
||||
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
|
||||
];
|
||||
var spendOutputs = [
|
||||
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'A', heightSpent: 10 }
|
||||
];
|
||||
var allOutputs = [{
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 1
|
||||
}, {
|
||||
satoshis: 20,
|
||||
txId: 'B',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 10
|
||||
}];
|
||||
var spendOutputs = [{
|
||||
spendInput: {
|
||||
prevTxId: 'A',
|
||||
outputIndex: 1
|
||||
},
|
||||
spentTx: 'A',
|
||||
heightSpent: 10
|
||||
}];
|
||||
|
||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||
address: address.toString(),
|
||||
transactions: ['A', 'B'],
|
||||
confirmed: { balance: 10, sent: 0, received: 10 },
|
||||
unconfirmed: { balance: 20, sent: 10, received: 30 }
|
||||
confirmed: {
|
||||
balance: 10,
|
||||
sent: 0,
|
||||
received: 10
|
||||
},
|
||||
unconfirmed: {
|
||||
balance: 20,
|
||||
sent: 10,
|
||||
received: 30
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('works with a medium amount of transactions', function() {
|
||||
var allOutputs = [
|
||||
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
||||
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 5 },
|
||||
{ satoshis: 30, txId: 'C', outputIndex: 1, heightConfirmed: 10 }
|
||||
];
|
||||
var spendOutputs = [
|
||||
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'D', heightSpent: 10 }
|
||||
];
|
||||
var allOutputs = [{
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 1
|
||||
}, {
|
||||
satoshis: 20,
|
||||
txId: 'B',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 5
|
||||
}, {
|
||||
satoshis: 30,
|
||||
txId: 'C',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 10
|
||||
}];
|
||||
var spendOutputs = [{
|
||||
spendInput: {
|
||||
prevTxId: 'A',
|
||||
outputIndex: 1
|
||||
},
|
||||
spentTx: 'D',
|
||||
heightSpent: 10
|
||||
}];
|
||||
|
||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||
address: address.toString(),
|
||||
transactions: ['A', 'B', 'C', 'D'],
|
||||
confirmed: { balance: 30, sent: 0, received: 30 },
|
||||
unconfirmed: { balance: 50, sent: 10, received: 60 }
|
||||
confirmed: {
|
||||
balance: 30,
|
||||
sent: 0,
|
||||
received: 30
|
||||
},
|
||||
unconfirmed: {
|
||||
balance: 50,
|
||||
sent: 10,
|
||||
received: 60
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('works with a transaction that includes twice the same address', function() {
|
||||
var allOutputs = [
|
||||
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
|
||||
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
||||
];
|
||||
var allOutputs = [{
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 0,
|
||||
heightConfirmed: 1
|
||||
}, {
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 1,
|
||||
heightConfirmed: 1
|
||||
}, ];
|
||||
var spendOutputs = [];
|
||||
|
||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||
address: address.toString(),
|
||||
transactions: ['A'],
|
||||
confirmed: { balance: 20, sent: 0, received: 20 },
|
||||
unconfirmed: { balance: 20, sent: 0, received: 20 }
|
||||
confirmed: {
|
||||
balance: 20,
|
||||
sent: 0,
|
||||
received: 20
|
||||
},
|
||||
unconfirmed: {
|
||||
balance: 20,
|
||||
sent: 0,
|
||||
received: 20
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('confirmed spent transactions change the balance', function() {
|
||||
var allOutputs = [
|
||||
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
|
||||
];
|
||||
var spendOutputs = [
|
||||
{ spendInput: { prevTxId: 'A', outputIndex: 0 }, spentTx: 'D', heightSpent: 2 }
|
||||
];
|
||||
var allOutputs = [{
|
||||
satoshis: 10,
|
||||
txId: 'A',
|
||||
outputIndex: 0,
|
||||
heightConfirmed: 1
|
||||
}, ];
|
||||
var spendOutputs = [{
|
||||
spendInput: {
|
||||
prevTxId: 'A',
|
||||
outputIndex: 0
|
||||
},
|
||||
spentTx: 'D',
|
||||
heightSpent: 2
|
||||
}];
|
||||
|
||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||
address: address.toString(),
|
||||
transactions: ['A', 'D'],
|
||||
confirmed: { balance: 0, sent: 10, received: 10 },
|
||||
unconfirmed: { balance: 0, sent: 10, received: 10 }
|
||||
confirmed: {
|
||||
balance: 0,
|
||||
sent: 10,
|
||||
received: 10
|
||||
},
|
||||
unconfirmed: {
|
||||
balance: 0,
|
||||
sent: 10,
|
||||
received: 10
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ var should = require('chai').should();
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var TransactionService = require('../../lib/services/transaction');
|
||||
|
||||
|
||||
73
ws/index.js
73
ws/index.js
@ -1,73 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// server-side socket behaviour
|
||||
var ios = null; // io is already taken in express
|
||||
var util = require('bitcore').util;
|
||||
var logger = require('../../lib/logger').logger;
|
||||
|
||||
module.exports.init = function(io_ext) {
|
||||
ios = io_ext;
|
||||
if (ios) {
|
||||
// when a new socket connects
|
||||
ios.sockets.on('connection', function(socket) {
|
||||
logger.verbose('New connection from ' + socket.id);
|
||||
// when it subscribes, make it join the according room
|
||||
socket.on('subscribe', function(topic) {
|
||||
logger.debug('subscribe to ' + topic);
|
||||
socket.join(topic);
|
||||
socket.emit('subscribed');
|
||||
});
|
||||
|
||||
// disconnect handler
|
||||
socket.on('disconnect', function() {
|
||||
logger.verbose('disconnected ' + socket.id);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
return ios;
|
||||
};
|
||||
|
||||
var simpleTx = function(tx) {
|
||||
return {
|
||||
txid: tx
|
||||
};
|
||||
};
|
||||
|
||||
var fullTx = function(tx) {
|
||||
var t = {
|
||||
txid: tx.txid,
|
||||
size: tx.size,
|
||||
};
|
||||
// Outputs
|
||||
var valueOut = 0;
|
||||
tx.vout.forEach(function(o) {
|
||||
valueOut += o.valueSat;
|
||||
});
|
||||
|
||||
t.valueOut = (valueOut.toFixed(8) / util.COIN);
|
||||
return t;
|
||||
};
|
||||
|
||||
module.exports.broadcastTx = function(tx) {
|
||||
if (ios) {
|
||||
var t = (typeof tx === 'string') ? simpleTx(tx) : fullTx(tx);
|
||||
ios.sockets.in('inv').emit('tx', t);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.broadcastBlock = function(block) {
|
||||
if (ios)
|
||||
ios.sockets.in('inv').emit('block', block);
|
||||
};
|
||||
|
||||
module.exports.broadcastAddressTx = function(txid, address) {
|
||||
if (ios) {
|
||||
ios.sockets.in(address).emit(address, txid);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.broadcastSyncInfo = function(historicSync) {
|
||||
if (ios)
|
||||
ios.sockets.in('sync').emit('status', historicSync);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user