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 $ = bitcore.util.preconditions;
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
|
|
||||||
var BitcoreNode = require('../../');
|
|
||||||
|
|
||||||
var Addresses = {};
|
var Addresses = {};
|
||||||
|
|
||||||
var node;
|
var node;
|
||||||
@ -23,7 +21,7 @@ Addresses.setNode = function(aNode) {
|
|||||||
* Finds an address' info by it's string representation
|
* Finds an address' info by it's string representation
|
||||||
*/
|
*/
|
||||||
Addresses.addressParam = function(req, res, next, address) {
|
Addresses.addressParam = function(req, res, next, address) {
|
||||||
if (!Address.isValid(address)) {
|
if (!Address.isValid(address, bitcore.Networks.defaultNetwork)) {
|
||||||
res.status(422);
|
res.status(422);
|
||||||
res.send('/v1/addresses/ parameter must be a valid bitcoin address');
|
res.send('/v1/addresses/ parameter must be a valid bitcoin address');
|
||||||
return;
|
return;
|
||||||
@ -76,7 +74,7 @@ Addresses.utxos = function(req, res) {
|
|||||||
$.checkState(_.all(req.addresses, function(addr) {
|
$.checkState(_.all(req.addresses, function(addr) {
|
||||||
return addr instanceof Address;
|
return addr instanceof Address;
|
||||||
}));
|
}));
|
||||||
node.getUTXOs(req.addresses)
|
node.addressService.getUnspent(req.addresses)
|
||||||
.then(function(utxos) {
|
.then(function(utxos) {
|
||||||
res.send(utxos);
|
res.send(utxos);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var _ = bitcore.deps._;
|
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var Block = bitcore.Block;
|
var Block = bitcore.Block;
|
||||||
|
|
||||||
var BitcoreNode = require('../../');
|
var errors = require('../../lib/errors');
|
||||||
|
|
||||||
var Blocks = {};
|
var Blocks = {};
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ Blocks.blockHashParam = function(req, res, next, blockHash) {
|
|||||||
req.block = block;
|
req.block = block;
|
||||||
})
|
})
|
||||||
.then(next)
|
.then(next)
|
||||||
.catch(BitcoreNode.errors.Blocks.NotFound, function() {
|
.catch(errors.Blocks.NotFound, function() {
|
||||||
res.status(404).send('Block with id ' + blockHash + ' not found');
|
res.status(404).send('Block with id ' + blockHash + ' not found');
|
||||||
})
|
})
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
@ -46,7 +45,7 @@ Blocks.heightParam = function(req, res, next, height) {
|
|||||||
req.block = block;
|
req.block = block;
|
||||||
})
|
})
|
||||||
.then(next)
|
.then(next)
|
||||||
.catch(BitcoreNode.errors.Blocks.NotFound, function() {
|
.catch(errors.Blocks.NotFound, function() {
|
||||||
res.status(404).send('Block with height ' + height + ' not found');
|
res.status(404).send('Block with height ' + height + ' not found');
|
||||||
})
|
})
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ var _ = bitcore.deps._;
|
|||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var Transaction = bitcore.Transaction;
|
var Transaction = bitcore.Transaction;
|
||||||
|
|
||||||
var BitcoreNode = require('../../');
|
var errors = require('../../lib/errors');
|
||||||
|
|
||||||
var Transactions = {};
|
var Transactions = {};
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ Transactions.txHashParam = function(req, res, next, txHash) {
|
|||||||
req.tx = tx;
|
req.tx = tx;
|
||||||
})
|
})
|
||||||
.then(next)
|
.then(next)
|
||||||
.catch(BitcoreNode.errors.Transactions.NotFound, function() {
|
.catch(errors.Transactions.NotFound, function() {
|
||||||
res.status(404).send('Transaction with id ' + txHash + ' not found');
|
res.status(404).send('Transaction with id ' + txHash + ' not found');
|
||||||
})
|
})
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
@ -81,24 +81,12 @@ Transactions.send = function(req, res) {
|
|||||||
.then(function() {
|
.then(function() {
|
||||||
res.send('Transaction broadcasted successfully');
|
res.send('Transaction broadcasted successfully');
|
||||||
})
|
})
|
||||||
.catch(BitcoreNode.errors.Transactions.CantBroadcast, function(err) {
|
.catch(errors.Transactions.CantBroadcast, function(err) {
|
||||||
res.status(422).send(err.message);
|
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) {
|
var buildIOHelper = function(name) {
|
||||||
$.checkArgument(name === 'inputs' || name === 'outputs');
|
$.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 + '.');
|
' for ' + req.tx.id + ' not found, it only has ' + req.tx[name].length + ' ' + name + '.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.send(req.tx[name][req.index].toJSON());
|
res.send(req.tx[name][req.index].toObject());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.send(req.tx[name].map(function(x) {
|
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';
|
'use strict';
|
||||||
|
|
||||||
var BitcoreHTTP = require('./lib/http');
|
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;
|
module.exports = BitcoreHTTP;
|
||||||
|
|||||||
@ -44,6 +44,8 @@ BitcoreHTTP.prototype.setupExpress = function() {
|
|||||||
app.use(morgan('dev'));
|
app.use(morgan('dev'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.set('json spaces', 2);
|
||||||
|
|
||||||
// install routes
|
// install routes
|
||||||
app.use('/', routes(this.node));
|
app.use('/', routes(this.node));
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,6 @@ function initRouter(node) {
|
|||||||
|
|
||||||
// Address routes
|
// Address routes
|
||||||
router.get('/addresses/:address', Addresses.get);
|
router.get('/addresses/:address', Addresses.get);
|
||||||
router.get('/addresses/:address/transactions', Transactions.list);
|
|
||||||
router.get('/addresses/:addresses/utxos', Addresses.utxos);
|
router.get('/addresses/:addresses/utxos', Addresses.utxos);
|
||||||
|
|
||||||
// error routes
|
// error routes
|
||||||
|
|||||||
@ -11,17 +11,11 @@ var _ = bitcore.deps._;
|
|||||||
|
|
||||||
|
|
||||||
var mockAddresses = require('../data/addresses');
|
var mockAddresses = require('../data/addresses');
|
||||||
var mockTransactions = require('../data/transactions');
|
|
||||||
|
|
||||||
describe('BitcoreHTTP v1 addresses routes', function() {
|
describe('BitcoreHTTP v1 addresses routes', function() {
|
||||||
|
|
||||||
// mocks
|
// mocks
|
||||||
var transactionList = _.values(mockTransactions);
|
|
||||||
var nodeMock, agent;
|
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) {
|
var utxos_for_addrs = function(addrs) {
|
||||||
return _.reduce(addrs, function(utxos, addr) {
|
return _.reduce(addrs, function(utxos, addr) {
|
||||||
return utxos.concat(mockAddresses[addr].utxos);
|
return utxos.concat(mockAddresses[addr].utxos);
|
||||||
@ -53,10 +47,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
|||||||
nodeMock.addressService.getSummary = function(address) {
|
nodeMock.addressService.getSummary = function(address) {
|
||||||
return Promise.resolve(mockAddresses[address.toString()].summary);
|
return Promise.resolve(mockAddresses[address.toString()].summary);
|
||||||
};
|
};
|
||||||
nodeMock.listTransactions = function(opts) {
|
nodeMock.addressService.getUnspent = function(addresses) {
|
||||||
return Promise.resolve(txs_for_addr(opts.address));
|
|
||||||
};
|
|
||||||
nodeMock.getUTXOs = function(addresses) {
|
|
||||||
return Promise.resolve(utxos_for_addrs(addresses));
|
return Promise.resolve(utxos_for_addrs(addresses));
|
||||||
};
|
};
|
||||||
agent = require('../app')(nodeMock);
|
agent = require('../app')(nodeMock);
|
||||||
@ -77,19 +68,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
|||||||
it('works with valid address ' + addr, function(cb) {
|
it('works with valid address ' + addr, function(cb) {
|
||||||
agent.get('/v1/addresses/' + addr)
|
agent.get('/v1/addresses/' + addr)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect(JSON.stringify(info.summary), cb);
|
.expect(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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -104,7 +83,7 @@ describe('BitcoreHTTP v1 addresses routes', function() {
|
|||||||
it('works with valid address ' + addr, function(cb) {
|
it('works with valid address ' + addr, function(cb) {
|
||||||
agent.get('/v1/addresses/' + addr + '/utxos')
|
agent.get('/v1/addresses/' + addr + '/utxos')
|
||||||
.expect(200)
|
.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';
|
var path = '/v1/addresses/' + list + '/utxos';
|
||||||
agent.get(path)
|
agent.get(path)
|
||||||
.expect(200)
|
.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) {
|
it('works with valid blockHash ...' + hash.substring(hash.length - 8), function(cb) {
|
||||||
agent.get('/v1/blocks/' + hash)
|
agent.get('/v1/blocks/' + hash)
|
||||||
.expect(200)
|
.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) {
|
it('works with valid height', function(cb) {
|
||||||
agent.get('/v1/blocks/100000')
|
agent.get('/v1/blocks/100000')
|
||||||
.expect(200)
|
.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) {
|
it('works with valid txHash ...' + hash.substring(hash.length - 8), function(cb) {
|
||||||
agent.get('/v1/transactions/' + hash)
|
agent.get('/v1/transactions/' + hash)
|
||||||
.expect(200)
|
.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 + '/')
|
agent.get('/v1/transactions/' + hash + '/' + name + '/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect(tx[name].map(function(x) {
|
.expect(tx[name].map(function(x) {
|
||||||
return x.toJSON();
|
return x.toObject();
|
||||||
}), cb);
|
}), cb);
|
||||||
});
|
});
|
||||||
var canGetSpecificInput = function(i) {
|
var canGetSpecificInput = function(i) {
|
||||||
@ -120,7 +120,7 @@ describe('BitcoreHTTP v1 transactions routes', function() {
|
|||||||
return function(cb) {
|
return function(cb) {
|
||||||
agent.get('/v1/transactions/' + hash + '/' + name + '/' + i)
|
agent.get('/v1/transactions/' + hash + '/' + name + '/' + i)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect(x.toJSON(), cb);
|
.expect(x.toObject(), cb);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
for (var i = 0; i < tx[name].length; i++) {
|
for (var i = 0; i < tx[name].length; i++) {
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
BitcoreNode:
|
BitcoreHTTP:
|
||||||
LevelUp: ./db
|
port: 8080
|
||||||
network: livenet
|
logging: true
|
||||||
NetworkMonitor:
|
BitcoreNode:
|
||||||
host: localhost
|
LevelUp: ./db
|
||||||
port: 8333
|
network: livenet
|
||||||
Reporter: none # none, simple, matrix
|
NetworkMonitor:
|
||||||
RPC:
|
host: localhost
|
||||||
user: user
|
port: 8333
|
||||||
pass: password
|
RPC:
|
||||||
protocol: http # http, https
|
user: user
|
||||||
#rejectUnauthorized: false
|
pass: password
|
||||||
#disableAgent: true
|
protocol: http #http, https
|
||||||
host: 127.0.0.1
|
#rejectUnauthorized: false
|
||||||
port: 8332
|
#disableAgent: true
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 8332
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
BitcoreNode:
|
BitcoreHTTP:
|
||||||
LevelUp: ./db
|
port: 8080
|
||||||
network: livenet
|
logging: true
|
||||||
NetworkMonitor:
|
BitcoreNode:
|
||||||
host: localhost
|
LevelUp: ./db
|
||||||
port: 8333
|
network: livenet
|
||||||
Reporter: none # none, simple, matrix
|
NetworkMonitor:
|
||||||
RPC:
|
host: localhost
|
||||||
user: user
|
port: 8333
|
||||||
pass: password
|
RPC:
|
||||||
protocol: http
|
user: user
|
||||||
host: 127.0.0.1
|
pass: password
|
||||||
port: 8332
|
protocol: http
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 8332
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
BitcoreNode:
|
BitcoreHTTP:
|
||||||
LevelUp: ./testnet-db
|
port: 18080
|
||||||
network: testnet
|
logging: true
|
||||||
NetworkMonitor:
|
BitcoreNode:
|
||||||
host: localhost
|
LevelUp: ./testnet-db
|
||||||
port: 18333
|
network: testnet
|
||||||
Reporter: none # none, simple, matrix
|
NetworkMonitor:
|
||||||
RPC:
|
host: localhost
|
||||||
user: user
|
port: 18333
|
||||||
pass: password
|
RPC:
|
||||||
protocol: http
|
user: user
|
||||||
host: 127.0.0.1
|
pass: password
|
||||||
port: 18332
|
protocol: http
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 18332
|
||||||
|
|||||||
31
index.js
31
index.js
@ -1,21 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var BitcoreNode = require('./lib/node');
|
var BitcoreNode = require('./lib/node');
|
||||||
var reporters = require('./lib/reporters');
|
var BitcoreHTTP = require('./api/lib/http');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
Promise.longStackTraces();
|
Promise.longStackTraces();
|
||||||
|
|
||||||
BitcoreNode.errors = require('./lib/errors');
|
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
var network = config.get('BitcoreNode').network;
|
var nodeConfig = config.get('BitcoreHTTP.BitcoreNode');
|
||||||
console.log('Starting bitcore-node', network, 'network');
|
var httpConfig = config.get('BitcoreHTTP');
|
||||||
|
var network = nodeConfig.network;
|
||||||
|
console.log('Starting bitcore-node-http', network, 'network');
|
||||||
bitcore.Networks.defaultNetwork = bitcore.Networks.get(network);
|
bitcore.Networks.defaultNetwork = bitcore.Networks.get(network);
|
||||||
|
var node = BitcoreNode.create(nodeConfig);
|
||||||
var node = BitcoreNode.create(config.get('BitcoreNode'));
|
|
||||||
node.start();
|
|
||||||
node.on('error', function(err) {
|
node.on('error', function(err) {
|
||||||
if (err.code === 'ECONNREFUSED') {
|
if (err.code === 'ECONNREFUSED') {
|
||||||
console.log('Connection to bitcoind failed');
|
console.log('Connection to bitcoind failed');
|
||||||
@ -23,18 +22,12 @@ if (require.main === module) {
|
|||||||
console.log('Error: ', err);
|
console.log('Error: ', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
process.on('SIGINT', function() {
|
var http = new BitcoreHTTP(node, httpConfig);
|
||||||
node.stop();
|
http.start()
|
||||||
process.exit();
|
.catch(function(err) {
|
||||||
});
|
http.stop();
|
||||||
|
throw err;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BitcoreNode;
|
module.exports = BitcoreNode;
|
||||||
|
|||||||
@ -79,8 +79,6 @@ BitcoreNode.prototype.initialize = function() {
|
|||||||
var prevHeight = 0;
|
var prevHeight = 0;
|
||||||
var statTimer = 5 * 1000;
|
var statTimer = 5 * 1000;
|
||||||
this.interval = setInterval(function() {
|
this.interval = setInterval(function() {
|
||||||
console.log('MB used:', process.memoryUsage().heapTotal / 1024 / 1024,
|
|
||||||
100 * self.getSyncProgress() + '% synced');
|
|
||||||
if (!self.blockchain) {
|
if (!self.blockchain) {
|
||||||
// not ready yet
|
// not ready yet
|
||||||
console.log('No blockchain yet');
|
console.log('No blockchain yet');
|
||||||
@ -94,7 +92,8 @@ BitcoreNode.prototype.initialize = function() {
|
|||||||
}
|
}
|
||||||
var delta = block.height - prevHeight;
|
var delta = block.height - prevHeight;
|
||||||
prevHeight = block.height;
|
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);
|
}, statTimer);
|
||||||
|
|
||||||
this.bus.register(bitcore.Block, function(block) {
|
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;
|
module.exports = BitcoreNode;
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
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 $ = bitcore.util.preconditions;
|
||||||
|
|
||||||
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
||||||
var LASTTXHASH = bitcore.util.buffer.fill(bitcore.util.buffer.emptyBuffer(32), -1).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);
|
opts = _.extend({}, opts);
|
||||||
this.transactionService = opts.transactionService;
|
this.transactionService = opts.transactionService;
|
||||||
this.blockService = opts.blockService;
|
this.blockService = opts.blockService;
|
||||||
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
|
this.database = opts.database;
|
||||||
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
|
this.rpc = opts.rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddressService.prototype.getSummary = function(address, confirmations) {
|
AddressService.prototype.getSummary = function(address, confirmations) {
|
||||||
@ -56,6 +56,7 @@ AddressService.processOutput = function(data) {
|
|||||||
|
|
||||||
var retrieveOutputs = function(indexFunction, processElement) {
|
var retrieveOutputs = function(indexFunction, processElement) {
|
||||||
return function(address) {
|
return function(address) {
|
||||||
|
$.checkArgument(address, 'address required');
|
||||||
var results = [];
|
var results = [];
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@ -81,11 +82,44 @@ AddressService.prototype.getAllOutputs = retrieveOutputs(
|
|||||||
|
|
||||||
AddressService.prototype.getSpent = retrieveOutputs(
|
AddressService.prototype.getSpent = retrieveOutputs(
|
||||||
TransactionService.Index.getSpentOutputsForAddress,
|
TransactionService.Index.getSpentOutputsForAddress,
|
||||||
function(element) {
|
function(e) {
|
||||||
return JSON.parse(element.value);
|
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) {
|
AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs, spent, confirmations) {
|
||||||
|
|
||||||
var result = {};
|
var result = {};
|
||||||
|
|||||||
@ -206,8 +206,6 @@ BlockService.prototype.listBlocks = function(from, to, offset, limit) {
|
|||||||
var start = from + offset;
|
var start = from + offset;
|
||||||
var end = Math.min(to, start + limit);
|
var end = Math.min(to, start + limit);
|
||||||
var blocks = [];
|
var blocks = [];
|
||||||
//console.log(from, to, offset, limit);
|
|
||||||
//console.log(start, end);
|
|
||||||
// TODO: optimize: precompute heights and fetch all blocks in parallel?
|
// TODO: optimize: precompute heights and fetch all blocks in parallel?
|
||||||
var fetchBlock = function(height) {
|
var fetchBlock = function(height) {
|
||||||
if (height >= end) {
|
if (height >= end) {
|
||||||
@ -284,37 +282,27 @@ BlockService.prototype.confirm = function(block, ops) {
|
|||||||
|
|
||||||
ops = ops || [];
|
ops = ops || [];
|
||||||
|
|
||||||
//console.log(0);
|
|
||||||
return Promise.try(function() {
|
return Promise.try(function() {
|
||||||
//console.log(0.5);
|
|
||||||
self._setHeader(ops, block);
|
self._setHeader(ops, block);
|
||||||
//console.log(1);
|
|
||||||
self._setNextBlock(ops, block.header.prevHash, block);
|
self._setNextBlock(ops, block.header.prevHash, block);
|
||||||
|
|
||||||
//console.log(3);
|
|
||||||
self._setBlockHeight(ops, block);
|
self._setBlockHeight(ops, block);
|
||||||
|
|
||||||
//console.log(3);
|
|
||||||
self._setBlockWork(ops, block);
|
self._setBlockWork(ops, block);
|
||||||
|
|
||||||
//console.log(4);
|
|
||||||
self._setBlockByTs(ops, block);
|
self._setBlockByTs(ops, block);
|
||||||
|
|
||||||
//console.log(4.1);
|
|
||||||
self._setTip(ops, block);
|
self._setTip(ops, block);
|
||||||
|
|
||||||
//console.log(5);
|
|
||||||
return Promise.all(block.transactions.map(function(transaction) {
|
return Promise.all(block.transactions.map(function(transaction) {
|
||||||
return self.transactionService._confirmTransaction(ops, block, transaction);
|
return self.transactionService._confirmTransaction(ops, block, transaction);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
//console.log(6);
|
|
||||||
return self.database.batchAsync(ops);
|
return self.database.batchAsync(ops);
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
//console.log(7);
|
|
||||||
return block;
|
return block;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -373,17 +361,14 @@ BlockService.prototype._setBlockByTs = function(ops, block) {
|
|||||||
/*
|
/*
|
||||||
var self = this;
|
var self = this;
|
||||||
var key = Index.timestamp + block.header.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 {
|
||||||
@ -501,7 +486,6 @@ BlockService.prototype.getBlockchain = function() {
|
|||||||
var fetchHeader = function(blockHash) {
|
var fetchHeader = function(blockHash) {
|
||||||
if (blockHash === BlockChain.NULL) {
|
if (blockHash === BlockChain.NULL) {
|
||||||
console.log('All headers fetched, total =', headers.length);
|
console.log('All headers fetched, total =', headers.length);
|
||||||
console.log(process.memoryUsage().heapTotal / 1024 / 1024, 'mb used');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var headerKey = Index.getBlockHeader(blockHash);
|
var headerKey = Index.getBlockHeader(blockHash);
|
||||||
|
|||||||
@ -20,12 +20,11 @@ var LevelUp = require('levelup');
|
|||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
var BitcoreNode = require('../../');
|
var errors = require('../errors');
|
||||||
|
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
|
|
||||||
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
|
|
||||||
var GENESISTX = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
var GENESISTX = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
||||||
|
|
||||||
var helper = function(name) {
|
var helper = function(name) {
|
||||||
@ -87,7 +86,7 @@ TransactionService.Index = Index;
|
|||||||
|
|
||||||
var txNotFound = function(error) {
|
var txNotFound = function(error) {
|
||||||
if (error.message === 'No information available about transaction') {
|
if (error.message === 'No information available about transaction') {
|
||||||
throw new BitcoreNode.errors.Transactions.NotFound();
|
throw new errors.Transactions.NotFound();
|
||||||
}
|
}
|
||||||
throw error;
|
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 Promise = require('bluebird');
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var _ = bitcore.deps._;
|
|
||||||
|
|
||||||
var AddressService = require('../../lib/services/address');
|
var AddressService = require('../../lib/services/address');
|
||||||
|
|
||||||
@ -43,7 +42,9 @@ describe('AddressService', function() {
|
|||||||
beforeEach(initialize);
|
beforeEach(initialize);
|
||||||
|
|
||||||
it('calls internal functions as expected', function(done) {
|
it('calls internal functions as expected', function(done) {
|
||||||
service.blockService = { getLatest: sinon.mock() };
|
service.blockService = {
|
||||||
|
getLatest: sinon.mock()
|
||||||
|
};
|
||||||
service.getAllOutputs = sinon.mock();
|
service.getAllOutputs = sinon.mock();
|
||||||
service.getSpent = sinon.mock();
|
service.getSpent = sinon.mock();
|
||||||
service.buildAddressSummary = sinon.mock();
|
service.buildAddressSummary = sinon.mock();
|
||||||
@ -105,17 +106,16 @@ describe('AddressService', function() {
|
|||||||
AddressService.processOutput = sinon.stub(AddressService, 'processOutput');
|
AddressService.processOutput = sinon.stub(AddressService, 'processOutput');
|
||||||
AddressService.processOutput.onFirstCall().returns('processed');
|
AddressService.processOutput.onFirstCall().returns('processed');
|
||||||
|
|
||||||
var element = {key: 'key', value: 'value'};
|
var element = {
|
||||||
|
key: 'key',
|
||||||
|
value: 'value'
|
||||||
|
};
|
||||||
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||||
service.getAllOutputs(address).then(function(arg) {
|
service.getAllOutputs(address).then(function(arg) {
|
||||||
service.database.createReadStream.firstCall.args[0].should.deep.equal(
|
service.database.createReadStream.firstCall.args[0].should.deep.equal({
|
||||||
{
|
gte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||||
gte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
lte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||||
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
|
});
|
||||||
lte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
|
||||||
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
AddressService.processOutput.firstCall.args[0].should.equal(element);
|
AddressService.processOutput.firstCall.args[0].should.equal(element);
|
||||||
AddressService.processOutput.reset();
|
AddressService.processOutput.reset();
|
||||||
arg[0].should.equal('processed');
|
arg[0].should.equal('processed');
|
||||||
@ -131,20 +131,22 @@ describe('AddressService', function() {
|
|||||||
var dataCall = new events.EventEmitter();
|
var dataCall = new events.EventEmitter();
|
||||||
service.database.createReadStream.onFirstCall().returns(dataCall);
|
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';
|
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||||
service.getSpent(address).then(function(arg) {
|
service.getSpent(address)
|
||||||
service.database.createReadStream.firstCall.args[0].should.deep.equal(
|
.then(function(arg) {
|
||||||
{
|
service.database.createReadStream.firstCall.args[0].should.deep.equal({
|
||||||
gte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
gte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||||
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
|
lte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-' + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||||
lte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
});
|
||||||
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
arg[0].should.deep.equal('processed');
|
||||||
}
|
done();
|
||||||
);
|
});
|
||||||
arg[0].should.deep.equal({a: 'b'});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
dataCall.emit('data', element);
|
dataCall.emit('data', element);
|
||||||
dataCall.emit('end');
|
dataCall.emit('end');
|
||||||
@ -158,107 +160,203 @@ describe('AddressService', function() {
|
|||||||
var tip = {
|
var tip = {
|
||||||
height: 10
|
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,
|
satoshis: 10,
|
||||||
txId: 'A',
|
txId: 'A',
|
||||||
outputIndex: 1,
|
outputIndex: 1,
|
||||||
heightConfirmed: 1
|
heightConfirmed: 1
|
||||||
}
|
}];
|
||||||
];
|
|
||||||
|
|
||||||
it('calculates balance correctly for confirmed balance', function() {
|
|
||||||
var allOutputs = [ { satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 } ];
|
|
||||||
var spendOutputs = [];
|
var spendOutputs = [];
|
||||||
|
|
||||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
transactions: ['A'],
|
transactions: ['A'],
|
||||||
confirmed: { balance: 10, sent: 0, received: 10 },
|
confirmed: {
|
||||||
unconfirmed: { balance: 10, sent: 0, received: 10 }
|
balance: 10,
|
||||||
|
sent: 0,
|
||||||
|
received: 10
|
||||||
|
},
|
||||||
|
unconfirmed: {
|
||||||
|
balance: 10,
|
||||||
|
sent: 0,
|
||||||
|
received: 10
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calculates balance correctly for unconfirmed balance', function() {
|
it('calculates balance correctly for unconfirmed balance', function() {
|
||||||
var allOutputs = [
|
var allOutputs = [{
|
||||||
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
|
satoshis: 20,
|
||||||
];
|
txId: 'B',
|
||||||
var spendOutputs = [ ];
|
outputIndex: 1,
|
||||||
|
heightConfirmed: 10
|
||||||
|
}];
|
||||||
|
var spendOutputs = [];
|
||||||
|
|
||||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
transactions: ['B'],
|
transactions: ['B'],
|
||||||
confirmed: { balance: 0, sent: 0, received: 0 },
|
confirmed: {
|
||||||
unconfirmed: { balance: 20, sent: 0, received: 20 }
|
balance: 0,
|
||||||
|
sent: 0,
|
||||||
|
received: 0
|
||||||
|
},
|
||||||
|
unconfirmed: {
|
||||||
|
balance: 20,
|
||||||
|
sent: 0,
|
||||||
|
received: 20
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with multiple transactions', function() {
|
it('works with multiple transactions', function() {
|
||||||
var allOutputs = [
|
var allOutputs = [{
|
||||||
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
satoshis: 10,
|
||||||
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
|
txId: 'A',
|
||||||
];
|
outputIndex: 1,
|
||||||
var spendOutputs = [
|
heightConfirmed: 1
|
||||||
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'A', heightSpent: 10 }
|
}, {
|
||||||
];
|
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({
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
transactions: ['A', 'B'],
|
transactions: ['A', 'B'],
|
||||||
confirmed: { balance: 10, sent: 0, received: 10 },
|
confirmed: {
|
||||||
unconfirmed: { balance: 20, sent: 10, received: 30 }
|
balance: 10,
|
||||||
|
sent: 0,
|
||||||
|
received: 10
|
||||||
|
},
|
||||||
|
unconfirmed: {
|
||||||
|
balance: 20,
|
||||||
|
sent: 10,
|
||||||
|
received: 30
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with a medium amount of transactions', function() {
|
it('works with a medium amount of transactions', function() {
|
||||||
var allOutputs = [
|
var allOutputs = [{
|
||||||
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
satoshis: 10,
|
||||||
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 5 },
|
txId: 'A',
|
||||||
{ satoshis: 30, txId: 'C', outputIndex: 1, heightConfirmed: 10 }
|
outputIndex: 1,
|
||||||
];
|
heightConfirmed: 1
|
||||||
var spendOutputs = [
|
}, {
|
||||||
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'D', heightSpent: 10 }
|
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({
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
transactions: ['A', 'B', 'C', 'D'],
|
transactions: ['A', 'B', 'C', 'D'],
|
||||||
confirmed: { balance: 30, sent: 0, received: 30 },
|
confirmed: {
|
||||||
unconfirmed: { balance: 50, sent: 10, received: 60 }
|
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() {
|
it('works with a transaction that includes twice the same address', function() {
|
||||||
var allOutputs = [
|
var allOutputs = [{
|
||||||
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
|
satoshis: 10,
|
||||||
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
txId: 'A',
|
||||||
];
|
outputIndex: 0,
|
||||||
|
heightConfirmed: 1
|
||||||
|
}, {
|
||||||
|
satoshis: 10,
|
||||||
|
txId: 'A',
|
||||||
|
outputIndex: 1,
|
||||||
|
heightConfirmed: 1
|
||||||
|
}, ];
|
||||||
var spendOutputs = [];
|
var spendOutputs = [];
|
||||||
|
|
||||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
transactions: ['A'],
|
transactions: ['A'],
|
||||||
confirmed: { balance: 20, sent: 0, received: 20 },
|
confirmed: {
|
||||||
unconfirmed: { balance: 20, sent: 0, received: 20 }
|
balance: 20,
|
||||||
|
sent: 0,
|
||||||
|
received: 20
|
||||||
|
},
|
||||||
|
unconfirmed: {
|
||||||
|
balance: 20,
|
||||||
|
sent: 0,
|
||||||
|
received: 20
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('confirmed spent transactions change the balance', function() {
|
it('confirmed spent transactions change the balance', function() {
|
||||||
var allOutputs = [
|
var allOutputs = [{
|
||||||
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
|
satoshis: 10,
|
||||||
];
|
txId: 'A',
|
||||||
var spendOutputs = [
|
outputIndex: 0,
|
||||||
{ spendInput: { prevTxId: 'A', outputIndex: 0 }, spentTx: 'D', heightSpent: 2 }
|
heightConfirmed: 1
|
||||||
];
|
}, ];
|
||||||
|
var spendOutputs = [{
|
||||||
|
spendInput: {
|
||||||
|
prevTxId: 'A',
|
||||||
|
outputIndex: 0
|
||||||
|
},
|
||||||
|
spentTx: 'D',
|
||||||
|
heightSpent: 2
|
||||||
|
}];
|
||||||
|
|
||||||
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
address: address.toString(),
|
address: address.toString(),
|
||||||
transactions: ['A', 'D'],
|
transactions: ['A', 'D'],
|
||||||
confirmed: { balance: 0, sent: 10, received: 10 },
|
confirmed: {
|
||||||
unconfirmed: { balance: 0, sent: 10, received: 10 }
|
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 Promise = require('bluebird');
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var _ = bitcore.deps._;
|
|
||||||
|
|
||||||
var TransactionService = require('../../lib/services/transaction');
|
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