UNPKG

ibctminer

Version:

```js const IntMiner = require('./src'); const Debug = require('./src/log')(); const fs = require('fs'); const COMP = '[SIPC]';

335 lines (309 loc) 10.2 kB
module.exports = function (classes) { 'use strict'; var q = classes.q, _ = classes.lodash, uuid = classes.uuid; var Debug = classes.debug; /** * @param {Socket} socket * @param {Boolean} isServer * @constructor */ var Client = classes.Base.$define('Client', { construct: function (socket, isServer) { var self = this; self.$super(); // although BFGMiner starts at id 0, we start at 1, because it makes sense self.currentId = 0; // our TCP_NODELAY and KeepAlive'd socket self.socket = self.$class.createSocket(socket); self.authorized = false; self.byServer = isServer === undefined ? false : isServer; self.subscription = ''; self.name = ''; self.pending = {}; // Uniquely identify this socket, regardless of clusters or inter-processes, // it's unique. self.id = uuid.v4(); // Last activity is now! self.setLastActivity(); self.socket.on('end', function clientSocketEnd() { if (self.emit) { self.emit('end', self); } }); self.socket.on('error', function clientSocketError(e) { if (self.emit) { self.emit('error', self, e); } }); // set 60s timeout self.socket.setTimeout(120000, function() { if (self.emit) { self.emit('timeout', self, 'Socket请求超时'); } }); self.socket.on('drain', function clientSocketDrain() { if (self.emit) { self.emit('drain', self); } }); if (isServer !== true) { self.socket.on('data', function (data) { self.handleData(self.socket, data); // classes.curry.predefine(self.handleData, [self], self) }); } }, /** * Keep track of idle sockets, update the last activity * * @param {Number} [time] Unix Timestamp * * @return {this} */ setLastActivity: function (time) { this.lastActivity = _.isNumber(time) ? time : Date.now(); return this; }, /** * Either emit an event, or fulfill a pending request by id */ fullfill: function (command) { var self = this, method; //Debug.IbctLogDbg('command', JSON.stringify(command)); //Debug.IbctLogDbg(JSON.stringify(self.pending)); if (_.has(command, 'method') && command.method === 'mining.notify') { // set command id = 0 command['id'] = 0; self.emit('mining', command, self, 'notify'); } else if (_.has(command, 'id') && (_.isNull(command['id']))) { // null id, it's a broadcast most likely, we need to check the last command if (_.has(command, 'method')) { method = command.method.split('mining.'); if (classes.Server.commands[method[1]].broadcast === true || method[1] === 'error') { command['method'] = method[1]; self.emit('mining', command, self, 'broadcast'); } else { self.emit('mining', command, self, 'broadcast'); //throw new Error('Server sent unknown command: ' + JSON.stringify(command)); } } else { throw new Error('Broadcast without a method: ' + JSON.stringify(command)); } } else if (_.has(command, 'id') && _.has(self.pending, command['id'])) { // need to resolve pending requests by id self.$class.debug('Received pending request response: ' + command + ' ' + self.pending); var type = self.pending[command['id']]; switch (type) { case 'mining.subscribe': self.subscription = command['result']; break; case 'mining.authorize': self.authorized = !!command['result'] || (command['error'] === null || command['error'] === undefined); break; } self.emit('mining', command, self, 'result', self.pending[command['id']]); if (type === self.pending[command['id']]) { delete self.pending[command['id']]; } } else if (_.has(command, 'id') && _.has(command, 'result')) { // regular result that wasnt issued by this socket self.emit('mining', command, self, 'result'); } else if (_.has(command, 'error') && (!_.isNull(command['error']) && !_.isEmpty(command['error']))) { // we have an error, we need to act on that, regardless of other members in the command received throw new Error(command); } else if (_.has(command, 'method') && command.method === 'mining.set_difficulty') { self.emit('mining', command, self, 'broadcast'); } else { throw new Error('No suitable command was issued from the server'); } }, /** * Get the current socket IP address * * @returns {{port: Number, address: String, family: String}} */ address: function () { return this.socket.address(); }, /** * This method is exposed just for testing purposes * * @param {Socket} socket * @param {Buffer} buffer * @private */ handleData: function (socket, buffer) { var c = classes.Server.getStratumCommands(buffer), cmds = c.cmds; classes.Server.processCommands.call(this, this, cmds); }, /** * Destroy the socket and unattach any listeners */ destroy: function () { this.removeAllListeners(); this.socket.destroy(); this.$destroy(); }, /** * Connect to somewhere * * @param {Object} opts Where to connect * @returns {Q.promise} */ connect: function (opts) { var d = q.defer(), self = this; this.socket.connect(opts, function clientSocketConnect() { d.resolve(self); }); return d.promise; }, /** * Don't use this functions directly, they are called from the server side, * it's not a client side command, but an answer * * @return {Q.promise} * @private */ set_difficulty: function (args) { return classes.Server.commands.set_difficulty.apply(this, [null].concat(args)); }, /** * Don't use this functions directly, they are called from the server side * it's not a client side command, but an answer * * @return {Q.promise} * @private */ notify: function (args) { return classes.Server.commands.notify.apply(this, [null].concat(args)); }, /** * Send HTTP header * * @param {String} hostname * @param {Number} port * * @return {Q.promise} */ stratumHttpHeader: function (hostname, port) { var result = '{"error": null, "result": false, "id": 0}', d = q.defer(), header = [ 'HTTP/1.1 200 OK', 'X-Stratum: stratum+tcp://' + hostname + ':' + port, 'Connection: Close', 'Content-Length: ' + (result.length + 1), '', '', result ]; this.$class.debug('Sending Stratum HTTP header'); this.socket.write(header.join('\n'), classes.curry.wrap(d.resolve, d)); return d.promise; }, /** * Subscribe to the pool * * @param {String} [UA] Send the User-Agent * @returns {Q.promise} */ stratumSubscribe: function (UA) { this.name = UA; return this.stratumSend({ 'method': 'mining.subscribe', 'id': this.currentId++, 'params': typeof UA !== 'undefined' ? [UA] : [] }, true); }, /** * Asks for authorization * * @param {String} user * @param {String} pass * @returns {Q.promise} */ stratumAuthorize: function (user, pass) { return this.stratumSend({ 'method': 'mining.authorize', 'id': this.currentId++, 'params': [user, pass] }, true); }, /** * Sends a share * * @param {String} worker * @param {String} job_id * @param {String} extranonce2 * @param {String} ntime * @param {String} nonce * @returns {Q.promise} */ stratumSubmit: function (worker, job_id, extranonce2, ntime, nonce) { this.setLastActivity(); return this.stratumSend({ 'method': 'mining.submit', 'id': this.currentId++, 'params': [worker, job_id, extranonce2, ntime, nonce] }); }, /** * Send Stratum command * * @param {Object} data * @param {Boolean} bypass Bypass unauthorized * @param {String} name Call from the server * * @returns {Q.promise} */ stratumSend: function (data, bypass, name) { if (this.authorized === true || bypass === true) { this.pending[data.id || this.currentId++] = name || data.method; return this.send(JSON.stringify(data) + '\n'); } else { var error = this.$class.debug(classes.Server.errors.UNAUTHORIZED_WORKER); this.emit('mining.error', error); return classes.Server.rejected(error); } }, /** * Send raw data to the server * * @param {*} data * @returns {Q.promise} */ send: function (data) { this.$class.debug('(' + this.id + ') Sent command ' + data); //Debug.IbctLogDbg('(' + this.id + ') Sent command ' + data); var d = q.defer(), self = this; try { self.socket.write(data, function clientSocketWrite(err) { if (err) { d.reject(err); } else { d.resolve(self); } }); } catch (err) { d.reject(err); } return d.promise; } }, { createSocket: function (socket) { if (!socket) { socket = new classes.net.Socket(); } socket.setNoDelay(true); socket.setKeepAlive(true, 120); return socket; } }); return Client; };