public static void Main(string[] args) { PoolConnectionFactory.RegisterCallbacks(PoolReceiveCallback, PoolErrorCallback, PoolDisconnectCallback); if (File.Exists("statistics.dat")) { try { statistics.Clear(); string[] lines = File.ReadAllLines("statistics.dat"); foreach (string line in lines) { string[] statisticsdata = line.Split(new string[] { SEP }, StringSplitOptions.None); string statid = statisticsdata[1]; long statnum = 0; long.TryParse(statisticsdata[0], out statnum); statistics.TryAdd(statid, statnum); } } catch (Exception ex) { Console.WriteLine("Error while reading statistics: {0}", ex); } } if (File.Exists("logins.dat")) { try { loginids.Clear(); string[] lines = File.ReadAllLines("logins.dat"); foreach (string line in lines) { string[] logindata = line.Split(new string[] { SEP }, StringSplitOptions.None); Credentials cred = new Credentials(); cred.Pool = logindata[1]; cred.Login = logindata[2]; cred.Password = logindata[3]; loginids.TryAdd(logindata[0], cred); } } catch (Exception ex) { Console.WriteLine("Error while reading logins: {0}", ex); } } FillPoolPool(); WebSocketServer server; #if (WSS) X509Certificate2 cert = new X509Certificate2("certificate.pfx", "miner"); #if (AEON) server = new WebSocketServer("wss://0.0.0.0:8282"); #else server = new WebSocketServer("wss://0.0.0.0:8181"); #endif server.Certificate = cert; #else #if (AEON) server = new WebSocketServer("ws://0.0.0.0:8282"); #else server = new WebSocketServer("ws://0.0.0.0:8181"); #endif #endif FleckLog.LogAction = (level, message, ex) => { switch (level) { case LogLevel.Debug: #if (DEBUG) Console.WriteLine("FLECK (Debug): " + message); #endif break; case LogLevel.Error: if (ex != null && !string.IsNullOrEmpty(ex.Message)) { Console.WriteLine("FLECK: " + message + " " + ex.Message); exceptionCounter++; if ((exceptionCounter % 200) == 0) { Helper.WriteTextAsyncWrapper("fleck_error.txt", ex.ToString()); } } else { Console.WriteLine("FLECK: " + message); } break; case LogLevel.Warn: if (ex != null && !string.IsNullOrEmpty(ex.Message)) { Console.WriteLine("FLECK: " + message + " " + ex.Message); exceptionCounter++; if ((exceptionCounter % 200) == 0) { Helper.WriteTextAsyncWrapper("fleck_warn.txt", ex.ToString()); } } else { Console.WriteLine("FLECK: " + message); } break; default: Console.WriteLine("FLECK: " + message); break; } }; server.RestartAfterListenError = true; server.ListenerSocket.NoDelay = false; server.Start(socket => { socket.OnOpen = () => { string ipadr = string.Empty; try { ipadr = socket.ConnectionInfo.ClientIpAddress; } catch { } Client client = new Client(); client.WebSocket = socket; client.Created = client.LastPoolJobTime = DateTime.Now; Guid guid = socket.ConnectionInfo.Id; clients.TryAdd(guid, client); Console.WriteLine("{0}: connected with ip {1}", guid, ipadr); }; socket.OnClose = () => { Guid guid = socket.ConnectionInfo.Id; RemoveClient(socket.ConnectionInfo.Id); Console.WriteLine(guid + ": closed"); }; socket.OnError = error => { Guid guid = socket.ConnectionInfo.Id; RemoveClient(socket.ConnectionInfo.Id); Console.WriteLine(guid + ": unexpected close"); }; socket.OnMessage = message => { string ipadr = string.Empty; try { ipadr = socket.ConnectionInfo.ClientIpAddress; } catch { } Guid guid = socket.ConnectionInfo.Id; if (message.Length > 3000) { RemoveClient(guid); // that can't be valid, do not even try to parse } JsonData msg = message.FromJson <JsonData> (); if (msg == null || !msg.ContainsKey("identifier")) { return; } Client client = null; // in very rare occasions, we get interference with onopen() // due to async code. wait a second and retry. for (int tries = 0; tries < 4; tries++) { if (clients.TryGetValue(guid, out client)) { break; } Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(1)); }).Wait(); } if (client == null) { // famous comment: this should not happen RemoveClient(guid); return; } string identifier = (string)msg["identifier"]; if (identifier == "handshake") { if (client.GotHandshake) { // no merci for malformed data. DisconnectClient(client, "Handshake already performed."); return; } client.GotHandshake = true; if (msg.ContainsKey("version")) { int.TryParse(msg["version"].GetString(), out client.Version); } if (msg.ContainsKey("loginid")) { string loginid = msg["loginid"].GetString(); if (loginid.Length != 36 && loginid.Length != 32) { Console.WriteLine("Invalid LoginId!"); DisconnectClient(client, "Invalid loginid."); return; } Credentials crdts; if (!loginids.TryGetValue(loginid, out crdts)) { Console.WriteLine("Unregistered LoginId! {0}", loginid); DisconnectClient(client, "Loginid not registered!"); return; } client.Login = crdts.Login; client.Password = crdts.Password; client.Pool = crdts.Pool; } else if (msg.ContainsKey("login") && msg.ContainsKey("password") && msg.ContainsKey("pool")) { client.Login = msg["login"].GetString(); client.Password = msg["password"].GetString(); client.Pool = msg["pool"].GetString(); } else { // no merci for malformed data. Console.WriteLine("Malformed handshake"); DisconnectClient(client, "Login, password and pool have to be specified."); return; } client.UserId = string.Empty; if (msg.ContainsKey("userid")) { string uid = msg["userid"].GetString(); if (uid.Length > 200) { RemoveClient(socket.ConnectionInfo.Id); return; } client.UserId = uid; } Console.WriteLine("{0}: handshake - {1}", guid, client.Pool); if (!string.IsNullOrEmpty(ipadr)) { Firewall.Update(ipadr, Firewall.UpdateEntry.Handshake); } PoolInfo pi; if (!PoolPool.TryGetValue(client.Pool, out pi)) { // we dont have that pool? DisconnectClient(client, "pool not known"); return; } // if pools have some default password if (client.Password == "") { client.Password = pi.EmptyPassword; } client.PoolConnection = PoolConnectionFactory.CreatePoolConnection( client, pi.Url, pi.Port, client.Login, client.Password); } else if (identifier == "solved") { if (!client.GotHandshake) { // no merci RemoveClient(socket.ConnectionInfo.Id); return; } Console.WriteLine("{0}: reports solved hash", guid); new Task(() => { if (!msg.ContainsKey("job_id") || !msg.ContainsKey("nonce") || !msg.ContainsKey("result")) { // no merci for malformed data. RemoveClient(guid); return; } string jobid = msg["job_id"].GetString(); JobInfo ji; if (!jobInfos.TryGetValue(jobid, out ji)) { // this job id is not known to us Console.WriteLine("Job unknown!"); return; } string reportedNonce = msg["nonce"].GetString(); string reportedResult = msg["result"].GetString(); if (ji.Solved.Contains(reportedNonce.ToLower())) { Console.WriteLine("Nonce collision!"); return; } if (reportedNonce.Length != 8 || (!Regex.IsMatch(reportedNonce, RegexIsHex))) { DisconnectClient(client, "nonce malformed"); return; } if (reportedResult.Length != 64 || (!Regex.IsMatch(reportedResult, RegexIsHex))) { DisconnectClient(client, "result malformed"); return; } double prob = ((double)HexToUInt32(ji.Target)) / ((double)0xffffffff); long howmanyhashes = ((long)(1.0 / prob)); totalHashes += howmanyhashes; if (ji.OwnJob) { // that was an "own" job. could be that the target does not match if (!CheckHashTarget(ji.Target, reportedResult)) { Console.WriteLine("Hash does not reach our target difficulty."); return; } totalOwnHashes += howmanyhashes; } // default chance to get hash-checked is 10% double chanceForACheck = 0.1; // check new clients more often, but prevent that to happen the first 30s the server is running if (Hearbeats > 3 && client.NumChecked < 9) { chanceForACheck = 1.0 - 0.1 * client.NumChecked; } bool performFullCheck = (rnd.NextDouble() < chanceForACheck && HashesCheckedThisHeartbeat < MaxHashChecksPerHeartbeat); if (performFullCheck) { client.NumChecked++; HashesCheckedThisHeartbeat++; } bool validHash = CheckHash(ji.Blob, reportedNonce, ji.Target, reportedResult, performFullCheck); if (!validHash) { Console.WriteLine("{0} got disconnected for WRONG HASH.", client.WebSocket.ConnectionInfo.Id.ToString()); if (!string.IsNullOrEmpty(ipadr)) { Firewall.Update(ipadr, Firewall.UpdateEntry.WrongHash); } RemoveClient(client.WebSocket.ConnectionInfo.Id); } else { if (performFullCheck) { Console.WriteLine("{0}: got hash-checked", client.WebSocket.ConnectionInfo.Id.ToString()); } if (!string.IsNullOrEmpty(ipadr)) { Firewall.Update(ipadr, Firewall.UpdateEntry.SolvedJob); } ji.Solved.TryAdd(reportedNonce.ToLower()); if (client.UserId != string.Empty) { long currentstat = 0; bool exists = statistics.TryGetValue(client.UserId, out currentstat); if (exists) { statistics[client.UserId] = currentstat + howmanyhashes; } else { statistics.TryAdd(client.UserId, howmanyhashes); } } if (!ji.OwnJob) { client.PoolConnection.Hashes += howmanyhashes; } Client jiClient = client; if (ji.OwnJob) { jiClient = ourself; } string msg1 = "{\"id\":\"" + jiClient.PoolConnection.PoolId + "\",\"job_id\":\"" + ji.InnerId + "\",\"nonce\":\"" + msg["nonce"].GetString() + "\",\"result\":\"" + msg["result"].GetString() + "\"}"; string msg0 = "{\"method\":\"" + "submit" + "\",\"params\":" + msg1 + ",\"id\":\"" + "1" + "\"}\n"; // TODO: check the "1" jiClient.PoolConnection.Send(jiClient, msg0); } }).Start(); } else if (identifier == "poolinfo") { if (!client.GotPoolInfo) { client.GotPoolInfo = true; client.WebSocket.Send(jsonPools); } } else if (identifier == "register") { string registerip = string.Empty; try { registerip = client.WebSocket.ConnectionInfo.ClientIpAddress; } catch { }; if (string.IsNullOrEmpty(registerip)) { DisconnectClient(guid, "Unknown error."); return; } int registeredThisSession = 0; if (credentialSpamProtector.TryGetValue(registerip, out registeredThisSession)) { registeredThisSession++; credentialSpamProtector[registerip] = registeredThisSession; } else { credentialSpamProtector.TryAdd(registerip, 0); } if (registeredThisSession > 10) { DisconnectClient(guid, "Too many registrations. You need to wait."); return; } if (!msg.ContainsKey("login") || !msg.ContainsKey("password") || !msg.ContainsKey("pool")) { // no merci for malformed data. DisconnectClient(guid, "Login, password and pool have to be specified!"); return; } // everything seems to be okay Credentials crdts = new Credentials(); crdts.Login = msg["login"].GetString(); crdts.Pool = msg["pool"].GetString(); crdts.Password = msg["password"].GetString(); PoolInfo pi; if (!PoolPool.TryGetValue(crdts.Pool, out pi)) { // we dont have that pool? DisconnectClient(client, "Pool not known!"); return; } bool loginok = false; try { loginok = Regex.IsMatch(crdts.Login, RegexIsXMR); } catch { } if (!loginok) { DisconnectClient(client, "Not a valid address."); return; } if (crdts.Password.Length > 120) { DisconnectClient(client, "Password too long."); return; } string newloginguid = Guid.NewGuid().ToString("N"); loginids.TryAdd(newloginguid, crdts); string smsg = "{\"identifier\":\"" + "registered" + "\",\"loginid\":\"" + newloginguid + "\"}"; client.WebSocket.Send(smsg); Console.WriteLine("Client registered!"); saveLoginIdsNextHeartbeat = true; } else if (identifier == "userstats") { if (!msg.ContainsKey("userid")) { return; } Console.WriteLine("Userstat request"); string uid = msg["userid"].GetString(); long hashn = 0; statistics.TryGetValue(uid, out hashn); string smsg = "{\"identifier\":\"" + "userstats" + "\",\"userid\":\"" + uid + "\",\"value\":" + hashn.ToString() + "}\n"; client.WebSocket.Send(smsg); } }; }); bool running = true; double totalspeed = 0, totalownspeed = 0; while (running) { Hearbeats++; Firewall.Heartbeat(Hearbeats); try { if (Hearbeats % SaveStatisticsEveryXHeartbeat == 0) { Console.WriteLine("Saving statistics..."); StringBuilder sb = new StringBuilder(); foreach (var stat in statistics) { sb.AppendLine(stat.Value.ToString() + SEP + stat.Key); } File.WriteAllText("statistics.dat", sb.ToString().TrimEnd('\r', '\n')); Console.WriteLine("done."); } } catch (Exception ex) { Console.WriteLine("Error saving statistics.dat: {0}", ex); } try { if (saveLoginIdsNextHeartbeat) { saveLoginIdsNextHeartbeat = false; Console.WriteLine("Saving logins..."); StringBuilder sb = new StringBuilder(); foreach (var lins in loginids) { sb.AppendLine(lins.Key + SEP + lins.Value.Pool + SEP + lins.Value.Login + SEP + lins.Value.Password); } File.WriteAllText("logins.dat", sb.ToString().TrimEnd('\r', '\n')); Console.WriteLine("done."); } } catch (Exception ex) { Console.WriteLine("Error saving logins.dat: {0}", ex); } try { Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(HeartbeatRate)); }).Wait(); if (Hearbeats % SpeedAverageOverXHeartbeats == 0) { totalspeed = (double)totalHashes / (double)(HeartbeatRate * SpeedAverageOverXHeartbeats); totalownspeed = (double)totalOwnHashes / (double)(HeartbeatRate * SpeedAverageOverXHeartbeats); totalHashes = 0; totalOwnHashes = 0; } Console.WriteLine("[{0}] heartbeat, connections: client {1}, pool {2}, jobqueue: {3}, total/own: {4}/{5} h/s", DateTime.Now.ToString(), clients.Count, PoolConnectionFactory.Connections.Count, jobQueue.Count, totalspeed, totalownspeed); while (jobQueue.Count > JobCacheSize) { string deq; if (jobQueue.TryDequeue(out deq)) { jobInfos.TryRemove(deq); } } DateTime now = DateTime.Now; List <PoolConnection> pcc = new List <PoolConnection> (PoolConnectionFactory.Connections.Values); foreach (PoolConnection pc in pcc) { PoolConnectionFactory.CheckPoolConnection(pc); } List <Client> cc = new List <Client> (clients.Values); foreach (Client c in cc) { try { if ((now - c.Created).TotalSeconds > GraceConnectionTime) { if (c.PoolConnection == null || c.PoolConnection.TcpClient == null) { DisconnectClient(c, "timeout."); } else if (!c.PoolConnection.TcpClient.Connected) { DisconnectClient(c, "lost pool connection."); } else if ((now - c.LastPoolJobTime).TotalSeconds > PoolTimeout) { DisconnectClient(c, "pool is not sending new jobs."); } } } catch { RemoveClient(c.WebSocket.ConnectionInfo.Id); } } if (clients.ContainsKey(Guid.Empty)) { if (clients.Count == 1) { RemoveClient(Guid.Empty); } } else { // we removed ourself because we got disconnected from the pool // make us alive again! if (clients.Count > 0) { Console.WriteLine("disconnected from own pool. trying to reconnect."); ownJob = new Job(); CreateOurself(); } } HashesCheckedThisHeartbeat = 0; if (Hearbeats % ForceGCEveryXHeartbeat == 0) { Console.WriteLine("Garbage collection. Currently using {0} MB.", Math.Round(((double)(GC.GetTotalMemory(false)) / 1024 / 1024))); DateTime tbc = DateTime.Now; // trust me, I am a professional GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); // DON'T DO THIS!!! Console.WriteLine("Garbage collected in {0} ms. Currently using {1} MB ({2} clients).", (DateTime.Now - tbc).Milliseconds, Math.Round(((double)(GC.GetTotalMemory(false)) / 1024 / 1024)), clients.Count); } } catch (Exception ex) { Console.WriteLine("{0} Exception caught in the main loop !", ex); } } }
public static void Main(string[] args) { CConsole.ColorInfo(() => { #if (DEBUG) Console.WriteLine("[{0}] webminerpool server started - DEBUG MODE", DateTime.Now); #else Console.WriteLine("[{0}] webminerpool server started", DateTime.Now); #endif Console.WriteLine(); }); try { PoolList = PoolList.LoadFromFile("pools.json"); } catch (Exception ex) { CConsole.ColorAlert(() => Console.WriteLine("Could not load pool list from pools.json: {0}", ex.Message)); return; } CConsole.ColorInfo(() => Console.WriteLine("Loaded {0} pools from pools.json.", PoolList.Count)); Exception exception = null; libHashAvailable = true; libHashAvailable = libHashAvailable && CheckLibHash("6465206f6d6e69627573206475626974616e64756d", "2f8e3df40bd11f9ac90c743ca8e32bb391da4fb98612aa3b6cdc639ee00b31f5", 0, 0, 0, out exception); libHashAvailable = libHashAvailable && CheckLibHash("38274c97c45a172cfc97679870422e3a1ab0784960c60514d816271415c306ee3a3ed1a77e31f6a885c3cb", "ed082e49dbd5bbe34a3726a0d1dad981146062b39d36d62c71eb1ed8ab49459b", 0, 1, 0, out exception); libHashAvailable = libHashAvailable && CheckLibHash("5468697320697320612074657374205468697320697320612074657374205468697320697320612074657374", "353fdc068fd47b03c04b9431e005e00b68c2168a3cc7335c8b9b308156591a4f", 0, 2, 0, out exception); libHashAvailable = libHashAvailable && CheckLibHash("5468697320697320612074657374205468697320697320612074657374205468697320697320612074657374", "f759588ad57e758467295443a9bd71490abff8e9dad1b95b6bf2f5d0d78387bc", 0, 4, 1806260, out exception); libHashAvailable = libHashAvailable && CheckLibHash("5468697320697320612074657374205468697320697320612074657374205468697320697320612074657374", "32f736ec1d2f3fc54c49beb8a0476cbfdd14c351b9c6d72c6f9ffcb5875be6b3", 4, 2, 0, out exception); if (!libHashAvailable) { CConsole.ColorWarning(() => { Console.WriteLine("libhash.so is not available. Checking user submitted hashes disabled:"); Console.WriteLine(" -> {0}", new StringReader(exception.ToString()).ReadLine()); }); } PoolConnectionFactory.RegisterCallbacks(PoolReceiveCallback, PoolErrorCallback, PoolDisconnectCallback); string loginsFilename = "logins.json"; if (File.Exists(loginsFilename)) { try { string json = File.ReadAllText(loginsFilename); JsonData data = json.FromJson <JsonData>(); foreach (string loginID in data.Keys) { JsonData jinfo = data[loginID] as JsonData; Credentials cred = new Credentials { Pool = jinfo["pool"].GetString(), Login = jinfo["login"].GetString(), Password = jinfo["password"].GetString() }; loginids.TryAdd(loginID, cred); } } catch (Exception ex) { CConsole.ColorAlert(() => Console.WriteLine("Error while reading logins: {0}", ex)); } } X509Certificate2 cert = null; try { cert = new X509Certificate2("certificate.pfx", "miner"); } catch (Exception e) { exception = e; cert = null; } bool certAvailable = (cert != null); if (!certAvailable) { CConsole.ColorWarning(() => { Console.WriteLine("SSL certificate could not be loaded. Secure connection disabled."); Console.WriteLine(" -> {0}", new StringReader(exception.ToString()).ReadLine()); }); } string localAddr = (certAvailable ? "wss://" : "ws://") + "0.0.0.0:8181"; WebSocketServer server = new WebSocketServer(localAddr); server.Certificate = cert; FleckLog.LogAction = (level, message, ex) => { switch (level) { case LogLevel.Debug: #if (DEBUG) Console.WriteLine("FLECK (Debug): " + message); #endif break; case LogLevel.Error: if (ex != null && !string.IsNullOrEmpty(ex.Message)) { CConsole.ColorAlert(() => Console.WriteLine("FLECK: " + message + " " + ex.Message)); exceptionCounter++; if ((exceptionCounter % 200) == 0) { Helper.WriteTextAsyncWrapper("fleck_error.txt", ex.ToString()); } } else { Console.WriteLine("FLECK: " + message); } break; case LogLevel.Warn: if (ex != null && !string.IsNullOrEmpty(ex.Message)) { Console.WriteLine("FLECK: " + message + " " + ex.Message); exceptionCounter++; if ((exceptionCounter % 200) == 0) { Helper.WriteTextAsyncWrapper("fleck_warn.txt", ex.ToString()); } } else { Console.WriteLine("FLECK: " + message); } break; default: Console.WriteLine("FLECK: " + message); break; } }; server.RestartAfterListenError = true; server.ListenerSocket.NoDelay = false; server.Start(socket => { socket.OnOpen = () => { string ipadr = string.Empty; try { ipadr = socket.ConnectionInfo.ClientIpAddress; } catch { } Client client = new Client(); client.WebSocket = socket; client.Created = client.LastPoolJobTime = DateTime.Now; Guid guid = socket.ConnectionInfo.Id; clients.TryAdd(guid, client); Console.WriteLine("{0}: connected with ip {1}", guid, ipadr); }; socket.OnClose = () => { Guid guid = socket.ConnectionInfo.Id; RemoveClient(socket.ConnectionInfo.Id); Console.WriteLine(guid + ": closed"); }; socket.OnError = error => { Guid guid = socket.ConnectionInfo.Id; RemoveClient(socket.ConnectionInfo.Id); Console.WriteLine(guid + ": unexpected close"); }; socket.OnMessage = message => { string ipadr = string.Empty; try { ipadr = socket.ConnectionInfo.ClientIpAddress; } catch { } Guid guid = socket.ConnectionInfo.Id; if (message.Length > 3000) { RemoveClient(guid); // that can't be valid, do not even try to parse } JsonData msg = message.FromJson <JsonData>(); if (msg == null || !msg.ContainsKey("identifier")) { return; } Client client = null; // in very rare occasions, we get interference with onopen() // due to async code. wait a second and retry. for (int tries = 0; tries < 4; tries++) { if (clients.TryGetValue(guid, out client)) { break; } Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(1)); }).Wait(); } if (client == null) { // famous comment: this should not happen RemoveClient(guid); return; } string identifier = (string)msg["identifier"]; if (identifier == "handshake") { if (client.GotHandshake) { // no merci for malformed data. DisconnectClient(client, "Handshake already performed."); return; } client.GotHandshake = true; if (msg.ContainsKey("version")) { int.TryParse(msg["version"].GetString(), out client.Version); } if (client.Version < 7) { DisconnectClient(client, "Client version too old."); return; } if (client.Version < 8) { CConsole.ColorWarning(() => Console.WriteLine("Warning: Outdated client connected. Make sure to update the clients")); } if (msg.ContainsKey("loginid")) { string loginid = msg["loginid"].GetString(); if (!loginids.TryGetValue(loginid, out Credentials crdts)) { Console.WriteLine("Unregistered LoginId! {0}", loginid); DisconnectClient(client, "Loginid not registered!"); return; } // prefer the password in the handshake, if it does not exist use the one in the loginid if (msg.ContainsKey("password")) { client.Password = msg["password"].GetString(); } else { client.Password = crdts.Password; } // use the values in the loginid for everything else client.Login = crdts.Login; client.Pool = crdts.Pool; } else if (msg.ContainsKey("login") && msg.ContainsKey("password") && msg.ContainsKey("pool")) { client.Login = msg["login"].GetString(); client.Password = msg["password"].GetString(); client.Pool = msg["pool"].GetString(); } else { // no merci for malformed data. Console.WriteLine("Malformed handshake"); DisconnectClient(client, "Login, password and pool have to be specified."); return; } client.UserId = string.Empty; if (msg.ContainsKey("userid")) { string uid = msg["userid"].GetString(); if (uid.Length > 200) { RemoveClient(socket.ConnectionInfo.Id); return; } client.UserId = uid; } Console.WriteLine("{0}: handshake - {1}, {2}", guid, client.Pool, (client.Login.Length > 8 ? client.Login.Substring(0, 8) + "..." : client.Login)); if (!string.IsNullOrEmpty(ipadr)) { Firewall.Update(ipadr, Firewall.UpdateEntry.Handshake); } PoolInfo pi; if (!PoolList.TryGetPool(client.Pool, out pi)) { // we dont have that pool? DisconnectClient(client, "pool not known"); return; } // if pools have some default password if (client.Password == "") { client.Password = pi.EmptyPassword; } client.PoolConnection = PoolConnectionFactory.CreatePoolConnection( client, pi.Url, pi.Port, client.Login, client.Password); client.PoolConnection.DefaultAlgorithm = pi.DefaultAlgorithm; } else if (identifier == "solved") { if (!client.GotHandshake) { RemoveClient(socket.ConnectionInfo.Id); return; } Console.WriteLine("{0}: reports solved hash", guid); new Task(() => { if (!msg.ContainsKey("job_id") || !msg.ContainsKey("nonce") || !msg.ContainsKey("result")) { // no merci for malformed data. RemoveClient(guid); return; } string jobid = msg["job_id"].GetString(); if (!jobInfos.TryGetValue(jobid, out JobInfo ji)) { // this job id is not known to us Console.WriteLine("Job unknown!"); return; } string reportedNonce = msg["nonce"].GetString(); string reportedResult = msg["result"].GetString(); if (ji.Solved.Contains(reportedNonce.ToLower())) { Console.WriteLine("Nonce collision!"); return; } if (reportedNonce.Length != 8 || (!Regex.IsMatch(reportedNonce, RegexIsHex))) { DisconnectClient(client, "nonce malformed"); return; } if (reportedResult.Length != 64 || (!Regex.IsMatch(reportedResult, RegexIsHex))) { DisconnectClient(client, "result malformed"); return; } double prob = ((double)HexToUInt32(ji.Target)) / ((double)0xffffffff); long howmanyhashes = ((long)(1.0 / prob)); totalHashes += howmanyhashes; if (ji.OwnJob) { // that was an "own" job. could be that the target does not match if (!CheckHashTarget(ji.Target, reportedResult)) { Console.WriteLine("Hash does not reach our target difficulty."); return; } totalOwnHashes += howmanyhashes; } // default chance to get hash-checked is 10% double chanceForACheck = 0.1; // check new clients more often, but prevent that to happen the first 30s the server is running if (Heartbeats > 3 && client.NumChecked < 9) { chanceForACheck = 1.0 - 0.1 * client.NumChecked; } bool performFullCheck = (Random2.NextDouble() < chanceForACheck && HashesCheckedThisHeartbeat < MaxHashChecksPerHeartbeat); if (performFullCheck) { client.NumChecked++; HashesCheckedThisHeartbeat++; } bool validHash = CheckHash(ji, reportedResult, reportedNonce, performFullCheck); if (!validHash) { CConsole.ColorWarning(() => Console.WriteLine("{0} got disconnected for WRONG hash.", client.WebSocket.ConnectionInfo.Id.ToString())); if (!string.IsNullOrEmpty(ipadr)) { Firewall.Update(ipadr, Firewall.UpdateEntry.WrongHash); } RemoveClient(client.WebSocket.ConnectionInfo.Id); } else { if (performFullCheck) { Console.WriteLine("{0}: got hash-checked", client.WebSocket.ConnectionInfo.Id.ToString()); } if (!string.IsNullOrEmpty(ipadr)) { Firewall.Update(ipadr, Firewall.UpdateEntry.SolvedJob); } ji.Solved.TryAdd(reportedNonce.ToLower()); if (!ji.OwnJob) { client.PoolConnection.Hashes += howmanyhashes; } Client jiClient = client; if (ji.OwnJob) { jiClient = ourself; } string msg1 = "{\"id\":\"" + jiClient.PoolConnection.PoolId + "\",\"job_id\":\"" + ji.InnerId + "\",\"nonce\":\"" + msg["nonce"].GetString() + "\",\"result\":\"" + msg["result"].GetString() + "\"}"; string msg0 = "{\"method\":\"" + "submit" + "\",\"params\":" + msg1 + ",\"id\":\"" + "1" + "\"}\n"; // TODO: check the "1" jiClient.PoolConnection.Send(jiClient, msg0); } }).Start(); } // identified == solved }; }); bool running = true; double totalSpeed = 0, totalOwnSpeed = 0; while (running) { Firewall.Heartbeat(Heartbeats++); try { Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(HeartbeatRate)); }).Wait(); if (Heartbeats % SpeedAverageOverXHeartbeats == 0) { totalSpeed = (double)totalHashes / (double)(HeartbeatRate * SpeedAverageOverXHeartbeats); totalOwnSpeed = (double)totalOwnHashes / (double)(HeartbeatRate * SpeedAverageOverXHeartbeats); totalHashes = 0; totalOwnHashes = 0; } CConsole.ColorInfo(() => Console.WriteLine("[{0}] heartbeat; connections client/pool: {1}/{2}; jobqueue: {3}k; speed: {4}kH/s", DateTime.Now.ToString(), clients.Count, PoolConnectionFactory.Connections.Count, ((double)jobQueue.Count / 1000.0d).ToString("F1"), ((double)totalSpeed / 1000.0d).ToString("F1"))); while (jobQueue.Count > JobCacheSize) { if (jobQueue.TryDequeue(out string deq)) { jobInfos.TryRemove(deq); } } DateTime now = DateTime.Now; List <PoolConnection> pcc = new List <PoolConnection>(PoolConnectionFactory.Connections.Values); foreach (PoolConnection pc in pcc) { PoolConnectionFactory.CheckPoolConnection(pc); } List <Client> cc = new List <Client>(clients.Values); foreach (Client c in cc) { try { if ((now - c.Created).TotalSeconds > GraceConnectionTime) { if (c.PoolConnection == null || c.PoolConnection.TcpClient == null) { DisconnectClient(c, "timeout."); } else if (!c.PoolConnection.TcpClient.Connected) { DisconnectClient(c, "lost pool connection."); } else if ((now - c.LastPoolJobTime).TotalSeconds > PoolTimeout) { DisconnectClient(c, "pool is not sending new jobs."); } } } catch { RemoveClient(c.WebSocket.ConnectionInfo.Id); } } if (clients.ContainsKey(Guid.Empty)) { if (clients.Count == 1) { RemoveClient(Guid.Empty); } } else { if (clients.Count > 4 && Donation.DonationLevel > double.Epsilon) { CConsole.ColorWarning(() => Console.WriteLine("disconnected from own pool. trying to reconnect.")); ownJob = new Job(); CreateOurself(); } } HashesCheckedThisHeartbeat = 0; if (Heartbeats % ForceGCEveryXHeartbeat == 0) { CConsole.ColorInfo(() => { Console.WriteLine("Currently using {1} MB ({0} clients).", clients.Count, Math.Round(((double)(GC.GetTotalMemory(false)) / 1024 / 1024))); }); } } catch (Exception ex) { CConsole.ColorAlert(() => Console.WriteLine("Exception caught in the main loop ! {0}", ex)); } } }