From 861a80d6783a88f4d932a1bafd04a5f9942d0897 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Wed, 19 Jul 2017 10:35:25 -0400 Subject: [PATCH] wip --- lib/services/address/encoding.js | 14 +- lib/services/header/encoding.js | 50 +++++++ lib/services/header/index.js | 192 +++++++++++++++++++++++++++ lib/services/timestamp/encoding.js | 12 +- lib/services/transaction/encoding.js | 10 +- 5 files changed, 260 insertions(+), 18 deletions(-) create mode 100644 lib/services/header/encoding.js create mode 100644 lib/services/header/index.js diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js index b8dd06c5..6cf5bb79 100644 --- a/lib/services/address/encoding.js +++ b/lib/services/address/encoding.js @@ -31,8 +31,8 @@ Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index inputBuffer.writeUInt8(input || 0); buffers.push(inputBuffer); - var timestampBuffer = new Buffer(8); - timestampBuffer.writeDoubleBE(timestamp || 0) + var timestampBuffer = new Buffer(4); + timestampBuffer.writeUInt32BE(timestamp || 0); buffers.push(timestampBuffer); return Buffer.concat(buffers); @@ -46,7 +46,7 @@ Encoding.prototype.decodeAddressIndexKey = function(buffer) { var txid = buffer.slice(addressSize + 8, addressSize + 40).toString('hex'); var index = buffer.readUInt32BE(addressSize + 40); var input = buffer.readUInt8(addressSize + 44); - var timestamp = buffer.readDoubleBE(addressSize + 45); + var timestamp = buffer.readUInt32BE(addressSize + 45); return { address: address, height: height, @@ -57,7 +57,7 @@ Encoding.prototype.decodeAddressIndexKey = function(buffer) { }; }; -Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex, timestamp) { +Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { var prefix = new Buffer('01', 'hex'); var buffers = [this.servicePrefix, prefix]; @@ -97,15 +97,15 @@ Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, timestamp, var satoshisBuffer = new Buffer(8); satoshisBuffer.writeDoubleBE(satoshis); var timestampBuffer = new Buffer(8); - timestampBuffer.writeDoubleBE(timestamp || 0) + 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(12); - var scriptBuffer = buffer.slice(20); + var timestamp = buffer.readDoubleBE(8); + var scriptBuffer = buffer.slice(12); return { height: height, satoshis: satoshis, diff --git a/lib/services/header/encoding.js b/lib/services/header/encoding.js new file mode 100644 index 00000000..67a4a577 --- /dev/null +++ b/lib/services/header/encoding.js @@ -0,0 +1,50 @@ +'use strict'; + + +function Encoding(servicePrefix) { + this._servicePrefix = servicePrefix; +} + + +// ---- hash --> height +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'); +}; + +Encoding.prototype.encodeHeaderValue = function(header) { + var versionBuf = new Buffer(4); + versionBuf.writeInt32BE(header.version); + var prevHash = new Buffer(header.prevHash, 'hex'); + var merkleRoot = new Buffer(header.merkleRoot, 'hex'); + var tsBuf = new Buffer(4); + tsBuf.writeUInt32BE(header.timestamp); + var bitsBuf = new Buffer(4); + bitsBuf.writeUInt32BE(header.bits); + var nonceBuf = new Buffer(4); + nonceBuf.writeUInt32BE(header.nonce); + return Buffer.concat([ versionBuf, prevHash, merkleRoot, tsBuf, bitsBuf, nonceBuf ]); +}; + +Encoding.prototype.decodeHeaderValue = function(buffer) { + var version = buffer.readInt32BE(); + var prevHash = buffer.slice(4, 36).toString('hex'); + var merkleRoot = buffer.slice(36, 68).toString('hex'); + var ts = buffer.readUInt32BE(68); + var bits = buffer.readUInt32BE(72); + var nonce = buffer.readUInt32BE(76); + return { + version: version, + prevHash: prevHash, + merkleRoot: merkleRoot, + timestakmp: ts, + bits: bits, + nonce: nonce + }; +}; + +module.exports = Encoding; + diff --git a/lib/services/header/index.js b/lib/services/header/index.js new file mode 100644 index 00000000..578cabc5 --- /dev/null +++ b/lib/services/header/index.js @@ -0,0 +1,192 @@ +'use strict'; + +var BaseService = require('../../service'); +var inherits = require('util').inherits; +var Encoding = require('./encoding'); +var index = require('../../'); +var log = index.log; +var utils = require('../../utils'); +var constants = require('../../constants'); + +var HeaderService = function(options) { + + BaseService.call(this, options); + + this._tip = null; + this._p2p = this.node.services.p2p; + this._db = this.node.services.db; +}; + +inherits(HeaderService, BaseService); + +HeaderService.dependencies = [ 'p2p', 'db' ]; + + +// --- public prototype functions +HeaderService.prototype.getAPIMethods = function() { + + var methods = [ + ['getAllHeaders', this, this.getHeaders, 0] + ]; + + return methods; + +}; + +HeaderService.prototype.start = function(callback) { + + var self = this; + + self._db.getPrefix(self.name, function(err, prefix) { + + if(err) { + return callback(err); + } + + self._db.getServiceTip(self.name, function(err, tip) { + + if(err) { + return callback(err); + } + + self._tip = tip; + self._encoding = new Encoding(prefix); + self._setListeners(); + self._startSubscriptions(); + callback(); + + }); + }); +}; + +HeaderService.prototype.stop = function(callback) { + callback(); +}; + +HeaderService.prototype._startSubscriptions = function() { + + if (this._subscribed) { + return; + } + + this._subscribed = true; + if (!this._bus) { + this._bus = this.node.openBus({remoteAddress: 'localhost'}); + } + + this._bus.on('p2p/headers', this._onHeaders.bind(this)); + this._bus.subscribe('p2p/headers'); +}; + +HeaderService.prototype._onHeaders = function(headers) { + + if (!headers || headers.length < 1) { + return; + } + + this._tip.hash = headers[headers.length - 1].hash; + this._tip.height = this._tip.height + headers.length; + + var operations = this._getHeaderOperations(headers); + + var tipOps = utils.encodeTip(this._tip, this.name); + + operations.push({ + type: 'put', + key: tipOps.key, + value: tipOps.value + }); + + this._db.batch(operations); + + if (this._tip.height >= this._bestHeight) { + log.info('Header download complete.'); + this.emit('headers'); + return; + } + + this._sync(); + +}; + +HeaderService.prototype._getHeaderOperations = function(headers) { + + var self = this; + return headers.map(function(header) { + return { + type: 'put', + key: self._encoding.encodeHeaderKey(header.hash), + value: self._encoding.encodeHeaderValue(header) + }; + }); + +}; + +HeaderService.prototype._setListeners = function() { + + this._p2p.once('bestHeight', this._onBestHeight.bind(this)); + +}; + +HeaderService.prototype._onBestHeight = function(height) { + this._bestHeight = height; + this._startSync(); +}; + +HeaderService.prototype._startSync = function() { + + this._numNeeded = this._bestHeight - this._tip.height; + if (this._numNeeded <= 0) { + return; + } + + log.info('Gathering: ' + this._numNeeded + ' ' + 'header(s) from the peer-to-peer network.'); + + this._p2pHeaderCallsNeeded = Math.ceil(this._numNeeded / 500); + this._sync(); + +}; + +HeaderService.prototype._sync = function() { + + if (--this._p2pHeaderCallsNeeded > 0) { + + 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) { + + var self = this; + var results = []; + var start = self._encoding.encodeHeaderKey(0); + var stream = self._db.createReadStream(criteria); + + var streamErr; + stream.on('error', function(error) { + streamErr = error; + }); + + stream.on('data', function(data) { + results.push({ + hash: self.__encoding.decodeHeaderKey(data.key), + header: self._encoding.decodeHeaderValue(data.value) + }); + }); + + stream.on('end', function() { + if (streamErr) { + return streamErr; + } + callback(null, results); + }); + +}; + +module.exports = HeaderService; + diff --git a/lib/services/timestamp/encoding.js b/lib/services/timestamp/encoding.js index 6b0edfb6..e34ffe3f 100644 --- a/lib/services/timestamp/encoding.js +++ b/lib/services/timestamp/encoding.js @@ -16,25 +16,25 @@ Encoding.prototype.decodeBlockTimestampKey = function(buffer) { }; Encoding.prototype.encodeBlockTimestampValue = function(timestamp) { - var timestampBuffer = new Buffer(new Array(8)); - timestampBuffer.writeDoubleBE(timestamp); + var timestampBuffer = new Buffer(4); + timestampBuffer.writeUInt32BE(timestamp); return timestampBuffer; }; Encoding.prototype.decodeBlockTimestampValue = function(buffer) { - return buffer.readDoubleBE(); + return buffer.readUInt32BE(); }; // ---- timestamp -> block hash Encoding.prototype.encodeTimestampBlockKey = function(timestamp) { - var timestampBuffer = new Buffer(new Array(8)); - timestampBuffer.writeDoubleBE(timestamp); + var timestampBuffer = new Buffer(4); + timestampBuffer.writeUInt32BE(timestamp); return Buffer.concat([this._servicePrefix, this._timestampPrefix, timestampBuffer]); }; Encoding.prototype.decodeTimestampBlockKey = function(buffer) { - return buffer.readDoubleBE(3); + return buffer.readUInt32BE(3); }; Encoding.prototype.encodeTimestampBlockValue = function(hash) { diff --git a/lib/services/transaction/encoding.js b/lib/services/transaction/encoding.js index c500a31d..20ff0b72 100644 --- a/lib/services/transaction/encoding.js +++ b/lib/services/transaction/encoding.js @@ -18,8 +18,8 @@ Encoding.prototype.encodeTransactionValue = function(transaction) { var heightBuffer = new Buffer(4); heightBuffer.writeUInt32BE(transaction.__height); - var timestampBuffer = new Buffer(8); - timestampBuffer.writeDoubleBE(transaction.__timestamp); + var timestampBuffer = new Buffer(4); + timestampBuffer.writeUInt32BE(transaction.__timestamp); var inputValues = transaction.__inputValues; var inputValuesBuffer = new Buffer(8 * inputValues.length); @@ -31,14 +31,14 @@ Encoding.prototype.encodeTransactionValue = function(transaction) { inputValuesLengthBuffer.writeUInt16BE(inputValues.length); return new Buffer.concat([heightBuffer, timestampBuffer, - inputValuesLengthBuffer, inputValuesBuffer, transaction.toBuffer()]); + inputValuesLengthBuffer, inputValuesBuffer, transaction.toRaw()]); }; Encoding.prototype.decodeTransactionValue = function(buffer) { var height = buffer.readUInt32BE(); - var timestamp = buffer.readDoubleBE(4); + var timestamp = buffer.readUInt32BE(4); - var inputValuesLength = buffer.readUInt16BE(12); + var inputValuesLength = buffer.readUInt16BE(8); var inputValues = []; for(var i = 0; i < inputValuesLength; i++) { inputValues.push(buffer.readDoubleBE(i * 8 + 14));