Beispiel #1
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));
                }
            }
        }
        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."));
                }
            }
        }