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.

1165 lines
49 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
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
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
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
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 - 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.Generic;
  19. using System.IO;
  20. using System.Net.Sockets;
  21. using System.Runtime.InteropServices;
  22. using System.Security.Cryptography.X509Certificates;
  23. using System.Text;
  24. using System.Text.RegularExpressions;
  25. using System.Threading.Tasks;
  26. using Fleck;
  27. using TinyJson;
  28. using JsonData = System.Collections.Generic.Dictionary<string, object>;
  29. namespace Server {
  30. public class Client {
  31. public PoolConnection PoolConnection;
  32. public IWebSocketConnection WebSocket;
  33. public string Pool = string.Empty;
  34. public string Login;
  35. public string Password;
  36. public bool GotHandshake;
  37. public bool GotPoolInfo;
  38. public DateTime Created;
  39. public DateTime LastPoolJobTime;
  40. public string LastTarget = string.Empty;
  41. public string UserId;
  42. public int NumChecked = 0;
  43. public double Fee = DevDonation.DonationLevel;
  44. public int Version = 1;
  45. }
  46. public class JobInfo {
  47. public string JobId;
  48. public string InnerId;
  49. public string Blob;
  50. public string Target;
  51. public int Variant;
  52. public CcHashset<string> Solved;
  53. public bool DevJob;
  54. }
  55. public class Job {
  56. public string Blob;
  57. public string Target;
  58. public int Variant;
  59. public string JobId;
  60. public DateTime Age = DateTime.MinValue;
  61. }
  62. public struct Credentials {
  63. public string Pool;
  64. public string Login;
  65. public string Password;
  66. }
  67. class MainClass {
  68. [DllImport ("libhash.so", CallingConvention = CallingConvention.StdCall)]
  69. static extern IntPtr hash_cn (string hex, int light, int variant);
  70. [DllImport ("libhash.so", CallingConvention = CallingConvention.StdCall)]
  71. static extern IntPtr hash_free (IntPtr ptr);
  72. public const string SEP = "<-|->";
  73. public const string RegexIsHex = "^[a-fA-F0-9]+$";
  74. public const string RegexIsXMR = "[a-zA-Z|\\d]{95}";
  75. public const int JobCacheSize = (int) 1e5;
  76. private static bool libHashAvailable = false;
  77. private struct PoolInfo {
  78. public int Port;
  79. public string Url;
  80. public string EmptyPassword; // some pools require a non-empty password
  81. public PoolInfo (string url, int port, string emptypw = "") { Port = port; Url = url; EmptyPassword = emptypw; }
  82. }
  83. private static Dictionary<string, PoolInfo> PoolPool = new Dictionary<string, PoolInfo> ();
  84. private const int GraceConnectionTime = 16; // time to connect to a pool in seconds
  85. private const int HeartbeatRate = 10; // server logic every x seconds
  86. private const int TimeDevJobsAreOld = 600; // after that job-age we do not forward dev jobs
  87. private const int PoolTimeout = 60 * 12; // in seconds, pool is not sending new jobs
  88. private const int SpeedAverageOverXHeartbeats = 10; // for the statistics shown every heartbeat
  89. private const int MaxHashChecksPerHeartbeat = 40; // try not to kill ourselfs
  90. private const int ForceGCEveryXHeartbeat = 40; // so we can keep an eye on the memory
  91. private const int SaveStatisticsEveryXHeartbeat = 40; // save statistics
  92. public const int BatchSize = 200; // mining with the same credentials (pool, login, password)
  93. // results in connections beeing "bundled" to a single connection
  94. // seen by the pool. that can result in large difficulties and
  95. // hashrate fluctuations. this parameter sets the number of clients
  96. // in one batch, e.g. for BatchSize = 100 and 1000 clients
  97. // there will be 10 pool connections.
  98. private static int Heartbeats = 0;
  99. private static int HashesCheckedThisHeartbeat = 0;
  100. private static string jsonPools = "";
  101. private static long totalHashes = 0;
  102. private static long totalDevHashes = 0;
  103. private static long exceptionCounter = 0;
  104. private static bool saveLoginIdsNextHeartbeat = false;
  105. private static CcDictionary<Guid, Client> clients = new CcDictionary<Guid, Client> ();
  106. private static CcDictionary<string, JobInfo> jobInfos = new CcDictionary<string, JobInfo> ();
  107. private static CcDictionary<string, long> statistics = new CcDictionary<string, long> ();
  108. private static CcDictionary<string, Credentials> loginids = new CcDictionary<string, Credentials> ();
  109. private static CcDictionary<string, int> credentialSpamProtector = new CcDictionary<string, int> ();
  110. private static CcHashset<Client> slaves = new CcHashset<Client> ();
  111. private static CcQueue<string> jobQueue = new CcQueue<string> ();
  112. private static Job devJob = new Job ();
  113. static Client ourself;
  114. private static void FillPoolPool () {
  115. PoolPool.Clear ();
  116. #if (AEON)
  117. PoolPool.Add ("aeon-pool.com", new PoolInfo ("mine.aeon-pool.com", 5555));
  118. PoolPool.Add ("minereasy.com", new PoolInfo ("aeon.minereasy.com", 3333));
  119. PoolPool.Add ("aeon.sumominer.com", new PoolInfo ("aeon.sumominer.com", 3333));
  120. PoolPool.Add ("aeon.rupool.tk", new PoolInfo ("aeon.rupool.tk", 4444));
  121. PoolPool.Add ("aeon.hashvault.pro", new PoolInfo ("pool.aeon.hashvault.pro", 3333, "x"));
  122. PoolPool.Add ("aeon.n-engine.com", new PoolInfo ("aeon.n-engine.com", 7333));
  123. PoolPool.Add ("aeonpool.xyz", new PoolInfo ("mine.aeonpool.xyz", 3333));
  124. PoolPool.Add ("aeonpool.dreamitsystems.com", new PoolInfo ("aeonpool.dreamitsystems.com", 13333, "x"));
  125. PoolPool.Add ("aeonminingpool.com", new PoolInfo ("pool.aeonminingpool.com", 3333, "x"));
  126. PoolPool.Add ("aeonhash.com", new PoolInfo ("pool.aeonhash.com", 3333));
  127. PoolPool.Add ("durinsmine.com", new PoolInfo ("mine.durinsmine.com", 3333, "x"));
  128. PoolPool.Add ("aeon.uax.io", new PoolInfo ("mine.uax.io", 4446));
  129. PoolPool.Add ("aeon-pool.sytes.net", new PoolInfo ("aeon-pool.sytes.net", 3333));
  130. PoolPool.Add ("aeonpool.net", new PoolInfo ("pool.aeonpool.net", 3333, "x"));
  131. PoolPool.Add ("supportaeon.com", new PoolInfo ("pool.supportaeon.com", 3333, "x"));
  132. PoolPool.Add ("pooltupi.com", new PoolInfo ("pooltupi.com", 8080, "x"));
  133. PoolPool.Add ("aeon.semipool.com", new PoolInfo ("pool.aeon.semipool.com", 3333, "x"));
  134. #else
  135. PoolPool.Add ("xmrpool.eu", new PoolInfo ("xmrpool.eu", 3333));
  136. PoolPool.Add ("moneropool.com", new PoolInfo ("mine.moneropool.com", 3333));
  137. PoolPool.Add ("monero.crypto-pool.fr", new PoolInfo ("xmr.crypto-pool.fr", 3333));
  138. PoolPool.Add ("monerohash.com", new PoolInfo ("monerohash.com", 3333));
  139. PoolPool.Add ("minexmr.com", new PoolInfo ("pool.minexmr.com", 4444));
  140. PoolPool.Add ("usxmrpool.com", new PoolInfo ("pool.usxmrpool.com", 3333, "x"));
  141. PoolPool.Add ("supportxmr.com", new PoolInfo ("pool.supportxmr.com", 5555, "x"));
  142. PoolPool.Add ("moneroocean.stream:100", new PoolInfo ("gulf.moneroocean.stream", 80, "x"));
  143. PoolPool.Add ("moneroocean.stream", new PoolInfo ("gulf.moneroocean.stream", 10001, "x"));
  144. PoolPool.Add ("poolmining.org", new PoolInfo ("xmr.poolmining.org", 3032, "x"));
  145. PoolPool.Add ("minemonero.pro", new PoolInfo ("pool.minemonero.pro", 3333, "x"));
  146. PoolPool.Add ("xmr.prohash.net", new PoolInfo ("xmr.prohash.net", 1111));
  147. PoolPool.Add ("minercircle.com", new PoolInfo ("xmr.minercircle.com", 3333));
  148. PoolPool.Add ("xmr.nanopool.org", new PoolInfo ("xmr-eu1.nanopool.org", 14444, "x"));
  149. PoolPool.Add ("xmrminerpro.com", new PoolInfo ("xmrminerpro.com", 3333, "x"));
  150. PoolPool.Add ("clawde.xyz", new PoolInfo ("clawde.xyz", 3333, "x"));
  151. PoolPool.Add ("dwarfpool.com", new PoolInfo ("xmr-eu.dwarfpool.com", 8005));
  152. PoolPool.Add ("xmrpool.net", new PoolInfo ("mine.xmrpool.net", 3333, "x"));
  153. PoolPool.Add ("monero.hashvault.pro", new PoolInfo ("pool.monero.hashvault.pro", 5555, "x"));
  154. PoolPool.Add ("osiamining.com", new PoolInfo ("osiamining.com", 4545, ""));
  155. PoolPool.Add ("killallasics", new PoolInfo ("killallasics.moneroworld.com", 3333));
  156. PoolPool.Add ("arhash.xyz", new PoolInfo ("arhash.xyz", 3333, "x"));
  157. // Due to POW changes the following
  158. // pools mights not work anymore with the current hashfunction.
  159. // TURTLE - bye bye turtle
  160. // PoolPool.Add ("slowandsteady.fun", new PoolInfo ("slowandsteady.fun", 3333));
  161. // PoolPool.Add ("trtl.flashpool.club", new PoolInfo ("trtl.flashpool.club", 3333));
  162. // SUMOKOIN - bye bye sumokoin
  163. // PoolPool.Add ("sumokoin.com", new PoolInfo ("pool.sumokoin.com", 3333));
  164. // PoolPool.Add ("sumokoin.hashvault.pro", new PoolInfo ("pool.sumokoin.hashvault.pro", 3333, "x"));
  165. // PoolPool.Add ("sumopool.sonofatech.com", new PoolInfo ("sumopool.sonofatech.com", 3333));
  166. // PoolPool.Add ("sumo.bohemianpool.com", new PoolInfo ("sumo.bohemianpool.com", 4444, "x"));
  167. // PoolPool.Add ("pool.sumokoin.ch", new PoolInfo ("pool.sumokoin.ch", 4444));
  168. // ELECTRONEUM
  169. PoolPool.Add ("etn.poolmining.org", new PoolInfo ("etn.poolmining.org", 3102));
  170. PoolPool.Add ("etn.nanopool.org", new PoolInfo ("etn-eu1.nanopool.org", 13333, "x"));
  171. PoolPool.Add ("etn.hashvault.pro", new PoolInfo ("pool.electroneum.hashvault.pro", 80, "x"));
  172. #endif
  173. int counter = 0;
  174. jsonPools = "{\"identifier\":\"" + "poolinfo";
  175. foreach (var pool in PoolPool) {
  176. counter++;
  177. jsonPools += "\",\"pool" + counter.ToString () + "\":\"" + pool.Key;
  178. }
  179. jsonPools += "\"}\n";
  180. }
  181. private static UInt32 HexToUInt32 (String hex) {
  182. int NumberChars = hex.Length;
  183. byte[] bytes = new byte[NumberChars / 2];
  184. for (int i = 0; i < NumberChars; i += 2)
  185. bytes[i / 2] = Convert.ToByte (hex.Substring (i, 2), 16);
  186. return BitConverter.ToUInt32 (bytes, 0);;
  187. }
  188. private static bool CheckHashTarget (string target, string result) {
  189. // first check if result meets target
  190. string ourtarget = result.Substring (56, 8);
  191. if (HexToUInt32 (ourtarget) >= HexToUInt32 (target))
  192. return false;
  193. else
  194. return true;
  195. }
  196. //private static object hashLocker = new object ();
  197. private static bool CheckHash (string blob, int variant, string nonce, string target, string result, bool fullcheck) {
  198. // first check if result meets target
  199. string ourtarget = result.Substring (56, 8);
  200. if (HexToUInt32 (ourtarget) >= HexToUInt32 (target))
  201. return false;
  202. if (libHashAvailable && fullcheck) {
  203. // recalculate the hash
  204. string parta = blob.Substring (0, 78);
  205. string partb = blob.Substring (86, blob.Length - 86);
  206. // hashlib should be thread safe. If you encounter problems
  207. // (mono crashing with sigsev)
  208. // a workaround is to uncomment the lock.
  209. //lock (hashLocker) {
  210. #if (AEON)
  211. IntPtr pStr = hash_cn (parta + nonce + partb, 1, variant);
  212. #else
  213. IntPtr pStr = hash_cn (parta + nonce + partb, 0, variant);
  214. #endif
  215. string ourresult = Marshal.PtrToStringAnsi (pStr);
  216. hash_free (pStr);
  217. if (ourresult != result) return false;
  218. //}
  219. }
  220. return true;
  221. }
  222. private static void PoolDisconnectCallback (Client client, string reason) {
  223. DisconnectClient (client, reason);
  224. }
  225. private static void PoolErrorCallback (Client client, JsonData msg) {
  226. if (msg["error"] == null) {
  227. // looks good
  228. string forward = "{\"identifier\":\"" + "hashsolved" + "\"}\n";
  229. client.WebSocket.Send (forward);
  230. Console.WriteLine ("{0}: solved job", client.WebSocket.ConnectionInfo.Id);
  231. } else {
  232. if (client != ourself) {
  233. string forward = "{\"identifier\":\"" + "error" +
  234. "\",\"param\":\"" + "pool rejected hash" + "\"}\n";
  235. client.WebSocket.Send (forward);
  236. Console.WriteLine ("{0}: got a pool rejection", client.WebSocket.ConnectionInfo.Id);
  237. }
  238. }
  239. }
  240. private static void PoolReceiveCallback (Client client, JsonData msg, CcHashset<string> hashset) {
  241. string jobId = Guid.NewGuid ().ToString ("N");
  242. client.LastPoolJobTime = DateTime.Now;
  243. // Todo: This can be done easier/nicer.
  244. // Don't care at the moment.
  245. JobInfo ji = new JobInfo ();
  246. ji.JobId = jobId;
  247. ji.Blob = msg["blob"].GetString ();
  248. ji.Target = msg["target"].GetString ();
  249. ji.InnerId = msg["job_id"].GetString ();
  250. ji.Solved = hashset;
  251. ji.DevJob = (client == ourself);
  252. if(!int.TryParse(msg["variant"].GetString (),out ji.Variant))
  253. { ji.Variant = -1; }
  254. jobInfos.TryAdd (jobId, ji); // Todo: We can combine these two
  255. jobQueue.Enqueue (jobId); // datastructures
  256. if (client == ourself) {
  257. devJob.Blob = ji.Blob;
  258. devJob.JobId = jobId;
  259. devJob.Age = DateTime.Now;
  260. devJob.Target = ji.Target;
  261. devJob.Variant = ji.Variant;
  262. List<Client> slavelist = new List<Client> (slaves.Values);
  263. foreach (Client slave in slavelist) {
  264. string newtarget;
  265. string forward;
  266. if (string.IsNullOrEmpty (slave.LastTarget)) {
  267. newtarget = devJob.Target;
  268. } else {
  269. uint diff1 = HexToUInt32 (slave.LastTarget);
  270. uint diff2 = HexToUInt32 (devJob.Target);
  271. if (diff1 > diff2)
  272. newtarget = slave.LastTarget;
  273. else
  274. newtarget = devJob.Target;
  275. }
  276. if (slave.Version < 4) {
  277. forward = "{\"identifier\":\"" + "job" +
  278. "\",\"job_id\":\"" + devJob.JobId +
  279. "\",\"blob\":\"" + devJob.Blob +
  280. "\",\"target\":\"" + newtarget + "\"}\n";
  281. } else {
  282. forward = "{\"identifier\":\"" + "job" +
  283. "\",\"job_id\":\"" + devJob.JobId +
  284. "\",\"variant\":\"" + devJob.Variant.ToString() +
  285. "\",\"blob\":\"" + devJob.Blob +
  286. "\",\"target\":\"" + newtarget + "\"}\n";
  287. }
  288. slave.WebSocket.Send (forward);
  289. Console.WriteLine ("Sending job to slave {0}", slave.WebSocket.ConnectionInfo.Id);
  290. }
  291. } else {
  292. // forward this to the websocket!
  293. string forward = string.Empty;
  294. bool tookdev = false;
  295. if (Random2.NextDouble () < client.Fee) {
  296. if ((DateTime.Now - devJob.Age).TotalSeconds < TimeDevJobsAreOld) {
  297. // okay, do not send devjob.Target, but
  298. // the last difficulty
  299. string newtarget = string.Empty;
  300. if (string.IsNullOrEmpty (client.LastTarget)) {
  301. newtarget = devJob.Target;
  302. } else {
  303. uint diff1 = HexToUInt32 (client.LastTarget);
  304. uint diff2 = HexToUInt32 (devJob.Target);
  305. if (diff1 > diff2)
  306. newtarget = client.LastTarget;
  307. else
  308. newtarget = devJob.Target;
  309. }
  310. if (client.Version < 4) {
  311. forward = "{\"identifier\":\"" + "job" +
  312. "\",\"job_id\":\"" + devJob.JobId +
  313. "\",\"blob\":\"" + devJob.Blob +
  314. "\",\"target\":\"" + newtarget + "\"}\n";
  315. } else {
  316. forward = "{\"identifier\":\"" + "job" +
  317. "\",\"job_id\":\"" + devJob.JobId +
  318. "\",\"variant\":\"" + devJob.Variant.ToString() +
  319. "\",\"blob\":\"" + devJob.Blob +
  320. "\",\"target\":\"" + newtarget + "\"}\n";
  321. }
  322. tookdev = true;
  323. }
  324. }
  325. if (!tookdev) {
  326. if (client.Version < 4) {
  327. forward = "{\"identifier\":\"" + "job" +
  328. "\",\"job_id\":\"" + jobId +
  329. "\",\"blob\":\"" + msg["blob"].GetString () +
  330. "\",\"target\":\"" + msg["target"].GetString () + "\"}\n";
  331. } else {
  332. forward = "{\"identifier\":\"" + "job" +
  333. "\",\"job_id\":\"" + jobId +
  334. "\",\"variant\":\"" + msg["variant"].GetString() +
  335. "\",\"blob\":\"" + msg["blob"].GetString () +
  336. "\",\"target\":\"" + msg["target"].GetString () + "\"}\n";
  337. }
  338. client.LastTarget = msg["target"].GetString ();
  339. }
  340. if (tookdev) {
  341. if (!slaves.Contains (client)) slaves.TryAdd (client);
  342. Console.WriteLine ("Send dev job!");
  343. } else {
  344. slaves.TryRemove (client);
  345. }
  346. client.WebSocket.Send (forward);
  347. Console.WriteLine ("{0}: got job from pool", client.WebSocket.ConnectionInfo.Id);
  348. }
  349. }
  350. public static void RemoveClient (Guid guid) {
  351. Client client;
  352. if (!clients.TryRemove (guid, out client)) return;
  353. slaves.TryRemove (client);
  354. try {
  355. var wsoc = client.WebSocket as WebSocketConnection;
  356. if (wsoc != null) wsoc.CloseSocket ();
  357. } catch { }
  358. try { client.WebSocket.Close (); } catch { }
  359. if (client.PoolConnection != null)
  360. PoolConnectionFactory.Close (client.PoolConnection, client);
  361. }
  362. public static void DisconnectClient (Client client, string reason) {
  363. if (client.WebSocket.IsAvailable) {
  364. string msg = "{\"identifier\":\"" + "error" +
  365. "\",\"param\":\"" + reason + "\"}\n";
  366. System.Threading.Tasks.Task t = client.WebSocket.Send (msg);
  367. t.ContinueWith ((prevTask) => {
  368. prevTask.Wait ();
  369. RemoveClient (client.WebSocket.ConnectionInfo.Id);
  370. });
  371. } else {
  372. RemoveClient (client.WebSocket.ConnectionInfo.Id);
  373. }
  374. }
  375. public static void DisconnectClient (Guid guid, string reason) {
  376. Client client = clients[guid];
  377. DisconnectClient (client, reason);
  378. }
  379. private static void CreateOurself () {
  380. ourself = new Client ();
  381. ourself.Login = DevDonation.DevAddress;
  382. ourself.Pool = DevDonation.DevPoolUrl;
  383. ourself.Created = ourself.LastPoolJobTime = DateTime.Now;
  384. ourself.Password = DevDonation.DevPoolPwd;
  385. ourself.WebSocket = new EmptyWebsocket ();
  386. clients.TryAdd (Guid.Empty, ourself);
  387. ourself.PoolConnection = PoolConnectionFactory.CreatePoolConnection (ourself,
  388. DevDonation.DevPoolUrl, DevDonation.DevPoolPort, DevDonation.DevAddress, DevDonation.DevPoolPwd);
  389. }
  390. private static bool CheckLibHash (out Exception ex) {
  391. // just check if we can successfully calculate a cn-hash.
  392. string testStr = new string ('1', 151) + '3';
  393. string hashedResult = string.Empty;
  394. try {
  395. IntPtr pStr = hash_cn (testStr, 0, 1);
  396. hashedResult = Marshal.PtrToStringAnsi (pStr);
  397. hash_free (pStr);
  398. } catch (Exception e) {
  399. ex = e;
  400. return false;
  401. }
  402. if (!hashedResult.StartsWith ("843ae6fc006")) {
  403. ex = new Exception ("Hash function returned wrong hash");
  404. return false;
  405. }
  406. ex = null;
  407. return true;
  408. }
  409. private static void ExcessiveHashTest () {
  410. Parallel.For (0, 10000, (i) => {
  411. string testStr = new string ('1', 151) + '3';
  412. IntPtr ptr = hash_cn (testStr, 0, 1);
  413. string str = Marshal.PtrToStringAnsi (ptr);
  414. hash_free (ptr);
  415. Console.WriteLine (i.ToString () + " " + str);
  416. });
  417. }
  418. public static void Main (string[] args) {
  419. //ExcessiveHashTest(); return;
  420. CConsole.ColorInfo (() => {
  421. #if (DEBUG)
  422. Console.WriteLine ("[{0}] webminerpool server started - DEBUG MODE", DateTime.Now);
  423. #else
  424. Console.WriteLine ("[{0}] webminerpool server started", DateTime.Now);
  425. #endif
  426. double devfee = (new Client ()).Fee;
  427. if (devfee > double.Epsilon)
  428. Console.WriteLine ("Developer fee of {0}% enabled. Thank You.", (devfee * 100.0d).ToString ("F1"));
  429. Console.WriteLine ();
  430. });
  431. Exception exception = null;
  432. libHashAvailable = CheckLibHash (out exception);
  433. if (!libHashAvailable) CConsole.ColorWarning (() =>
  434. Console.WriteLine ("libhash.so is not available. Checking user submitted hashes disabled.")
  435. );
  436. PoolConnectionFactory.RegisterCallbacks (PoolReceiveCallback, PoolErrorCallback, PoolDisconnectCallback);
  437. FillPoolPool ();
  438. if (File.Exists ("statistics.dat")) {
  439. try {
  440. statistics.Clear ();
  441. string[] lines = File.ReadAllLines ("statistics.dat");
  442. foreach (string line in lines) {
  443. string[] statisticsdata = line.Split (new string[] { SEP }, StringSplitOptions.None);
  444. string statid = statisticsdata[1];
  445. long statnum = 0;
  446. long.TryParse (statisticsdata[0], out statnum);
  447. statistics.TryAdd (statid, statnum);
  448. }
  449. } catch (Exception ex) {
  450. CConsole.ColorAlert (() =>
  451. Console.WriteLine ("Error while reading statistics: {0}", ex));
  452. }
  453. }
  454. if (File.Exists ("logins.dat")) {
  455. try {
  456. loginids.Clear ();
  457. string[] lines = File.ReadAllLines ("logins.dat");
  458. foreach (string line in lines) {
  459. string[] logindata = line.Split (new string[] { SEP }, StringSplitOptions.None);
  460. Credentials cred = new Credentials ();
  461. cred.Pool = logindata[1];
  462. cred.Login = logindata[2];
  463. cred.Password = logindata[3];
  464. loginids.TryAdd (logindata[0], cred);
  465. }
  466. } catch (Exception ex) {
  467. CConsole.ColorAlert (() =>
  468. Console.WriteLine ("Error while reading logins: {0}", ex));
  469. }
  470. }
  471. X509Certificate2 cert = null;
  472. try { cert = new X509Certificate2 ("certificate.pfx", "miner"); } catch (Exception e) { exception = e; cert = null; }
  473. bool certAvailable = (cert != null);
  474. if (!certAvailable)
  475. CConsole.ColorWarning (() => Console.WriteLine ("SSL certificate could not be loaded. Secure connection disabled."));
  476. WebSocketServer server;
  477. #if (AEON)
  478. string localAddr = (certAvailable ? "wss://" : "ws://") + "0.0.0.0:8282";
  479. #else
  480. string localAddr = (certAvailable ? "wss://" : "ws://") + "0.0.0.0:8181";
  481. #endif
  482. server = new WebSocketServer (localAddr);
  483. server.Certificate = cert;
  484. FleckLog.LogAction = (level, message, ex) => {
  485. switch (level) {
  486. case LogLevel.Debug:
  487. #if (DEBUG)
  488. Console.WriteLine ("FLECK (Debug): " + message);
  489. #endif
  490. break;
  491. case LogLevel.Error:
  492. if (ex != null && !string.IsNullOrEmpty (ex.Message)) {
  493. CConsole.ColorAlert (() => Console.WriteLine ("FLECK: " + message + " " + ex.Message));
  494. exceptionCounter++;
  495. if ((exceptionCounter % 200) == 0) {
  496. Helper.WriteTextAsyncWrapper ("fleck_error.txt", ex.ToString ());
  497. }
  498. } else Console.WriteLine ("FLECK: " + message);
  499. break;
  500. case LogLevel.Warn:
  501. if (ex != null && !string.IsNullOrEmpty (ex.Message)) {
  502. Console.WriteLine ("FLECK: " + message + " " + ex.Message);
  503. exceptionCounter++;
  504. if ((exceptionCounter % 200) == 0) {
  505. Helper.WriteTextAsyncWrapper ("fleck_warn.txt", ex.ToString ());
  506. }
  507. } else Console.WriteLine ("FLECK: " + message);
  508. break;
  509. default:
  510. Console.WriteLine ("FLECK: " + message);
  511. break;
  512. }
  513. };
  514. server.RestartAfterListenError = true;
  515. server.ListenerSocket.NoDelay = false;
  516. server.Start (socket => {
  517. socket.OnOpen = () => {
  518. string ipadr = string.Empty;
  519. try { ipadr = socket.ConnectionInfo.ClientIpAddress; } catch { }
  520. Client client = new Client ();
  521. client.WebSocket = socket;
  522. client.Created = client.LastPoolJobTime = DateTime.Now;
  523. Guid guid = socket.ConnectionInfo.Id;
  524. clients.TryAdd (guid, client);
  525. Console.WriteLine ("{0}: connected with ip {1}", guid, ipadr);
  526. };
  527. socket.OnClose = () => {
  528. Guid guid = socket.ConnectionInfo.Id;
  529. RemoveClient (socket.ConnectionInfo.Id);
  530. Console.WriteLine (guid + ": closed");
  531. };
  532. socket.OnError = error => {
  533. Guid guid = socket.ConnectionInfo.Id;
  534. RemoveClient (socket.ConnectionInfo.Id);
  535. Console.WriteLine (guid + ": unexpected close");
  536. };
  537. socket.OnMessage = message => {
  538. string ipadr = string.Empty;
  539. try { ipadr = socket.ConnectionInfo.ClientIpAddress; } catch { }
  540. Guid guid = socket.ConnectionInfo.Id;
  541. if (message.Length > 3000) {
  542. RemoveClient (guid); // that can't be valid, do not even try to parse
  543. }
  544. JsonData msg = message.FromJson<JsonData> ();
  545. if (msg == null || !msg.ContainsKey ("identifier")) return;
  546. Client client = null;
  547. // in very rare occasions, we get interference with onopen()
  548. // due to async code. wait a second and retry.
  549. for (int tries = 0; tries < 4; tries++) {
  550. if (clients.TryGetValue (guid, out client)) break;
  551. Task.Run (async delegate { await Task.Delay (TimeSpan.FromSeconds (1)); }).Wait ();
  552. }
  553. if (client == null) {
  554. // famous comment: this should not happen
  555. RemoveClient (guid);
  556. return;
  557. }
  558. string identifier = (string) msg["identifier"];
  559. if (identifier == "handshake") {
  560. if (client.GotHandshake) {
  561. // no merci for malformed data.
  562. DisconnectClient (client, "Handshake already performed.");
  563. return;
  564. }
  565. client.GotHandshake = true;
  566. if (msg.ContainsKey ("version")) {
  567. int.TryParse (msg["version"].GetString (), out client.Version);
  568. }
  569. if (msg.ContainsKey ("loginid")) {
  570. string loginid = msg["loginid"].GetString ();
  571. if (loginid.Length != 36 && loginid.Length != 32) {
  572. Console.WriteLine ("Invalid LoginId!");
  573. DisconnectClient (client, "Invalid loginid.");
  574. return;
  575. }
  576. Credentials crdts;
  577. if (!loginids.TryGetValue (loginid, out crdts)) {
  578. Console.WriteLine ("Unregistered LoginId! {0}", loginid);
  579. DisconnectClient (client, "Loginid not registered!");
  580. return;
  581. }
  582. client.Login = crdts.Login;
  583. client.Password = crdts.Password;
  584. client.Pool = crdts.Pool;
  585. } else if (msg.ContainsKey ("login") && msg.ContainsKey ("password") && msg.ContainsKey ("pool")) {
  586. client.Login = msg["login"].GetString ();
  587. client.Password = msg["password"].GetString ();
  588. client.Pool = msg["pool"].GetString ();
  589. } else {
  590. // no merci for malformed data.
  591. Console.WriteLine ("Malformed handshake");
  592. DisconnectClient (client, "Login, password and pool have to be specified.");
  593. return;
  594. }
  595. client.UserId = string.Empty;
  596. if (msg.ContainsKey ("userid")) {
  597. string uid = msg["userid"].GetString ();
  598. if (uid.Length > 200) { RemoveClient (socket.ConnectionInfo.Id); return; }
  599. client.UserId = uid;
  600. }
  601. Console.WriteLine ("{0}: handshake - {1}, {2}", guid, client.Pool,
  602. (client.Login.Length > 8 ? client.Login.Substring (0, 8) + "..." : client.Login));
  603. if (!string.IsNullOrEmpty (ipadr)) Firewall.Update (ipadr, Firewall.UpdateEntry.Handshake);
  604. PoolInfo pi;
  605. if (!PoolPool.TryGetValue (client.Pool, out pi)) {
  606. // we dont have that pool?
  607. DisconnectClient (client, "pool not known");
  608. return;
  609. }
  610. // if pools have some default password
  611. if (client.Password == "") client.Password = pi.EmptyPassword;
  612. client.PoolConnection = PoolConnectionFactory.CreatePoolConnection (
  613. client, pi.Url, pi.Port, client.Login, client.Password);
  614. } else if (identifier == "solved") {
  615. if (!client.GotHandshake) {
  616. // no merci
  617. RemoveClient (socket.ConnectionInfo.Id);
  618. return;
  619. }
  620. Console.WriteLine ("{0}: reports solved hash", guid);
  621. new Task (() => {
  622. if (!msg.ContainsKey ("job_id") ||
  623. !msg.ContainsKey ("nonce") ||
  624. !msg.ContainsKey ("result")) {
  625. // no merci for malformed data.
  626. RemoveClient (guid);
  627. return;
  628. }
  629. string jobid = msg["job_id"].GetString ();
  630. JobInfo ji;
  631. if (!jobInfos.TryGetValue (jobid, out ji)) {
  632. // this job id is not known to us
  633. Console.WriteLine ("Job unknown!");
  634. return;
  635. }
  636. string reportedNonce = msg["nonce"].GetString ();
  637. string reportedResult = msg["result"].GetString ();
  638. if (ji.Solved.Contains (reportedNonce.ToLower ())) {
  639. Console.WriteLine ("Nonce collision!");
  640. return;
  641. }
  642. if (reportedNonce.Length != 8 || (!Regex.IsMatch (reportedNonce, RegexIsHex))) {
  643. DisconnectClient (client, "nonce malformed");
  644. return;
  645. }
  646. if (reportedResult.Length != 64 || (!Regex.IsMatch (reportedResult, RegexIsHex))) {
  647. DisconnectClient (client, "result malformed");
  648. return;
  649. }
  650. double prob = ((double) HexToUInt32 (ji.Target)) / ((double) 0xffffffff);
  651. long howmanyhashes = ((long) (1.0 / prob));
  652. totalHashes += howmanyhashes;
  653. if (ji.DevJob) {
  654. // that was an "dev" job. could be that the target does not match
  655. if (!CheckHashTarget (ji.Target, reportedResult)) {
  656. Console.WriteLine ("Hash does not reach our target difficulty.");
  657. return;
  658. }
  659. totalDevHashes += howmanyhashes;
  660. }
  661. // default chance to get hash-checked is 10%
  662. double chanceForACheck = 0.1;
  663. // check new clients more often, but prevent that to happen the first 30s the server is running
  664. if (Heartbeats > 3 && client.NumChecked < 9) chanceForACheck = 1.0 - 0.1 * client.NumChecked;
  665. bool performFullCheck = (Random2.NextDouble () < chanceForACheck && HashesCheckedThisHeartbeat < MaxHashChecksPerHeartbeat);
  666. if (performFullCheck) {
  667. client.NumChecked++;
  668. HashesCheckedThisHeartbeat++;
  669. }
  670. bool validHash = CheckHash (ji.Blob, ji.Variant, reportedNonce, ji.Target, reportedResult, performFullCheck);
  671. if (!validHash) {
  672. CConsole.ColorWarning (() =>
  673. Console.WriteLine ("{0} got disconnected for WRONG hash.", client.WebSocket.ConnectionInfo.Id.ToString ()));
  674. if (!string.IsNullOrEmpty (ipadr)) Firewall.Update (ipadr, Firewall.UpdateEntry.WrongHash);
  675. RemoveClient (client.WebSocket.ConnectionInfo.Id);
  676. } else {
  677. if (performFullCheck)
  678. Console.WriteLine ("{0}: got hash-checked", client.WebSocket.ConnectionInfo.Id.ToString ());
  679. if (!string.IsNullOrEmpty (ipadr)) Firewall.Update (ipadr, Firewall.UpdateEntry.SolvedJob);
  680. ji.Solved.TryAdd (reportedNonce.ToLower ());
  681. if (client.UserId != string.Empty) {
  682. long currentstat = 0;
  683. bool exists = statistics.TryGetValue (client.UserId, out currentstat);
  684. if (exists) statistics[client.UserId] = currentstat + howmanyhashes;
  685. else statistics.TryAdd (client.UserId, howmanyhashes);
  686. }
  687. if (!ji.DevJob) client.PoolConnection.Hashes += howmanyhashes;
  688. Client jiClient = client;
  689. if (ji.DevJob) jiClient = ourself;
  690. string msg1 = "{\"id\":\"" + jiClient.PoolConnection.PoolId +
  691. "\",\"job_id\":\"" + ji.InnerId +
  692. "\",\"nonce\":\"" + msg["nonce"].GetString () +
  693. "\",\"result\":\"" + msg["result"].GetString () +
  694. "\"}";
  695. string msg0 = "{\"method\":\"" + "submit" +
  696. "\",\"params\":" + msg1 +
  697. ",\"id\":\"" + "1" + "\"}\n"; // TODO: check the "1"
  698. jiClient.PoolConnection.Send (jiClient, msg0);
  699. }
  700. }).Start ();
  701. } else if (identifier == "poolinfo") {
  702. if (!client.GotPoolInfo) {
  703. client.GotPoolInfo = true;
  704. client.WebSocket.Send (jsonPools);
  705. }
  706. } else
  707. if (identifier == "register") {
  708. string registerip = string.Empty;
  709. try { registerip = client.WebSocket.ConnectionInfo.ClientIpAddress; } catch { };
  710. if (string.IsNullOrEmpty (registerip)) { DisconnectClient (guid, "Unknown error."); return; }
  711. int registeredThisSession = 0;
  712. if (credentialSpamProtector.TryGetValue (registerip, out registeredThisSession)) {
  713. registeredThisSession++;
  714. credentialSpamProtector[registerip] = registeredThisSession;
  715. } else {
  716. credentialSpamProtector.TryAdd (registerip, 0);
  717. }
  718. if (registeredThisSession > 10) {
  719. DisconnectClient (guid, "Too many registrations. You need to wait.");
  720. return;
  721. }
  722. if (!msg.ContainsKey ("login") ||
  723. !msg.ContainsKey ("password") ||
  724. !msg.ContainsKey ("pool")) {
  725. // no merci for malformed data.
  726. DisconnectClient (guid, "Login, password and pool have to be specified!");
  727. return;
  728. }
  729. // everything seems to be okay
  730. Credentials crdts = new Credentials ();
  731. crdts.Login = msg["login"].GetString ();
  732. crdts.Pool = msg["pool"].GetString ();
  733. crdts.Password = msg["password"].GetString ();
  734. PoolInfo pi;
  735. if (!PoolPool.TryGetValue (crdts.Pool, out pi)) {
  736. // we dont have that pool?
  737. DisconnectClient (client, "Pool not known!");
  738. return;
  739. }
  740. bool loginok = false;
  741. try { loginok = Regex.IsMatch (crdts.Login, RegexIsXMR); } catch { }
  742. if (!loginok) {
  743. DisconnectClient (client, "Not a valid address.");
  744. return;
  745. }
  746. if (crdts.Password.Length > 120) {
  747. DisconnectClient (client, "Password too long.");
  748. return;
  749. }
  750. string newloginguid = Guid.NewGuid ().ToString ("N");
  751. loginids.TryAdd (newloginguid, crdts);
  752. string smsg = "{\"identifier\":\"" + "registered" +
  753. "\",\"loginid\":\"" + newloginguid +
  754. "\"}";
  755. client.WebSocket.Send (smsg);
  756. Console.WriteLine ("Client registered!");
  757. saveLoginIdsNextHeartbeat = true;
  758. } else if (identifier == "userstats") {
  759. if (!msg.ContainsKey ("userid")) return;
  760. Console.WriteLine ("Userstat request");
  761. string uid = msg["userid"].GetString ();
  762. long hashn = 0;
  763. statistics.TryGetValue (uid, out hashn);
  764. string smsg = "{\"identifier\":\"" + "userstats" +
  765. "\",\"userid\":\"" + uid +
  766. "\",\"value\":" + hashn.ToString () + "}\n";
  767. client.WebSocket.Send (smsg);
  768. }
  769. };
  770. });
  771. bool running = true;
  772. double totalSpeed = 0, totalDevSpeed = 0;
  773. while (running) {
  774. Heartbeats++;
  775. Firewall.Heartbeat (Heartbeats);
  776. try {
  777. if (Heartbeats % SaveStatisticsEveryXHeartbeat == 0) {
  778. CConsole.ColorInfo (() => Console.WriteLine ("Saving statistics."));
  779. StringBuilder sb = new StringBuilder ();
  780. foreach (var stat in statistics) {
  781. sb.AppendLine (stat.Value.ToString () + SEP + stat.Key);
  782. }
  783. File.WriteAllText ("statistics.dat", sb.ToString ().TrimEnd ('\r', '\n'));
  784. }
  785. } catch (Exception ex) {
  786. CConsole.ColorAlert (() => Console.WriteLine ("Error saving statistics.dat: {0}", ex));
  787. }
  788. try {
  789. if (saveLoginIdsNextHeartbeat) {
  790. saveLoginIdsNextHeartbeat = false;
  791. CConsole.ColorInfo (() => Console.WriteLine ("Saving logins."));
  792. StringBuilder sb = new StringBuilder ();
  793. foreach (var lins in loginids) {
  794. sb.AppendLine (lins.Key + SEP + lins.Value.Pool + SEP + lins.Value.Login + SEP + lins.Value.Password);
  795. }
  796. File.WriteAllText ("logins.dat", sb.ToString ().TrimEnd ('\r', '\n'));
  797. }
  798. } catch (Exception ex) {
  799. CConsole.ColorAlert (() => Console.WriteLine ("Error saving logins.dat: {0}", ex));
  800. }
  801. try {
  802. Task.Run (async delegate { await Task.Delay (TimeSpan.FromSeconds (HeartbeatRate)); }).Wait ();
  803. if (Heartbeats % SpeedAverageOverXHeartbeats == 0) {
  804. totalSpeed = (double) totalHashes / (double) (HeartbeatRate * SpeedAverageOverXHeartbeats);
  805. totalDevSpeed = (double) totalDevHashes / (double) (HeartbeatRate * SpeedAverageOverXHeartbeats);
  806. totalHashes = 0;
  807. totalDevHashes = 0;
  808. }
  809. CConsole.ColorInfo (() =>
  810. Console.WriteLine ("[{0}] heartbeat; connections client/pool: {1}/{2}; jobqueue: {3}k; speed total: {4}kH/s",
  811. DateTime.Now.ToString (),
  812. clients.Count,
  813. PoolConnectionFactory.Connections.Count,
  814. ((double) jobQueue.Count / 1000.0d).ToString ("F1"),
  815. ((double) totalSpeed / 1000.0d).ToString ("F2")));
  816. while (jobQueue.Count > JobCacheSize) {
  817. string deq;
  818. if (jobQueue.TryDequeue (out deq)) {
  819. jobInfos.TryRemove (deq);
  820. }
  821. }
  822. DateTime now = DateTime.Now;
  823. List<PoolConnection> pcc = new List<PoolConnection> (PoolConnectionFactory.Connections.Values);
  824. foreach (PoolConnection pc in pcc) {
  825. PoolConnectionFactory.CheckPoolConnection (pc);
  826. }
  827. List<Client> cc = new List<Client> (clients.Values);
  828. foreach (Client c in cc) {
  829. try {
  830. if ((now - c.Created).TotalSeconds > GraceConnectionTime) {
  831. if (c.PoolConnection == null || c.PoolConnection.TcpClient == null) DisconnectClient (c, "timeout.");
  832. else if (!c.PoolConnection.TcpClient.Connected) DisconnectClient (c, "lost pool connection.");
  833. else if ((now - c.LastPoolJobTime).TotalSeconds > PoolTimeout) {
  834. DisconnectClient (c, "pool is not sending new jobs.");
  835. }
  836. }
  837. } catch { RemoveClient (c.WebSocket.ConnectionInfo.Id); }
  838. }
  839. if (clients.ContainsKey (Guid.Empty)) {
  840. if (clients.Count == 1)
  841. RemoveClient (Guid.Empty);
  842. } else {
  843. // we removed ourself because we got disconnected from the pool
  844. // make us alive again!
  845. if (clients.Count > 0 && DevDonation.DonationLevel > double.Epsilon) {
  846. CConsole.ColorWarning (() =>
  847. Console.WriteLine ("disconnected from dev pool. trying to reconnect."));
  848. devJob = new Job ();
  849. CreateOurself ();
  850. }
  851. }
  852. HashesCheckedThisHeartbeat = 0;
  853. if (Heartbeats % ForceGCEveryXHeartbeat == 0) {
  854. CConsole.ColorInfo (() => {
  855. Console.WriteLine ("Garbage collection. Currently using {0} MB.", Math.Round (((double) (GC.GetTotalMemory (false)) / 1024 / 1024)));
  856. DateTime tbc = DateTime.Now;
  857. // trust me, I am a professional
  858. GC.Collect (GC.MaxGeneration, GCCollectionMode.Forced); // DON'T DO THIS!!!
  859. Console.WriteLine ("Garbage collected in {0} ms. Currently using {1} MB ({2} clients).", (DateTime.Now - tbc).Milliseconds,
  860. Math.Round (((double) (GC.GetTotalMemory (false)) / 1024 / 1024)), clients.Count);
  861. });
  862. }
  863. } catch (Exception ex) {
  864. CConsole.ColorAlert (() =>
  865. Console.WriteLine ("Exception caught in the main loop ! {0}", ex));
  866. }
  867. }
  868. }
  869. }
  870. }