Exemple #1
0
        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);
                }
            }
        }
Exemple #2
0
        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));
                }
            }
        }