coin-hive-stratum
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -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% | ||
} | ||
] | ||
}; |
{ | ||
"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 @@ ``` |
767
src/proxy.js
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28110
683
147
3