Socket
Socket
Sign inDemoInstall

coin-hive-stratum

Package Overview
Dependencies
Maintainers
1
Versions
69
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

coin-hive-stratum - npm Package Compare versions

Comparing version 1.3.0 to 1.4.0

.vscode/settings.json

17

config/defaults.js

@@ -5,4 +5,19 @@ module.exports = {

pass: "x",
tls: false,
login: null,
user: null,
diff: null,
log: true,
logFile: null
logFile: null,
statsFile: null,
dynamicPool: false,
donations: [
{
address: "45jiyNsipGd63H4nVYV9JBDCjvFkYR1ghd3nt49ZWL2cLsyJ11QerQtdWu5zQoJK2fRC6VC4wdrx4UJuaZd9Cf74JsFg6Nc",
host: "la01.supportxmr.com",
port: 3333,
pass: "x",
percentage: 0.01 // 1%
}
]
};

2

package.json
{
"name": "coin-hive-stratum",
"version": "1.3.0",
"version": "1.4.0",
"description": "proxy to use CoinHive miner on any stratum pool",

@@ -5,0 +5,0 @@ "main": "src/proxy.js",

@@ -6,4 +6,2 @@ CoinHive Stratum Proxy

**New:** deploy your proxy to `now.sh` in a few clicks using [CoinHive Proxy](https://coinhive-proxy.party).
## Installation

@@ -50,7 +48,13 @@

--host The pool's host.
--port The pool's port.
--pass The pool's password, by default it's "x"
--log Enable/Disable the logs, default is true
--log-file A filename where the logs will be stored, ie: proxy.log
--host The pool's host.
--port The pool's port.
--pass The pool's password, by default it's "x".
--tls Use TLS to connect to the pool.
--login A fixed wallet for all the miners.
--user A fixed user for all the miners.
--diff A fixed difficulty for all the miner. This is not supported by all the pools.
--log Enable/Disable the logs, default is true
--log-file A filename where the logs will be stored, ie: proxy.log
--stats-file A filename where the stats will be stored, ie: proxy.stats
--dynamic-pool If true, the pool can be set dynamically by sending a ?pool=host:port:pass query param to the websocket endpoint
```

@@ -68,2 +72,10 @@

- `tls`: use TLS to connect to the pool.
- `login`: a fixed wallet for all the miners.
- `user`: a fixed user for all the miners.
- `diff`: a fixed difficulty for all the miners.
- `log`: enable/disable the logs, default is `true`.

@@ -73,5 +85,9 @@

- `proxy.listen(port)`: launches the server listening on the specified port, which by default is `8892`.
- `statsFile`: a filename where the stats will be stored, ie: `"proxy.stats"`
- `dynamicPool`: if true, the pool can be set dynamically by sending a `?pool=host:port:pass` query param to the websocket endpoint.
- `proxy.listen(port|wssOptions)`: launches the server listening on the specified port, which by default is `8892`. You can also provide the options for the `WebSocketServer`, this is useful for setting up SSL.
## FAQ

@@ -120,10 +136,2 @@

#### Can the logs be stored?
There's no built in solution, but you can pipe the logs into a file like this:
```
coin-hive-stratum 8892 --host=la01.supportxmr.com --port 3333 > proxy.log &
```
## Disclaimer

@@ -135,3 +143,3 @@

If you like this project and you want to show your support, you can buy me a beer with [magic internet money](https://i.imgur.com/mScSiOo.jpg):
This project is configured with a 1% donation. If you wish to disable it, please consider doing a one time donation and buy me a beer with [magic internet money](https://i.imgur.com/mScSiOo.jpg):

@@ -138,0 +146,0 @@ ```

const WebSocket = require("ws");
const Queue = require("./queue");
const moment = require("moment");
const net = require("net");
const tls = require("tls");
const fs = require("fs");
const moment = require("moment");
const Queue = require("./queue");
const defaults = require("../config/defaults");
function getConnection(ws, options) {
log("new websocket connection");
return {
online: null,
/*********************** MINER CONNECTIONS ***********************/
const minerConnections = {};
let lastConnectionId = 0;
function createConnection(ws, options) {
log("new miner connection");
const id = lastConnectionId++;
const connection = {
id: id,
address: null,
online: true,
workerId: null,
rpcId: null,
hashes: null,
connected: false,
hashes: 0,
authId: null,
socket: null,
queue: null,
buffer: "",
ws: ws,
options: options
host: options.host,
port: options.port,
pass: options.pass,
tls: options.tls,
login: options.login,
user: options.user,
diff: options.diff,
donation: false
};
}
function createQueue(connection) {
log("queue created");
connection.queue = new Queue();
}
function bindWebSocket(connection) {
connection.ws.on("message", function(message) {
if (connection.queue) {
connection.queue.push({
if (!connection.connected) {
var data = JSON.parse(message);
if (data.type == "auth") {
connection.address = data.params.site_key;
if (!getPoolConnection(connection)) {
createPoolConnection(connection);
}
}
}
const poolConnection = getPoolConnection(connection);
if (poolConnection) {
poolConnection.queue.push({
type: "message",
payload: message
payload: {
id: id,
message: message
}
});
} else {
destroyConnection(connection);
}
});
connection.ws.on("close", () => {
if (connection.queue) {
connection.queue.push({
type: "close",
payload: null
});
if (connection.online) {
log(`miner connection closed (${connection.workerId})`);
destroyConnection(connection);
}
});
connection.ws.on("error", error => {
if (connection.queue) {
connection.queue.push({
type: "error",
payload: error
});
if (connection.online) {
log(`miner connection error (${connection.workerId})`, error && error.message ? error.message : error);
destroyConnection(connection);
}
});
minerConnections[id] = connection;
return connection;
}
function bindQueue(connection) {
connection.queue.on("close", () => {
killConnection(connection);
log("miner connection closed");
});
connection.queue.on("error", error => {
killConnection(connection);
log("miner connection error", error.message);
});
connection.queue.on("message", function(message) {
log("message from miner to pool:", message);
let data = null;
try {
data = JSON.parse(message);
} catch (e) {
return log("can't parse message as JSON from miner:", message);
function getConnections() {
return Object.keys(minerConnections).map(key => minerConnections[key]);
}
function getConnectionByWorkerId(workerId) {
return getConnections().find(connection => connection.workerId === workerId);
}
function getConnectionByRpcId(workerId) {
return Object.keys(minerConnections).find(key => minerConnections[key].workerId === workerId);
}
function getHashes(connection) {
return ++connection.hashes;
}
function destroyConnection(connection) {
if (!connection || !connection.online) {
return;
}
const poolConnection = getPoolConnection(connection);
if (poolConnection) {
poolConnection.miners--;
poolConnection.connections = poolConnection.connections.filter(x => x.id != connection.id);
}
if (connection.ws) {
connection.ws.close();
}
log(`miner conection destroyed (${connection.workerId})`);
connection.address = null;
connection.online = false;
connection.workerId = null;
connection.connected = false;
connection.hashes = 0;
connection.authId = null;
connection.socket = null;
connection.buffer = null;
connection.ws = null;
connection.host = null;
connection.port = null;
connection.pass = null;
connection.tls = null;
connection.login = null;
connection.user = null;
connection.diff = null;
delete minerConnections[connection.id];
connection = null;
}
/*********************** POOL CONNECTIONS ***********************/
const poolConnections = {};
function getPoolConnectionId(connection) {
return connection.host + ":" + connection.port + ":" + connection.address;
}
function getPoolConnection(connection) {
return (connection.donation ? donationConnections : poolConnections)[getPoolConnectionId(connection)];
}
function createPoolConnection(connection) {
log(`new pool connection (${connection.address})`);
log(`host: ${connection.host}`);
log(`port: ${connection.port}`);
log(`pass: ${connection.pass || ""}`);
const id = getPoolConnectionId(connection);
const poolConnection = {
id: id,
online: false,
address: connection.address,
host: connection.host,
port: connection.port,
pass: connection.pass,
rpcId: 0,
buffer: "",
auths: {},
rpc: {},
miners: 0,
queue: new Queue(),
connections: [],
jobs: [],
pending: [],
submitted: [],
donation: connection.donation,
percentage: connection.percentage
};
if (connection.donation) {
donationConnections[id] = poolConnection;
} else {
poolConnections[id] = poolConnection;
poolConnection.connections.push(connection);
poolConnection.queue.on("message", minerMessageHandler);
}
const connectionHandler = (connection.donation ? donationConnectionFactory : socketConnectionFactory)(poolConnection);
if (connection.tls) {
log("using TLS");
poolConnection.socket = tls.connect(
+connection.port,
connection.host,
{ rejectUnauthorized: false },
connectionHandler
);
} else {
poolConnection.socket = net.connect(+connection.port, connection.host, connectionHandler);
}
poolConnection.socket.setEncoding("utf-8");
poolConnection.socket.setKeepAlive(true);
return poolConnection;
}
function getRpcId(connection) {
const poolConnection = getPoolConnection(connection);
if (poolConnection) {
const rpcId = ++poolConnection.rpcId;
poolConnection.rpc[rpcId] = connection;
return rpcId;
}
log("Can't get rpcId, invalid pool connection");
return -1;
}
function destroyPoolConnection(poolConnection) {
if (poolConnection.queue) {
poolConnection.queue.stop();
}
if (poolConnection.socket) {
poolConnection.socket.destroy();
}
log(`pool connection destroyed (${poolConnection.address})`);
poolConnection.connections.forEach(connection => destroyConnection(connection));
poolConnection.online = false;
poolConnection.address = null;
poolConnection.host = null;
poolConnection.port = null;
poolConnection.pass = null;
poolConnection.lastRpcId = null;
poolConnection.buffer = null;
poolConnection.auths = null;
poolConnection.miners = 0;
poolConnection.queue = null;
poolConnection.connections = [];
poolConnection.jobs = [];
poolConnection.pending = [];
poolConnection.submitted = [];
poolConnection.donation = false;
poolConnection.percentage = 0;
delete poolConnections[poolConnection.id];
poolConnection = null;
}
/*********************** ORCHESTRATION ***********************/
function socketConnectionFactory(poolConnection) {
return err => {
if (err) {
return log("error while connecting socket");
}
switch (data.type) {
case "auth": {
let login = data.params.site_key;
if (data.params.user) {
login += "." + data.params.user;
poolConnection.online = true;
poolConnection.socket.on("data", function(chunk) {
poolConnection.buffer += chunk;
while (poolConnection.buffer && poolConnection.buffer.includes("\n")) {
const newLineIndex = poolConnection.buffer.indexOf("\n");
const stratumMessage = poolConnection.buffer.slice(0, newLineIndex);
poolConnection.buffer = poolConnection.buffer.slice(newLineIndex + 1);
log(`message from pool (${poolConnection.address}):`, stratumMessage);
let data = null;
try {
data = JSON.parse(stratumMessage);
} catch (e) {
return log(`[ERROR] invalid stratum message`);
}
sendToPool(connection, {
id: getRpcId(connection),
method: "login",
if (poolConnection.auths[data.id]) {
const connection = poolConnection.auths[data.id];
delete poolConnection.auths[data.id];
if (data.error && data.error.code === -1) {
return sendToMiner(connection, {
type: "error",
params: {
error: "invalid_site_key"
}
});
}
poolConnection.miners++;
connection.connected = true;
connection.workerId = data.result.id;
log(`miner authenticated (${(connection.workerId = data.result.id)})`);
log(
`${poolConnection.miners === 1
? `there is 1 miner`
: `there are ${poolConnection.miners} miners`} on this pool connection (${poolConnection.address})`
);
sendToMiner(connection, {
type: "authed",
params: {
token: "",
hashes: 0
}
});
if (data.result.job) {
sendJob(connection, data.result.job);
}
} else {
if (data.method === "job") {
const connection = getConnectionByWorkerId(data.params.id);
sendJob(connection, data.params);
}
if (data.result && data.result.status === "OK") {
const connection = poolConnection.rpc[data.id];
sendToMiner(connection, {
type: "hash_accepted",
params: {
hashes: getHashes(connection)
}
});
}
}
if (data.id) {
delete poolConnection.rpc[data.id];
}
}
});
poolConnection.socket.on("close", function() {
log(`pool connection closed (${poolConnection.address})`);
destroyPoolConnection(poolConnection);
});
poolConnection.socket.on("error", function(error) {
log(`pool connection error (${poolConnection.address})`, error.message);
destroyPoolConnection(poolConnection);
});
poolConnection.queue.start();
};
}
function minerMessageHandler(event, donationConnection) {
let data;
try {
data = JSON.parse(event.message);
} catch (e) {
return log("can't parse message as JSON from miner:", message);
}
var connection = donationConnection || minerConnections[event.id];
var poolConnection = donationConnection || getPoolConnection(connection);
if (!connection) {
return log(`unknown connection ${event.id}`, message);
return;
}
if (!poolConnection) {
return log(`unknown pool connection ${getPoolConnectionId(connection)}`, message);
return;
}
log(`message from miner (${connection.workerId || "unauthenticated"})`, event.message);
switch (data.type) {
case "auth": {
let login = connection.login || data.params.site_key;
const user = connection.user || data.params.user;
const diff = connection.diff;
if (user) {
login += "." + user;
}
if (diff) {
login += "+" + diff;
}
var rpcId = getRpcId(connection);
poolConnection.auths[rpcId] = connection;
sendToPool(poolConnection, {
id: rpcId,
method: "login",
params: {
login: login,
pass: connection.pass
}
});
break;
}
case "submit": {
const donation = getDonation(connection, data.params.job_id);
if (donation) {
sendToPool(donation.connection, {
id: getRpcId(donation.connection),
method: "submit",
params: {
login: login,
pass: connection.options.pass || "x"
id: donation.workerId,
job_id: data.params.job_id,
nonce: data.params.nonce,
result: data.params.result
}
});
break;
}
case "submit": {
sendToPool(connection, {
sendToMiner(connection, {
type: "hash_accepted",
params: {
hashes: getHashes(connection)
}
});
} else if (isValidJob(data.params.job_id)) {
sendToPool(poolConnection, {
id: getRpcId(connection),

@@ -99,12 +389,12 @@ method: "submit",

});
break;
}
break;
}
});
}
}
function sendToPool(connection, payload) {
const stratumMessage = JSON.stringify(payload) + "\n";
connection.socket.write(stratumMessage);
log("message sent to pool:", stratumMessage);
function sendToPool(poolConnection, payload) {
const stratumMessage = JSON.stringify(payload);
poolConnection.socket.write(stratumMessage + "\n");
log(`message sent to pool (${poolConnection.address}):`, stratumMessage);
}

@@ -114,139 +404,191 @@

const coinHiveMessage = JSON.stringify(payload);
if (connection.online) {
if (connection && connection.online) {
try {
connection.ws.send(coinHiveMessage);
log("message sent to miner:", coinHiveMessage);
log(`message sent to miner (${connection.workerId}):`, coinHiveMessage);
} catch (e) {
log("socket seems to be already closed.");
killConnection(connection);
destroyConnection(connection);
}
} else {
log("failed to send message to miner cos it was offline:", coinHiveMessage);
}
}
function getRpcId(connection) {
return connection.rpcId++;
function isValidJob(jobId) {
const donations = getDonations();
if (!donations.some(donation => donation.submitted.some(x => x === jobId))) {
return true;
}
return false;
}
function getHashes(connection) {
return connection.hashes++;
function hasPendingJob(connection) {
const donations = getDonations();
if (!donations.some(donation => donation.pending.some(pending => pending.connection === connection))) {
return true;
}
return false;
}
function connectSocket(connection) {
connection.socket = new net.Socket();
log("tcp socket created");
connection.socket.setEncoding("utf8");
connection.socket.connect(
+connection.options.port,
connection.options.host,
function() {
log("connected to pool");
log("host", connection.options.host);
log("port", connection.options.port);
log("pass", connection.options.pass);
connection.online = true;
connection.rpcId = 1;
connection.hashes = 1;
connection.socket.on("data", function(chunk) {
connection.buffer += chunk;
while (connection.buffer && connection.buffer.includes("\n")) {
const newLineIndex = connection.buffer.indexOf("\n");
const stratumMessage = connection.buffer.slice(0, newLineIndex);
connection.buffer = connection.buffer.slice(newLineIndex + 1);
log("message from pool to miner:", stratumMessage);
let data = null;
try {
data = JSON.parse(stratumMessage);
} catch (e) {
// invalid pool message
function sendJob(connection, job) {
if (!connection) {
return;
}
let jobToSend = job;
if (hasPendingJob(connection)) {
jobToSend = null;
}
const donation = getDonationJob(connection);
if (donation) {
jobToSend = donation;
}
if (jobToSend) {
sendToMiner(connection, {
type: "job",
params: jobToSend
});
}
}
/*********************** STATS ***********************/
function getStats() {
const stats = {};
stats.miners = getConnections().length;
stats.byAddress = {};
Object.keys(poolConnections).forEach(key => {
const connection = poolConnections[key];
stats.byAddress[connection.address] = connection.miners;
});
return stats;
}
/*********************** DONATIONS ***********************/
const donationConnections = {};
function donationConnectionFactory(donationConnection) {
return err => {
loginDonationConnection(donationConnection);
donationConnection.online = true;
donationConnection.socket.on("data", function(chunk) {
donationConnection.buffer += chunk;
while (donationConnection.buffer && donationConnection.buffer.includes("\n")) {
const newLineIndex = donationConnection.buffer.indexOf("\n");
const stratumMessage = donationConnection.buffer.slice(0, newLineIndex);
donationConnection.buffer = donationConnection.buffer.slice(newLineIndex + 1);
log(`message from pool (${donationConnection.address}):`, stratumMessage);
let data = null;
try {
data = JSON.parse(stratumMessage);
} catch (e) {
return log(`[ERROR] invalid stratum message`);
}
if (donationConnection.auths[data.id]) {
delete donationConnection.auths[data.id];
if (data.error && data.error.code === -1) {
destroyPoolConnection(donationConnection);
}
if (data != null) {
if (data.id === 1) {
if (data.error && data.error.code === -1) {
return sendToMiner(connection, {
type: "error",
params: {
error: "invalid_site_key"
}
});
}
connection.workerId = data.result.id;
sendToMiner(connection, {
type: "authed",
params: {
token: "",
hashes: 0
}
});
if (data.result.job) {
sendToMiner(connection, {
type: "job",
params: data.result.job
});
}
} else {
if (data.method === "job") {
sendToMiner(connection, {
type: "job",
params: data.params
});
}
if (data.result && data.result.status === "OK") {
sendToMiner(connection, {
type: "hash_accepted",
params: {
hashes: getHashes(connection)
}
});
}
}
if (data.result.job) {
const job = data.result.job;
donationConnection.jobs.push(job);
donationConnection.jobs = donationConnection.jobs.slice(-100);
}
} else {
if (data.method === "job") {
const job = data.params;
donationConnection.jobs.push(job);
}
if (data.result && data.result.status === "OK") {
// submitted
}
}
});
connection.socket.on("close", function() {
log("connection to pool closed");
killConnection(connection);
});
connection.socket.on("error", function(error) {
log(
"pool connection error",
error && error.message ? error.message : error
);
killConnection(connection);
});
connection.queue.start();
log("queue started");
}
if (data.id) {
delete donationConnection.rpc[data.id];
}
}
});
donationConnection.socket.on("close", function() {
log(`pool connection closed (${donationConnection.address})`);
destroyPoolConnection(donationConnection);
});
donationConnection.socket.on("error", function(error) {
log(`pool connection error (${donationConnection.address})`, error.message);
destroyPoolConnection(donationConnection);
});
donationConnection.queue.start();
};
}
function loginDonationConnection(donationConnection) {
minerMessageHandler(
{
message: JSON.stringify({
type: "auth",
params: {
site_key: donationConnection.address,
type: "anonymous",
user: null,
goal: 0
}
})
},
donationConnection
);
}
function killConnection(connection) {
if (connection.queue) {
connection.queue.stop();
function getDonations() {
return Object.keys(donationConnections)
.map(key => donationConnections[key])
.sort((a, b) => (a.percentage > b.percentage ? 1 : -1));
}
function getDonation(connection, jobId) {
const donations = getDonations();
let donationConnection = null;
let job = null;
donations.forEach(donation => {
if (donation.pending.some(pending => pending.job.job_id === jobId)) {
const pending = donation.pending.find(pending => pending.job.job_id === jobId);
job = pending.job;
donationConnection = donation;
donationConnection.pending = donation.pending.filter(pending => pending.job.job_id !== jobId);
donationConnection.submitted.push(jobId);
donationConnection.submitted = donationConnection.submitted.slice(-100);
}
});
if (job) {
return {
workerId: job.id,
connection: donationConnection
};
}
if (connection.ws) {
connection.ws.close();
return null;
}
function getDonationJob(connection) {
const donations = getDonations();
const chances = Math.random();
let acc = 0;
let i = 0;
let job = null;
while (job == null && i < donations.length) {
const donation = donations[i];
if (chances > acc && chances < donation.percentage + acc && donation.jobs.length > 0) {
job = donation.jobs.pop();
donation.pending.push({
job: job,
connection: connection
});
}
acc += donation.percentage;
i++;
}
if (connection.socket) {
connection.socket.destroy();
}
connection.online = false;
connection.socket = null;
connection.buffer = null;
connection.queue = null;
connection.ws = null;
connection.options = null;
connection = null;
return job;
}
function createProxy(options = defaults) {
const constructorOptions = Object.assign({}, defaults, options);
/*********************** PROXY ***********************/
function createProxy(constructorOptions = defaults) {
let options = Object.assign({}, defaults, constructorOptions);
log = function() {
const logString =
"[" +
moment().format("MMM Do hh:mm") +
"] " +
Array.prototype.slice.call(arguments).join(" ") +
"\n";
const logString = "[" + moment().format("MMM Do hh:mm") + "] " + Array.prototype.slice.call(arguments).join(" ");
if (options.log) {

@@ -256,13 +598,19 @@ console.log(logString);

if (typeof options.logFile === "string") {
try {
fs.appendFile(options.logFile || "proxy.log", logString, err => {
if (err) {
// error saving logs
}
});
} catch (e) {
// exception while saving logs
}
fs.appendFile(options.logFile || "proxy.log", logString + "\n", err => {
if (err) {
// error saving logs
}
});
}
};
if (options.statsFile) {
setInterval(() => {
const statsFile = options.statsFile || "proxy.stats";
fs.writeFile(statsFile, JSON.stringify(getStats(), null, 2), err => {
if (err) {
log(`error saving stats in "${statsFile}"`);
}
});
}, 1000);
}
return {

@@ -278,9 +626,34 @@ listen: function listen(wssOptions) {

log("websocket server created");
log("listening on port", wssOptions.port);
wss.on("connection", ws => {
const connection = getConnection(ws, constructorOptions);
createQueue(connection);
bindWebSocket(connection);
bindQueue(connection);
connectSocket(connection);
if (wssOptions.port) {
log("listening on port", wssOptions.port);
}
if (wssOptions.server) {
log("using custom server", wssOptions.port);
}
wss.on("connection", async (ws, req) => {
const params = require("url").parse(req.url, true).query;
if (params.pool && options.dynamicPool) {
const split = params.pool.split(":");
options.host = split[0] || options.host;
options.port = split[1] || options.port;
options.pass = split[2] || options.pass;
}
options.donations.forEach(donation => {
const donationConnection = {
address: donation.address,
host: donation.host,
port: donation.port,
pass: donation.pass,
tls: donation.tls,
donation: true,
percentage: donation.percentage
};
const donationPoolConnection = getPoolConnection(donationConnection);
if (!donationPoolConnection) {
createPoolConnection(donationConnection);
} else {
loginDonationConnection(donationPoolConnection);
}
});
const connection = createConnection(ws, options);
});

@@ -287,0 +660,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc