/* Proxy pour les mineurs bitcoin. Il relai la communication au pool disponible. Si aucun pool ne répond (panne internet) il envoie du travail bidon pour forcer les mineurs à travailler, continuant à chauffer la serre. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "json.h" using namespace std; static DEBUG_KEY dmsg ("msg","Protocol stratum"); static DEBUG_KEY dcon ("con","Connexions"); static DEBUG_KEY dctrl ("ctrl","Controle du failmode"); struct CLIENTINFO: public ARRAY_OBJ{ int tofd = -1; unsigned noserver = 0; // Connexion asynchrone, on se rappelle le serveur qu'on a essayé enum class TYPE {UNKNOWN,MINEUR,SERVEUR,CONTROL,CONTROLTCP} type = TYPE::UNKNOWN; time_t start = time(nullptr); unsigned long nbmsg = 0; unsigned long from = 0; string server; // A quel serveur sommes nous connecté. const char *typestr() const { const char *ret = "???"; switch (type){ case TYPE::UNKNOWN: ret = "unknown"; break; case TYPE::MINEUR: ret = "mineur"; break; case TYPE::SERVEUR: ret = "serveur"; break; case TYPE::CONTROL: ret = "control"; break; case TYPE::CONTROLTCP: ret = "controltcp"; break; } return ret; } }; static int stratum_connect (const vector &servers, const char *port, unsigned &no, string &server) { int ret = -1; for (; no < servers.size(); ){ auto &s = servers[no]; no++; server = s; ret = cmdsock_connect_async ("0.0.0.0",s.c_str(),port); debug_printf (dcon,"connect no=%u ret=%d s=%s\n",no,ret,s.c_str()); if (ret != -1) break; tlmp_error ("Ne peut connecter au serveur %s\n",s.c_str()); } return ret; } /* Transmet une ligne à plusieurs clients */ static void stratum_printcons (const set &printcons, const char *type, PARAM_STRING line) { int len = strlen(line.ptr); string typestr = string_f("%s: ",type); for (auto fd:printcons){ write (fd,typestr.c_str(),typestr.size()); write (fd,line.ptr,len); // Il y a parfois un \n ou pas if (len > 0 && line.ptr[len-1] != '\n') write (fd,"\n",1); } } static void statum_fake(_F_TCPSERVER_V1 *c, const char *line, const set &printcons) { glocal int id=-1; glocal string method; glocal string mask; (line); if (is_eq(name,"id")){ glocal.id = atoi(value); }else if (is_eq(name,"method")){ glocal.method = value; }else if (is_eq(name,"version-rolling.mask")){ glocal.mask = value; } //printf ("JSON: parent=%s name=%s value=%s\n",parent,name,value); string res; if (glocal.method == "mining.configure"){ res = string_f ("{\"id\":%d,\"result\":{\"version-rolling\":true,\"version-rolling.mask\":\"%s\"},\"error\":null}\n" ,glocal.id,glocal.mask.c_str()); res += string_f("{\"id\":null,\"method\":\"mining.set_version_mask\",\"params\":[\"%s\"]}\n",glocal.mask.c_str()); }else if (glocal.method == "mining.subscribe"){ res = string_f("{\"id\":%d,\"result\":[[[\"mining.set_difficulty\",\"1\"],[\"mining.notify\",\"1\"]],\"1565040039010e\",8],\"error\":null}\n" ,glocal.id); res += string_f("{\"id\":null,\"method\":\"mining.set_difficulty\",\"params\":[8192]}\n"); res += "{\"id\":null,\"method\":\"mining.notify\",\"params\":[\"c09569500\",\"1f4b172d72b146ed51f3fac500ad1d5f58f333f5000030260000000000000000\",\"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4b036f0c0bfabe6d6d84dee25ef6dea92ed3c5b95894564e1201d674b19ffb610e893b982c919ca4070100000000000000\",\"009556090c2f736c7573682f0000000003693a7525000000001976a9147c154ed1dc59609e3d26abb2df2ea3d587cd8c4188ac00000000000000002c6a4c2952534b424c4f434b3a2256673ff14df0bd71d86f522e380873aa00a1e4d27ed13a732af236003e87480000000000000000266a24aa21a9edf6fa51359437f1a20362246d59efa981f1803ea27194f50c24799b912ba7a21d00000000\",[\"288c90583a3c5c290cce6cf2f30e861626644f34fc401ef096b94508c2fb6d39\",\"96169709c315ffb1666f91cc0e119325ecb06cf7df4d856eef771aa7ee5142ce\",\"a9ee4a128ca4e654a880d4d69128644acd911028815d305ecffdf4f267d986a7\",\"fa70f6ad1f07b577ec5b107eb3f8b05018313d021fd22c44f5a0ec9441caa105\",\"2081dc167ca39b7e8fd25c2c137235e8eec60400229a69a6148ceb35c5c9a76b\",\"02ed56f2e8bb1bfdc5441d6359035d011c2f73b8bc60991644a807280a715041\",\"617eb69ade334107ad80294de90cba790d069876e71bad74aafffc842d4cd05e\",\"6ecc44565dc7bfe54596972d18d19ba70d36846bc629f40514a287eac9b382b6\",\"f6759d5376f8a601955f73804200c99345d8b84ed5925c233a742283017b7b2b\",\"c3ee09adacf84220ead3f601e23fdfaff15d2c0599775e5aee2bb12cdbb58b6d\",\"91ae4710cfe8318b6e672cf8ddd3d3b50fdee4b0fa9a736a0c694c57f120bd78\",\"e529e6e4bbd7ceaf62d56b5bc86c74075b2fdb3d47df96fb0e3036f1cc7f7205\"],\"20000004\",\"170a1078\",\"62114c69\",true]}\n"; }else{ res = string_f("{\"id\":%d,\"result\":true,\"error\":null}\n",glocal.id); } debug_printf (dmsg,"res=%s",res.c_str()); c->send (res.c_str()); stratum_printcons (printcons,"SIMUL",res); } /* Ferme tous les connexions provenant des mineurs */ template static void stratum_closeminers(T *tc) { for (auto x:tc->get_connections()){ auto c = (CLIENTINFO*)x.data; if (is_any_of(c->type,CLIENTINFO::TYPE::MINEUR,CLIENTINFO::TYPE::SERVEUR)){ tc->closeclient (x.fd); } } } /* Compte le nombre de mineurs et de serveurs */ template static void stratum_countminers(T *tc, unsigned &nbmineurs, unsigned &nbserveurs) { nbmineurs = 0; nbserveurs = 0; for (auto x:tc->get_connections()){ auto c = (CLIENTINFO*)x.data; if (c->type == CLIENTINFO::TYPE::MINEUR){ nbmineurs++; }else if (c->type == CLIENTINFO::TYPE::SERVEUR){ nbserveurs++; } } } static void stratum_controlcmd(const char *unixport, const char *cmd) { glocal cmd; ("unix:",unixport,5); sendf ("%s\n",glocal.cmd); printf ("%s\n",line); } static string stratum_formattime(time_t t) { struct tm *tt = localtime (&t); return string_f ("%04d-%02d-%02d %02d:%02d:%02d" ,tt->tm_year+1900,tt->tm_mon+1,tt->tm_mday ,tt->tm_hour,tt->tm_min,tt->tm_sec); } int main (int argc, char *argv[]) { glocal int ret = -1; glocal const char *unixport = "/var/run/stratumproxy.sock"; glocal const char *tcpport = "3333"; glocal const char *serverport = "3333"; glocal const char *controltcp = "3334"; glocal vector servers; glocal bool failmode = false; glocal bool daemon = false; glocal bool serveur = false; glocal const char *user = "root"; glocal const char *pidfile = "/var/run/stratumproxy.pid"; // Options pour envoyer des commandes au serveur glocal bool status = false; glocal bool printcon = false; glocal bool cmd_failmode = false; glocal bool cmd_onmode = false; glocal bool cmd_noreconmode = false; glocal bool cmd_reconmode = false; glocal bool quit = false; glocal bool connections = false; glocal int maxidle = 30; for (auto s:{"ca.stratum.slushpool.com","us-east.stratum.slushpool.com","stratum.slushpool.com"}){ glocal.servers.push_back(s); } glocal.ret = (argc,argv); setproginfo ("stratumproxy","0.0" ,"Proxy/Simulateur pour protocol stratum V1"); setarg ('f',"failmode","Ne connecte jamais à un serveur",glocal.failmode,false); setarg ('p',"tcpport","Port TCP",glocal.tcpport,false); setarg ('P',"serverport","Port TCP du serveur",glocal.serverport,false); setarg ('s',"serverpool","Serveur POOL",glocal.servers,false); setarg ('u',"control","Port unix pour le contrôle",glocal.unixport,false); setarg (' ',"controltcp","Port TCP pour le contrôle",glocal.controltcp,false); setarg (' ',"maxidle","Durée d'inactivité déclenchant le failmode",glocal.maxidle,false); setgrouparg ("Mode serveur"); setarg (' ',"serveur","Mode serveur",glocal.serveur,false); setarg (' ',"daemon","Mode daemon",glocal.daemon,false); setarg (' ',"pidfile","Fichier PID",glocal.pidfile,false); setarg (' ',"user","Exécute en tant que l'utilisateur",glocal.user,false); setgrouparg ("client"); setarg (' ',"status","État du service",glocal.status,false); setarg (' ',"connections","État des connexions",glocal.connections,false); setarg (' ',"printcon","Affiche les communications entre les mineurs et les serveurs",glocal.printcon,false); setarg (' ',"cmd_failmode","Active failmode",glocal.cmd_failmode,false); setarg (' ',"cmd_onmode","Revient au mode normal",glocal.cmd_onmode,false); setarg (' ',"cmd_reconmode","Test l'Internet en mode failmode",glocal.cmd_reconmode,false); setarg (' ',"cmd_noreconmode","Ne test pas l'Internet en mode failmode",glocal.cmd_noreconmode,false); setarg (' ',"quit","Demande au serveur de terminer",glocal.quit,false); if (glocal.daemon){ syslog (LOG_ERR,"%s",msg); }else{ fprintf (stderr,"%s",msg); } if (glocal.daemon){ syslog (LOG_WARNING,"%s",msg); }else{ fprintf (stderr,"%s",msg); } glocal set printcons; // Socket client qui reçoivent une copie des communications glocal time_t last_server_msg = time(nullptr); glocal int ret = 0; if (glocal.status){ stratum_controlcmd (glocal.unixport,"status"); }else if (glocal.printcon){ stratum_controlcmd (glocal.unixport,"printcon"); }else if (glocal.cmd_failmode){ stratum_controlcmd (glocal.unixport,"failmode"); }else if (glocal.cmd_onmode){ stratum_controlcmd (glocal.unixport,"onmode"); }else if (glocal.cmd_reconmode){ stratum_controlcmd (glocal.unixport,"reconmode"); }else if (glocal.cmd_noreconmode){ stratum_controlcmd (glocal.unixport,"noreconmode"); }else if (glocal.connections){ stratum_controlcmd (glocal.unixport,"connections"); }else if (glocal.quit){ stratum_controlcmd (glocal.unixport,"quit"); }else if (glocal.serveur){ glocal bool reconmode = true; // Test l'état de l'Internet en mode failmode glocal string recon_status; glocal time_t failed_since = (time_t)0; glocal FORKTASK *recon = nullptr; glocal TCPSERVER_V1 *tcpo = nullptr; glocal function fstatus; glocal function fconnections; glocal function set_failmode; glocal.fstatus = [&](_F_TCPSERVER_V1 *c){ c->sendf ("failmode=%d\n",glocal.failmode); if (glocal.failmode){ c->sendf ("failed_since=%s\n",stratum_formattime(glocal.failed_since).c_str()); }else{ c->send ("failed_since=\n"); } c->sendf ("reconmode=%d\n",glocal.reconmode); c->sendf ("reconstatus=%s\n",glocal.recon_status.c_str()); c->sendf ("maxidle=%d\n",glocal.maxidle); c->sendf ("lastmsg=%ld\n",time(nullptr)-glocal.last_server_msg); c->sendf ("printcons=%zu\n",glocal.printcons.size()); unsigned nbserveurs = 0; unsigned nbmineurs = 0; stratum_countminers(c,nbmineurs,nbserveurs); c->sendf ("nbmineurs=%u\n",nbmineurs); c->sendf ("nbserveurs=%u\n",nbserveurs); }; glocal.fconnections = [](_F_TCPSERVER_V1 *tc){ for (auto x:tc->get_connections()){ auto c = (CLIENTINFO*)x.data; tc->sendf ("%3d(%3d): %-10s %s %lu.%lu.%lu.%lu %s %lu\n",x.fd,c->tofd,c->typestr() ,stratum_formattime(c->start).c_str() ,c->from>>24,(c->from>>16)&0xff,(c->from>>8)&0xff,c->from&0xff,c->server.c_str(),c->nbmsg); } }; glocal.set_failmode = [&](bool mode){ glocal.failmode = mode; stratum_closeminers(glocal.tcpo); glocal.recon_status.clear(); glocal.failed_since = (time_t)0; if (mode){ glocal.recon->send ("essai"); // On commence immédiatement à vérifier si l'internet revient tlmp_warning ("Active failmode\n"); glocal.failed_since = time(nullptr); } }; (string_f("unix:%s",glocal.unixport),10); CLIENTINFO *c = new CLIENTINFO; if (strncmp(info.port,"unix:",5)==0){ c->type = CLIENTINFO::TYPE::CONTROL; }else if (is_eq(info.port,glocal.controltcp)){ c->type = CLIENTINFO::TYPE::CONTROLTCP; c->from = from; }else if (is_eq(info.port,glocal.tcpport)){ settcpnodelay(true); c->type = CLIENTINFO::TYPE::MINEUR; c->from = from; unsigned noserver = 0; string server; if (!glocal.failmode){ // Essai de connecter au premier serveur pool c->tofd = stratum_connect (glocal.servers,glocal.serverport,noserver,server); } // Si on arrive pas à connecter à un serveur, on simulera des réponses // pour forcer le mineur à travailler // Donc ça ne fait rien si c->tofd == -1 if (c->tofd != -1){ c->server = server; CLIENTINFO *sc = new CLIENTINFO; sc->tofd = no; sc->type = CLIENTINFO::TYPE::SERVEUR; sc->noserver = noserver; sc->from = from; sc->server = server; if (inject_connecting (c->tofd,sc) == 0){ // N'écoute pas le client tant que le serveur n'est pas connecté setlisten (no,false); }else{ settcpnodelay(c->tofd,true); } } } info.data = c; CLIENTINFO *c = (CLIENTINFO*)info.data; //fprintf (stderr,"endclient no=%d %s\n",no,c->typestr()); if (is_any_of(c->type,CLIENTINFO::TYPE::MINEUR,CLIENTINFO::TYPE::SERVEUR)){ closeclient (c->tofd); } glocal.printcons.erase(no); CLIENTINFO *c = (CLIENTINFO*)data; if (ev == TCPSERVER_CONNECTED){ debug_printf (dcon,"Serveur %d connecté tofd=%d\n",no,c->tofd); settcpnodelay(true); setlisten (c->tofd,true); }else if (ev == TCPSERVER_CONNECTFAIL){ unsigned noserver = c->noserver; string server; int nofd = stratum_connect (glocal.servers,glocal.serverport,c->noserver,server); debug_printf (dcon,"Serveur %d ne peut connecter, noserver=%u nofd=%d\n",no,noserver,nofd); if (nofd == -1){ closeclient (c->tofd); // On arrive pas à connecter un des serveurs pool. if (!glocal.failmode){ tlmp_warning ("Active failmode parce que les connexions aux pools ont toutes échouées\n"); glocal.set_failmode(true); } }else{ CLIENTINFO *sc = new CLIENTINFO; sc->tofd = c->tofd; sc->from = c->from; sc->type = CLIENTINFO::TYPE::SERVEUR; sc->noserver = c->noserver; sc->server = server; CLIENTINFO *cc = (CLIENTINFO*)getclientdata(c->tofd); cc->tofd = nofd; cc->server = server; inject_connecting (nofd,sc); } }else if (ev == TCPSERVER_OUTFULL){ tlmp_warning ("TCPSERVER_OUTFULL %d %s\n",no,c->typestr()); if (is_any_of(c->type,CLIENTINFO::TYPE::MINEUR,CLIENTINFO::TYPE::MINEUR)){ glocal.set_failmode (true); } endclient = true; } CLIENTINFO *c = (CLIENTINFO*)info.data; c->nbmsg++; if (c->type == CLIENTINFO::TYPE::CONTROL){ endclient = true; if (is_eq(line,"status")){ glocal.fstatus(this); }else if (is_eq(line,"failmode")){ if (glocal.failmode){ send ("Mode dépannage déjà actif\n"); }else{ glocal.set_failmode (true); send ("Mode dépannage actvé\n"); tlmp_warning ("Activation manuelle du mode de dépannage\n"); } }else if (is_eq(line,"onmode")){ if (glocal.failmode){ glocal.set_failmode(false); send ("Mode normal actvé\n"); tlmp_warning ("Activation manuelle du mode production\n"); }else{ send ("Mode normal déjà actif\n"); } }else if (is_eq(line,"reconmode")){ if (glocal.reconmode){ send ("reconmode déjà actif\n"); }else{ tlmp_warning ("Mode reconmode activé par l'opérateur\n"); glocal.reconmode = true; send ("Mode reconmode activé\n"); if (glocal.failmode){ glocal.recon->send ("essai"); // On commence immédiatement à vérifier si l'internet revient } } }else if (is_eq(line,"noreconmode")){ if (!glocal.reconmode){ send ("noreconmode déjà actif\n"); }else{ tlmp_warning ("Mode noreconmode activé par l'opérateur\n"); glocal.reconmode = false; // Il faudra repartir le serveur pour revenir normal // ou appeler cmp_reconmode send ("Mode noreconmode activé\n"); } }else if (is_eq(line,"printcon")){ // Les communications seront transmises au client glocal.printcons.insert(no); endclient = false; }else if (is_eq(line,"quit")){ send ("Le serveur termine\n"); endserver = true; }else if (is_eq(line,"connections")){ glocal.fconnections(this); }else{ sendf ("Commande invalide\n"); } }else if (c->type == CLIENTINFO::TYPE::CONTROLTCP){ endclient = true; if (is_eq(line,"status")){ glocal.fstatus(this); }else if (is_eq(line,"connections")){ glocal.fconnections(this); }else{ sendf ("Commande invalide\n"); } }else{ if (c->type == CLIENTINFO::TYPE::SERVEUR) glocal.last_server_msg = time(nullptr); debug_printf (dmsg,"MSG: %s: %s\n",c->typestr(),line); stratum_printcons (glocal.printcons,c->typestr(),line); if (c->tofd == -1){ statum_fake (this,line,glocal.printcons); }else{ sendtof (c->tofd,"%s\n",line); } } if (o.is_ok()){ o.setnonblock(true,100000); for (auto port:{glocal.tcpport,glocal.controltcp}){ int tcphandle = o.listen(port); if (tcphandle == -1){ tlmp_error ("Ne peut écouter sur le port %s (%s)\n",port,strerror(errno)); exit (-1); } } chmod (glocal.unixport,0666); if (glocal.daemon) daemon_init (glocal.pidfile,glocal.user); glocal.tcpo = &o; // Tache pour verifier si la connectivité avec les pools est revenu (); /* On tente de se connecter à un des serveurs. On a tout notre temps. On peut bloquer. On est dans un autre processus. On envoie ok si on a réussi, fail sinon. Pour éviter des flip/flop parce que l'Internet est instable, on tente une connexion à toutes les 5 secondes. Si ça on arrive à avoir nbtry succès, on retourne ok, sinon fail. */ int nbok = 0; const int nbtry = 24; for (int i=0; i // Il se peut que l'operateur ait forcé failmode=false via la commande cmd_onmode // Donc on doit vérifier si failmode=true pour faire quoi que ce soit if (glocal.failmode && glocal.reconmode){ const char *pt; if (is_eq(line,"ok")){ tlmp_warning ("Connectivité revenue, désactive failmode\n"); glocal.set_failmode(false); }else if (is_start_any_of(line,pt,"status")){ glocal.recon_status = str_skip(pt); }else{ // On demande de recommencer debug_printf (dcon,"recon main recoit fail\n"); send ("essai"); // Le message n'a pas d'importance } } glocal.recon = &recon; (5); debug_printf (dcon,"main idle %s\n",line); // Si on dépasse un délai d'inactivité, alors on passe en 'failmode' // et on ferme tous les clients. Ils vont se reconnecter et passer en mode simulation (fake). time_t now = time(nullptr); if (glocal.failmode){ glocal.last_server_msg = now; }else{ auto diff = now-glocal.last_server_msg; unsigned nbmineurs,nbserveurs; stratum_countminers(glocal.tcpo,nbmineurs,nbserveurs); if (nbmineurs > 0){ debug_printf (dctrl,"dernier message depuis %ld, nbmineurs=%u\n",diff,nbmineurs); if (diff > glocal.maxidle){ glocal.set_failmode(true); debug_printf (dctrl,"Active failmode\n"); } }else{ // On remet à zéro, sinon, lors de la première connexion // d'un mineur, il risque de se faire déconnecté et on // va tomber en failmode. glocal.last_server_msg = now; } } netevent_loop (o,idle,recon); } }else{ usage(); } return glocal.ret; return glocal.ret; }