diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js index 6cf5bb79..3f13ac1d 100644 --- a/lib/services/address/encoding.js +++ b/lib/services/address/encoding.js @@ -96,16 +96,16 @@ Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, timestamp, heightBuffer.writeUInt32BE(height); var satoshisBuffer = new Buffer(8); satoshisBuffer.writeDoubleBE(satoshis); - var timestampBuffer = new Buffer(8); - timestampBuffer.writeUInt32BE(timestamp || 0) + var timestampBuffer = new Buffer(4); + timestampBuffer.writeUInt32BE(timestamp || 0); return Buffer.concat([heightBuffer, satoshisBuffer, timestampBuffer, scriptBuffer]); }; Encoding.prototype.decodeUtxoIndexValue = function(buffer) { var height = buffer.readUInt32BE(); var satoshis = buffer.readDoubleBE(4); - var timestamp = buffer.readDoubleBE(8); - var scriptBuffer = buffer.slice(12); + var timestamp = buffer.readUInt32BE(12); + var scriptBuffer = buffer.slice(16); return { height: height, satoshis: satoshis, diff --git a/lib/services/header/encoding.js b/lib/services/header/encoding.js index 67a4a577..348bbb19 100644 --- a/lib/services/header/encoding.js +++ b/lib/services/header/encoding.js @@ -6,13 +6,13 @@ function Encoding(servicePrefix) { } -// ---- hash --> height +// ---- hash --> header Encoding.prototype.encodeHeaderKey = function(hash) { return Buffer.concat([ this._servicePrefix, new Buffer(hash, 'hex') ]); }; Encoding.prototype.decodeHeaderKey = function(buffer) { - return buffer.slice(3).toString('hex'); + return buffer.slice(2).toString('hex'); }; Encoding.prototype.encodeHeaderValue = function(header) { @@ -26,7 +26,9 @@ Encoding.prototype.encodeHeaderValue = function(header) { bitsBuf.writeUInt32BE(header.bits); var nonceBuf = new Buffer(4); nonceBuf.writeUInt32BE(header.nonce); - return Buffer.concat([ versionBuf, prevHash, merkleRoot, tsBuf, bitsBuf, nonceBuf ]); + var heightBuf = new Buffer(4); + heightBuf.writeUInt32BE(header.height); + return Buffer.concat([ versionBuf, prevHash, merkleRoot, tsBuf, bitsBuf, nonceBuf, heightBuf ]); }; Encoding.prototype.decodeHeaderValue = function(buffer) { @@ -36,13 +38,15 @@ Encoding.prototype.decodeHeaderValue = function(buffer) { var ts = buffer.readUInt32BE(68); var bits = buffer.readUInt32BE(72); var nonce = buffer.readUInt32BE(76); + var height = buffer.readUInt32BE(80); return { version: version, prevHash: prevHash, merkleRoot: merkleRoot, - timestakmp: ts, + timestamp: ts, bits: bits, - nonce: nonce + nonce: nonce, + height: height }; }; diff --git a/lib/services/header/index.js b/lib/services/header/index.js index 578cabc5..5c82d06f 100644 --- a/lib/services/header/index.js +++ b/lib/services/header/index.js @@ -6,7 +6,6 @@ var Encoding = require('./encoding'); var index = require('../../'); var log = index.log; var utils = require('../../utils'); -var constants = require('../../constants'); var HeaderService = function(options) { @@ -26,7 +25,7 @@ HeaderService.dependencies = [ 'p2p', 'db' ]; HeaderService.prototype.getAPIMethods = function() { var methods = [ - ['getAllHeaders', this, this.getHeaders, 0] + ['getAllHeaders', this, this.getAllHeaders, 0] ]; return methods; @@ -112,7 +111,9 @@ HeaderService.prototype._onHeaders = function(headers) { HeaderService.prototype._getHeaderOperations = function(headers) { var self = this; + var runningHeight = this._tip.height; return headers.map(function(header) { + header.height = ++runningHeight; return { type: 'put', key: self._encoding.encodeHeaderKey(header.hash), @@ -153,18 +154,26 @@ HeaderService.prototype._sync = function() { log.info('Headers download progress: ' + this._tip.height + '/' + this._numNeeded + ' (' + (this._tip.height / this._numNeeded*100).toFixed(2) + '%)'); + this._p2p.getHeaders({ startHash: this._tip.hash }); + return; } }; -HeaderService.prototype.getHeaders = function(callback) { +HeaderService.prototype.getAllHeaders = function(callback) { var self = this; var results = []; var start = self._encoding.encodeHeaderKey(0); + var end = self._encoding.encodeHeaderKey(0xffffffff); + var criteria = { + gte: start, + lte: end + }; + var stream = self._db.createReadStream(criteria); var streamErr; @@ -173,10 +182,9 @@ HeaderService.prototype.getHeaders = function(callback) { }); stream.on('data', function(data) { - results.push({ - hash: self.__encoding.decodeHeaderKey(data.key), - header: self._encoding.decodeHeaderValue(data.value) - }); + var res = {}; + res[self._encoding.decodeHeaderKey(data.key)] = self._encoding.decodeHeaderValue(data.value); + results.push(res); }); stream.on('end', function() { diff --git a/lib/services/transaction/encoding.js b/lib/services/transaction/encoding.js index 20ff0b72..962b9d39 100644 --- a/lib/services/transaction/encoding.js +++ b/lib/services/transaction/encoding.js @@ -1,6 +1,6 @@ 'use strict'; -var tx = require('bcoin').tx; +var Tx = require('bcoin').tx; function Encoding(servicePrefix) { this.servicePrefix = servicePrefix; @@ -41,9 +41,11 @@ Encoding.prototype.decodeTransactionValue = function(buffer) { var inputValuesLength = buffer.readUInt16BE(8); var inputValues = []; for(var i = 0; i < inputValuesLength; i++) { - inputValues.push(buffer.readDoubleBE(i * 8 + 14)); + inputValues.push(buffer.readDoubleBE(i * 8 + 10)); } - var transaction = tx.fromRaw(buffer.slice(inputValues.length * 8 + 14)); + + var txBuf = buffer.slice(inputValues.length * 8 + 10); + var transaction = Tx.fromRaw(txBuf); transaction.__height = height; transaction.__inputValues = inputValues; diff --git a/test/services/address/encoding.unit.js b/test/services/address/encoding.unit.js index 6dfa6b4f..fa598846 100644 --- a/test/services/address/encoding.unit.js +++ b/test/services/address/encoding.unit.js @@ -15,8 +15,8 @@ describe('Address service encoding', function() { var prefix0 = new Buffer('00', 'hex'); var prefix1 = new Buffer('01', 'hex'); var ts = Math.floor(new Date('2017-02-28').getTime() / 1000); - var tsBuf = new Buffer(8); - tsBuf.writeDoubleBE(ts); + var tsBuf = new Buffer(4); + tsBuf.writeUInt32BE(ts); addressSizeBuf.writeUInt8(address.length); var addressIndexKeyBuf = Buffer.concat([ servicePrefix, diff --git a/test/services/header/encoding.unit.js b/test/services/header/encoding.unit.js new file mode 100644 index 00000000..3c76671f --- /dev/null +++ b/test/services/header/encoding.unit.js @@ -0,0 +1,70 @@ +'use strict'; + +var should = require('chai').should(); + +var Encoding = require('../../../lib/services/header/encoding'); + +describe('Header service encoding', function() { + + var servicePrefix = new Buffer('0000', 'hex'); + var encoding = new Encoding(servicePrefix); + var hash = '91b58f19b6eecba94ed0f6e463e8e334ec0bcda7880e2985c82a8f32e4d03add'; + var header = { + prevHash: '91b58f19b6eecba94ed0f6e463e8e334ec0bcda7880e2985c82a8f32e4d03ade', + version: 0x2000012, + merkleRoot: '91b58f19b6eecba94ed0f6e463e8e334ec0bcda7880e2985c82a8f32e4d03adf', + timestamp: 1E9, + bits: 400000, + nonce: 123456, + height: 123 + }; + var versionBuf = new Buffer(4); + var prevHash = new Buffer(header.prevHash, 'hex'); + var merkleRoot = new Buffer(header.merkleRoot, 'hex'); + var tsBuf = new Buffer(4); + var bitsBuf = new Buffer(4); + var nonceBuf = new Buffer(4); + var heightBuf = new Buffer(4); + + it('should encode header key' , function() { + var hashBuf = new Buffer(hash, 'hex'); + encoding.encodeHeaderKey(hash).should.deep.equal(Buffer.concat([servicePrefix, hashBuf])); + }); + + it('should decode header key', function() { + var hashBuf = new Buffer(hash, 'hex'); + encoding.decodeHeaderKey(Buffer.concat([servicePrefix, hashBuf])) + .should.equal(hash); + }); + + it('should encode header value', function() { + versionBuf.writeInt32BE(header.version); // signed + tsBuf.writeUInt32BE(header.timestamp); + bitsBuf.writeUInt32BE(header.bits); + nonceBuf.writeUInt32BE(header.nonce); + heightBuf.writeUInt32BE(header.height); + encoding.encodeHeaderValue(header).should.deep.equal(Buffer.concat([ + versionBuf, + prevHash, + merkleRoot, + tsBuf, + bitsBuf, + nonceBuf, + heightBuf + ])); + + }); + + it('should decode header value', function() { + encoding.decodeHeaderValue(Buffer.concat([ + versionBuf, + prevHash, + merkleRoot, + tsBuf, + bitsBuf, + nonceBuf, + heightBuf + ])).should.deep.equal(header); + }); +}); + diff --git a/test/services/header/index.unit.js b/test/services/header/index.unit.js new file mode 100644 index 00000000..d3561ee4 --- /dev/null +++ b/test/services/header/index.unit.js @@ -0,0 +1,137 @@ +'use strict'; + +var sinon = require('sinon'); +var HeaderService = require('../../../lib/services/header'); +var Tx = require('bcoin').tx; +var expect = require('chai').expect; +var Encoding = require('../../../lib/services/header/encoding'); +var utils = require('../../../lib/utils'); +var EventEmitter = require('events').EventEmitter; + +describe('Header Service', function() { + + var headerService; + var sandbox; + beforeEach(function() { + sandbox = sinon.sandbox.create(); + headerService = new HeaderService({ + node: { + getNetworkName: function() { return 'regtest'; }, + services: [] + } + }); + headerService._encoding = new Encoding(new Buffer('0000', 'hex')); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('#start', function() { + + + it('should get prefix for database', function(done) { + + var getServiceTip = sandbox.stub().callsArgWith(1, null, { height: 123, hash: 'a' }); + var startSubs = sandbox.stub(headerService, '_startSubscriptions'); + var setListeners = sandbox.stub(headerService, '_setListeners'); + var getPrefix = sandbox.stub().callsArgWith(1, null, new Buffer('ffee', 'hex')); + + headerService._db = { getPrefix: getPrefix, getServiceTip: getServiceTip }; + + headerService.start(function() { + expect(startSubs.calledOnce).to.be.true; + expect(setListeners.calledOnce).to.be.true; + expect(headerService._tip).to.be.deep.equal({ height: 123, hash: 'a' }); + expect(headerService._encoding).to.be.instanceOf(Encoding); + done(); + }); + + }); + + }); + + describe('#stop', function() { + it('should stop the service', function(done) { + headerService.stop(function() { + done(); + }); + }); + }); + + describe('#getAllHeaders', function() { + it('should get all the headers', function(done) { + + var stream = new EventEmitter(); + var createReadStream = sandbox.stub().returns(stream); + var hash = sandbox.stub().returns('a'); + var header = sandbox.stub().returns({}); + var hashKey = sandbox.stub(); + var headerVal = sandbox.stub(); + + headerService._db = { createReadStream: createReadStream }; + headerService._encoding = { decodeHeaderKey: hash, decodeHeaderValue: header, encodeHeaderKey: hashKey, encodeHeaderValue: headerVal }; + + headerService.getAllHeaders(function(err, headers) { + + if (err) { + return callback(err); + } + + expect(headers).to.be.deep.equal([ { a: {} } ]); + done(); + + }); + + stream.emit('data', { key: 'a', value: 'a' }); + stream.emit('end'); + }); + }); + + describe('#_startSync', function() { + + it('should start the sync process', function() { + headerService._bestHeight = 123; + headerService._tip = { height: 121, hash: 'a' }; + var sync = sandbox.stub(headerService, '_sync'); + headerService._startSync(); + expect(sync.calledOnce).to.be.true; + expect(headerService._numNeeded).to.equal(2); + }); + + }); + + describe('#_sync', function() { + it('should sync header', function() { + headerService._p2pHeaderCallsNeeded = 10; + headerService._numNeeded = 1000; + headerService._tip = { height: 121, hash: 'a' }; + var getHeaders = sandbox.stub(); + headerService._p2p = { getHeaders: getHeaders }; + headerService._sync(); + expect(getHeaders.calledOnce).to.be.true; + expect(headerService._p2pHeaderCallsNeeded).to.equal(9); + }); + }); + + describe('#_onHeaders', function() { + + it('should handle new headers received', function() { + var headers = [ { hash: 'b' } ]; + headerService._tip = { height: 123, hash: 'a' }; + headerService._bestHeight = 123; + var getHeaderOps = sandbox.stub(headerService, '_getHeaderOperations').returns([]); + var encodeTip = sandbox.stub().returns({ key: 'b', value: 'b' }); + var batch = sandbox.stub(); + var sync = sandbox.stub(headerService, '_sync'); + utils.encodeTip = encodeTip; + headerService._db = { batch: batch }; + headerService._onHeaders(headers); + expect(getHeaderOps.calledOnce).to.be.true; + expect(encodeTip.calledOnce).to.be.true; + expect(batch.calledOnce).to.be.true; + expect(sync.calledOnce).to.be.false; + }); + }); + +}); diff --git a/test/services/timestamp/encoding.unit.js b/test/services/timestamp/encoding.unit.js index bfe4a02d..b5c1ab49 100644 --- a/test/services/timestamp/encoding.unit.js +++ b/test/services/timestamp/encoding.unit.js @@ -11,8 +11,8 @@ describe('Timestamp service encoding', function() { var encoding = new Encoding(servicePrefix); var blockhash = '00000000000000000115b92b1ff4377441049bff75c6c48b626eb99e8b744297'; var timestamp = 5; - var timestampBuf = new Buffer(8); - timestampBuf.writeDoubleBE(timestamp); + var timestampBuf = new Buffer(4); + timestampBuf.writeUInt32BE(timestamp); it('should encode block timestamp key' , function() { encoding.encodeBlockTimestampKey(blockhash).should.deep.equal(Buffer.concat([servicePrefix, blockPrefix, new Buffer(blockhash, 'hex')])); @@ -47,5 +47,3 @@ describe('Timestamp service encoding', function() { encoding.decodeTimestampBlockValue(new Buffer(blockhash, 'hex')).should.equal(blockhash); }); }); - - diff --git a/test/services/transaction/encoding.unit.js b/test/services/transaction/encoding.unit.js index 1c1e5dc2..ef66dd78 100644 --- a/test/services/transaction/encoding.unit.js +++ b/test/services/transaction/encoding.unit.js @@ -1,7 +1,7 @@ 'use strict'; var should = require('chai').should(); -var bitcore = require('bitcore-lib'); +var Tx = require('bcoin').tx; var Encoding = require('../../../lib/services/transaction/encoding'); @@ -11,8 +11,8 @@ describe('Transaction service encoding', function() { var encoding = new Encoding(servicePrefix); var txid = '91b58f19b6eecba94ed0f6e463e8e334ec0bcda7880e2985c82a8f32e4d03add'; var txHex = '0100000001cc3ffe0638792c8b39328bb490caaefe2cf418f2ce0144956e0c22515f29724d010000006a473044022030ce9fa68d1a32abf0cd4adecf90fb998375b64fe887c6987278452b068ae74c022036a7d00d1c8af19e298e04f14294c807ebda51a20389ad751b4ff3c032cf8990012103acfcb348abb526526a9f63214639d79183871311c05b2eebc727adfdd016514fffffffff02f6ae7d04000000001976a9144455183e407ee4d3423858c8a3275918aedcd18e88aca99b9b08010000001976a9140beceae2c29bfde08d2b6d80b33067451c5887be88ac00000000'; - var tx = new bitcore.Transaction(txHex); - var txEncoded = Buffer.concat([new Buffer('00000002', 'hex'), new Buffer('3ff0000000000000', 'hex'), new Buffer('0002', 'hex'), new Buffer('40000000000000004008000000000000', 'hex'), tx.toBuffer()]); + var tx = Tx.fromRaw(txHex, 'hex'); + var txEncoded = Buffer.concat([new Buffer('00000002', 'hex'), new Buffer('00000001', 'hex'), new Buffer('0002', 'hex'), new Buffer('40000000000000004008000000000000', 'hex'), tx.toRaw()]); it('should encode transaction key' , function() { var txBuf = new Buffer(txid, 'hex');