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)); } } }
private static void ReceiveCallback(IAsyncResult result) { PoolConnection mypc = result.AsyncState as PoolConnection; TcpClient client = mypc.TcpClient; if (!client.Connected) { return; } NetworkStream networkStream; try { networkStream = client.GetStream(); } catch { return; } int bytesread = 0; try { bytesread = networkStream.EndRead(result); } catch { return; } string json = string.Empty; try { if (bytesread == 0) // disconnected { // slow that down a bit to avoid negative feedback loop Task.Run(async delegate { await Task.Delay(TimeSpan.FromSeconds(4)); List <Client> cllist = new List <Client> (mypc.WebClients.Values); foreach (Client ev in cllist) { Disconnect(ev, "lost pool connection."); } }); return; } json = ASCIIEncoding.ASCII.GetString(mypc.ReceiveBuffer, 0, bytesread); networkStream.BeginRead(mypc.ReceiveBuffer, 0, mypc.ReceiveBuffer.Length, new AsyncCallback(ReceiveCallback), mypc); } catch { return; } if (bytesread == 0 || string.IsNullOrEmpty(json)) { return; //?! } var msg = json.FromJson <JsonData> (); if (msg == null) { return; } if (string.IsNullOrEmpty(mypc.PoolId)) { // this "protocol" is strange if (!msg.ContainsKey("result")) { string additionalInfo = "none"; // try to get the error if (msg.ContainsKey("error")) { msg = msg["error"] as JsonData; if (msg != null && msg.ContainsKey("message")) { additionalInfo = msg["message"].GetString(); } } List <Client> cllist = new List <Client> (mypc.WebClients.Values); foreach (Client ev in cllist) { Disconnect(ev, "can not connect. additional information: " + additionalInfo); } return; } msg = msg["result"] as JsonData; if (msg == null) { return; } if (!msg.ContainsKey("id")) { return; } if (!msg.ContainsKey("job")) { return; } mypc.PoolId = msg["id"].GetString(); var lastjob = msg["job"] as JsonData; if (!VerifyJob(lastjob)) { CConsole.ColorWarning(() => Console.WriteLine("Failed to verify job: {0}", json)); return; } // extended stratum if (!lastjob.ContainsKey("variant")) { lastjob.Add("variant", mypc.DefaultVariant); } if (!lastjob.ContainsKey("algo")) { lastjob.Add("algo", mypc.DefaultAlgorithm); } string normalized; if (!AlgorithmHelper.Normalize(lastjob["algo"].GetString(), out normalized)) { CConsole.ColorAlert(() => { Console.WriteLine("Pool " + mypc.Url + " requests unknown algorithm: " + lastjob["algo"].GetString()); Console.WriteLine("Job ignored!"); }); return; } lastjob["algo"] = normalized; mypc.LastJob = lastjob; mypc.LastInteraction = DateTime.Now; mypc.LastSolved = new CcHashset <string> (); List <Client> cllist2 = new List <Client> (mypc.WebClients.Values); foreach (Client ev in cllist2) { ReceiveJob(ev, mypc.LastJob, mypc.LastSolved); } } else if (msg.ContainsKey("method") && msg["method"].GetString() == "job") { if (!msg.ContainsKey("params")) { return; } var lastjob = msg["params"] as JsonData; if (!VerifyJob(lastjob)) { CConsole.ColorWarning(() => Console.WriteLine("Failed to verify job: {0}", json)); return; } // extended stratum if (!lastjob.ContainsKey("variant")) { lastjob.Add("variant", mypc.DefaultVariant); } if (!lastjob.ContainsKey("algo")) { lastjob.Add("algo", mypc.DefaultAlgorithm); } string normalized; if (!AlgorithmHelper.Normalize(lastjob["algo"].GetString(), out normalized)) { CConsole.ColorAlert(() => { Console.WriteLine("Pool " + mypc.Url + " requests unknown algorithm: " + lastjob["algo"].GetString()); Console.WriteLine("Job ignored!"); }); return; } lastjob["algo"] = normalized; mypc.LastJob = lastjob; mypc.LastInteraction = DateTime.Now; mypc.LastSolved = new CcHashset <string> (); List <Client> cllist2 = new List <Client> (mypc.WebClients.Values); Console.WriteLine("Sending job to {0} client(s)!", cllist2.Count); foreach (Client ev in cllist2) { ReceiveJob(ev, mypc.LastJob, mypc.LastSolved); } } else { if (msg.ContainsKey("error")) { // who knows? ReceiveError(mypc.LastSender, msg); } else { CConsole.ColorWarning(() => Console.WriteLine("Pool is sending nonsense.")); } } }