Complete sources for a monero webminer.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
15 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. // The MIT License (MIT)
  2. // Copyright (c) 2018-2019 - the webminerpool developer
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of
  4. // this software and associated documentation files (the "Software"), to deal in
  5. // the Software without restriction, including without limitation the rights to
  6. // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  7. // the Software, and to permit persons to whom the Software is furnished to do so,
  8. // subject to the following conditions:
  9. // The above copyright notice and this permission notice shall be included in all
  10. // copies or substantial portions of the Software.
  11. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  13. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  15. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  16. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  17. using System;
  18. using System.Collections.Concurrent;
  19. using System.Collections.Generic;
  20. using System.Net.Sockets;
  21. using System.Text;
  22. using System.Text.RegularExpressions;
  23. using System.Threading.Tasks;
  24. using TinyJson;
  25. using JsonData = System.Collections.Generic.Dictionary<string, object>;
  26. using Fleck;
  27. namespace Server
  28. {
  29. public class PoolConnection
  30. {
  31. public TcpClient TcpClient;
  32. public byte[] ReceiveBuffer;
  33. public string Login;
  34. public string Password;
  35. public int Port;
  36. public string Url;
  37. public bool Closed;
  38. public string PoolId;
  39. public string Credentials;
  40. public long Hashes = 0;
  41. public Client LastSender;
  42. public JsonData LastJob;
  43. public DateTime LastInteraction = DateTime.Now;
  44. public CcHashset<string> LastSolved;
  45. public string DefaultAlgorithm = "cn";
  46. public int DefaultVariant = -1;
  47. public CcHashset<Client> WebClients = new CcHashset<Client>();
  48. public void Send(Client client, string msg)
  49. {
  50. try
  51. {
  52. Byte[] bytesSent = Encoding.ASCII.GetBytes(msg);
  53. TcpClient.GetStream().BeginWrite(bytesSent, 0, bytesSent.Length, SendCallback, null);
  54. this.LastSender = client;
  55. }
  56. catch { }
  57. }
  58. private void SendCallback(IAsyncResult result)
  59. {
  60. if (!TcpClient.Connected) return;
  61. try
  62. {
  63. NetworkStream networkStream = TcpClient.GetStream();
  64. networkStream.EndWrite(result);
  65. }
  66. catch { }
  67. }
  68. }
  69. public class PoolConnectionFactory
  70. {
  71. public delegate void ReceiveJobDelegate(Client client, JsonData json, CcHashset<string> hashset);
  72. public delegate void ReceiveErrorDelegate(Client client, JsonData json);
  73. public delegate void DisconnectedDelegate(Client client, string reason);
  74. private static ReceiveErrorDelegate ReceiveError;
  75. private static ReceiveJobDelegate ReceiveJob;
  76. private static DisconnectedDelegate Disconnect;
  77. public static CcDictionary<string, PoolConnection> Connections = new CcDictionary<string, PoolConnection>();
  78. private static bool VerifyJob(JsonData data)
  79. {
  80. if (data == null) return false;
  81. if (!data.ContainsKey("job_id")) return false;
  82. if (!data.ContainsKey("blob")) return false;
  83. if (!data.ContainsKey("target")) return false;
  84. string blob = data["blob"].GetString();
  85. string target = data["target"].GetString();
  86. if (blob.Length < 152 || blob.Length > 180) return false;
  87. if (target.Length != 8) return false;
  88. if (!Regex.IsMatch(blob, MainClass.RegexIsHex)) return false;
  89. if (!Regex.IsMatch(target, MainClass.RegexIsHex)) return false;
  90. return true;
  91. }
  92. private static void ReceiveCallback(IAsyncResult result)
  93. {
  94. PoolConnection mypc = result.AsyncState as PoolConnection;
  95. TcpClient client = mypc.TcpClient;
  96. if (mypc.Closed || !client.Connected) return;
  97. NetworkStream networkStream;
  98. try { networkStream = client.GetStream(); } catch { return; }
  99. int bytesread = 0;
  100. try { bytesread = networkStream.EndRead(result); } catch { return; }
  101. string json = string.Empty;
  102. try
  103. {
  104. if (bytesread == 0) // disconnected
  105. {
  106. // slow that down a bit to avoid negative feedback loop
  107. Task.Run(async delegate
  108. {
  109. await Task.Delay(TimeSpan.FromSeconds(4));
  110. List<Client> cllist = new List<Client>(mypc.WebClients.Values);
  111. foreach (Client ev in cllist) Disconnect(ev, "lost pool connection.");
  112. });
  113. return;
  114. }
  115. json = Encoding.ASCII.GetString(mypc.ReceiveBuffer, 0, bytesread);
  116. networkStream.BeginRead(mypc.ReceiveBuffer, 0, mypc.ReceiveBuffer.Length, new AsyncCallback(ReceiveCallback), mypc);
  117. }
  118. catch { return; }
  119. if (bytesread == 0 || string.IsNullOrEmpty(json)) return; //?!
  120. var msg = json.FromJson<JsonData>();
  121. if (msg == null) return;
  122. if (string.IsNullOrEmpty(mypc.PoolId))
  123. {
  124. // this "protocol" is strange
  125. if (!msg.ContainsKey("result"))
  126. {
  127. string additionalInfo = "none";
  128. // try to get the error
  129. if (msg.ContainsKey("error"))
  130. {
  131. msg = msg["error"] as JsonData;
  132. if (msg != null && msg.ContainsKey("message"))
  133. additionalInfo = msg["message"].GetString();
  134. }
  135. List<Client> cllist = new List<Client>(mypc.WebClients.Values);
  136. foreach (Client ev in cllist)
  137. Disconnect(ev, "can not connect. additional information: " + additionalInfo);
  138. return;
  139. }
  140. msg = msg["result"] as JsonData;
  141. if (msg == null)
  142. return;
  143. if (!msg.ContainsKey("id"))
  144. return;
  145. if (!msg.ContainsKey("job"))
  146. return;
  147. mypc.PoolId = msg["id"].GetString();
  148. var lastjob = msg["job"] as JsonData;
  149. if (!VerifyJob(lastjob))
  150. {
  151. CConsole.ColorWarning(() =>
  152. Console.WriteLine("Failed to verify job: {0}", json));
  153. return;
  154. }
  155. // extended stratum
  156. if (!lastjob.ContainsKey("variant")) lastjob.Add("variant", mypc.DefaultVariant);
  157. if (!lastjob.ContainsKey("algo")) lastjob.Add("algo", mypc.DefaultAlgorithm);
  158. AlgorithmHelper.NormalizeAlgorithmAndVariant(lastjob);
  159. mypc.LastJob = lastjob;
  160. mypc.LastInteraction = DateTime.Now;
  161. mypc.LastSolved = new CcHashset<string>();
  162. List<Client> cllist2 = new List<Client>(mypc.WebClients.Values);
  163. foreach (Client ev in cllist2)
  164. {
  165. ReceiveJob(ev, mypc.LastJob, mypc.LastSolved);
  166. }
  167. }
  168. else if (msg.ContainsKey("method") && msg["method"].GetString() == "job")
  169. {
  170. if (!msg.ContainsKey("params"))
  171. return;
  172. var lastjob = msg["params"] as JsonData;
  173. if (!VerifyJob(lastjob))
  174. {
  175. CConsole.ColorWarning(() =>
  176. Console.WriteLine("Failed to verify job: {0}", json));
  177. return;
  178. }
  179. // extended stratum
  180. if (!lastjob.ContainsKey("variant")) lastjob.Add("variant", mypc.DefaultVariant);
  181. if (!lastjob.ContainsKey("algo")) lastjob.Add("algo", mypc.DefaultAlgorithm);
  182. AlgorithmHelper.NormalizeAlgorithmAndVariant(lastjob);
  183. mypc.LastJob = lastjob;
  184. mypc.LastInteraction = DateTime.Now;
  185. mypc.LastSolved = new CcHashset<string>();
  186. List<Client> cllist2 = new List<Client>(mypc.WebClients.Values);
  187. Console.WriteLine("Sending job to {0} client(s)!", cllist2.Count);
  188. foreach (Client ev in cllist2)
  189. {
  190. ReceiveJob(ev, mypc.LastJob, mypc.LastSolved);
  191. }
  192. }
  193. else
  194. {
  195. if (msg.ContainsKey("error"))
  196. {
  197. // who knows?
  198. ReceiveError(mypc.LastSender, msg);
  199. }
  200. else
  201. {
  202. CConsole.ColorWarning(() =>
  203. Console.WriteLine("Pool is sending nonsense."));
  204. }
  205. }
  206. }
  207. private static void ConnectCallback(IAsyncResult result)
  208. {
  209. PoolConnection mypc = result.AsyncState as PoolConnection;
  210. TcpClient client = mypc.TcpClient;
  211. if (!mypc.Closed && client.Connected)
  212. {
  213. try
  214. {
  215. NetworkStream networkStream = client.GetStream();
  216. mypc.ReceiveBuffer = new byte[client.ReceiveBufferSize];
  217. networkStream.BeginRead(mypc.ReceiveBuffer, 0, mypc.ReceiveBuffer.Length, new AsyncCallback(ReceiveCallback), mypc);
  218. // keep things stupid and simple
  219. // https://github.com/xmrig/xmrig-proxy/blob/dev/doc/STRATUM_EXT.md#mining-algorithm-negotiation
  220. string msg0 = "{\"method\":\"login\",\"params\":{\"login\":\"";
  221. string msg1 = "\",\"pass\":\"";
  222. string msg2 = "\",\"agent\":\"webminerpool.com\",\"algo\": [\"cn/0\",\"cn/1\",\"cn/2\",\"cn-lite/0\",\"cn-lite/1\",\"cn-lite/2\"]}, \"id\":1}";
  223. string msg = msg0 + mypc.Login + msg1 + mypc.Password + msg2 + "\n";
  224. mypc.Send(mypc.LastSender, msg);
  225. }
  226. catch { return; }
  227. }
  228. else
  229. {
  230. // slow that down a bit
  231. Task.Run(async delegate
  232. {
  233. await Task.Delay(TimeSpan.FromSeconds(4));
  234. List<Client> cllist = new List<Client>(mypc.WebClients.Values);
  235. foreach (Client ev in cllist)
  236. Disconnect(ev, "can not connect to pool.");
  237. });
  238. }
  239. }
  240. public static void Close(Client client)
  241. {
  242. PoolConnection connection = client.PoolConnection;
  243. connection.WebClients.TryRemove(client);
  244. if (connection.WebClients.Count == 0)
  245. {
  246. connection.Closed = true;
  247. try
  248. {
  249. var networkStream = connection.TcpClient.GetStream();
  250. networkStream.EndRead(null);
  251. }
  252. catch { }
  253. try { connection.TcpClient.Close(); } catch { }
  254. try { connection.TcpClient.Client.Close(); } catch { }
  255. try { connection.ReceiveBuffer = null; } catch { }
  256. Connections.TryRemove(connection.Credentials);
  257. Console.WriteLine("{0}: closed a pool connection.", client.WebSocket.ConnectionInfo.Id);
  258. }
  259. }
  260. public static void RegisterCallbacks(ReceiveJobDelegate receiveJob, ReceiveErrorDelegate receiveError, DisconnectedDelegate disconnect)
  261. {
  262. PoolConnectionFactory.ReceiveJob = receiveJob;
  263. PoolConnectionFactory.ReceiveError = receiveError;
  264. PoolConnectionFactory.Disconnect = disconnect;
  265. }
  266. public static void CheckPoolConnection(PoolConnection connection)
  267. {
  268. if (connection.Closed) return;
  269. if ((DateTime.Now - connection.LastInteraction).TotalMinutes < 10)
  270. return;
  271. CConsole.ColorWarning(() => Console.WriteLine("Initiating reconnect! {0}:{1}", connection.Url, connection.Login));
  272. try
  273. {
  274. var networkStream = connection.TcpClient.GetStream();
  275. networkStream.EndRead(null);
  276. }
  277. catch { }
  278. try { connection.TcpClient.Close(); } catch { }
  279. try { connection.TcpClient.Client.Close(); } catch { }
  280. connection.ReceiveBuffer = null;
  281. connection.LastInteraction = DateTime.Now;
  282. connection.PoolId = "";
  283. connection.LastJob = null;
  284. connection.TcpClient = new TcpClient();
  285. Fleck.SocketExtensions.SetKeepAlive(connection.TcpClient.Client, 60000, 1000);
  286. connection.TcpClient.Client.ReceiveBufferSize = 4096 * 2;
  287. try { connection.TcpClient.BeginConnect(connection.Url, connection.Port, new AsyncCallback(ConnectCallback), connection); } catch { }
  288. }
  289. public static PoolConnection CreatePoolConnection(Client client, string url, int port, string login, string password)
  290. {
  291. string credential = url + port.ToString() + login + password;
  292. PoolConnection lpc, mypc = null;
  293. int batchCounter = 0;
  294. while (Connections.TryGetValue(credential + batchCounter.ToString(), out lpc))
  295. {
  296. if (lpc.WebClients.Count > MainClass.BatchSize) batchCounter++;
  297. else { mypc = lpc; break; }
  298. }
  299. credential += batchCounter.ToString();
  300. if (mypc == null)
  301. {
  302. CConsole.ColorInfo(() =>
  303. {
  304. Console.WriteLine("{0}: initiated new pool connection", client.WebSocket.ConnectionInfo.Id);
  305. Console.WriteLine("{0} {1} {2}", login, password, url);
  306. });
  307. mypc = new PoolConnection();
  308. mypc.Credentials = credential;
  309. mypc.LastSender = client;
  310. mypc.TcpClient = new TcpClient();
  311. Fleck.SocketExtensions.SetKeepAlive(mypc.TcpClient.Client, 60000, 1000);
  312. mypc.TcpClient.Client.ReceiveBufferSize = 4096 * 2;
  313. mypc.Login = login;
  314. mypc.Password = password;
  315. mypc.Port = port;
  316. mypc.Url = url;
  317. mypc.WebClients.TryAdd(client);
  318. Connections.TryAdd(credential, mypc);
  319. try { mypc.TcpClient.Client.BeginConnect(url, port, new AsyncCallback(ConnectCallback), mypc); } catch { }
  320. }
  321. else
  322. {
  323. Console.WriteLine("{0}: reusing pool connection", client.WebSocket.ConnectionInfo.Id);
  324. mypc.WebClients.TryAdd(client);
  325. if (mypc.LastJob != null) ReceiveJob(client, mypc.LastJob, mypc.LastSolved);
  326. else Console.WriteLine("{0} no job yet.", client.WebSocket.ConnectionInfo.Id);
  327. }
  328. client.PoolConnection = mypc;
  329. return mypc;
  330. }
  331. }
  332. }