diff --git a/API-README b/API-README index a1c54c55..7e5fac88 100644 --- a/API-README +++ b/API-README @@ -383,7 +383,7 @@ miner.php - an example web page to access the API Feature Changelog for external applications using the API: -API V1.19 +API V1.19 (cgminer v2.7.6) Added API commands: 'debug' diff --git a/Makefile.am b/Makefile.am index e37abf46..f4ed5e1d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,7 +32,7 @@ cgminer_CPPFLAGS = -I$(top_builddir)/lib -I$(top_srcdir)/lib @OPENCL_FLAGS@ cgminer_SOURCES := cgminer.c cgminer_SOURCES += elist.h miner.h compat.h bench_block.h \ - util.c uthash.h logging.h \ + util.c util.h uthash.h logging.h \ sha2.c sha2.h api.c cgminer_SOURCES += logging.c diff --git a/NEWS b/NEWS index 115bf83c..ac6e4a56 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,230 @@ +Version 2.8.3 - October 12, 2012 + +- Left align values that are suffix_string generated. +- Share_diff should not be converting the work data to hex. +- Off by one error. +- Prevent overflows of the port char array in extract_sockaddr. +- Disable stratum detection with scrypt. +- Use the suffix string function when displaying device hashrates. +- Be consistent with the get_statline function. +- Use the suffix string function for displaying hashrate with 4 significant +digits. +- Display the actual share diff next to the pool required diff, using a suffix +creation function to prevent values of >1000 being shown in their entirety. +- Fix 4 * 0 being 0 that would break dynamic intensity mode. +- Fix wrong byteswap macro being used on mingw32 which was breaking target +generation on stratum. + + +Version 2.8.2 - October 11, 2012 + +- Reinstate the history on dynamic intensity mode to damp fluctuations in +intensity but use an upper limit on how much the value can increase at any time +to cope with rare overflows. +- Create a fix-protocol option which prevents cgminer from switching to stratum +if it's detected. +- Simplify target generation code. +- Add support for client.get_version for stratum. +- Use a 64 bit unsigned integer on the diff target to generate the hex target. +- Update reconnect message to show whole address including port. +- Look for null values and parse correct separate array entries for url and port +with client reconnect commands for stratum. +- The command for stratum is client.reconnect, not mining.reconnect. +- Only copy the stratum url to the rpc url if an rpc url does not exist. +- Implement rudimentary mining.reconnect support for stratum. +- Ignore the value of stratum_active on calling initiate_stratum and assume +we're always trying to reinitiate it, and set the active flag to false in that +function. +- stratum auth can be unset if we fail to authorise on subsequent calls to +auth_stratum which undoes the requirement of setting it in one place so set it +in pool_active. + + +Version 2.8.1 - October 8, 2012 + +- Use the stratum url as the rpc url advertised if we switch to it. +- Count an invalid nonce count as a hardware error on opencl. +- Count each stratum work item as local work. +- Cope with one stratum pool being the only active pool when it dies by sleeping +for 5 seconds before retrying to get work from it instead of getting work +indefinitely. +- Detect stratum outage based on either select timing out or receiving an empty +buffer and properly re-establish connection by disabling the stratum_active +flag, coping with empty buffers in parse_stratum. + + +Version 2.8.0 - October 7, 2012 + +- Major upgrade - support for the stratum mining protocol. +- Fix various modminer warnings on mingw. +- Fix sign warning on windows build for bitforce. +- Cast socketfail to integer since SOCKET is an unsigned int on windows. +- Use strtod not strtol for bitforce temp backup. +- Cope with broken drivers returning nonsense values for bitforce temperatures. +- Minor warning fixes. +- Use the stratum thread to detect when a stratum pool has died based on no +message for 2 minutes. +- Only set the stratum auth flag once and once the stratum thread is started, +use that to set/unset the stratum active flag. +- Only hand off to stratum from getwork if we succeed in initiating the +protocol. +- Target should only be 32 bytes copied. +- Use a static array for work submission data instead of stack memory. +- Clear the buffer data before sprinting to it. +- Clear work stratum strings before setting them and add them to debug output. +- Drop stratum connect failed message to verbose level only since it's a regular +probing message. +- TCP Keepalive in curl is only in very recent versions and not required with +regular messages on stratum anyway. +- Move stratum sockets to curl infrastructure with locking around send+recv to +begin support for proxies and ssl. +- Make detect stratum fail if a proxy has been set up. +- Stratum does not currently have any proxy support so do not try to switch to +stratum if a proxy has been specified. +- Windows doesn't work with MSG_PEEK on recv so move to a continuously updating +buffer for incoming messages. +- Alloca is unreliable on windows so use static arrays in util.c stratum code. +- Begin support for mingw stratum build. +- Add space to reject reason. +- Parse the reject reason where possible from stratum share submission. +- Pass json error value to share result function to be able to parse reject +reason in stratum. +- Don't try to parse unneeded parameters in response to mining.subscribe. +- Remove the sshare hash entry if we failed to send it. +- Change notify message to info level to avoid spamming repeatedly when a pool +is down. +- Check the stratum pool difference has not changed compared to the work diff +when testing whether a share meets the target or not and retarget if necessary. +- Bit error in target calculation for stratum. +- Set work_block in gen_stratum_work for when work is reused to avoid thinking +it's all stale. +- Offset the current block detection to the prev block hash. +- We should be testing for id_val, not id in parse stratum response. +- Make target on stratum scale to any size by clearing sequential bits according +to diff. +- Correct target calculation in gen_stratum_work. +- If a share result has an error code but still has an id, it is likely a +reject, not an error. +- Initiate stratum the first time in pool_active only, allowing us to switch to +it on getting a failed getwork and detecting the presence of stratum on the url +at that time. +- Use 5 second timeout on sock full for now as a temporary workaround. +- If no stratum url is set by the end of the detect stratum routine, copy the +sockaddr url. +- Make all buffers slightly larger to prevent overflow. +- Make the stratum recv buffer larger than the recvsize. +- Userpass needs to be copied to user and pass earlier to allow stratum +authorisation to work with it. +- Store a sockaddr url of the stripped url used in determining sockaddr to not +confuse it with the stratum url and fix build warnings. +- Decrease the queued count with stratum work once it's staged as well. +- Allow the stratum retry to initiate and auth stratum in pool_alive to make +sure the stratum thread is started. +- Avoid duplicating pool->rpc_url and setting pool->stratum_url twice to itself. +- Detect if a getwork based pool has the X-Stratum header on startup, and if so, +switch to the stratum based pool. +- Comment update. +- Minor message change. +- Create a work item from a "clean" request from stratum allowing the new block +to be detected and the appropriate block change message to be given. +- Use statically allocated stratum strings in struct work to cope with the +inability to safely deallocate dynamically allocated ram. +- Use the current pool when deciding whether to reuse work from a stratum source +rather than the work's previous pool. +- Copy the stratum url to the rpc url to avoid none being set. +- Provide locking around stratum send operations to avoid races. +- Submit shares from stratum through the abstracted submit share function +detecting what message they belong to and showing the data from the associated +work, and then deleting it from the hash. +- Use a more robust mechanism to obtain a \n terminated string over a socket. +- Abstract out share submit as a function to be useable by stratum. +- Rename parse_stratum to parse_method as it is only for stratum messages that +contain methods. +- Display stratum as mechanism in status line when current pool is running it. +- Count each stratum notify as a getwork equivalent. +- Correct nonce submitted with share. +- Extranonce2 should be added before coinbase2. +- We should be hashing the binary coinbase, not the hex one. +- Fix endianness of nonce submitted for stratum. +- Check that stratum is already active in initiate_stratum to avoid +de-authorising ourselves by subscribing again. +- Begin implementing a hash database of submissions and attempt sending results. +- Copy parameters from stratum work required for share submission. +- Set lagging flag on first adding a pool to prevent pool slow warning at +startup. +- Fix work->target being a 32 byte binary in gen_stratum_work. +- Store and display stripped url in its own variable. +- Create machinery to divert work requests to stratum. +- Generate the work target in gen_stratum_work, setting default diff to 1 in +case it is not yet set. +- Generate work data, midstate and hash1 in gen_stratum_work. +- Generate header created from stratum structures in gen_stratum_work. +- Generate merkle root hash in gen_stratum_work. +- Generate the coinbase for generation of stratum based work. +- The number of transactions is variable so make merkle a variable length +dynamically allocated array and track how many there are for stratum. +- Rename nonce2 to n2size reflecting that it's a size variable and not the +actual nonce. +- Provide rudimentary support for stratum clean work command in the stratum +thread. +- Cope with pools being removed in the stratum thread. +- Use the pool sock value directly in the stratum thread in case it changes +after reconnecting. +- Create a stratum thread per pool that has stratum that monitors the socket and +serves received data. +- Check return value of stratum_parse. +- Complete authorisation in stratum. +- Implement stratum parsing of notify parameters and storing them in the pool +stratum work structure. +- Create helper functions for duplicating json strings to avoid keeping json +references in use. +- Append \n in the sock_send function instead of adding it when constructing +json in stratum. +- Don't keep any json references around with stratum structures. +- Create parse_stratum function that hands off stratum parameters to other +functions to manage pool stratum work struct variables. Implement mining +difficulty setting. +- Create helper functions for checking when a socket is ready to read on and +receive a single line at a time. Begin stratum authorisation process. +- Provide a helper function for reading a single \n terminated string from a +socket. +- Create a stratum work structure to store current work variables. +- Test specifically for stratum being active in pool_active. +- Detect stratum in common place when adding urls, and use a bool to tell us +when it's active. +- Fix warnings. +- Extract and store various parameters on stratum init confirming successful +mining notify. +- Use existing socket macros and close the socket on failure in init stratum. +- Initiate stratum and grab first json result. +- Get detailed addressinfo from the parsed URL for future raw socket usage when +possible. IPV4 only for now. +- Prepare for getaddrinfo call. +- Add data structures to pool struct for socket communications. +- Put all socket definitions in util.h to allow reusing by added socket +functions to be used in util.c. + + +Version 2.7.7 - October 7, 2012 + +- Fix unused warnings on ming build. +- Fix sign warning in ocl.c +- fds need to be zeroed before set in modminer. +- Put scrypt warning on separate line to avoid 0 being shown on windows as +bufsize. +- Display correct pool number when block is found. +- Prevent corrupt values returned from the opencl code from trying to read +beyond the end of the buffer by masking the value to a max of 15. +- Icarus USB write failure is also a comms error +- api.c DEBUG message has no paramter +- Icarus catch more USB errors and close/reopen the port +- API-README update cgminer verison number +- hashmeter fix stats kh/s on 32bit windows + + Version 2.7.6 - September 24, 2012 +- Reorder libztex header include order to fix missing struct definition. - Display share difficulty on log with a shortened hash display on submission. - API stats add some pool getwork difficulty stats - Ignore any pings pushed to the worker threads if the thread is still paused to diff --git a/README b/README index c034a7c5..5dc33695 100644 --- a/README +++ b/README @@ -146,6 +146,7 @@ Options for both config file and command line: --debug|-D Enable debug output --expiry|-E Upper bound on how many seconds after getting work we consider a share from it stale (default: 120) --failover-only Don't leak work to backup pools when primary pool is lagging +--fix-protocol Do not redirect to a different getwork protocol (eg. stratum) --kernel-path|-K Specify a path to where bitstream and kernel files are (default: "/usr/local/bin") --load-balance Change multipool strategy from failover to efficiency based balance --log|-l Interval in seconds between log output (default: 5) @@ -304,6 +305,10 @@ Single pool with a socks5 proxy, regular desktop: cgminer -o "socks5:proxy:port|http://pool:port" -u username -p password +Single pool with stratum protocol support: + +cgminer -o stratum+tcp://pool:port -u username -p password + The list of proxy types are: http: standard http 1.1 proxy http0: http 1.0 proxy @@ -377,6 +382,17 @@ Thread 1: 60.2 Mh/s Enabled ALIVE Or press any other key to continue +The running log shows output like this: + + [2012-10-12 18:02:20] Accepted f0c05469 Diff 1/1 GPU 0 pool 1 + [2012-10-12 18:02:22] Accepted 218ac982 Diff 7/1 GPU 1 pool 1 + [2012-10-12 18:02:23] Accepted d8300795 Diff 1/1 GPU 3 pool 1 + [2012-10-12 18:02:24] Accepted 122c1ff1 Diff 14/1 GPU 1 pool 1 + +The 8 byte hex value are the 2nd 8 bytes of the share being submitted to the +pool. The 2 diff values are the actual difficulty target that share reached +followed by the difficulty target the pool is currently asking for. + --- Also many issues and FAQs are covered in the forum thread dedicated to this program, @@ -908,6 +924,20 @@ To permanently give your account the 'dialout' group: sudo usermod -G dialout -a `whoami` Then logout and back in again +Q: What is stratum and how do I use it? +A: Stratum is a protocol designed for pooled mining in such a way as to +minimise the amount of network communications, yet scale to hardware of any +speed. With versions of cgminer 2.8.0+, if a pool has stratum support, cgminer +will automatically detect it and switch to the support as advertised if it can. +Stratum uses direct TCP connections to the pool and thus it will NOT currently +work through a http proxy but will work via a socks proxy if you need to use +one. If you input the stratum port directly into your configuration, or use the +special prefix "stratum+tcp://" instead of "http://", cgminer will ONLY try to +use stratum protocol mining. The advantages of stratum to the miner are no +delays in getting more work for the miner, less rejects across block changes, +and far less network communications for the same amount of mining hashrate. If +you do NOT wish cgminer to automatically switch to stratum protocol even if it +is detected, add the --fix-protocol option. --- diff --git a/api.c b/api.c index 8ebf94da..8c358af4 100644 --- a/api.c +++ b/api.c @@ -25,122 +25,13 @@ #include "compat.h" #include "miner.h" +#include "util.h" #include "driver-cpu.h" /* for algo_names[], TODO: re-factor dependency */ #if defined(USE_BITFORCE) || defined(USE_ICARUS) || defined(USE_ZTEX) || defined(USE_MODMINER) #define HAVE_AN_FPGA 1 #endif -#if defined(unix) || defined(__APPLE__) - #include - #include - #include - #include - - #define SOCKETTYPE int - #define SOCKETFAIL(a) ((a) < 0) - #define INVSOCK -1 - #define INVINETADDR -1 - #define CLOSESOCKET close - - #define SOCKERRMSG strerror(errno) -#endif - -#ifdef WIN32 - #include - #include - - #define SOCKETTYPE SOCKET - #define SOCKETFAIL(a) ((a) == SOCKET_ERROR) - #define INVSOCK INVALID_SOCKET - #define INVINETADDR INADDR_NONE - #define CLOSESOCKET closesocket - - static char WSAbuf[1024]; - - struct WSAERRORS { - int id; - char *code; - } WSAErrors[] = { - { 0, "No error" }, - { WSAEINTR, "Interrupted system call" }, - { WSAEBADF, "Bad file number" }, - { WSAEACCES, "Permission denied" }, - { WSAEFAULT, "Bad address" }, - { WSAEINVAL, "Invalid argument" }, - { WSAEMFILE, "Too many open sockets" }, - { WSAEWOULDBLOCK, "Operation would block" }, - { WSAEINPROGRESS, "Operation now in progress" }, - { WSAEALREADY, "Operation already in progress" }, - { WSAENOTSOCK, "Socket operation on non-socket" }, - { WSAEDESTADDRREQ, "Destination address required" }, - { WSAEMSGSIZE, "Message too long" }, - { WSAEPROTOTYPE, "Protocol wrong type for socket" }, - { WSAENOPROTOOPT, "Bad protocol option" }, - { WSAEPROTONOSUPPORT, "Protocol not supported" }, - { WSAESOCKTNOSUPPORT, "Socket type not supported" }, - { WSAEOPNOTSUPP, "Operation not supported on socket" }, - { WSAEPFNOSUPPORT, "Protocol family not supported" }, - { WSAEAFNOSUPPORT, "Address family not supported" }, - { WSAEADDRINUSE, "Address already in use" }, - { WSAEADDRNOTAVAIL, "Can't assign requested address" }, - { WSAENETDOWN, "Network is down" }, - { WSAENETUNREACH, "Network is unreachable" }, - { WSAENETRESET, "Net connection reset" }, - { WSAECONNABORTED, "Software caused connection abort" }, - { WSAECONNRESET, "Connection reset by peer" }, - { WSAENOBUFS, "No buffer space available" }, - { WSAEISCONN, "Socket is already connected" }, - { WSAENOTCONN, "Socket is not connected" }, - { WSAESHUTDOWN, "Can't send after socket shutdown" }, - { WSAETOOMANYREFS, "Too many references, can't splice" }, - { WSAETIMEDOUT, "Connection timed out" }, - { WSAECONNREFUSED, "Connection refused" }, - { WSAELOOP, "Too many levels of symbolic links" }, - { WSAENAMETOOLONG, "File name too long" }, - { WSAEHOSTDOWN, "Host is down" }, - { WSAEHOSTUNREACH, "No route to host" }, - { WSAENOTEMPTY, "Directory not empty" }, - { WSAEPROCLIM, "Too many processes" }, - { WSAEUSERS, "Too many users" }, - { WSAEDQUOT, "Disc quota exceeded" }, - { WSAESTALE, "Stale NFS file handle" }, - { WSAEREMOTE, "Too many levels of remote in path" }, - { WSASYSNOTREADY, "Network system is unavailable" }, - { WSAVERNOTSUPPORTED, "Winsock version out of range" }, - { WSANOTINITIALISED, "WSAStartup not yet called" }, - { WSAEDISCON, "Graceful shutdown in progress" }, - { WSAHOST_NOT_FOUND, "Host not found" }, - { WSANO_DATA, "No host data of that type was found" }, - { -1, "Unknown error code" } - }; - - static char *WSAErrorMsg() - { - int i; - int id = WSAGetLastError(); - - /* Assume none of them are actually -1 */ - for (i = 0; WSAErrors[i].id != -1; i++) - if (WSAErrors[i].id == id) - break; - - sprintf(WSAbuf, "Socket Error: (%d) %s", id, WSAErrors[i].code); - - return &(WSAbuf[0]); - } - - #define SOCKERRMSG WSAErrorMsg() - - #ifndef SHUT_RDWR - #define SHUT_RDWR SD_BOTH - #endif - - #ifndef in_addr_t - #define in_addr_t uint32_t - #endif -#endif - // Big enough for largest API request // though a PC with 100s of PGAs/CPUs may exceed the size ... // Current code assumes it can socket send this size also @@ -153,6 +44,80 @@ // However lots of PGA's may mean more #define QUEUE 100 +#if defined WIN32 +static char WSAbuf[1024]; + +struct WSAERRORS { + int id; + char *code; +} WSAErrors[] = { + { 0, "No error" }, + { WSAEINTR, "Interrupted system call" }, + { WSAEBADF, "Bad file number" }, + { WSAEACCES, "Permission denied" }, + { WSAEFAULT, "Bad address" }, + { WSAEINVAL, "Invalid argument" }, + { WSAEMFILE, "Too many open sockets" }, + { WSAEWOULDBLOCK, "Operation would block" }, + { WSAEINPROGRESS, "Operation now in progress" }, + { WSAEALREADY, "Operation already in progress" }, + { WSAENOTSOCK, "Socket operation on non-socket" }, + { WSAEDESTADDRREQ, "Destination address required" }, + { WSAEMSGSIZE, "Message too long" }, + { WSAEPROTOTYPE, "Protocol wrong type for socket" }, + { WSAENOPROTOOPT, "Bad protocol option" }, + { WSAEPROTONOSUPPORT, "Protocol not supported" }, + { WSAESOCKTNOSUPPORT, "Socket type not supported" }, + { WSAEOPNOTSUPP, "Operation not supported on socket" }, + { WSAEPFNOSUPPORT, "Protocol family not supported" }, + { WSAEAFNOSUPPORT, "Address family not supported" }, + { WSAEADDRINUSE, "Address already in use" }, + { WSAEADDRNOTAVAIL, "Can't assign requested address" }, + { WSAENETDOWN, "Network is down" }, + { WSAENETUNREACH, "Network is unreachable" }, + { WSAENETRESET, "Net connection reset" }, + { WSAECONNABORTED, "Software caused connection abort" }, + { WSAECONNRESET, "Connection reset by peer" }, + { WSAENOBUFS, "No buffer space available" }, + { WSAEISCONN, "Socket is already connected" }, + { WSAENOTCONN, "Socket is not connected" }, + { WSAESHUTDOWN, "Can't send after socket shutdown" }, + { WSAETOOMANYREFS, "Too many references, can't splice" }, + { WSAETIMEDOUT, "Connection timed out" }, + { WSAECONNREFUSED, "Connection refused" }, + { WSAELOOP, "Too many levels of symbolic links" }, + { WSAENAMETOOLONG, "File name too long" }, + { WSAEHOSTDOWN, "Host is down" }, + { WSAEHOSTUNREACH, "No route to host" }, + { WSAENOTEMPTY, "Directory not empty" }, + { WSAEPROCLIM, "Too many processes" }, + { WSAEUSERS, "Too many users" }, + { WSAEDQUOT, "Disc quota exceeded" }, + { WSAESTALE, "Stale NFS file handle" }, + { WSAEREMOTE, "Too many levels of remote in path" }, + { WSASYSNOTREADY, "Network system is unavailable" }, + { WSAVERNOTSUPPORTED, "Winsock version out of range" }, + { WSANOTINITIALISED, "WSAStartup not yet called" }, + { WSAEDISCON, "Graceful shutdown in progress" }, + { WSAHOST_NOT_FOUND, "Host not found" }, + { WSANO_DATA, "No host data of that type was found" }, + { -1, "Unknown error code" } +}; + +char *WSAErrorMsg(void) { + int i; + int id = WSAGetLastError(); + + /* Assume none of them are actually -1 */ + for (i = 0; WSAErrors[i].id != -1; i++) + if (WSAErrors[i].id == id) + break; + + sprintf(WSAbuf, "Socket Error: (%d) %s", id, WSAErrors[i].code); + + return &(WSAbuf[0]); +} +#endif static char *io_buffer = NULL; static char *msg_buffer = NULL; static SOCKETTYPE sock = INVSOCK; @@ -558,7 +523,7 @@ struct CODES { { SEVERITY_ERR, MSG_INVBOOL, PARAM_NONE, "Invalid parameter should be true or false" }, { SEVERITY_SUCC, MSG_FOO, PARAM_BOOL, "Failover-Only set to %s" }, { SEVERITY_SUCC, MSG_MINECOIN,PARAM_NONE, "CGMiner coin" }, - { SEVERITY_SUCC, MSG_DEBUGSET,PARAM_STR, "Debug settings" }, + { SEVERITY_SUCC, MSG_DEBUGSET,PARAM_NONE, "Debug settings" }, #ifdef HAVE_AN_FPGA { SEVERITY_SUCC, MSG_PGAIDENT,PARAM_PGA, "Identify command sent to PGA%d" }, { SEVERITY_WARN, MSG_PGANOID, PARAM_PGA, "PGA%d does not support identify" }, @@ -2210,6 +2175,7 @@ exitsama: static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group) { char *url, *user, *pass; + struct pool *pool; char *ptr; if (param == NULL || *param == '\0') { @@ -2226,7 +2192,9 @@ static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __may return; } - add_pool_details(true, url, user, pass); + pool = add_pool(); + detect_stratum(pool, url); + add_pool_details(pool, true, url, user, pass); ptr = escape_string(url, isjson); strcpy(io_buffer, message(MSG_ADDPOOL, 0, ptr, isjson)); diff --git a/cgminer.c b/cgminer.c index 10c92207..b55aed95 100644 --- a/cgminer.c +++ b/cgminer.c @@ -129,6 +129,7 @@ bool use_curses; static bool opt_submit_stale = true; static int opt_shares; bool opt_fail_only; +static bool opt_fix_protocol; bool opt_autofan; bool opt_autoengine; bool opt_noadl; @@ -170,6 +171,7 @@ static pthread_mutex_t *stgd_lock; pthread_mutex_t console_lock; pthread_mutex_t ch_lock; static pthread_rwlock_t blk_lock; +static pthread_mutex_t sshare_lock; pthread_rwlock_t netacc_lock; @@ -225,6 +227,20 @@ struct block { static struct block *blocks = NULL; + +int swork_id; + +/* For creating a hash database of stratum shares submitted that have not had + * a response yet */ +struct stratum_share { + UT_hash_handle hh; + bool block; + struct work work; + int id; +}; + +static struct stratum_share *stratum_shares = NULL; + char *opt_socks_proxy = NULL; static const char def_conf[] = "cgminer.conf"; @@ -403,7 +419,7 @@ static void sharelog(const char*disposition, const struct work*work) } /* Return value is ignored if not called from add_pool_details */ -static struct pool *add_pool(void) +struct pool *add_pool(void) { struct pool *pool; @@ -417,6 +433,8 @@ static struct pool *add_pool(void) quit(1, "Failed to pthread_mutex_init in add_pool"); if (unlikely(pthread_cond_init(&pool->cr_cond, NULL))) quit(1, "Failed to pthread_cond_init in add_pool"); + if (unlikely(pthread_mutex_init(&pool->stratum_lock, NULL))) + quit(1, "Failed to pthread_mutex_init in add_pool"); INIT_LIST_HEAD(&pool->curlring); /* Make sure the pool doesn't think we've been idle since time 0 */ @@ -543,6 +561,25 @@ static char *set_rr(enum pool_strategy *strategy) return NULL; } +/* Detect that url is for a stratum protocol either via the presence of + * stratum+tcp or by detecting a stratum server response */ +bool detect_stratum(struct pool *pool, char *url) +{ + if (opt_scrypt) + return false; + + if (!extract_sockaddr(pool, url)) + return false; + + if (!strncasecmp(url, "stratum+tcp://", 14)) { + pool->has_stratum = true; + pool->stratum_url = pool->sockaddr_url; + return true; + } + + return false; +} + static char *set_url(char *arg) { struct pool *pool; @@ -554,6 +591,12 @@ static char *set_url(char *arg) arg = get_proxy(arg, pool); + if (detect_stratum(pool, arg)) { + if (!pool->rpc_url) + pool->rpc_url = strdup(pool->stratum_url); + return NULL; + } + opt_set_charp(arg, &pool->rpc_url); if (strncmp(arg, "http://", 7) && strncmp(arg, "https://", 8)) { @@ -605,6 +648,7 @@ static char *set_pass(const char *arg) static char *set_userpass(const char *arg) { struct pool *pool; + char *updup; if (total_users || total_passes) return "Use only user + pass or userpass, but not both"; @@ -613,7 +657,14 @@ static char *set_userpass(const char *arg) add_pool(); pool = pools[total_userpasses - 1]; + updup = strdup(arg); opt_set_charp(arg, &pool->rpc_userpass); + pool->rpc_user = strtok(updup, ":"); + if (!pool->rpc_user) + return "Failed to find : delimited user info"; + pool->rpc_pass = strtok(NULL, ":"); + if (!pool->rpc_pass) + return "Failed to find : delimited pass info"; return NULL; } @@ -846,6 +897,9 @@ static struct opt_table opt_config_table[] = { OPT_WITHOUT_ARG("--failover-only", opt_set_bool, &opt_fail_only, "Don't leak work to backup pools when primary pool is lagging"), + OPT_WITHOUT_ARG("--fix-protocol", + opt_set_bool, &opt_fix_protocol, + "Do not redirect to a different getwork protocol (eg. stratum)"), #ifdef HAVE_OPENCL OPT_WITH_ARG("--gpu-dyninterval", set_int_1_to_65535, opt_show_intval, &opt_dynamic_interval, @@ -1421,28 +1475,71 @@ void tailsprintf(char *f, const char *fmt, ...) va_end(ap); } +/* Convert a uint64_t value into a truncated string for displaying with its + * associated suitable for Mega, Giga etc. Buf array needs to be long enough */ +static void suffix_string(uint64_t val, char *buf, int sigdigits) +{ + const double dkilo = 1000.0; + const uint64_t kilo = 1000ull; + const uint64_t mega = 1000000ull; + const uint64_t giga = 1000000000ull; + const uint64_t tera = 1000000000000ull; + const uint64_t peta = 1000000000000000ull; + const uint64_t exa = 1000000000000000000ull; + char suffix[2] = ""; + double dval; + + if (val >= exa) { + val /= peta; + dval = (double)val / dkilo; + sprintf(suffix, "E"); + } else if (val >= peta) { + val /= tera; + dval = (double)val / dkilo; + sprintf(suffix, "P"); + } else if (val >= tera) { + val /= giga; + dval = (double)val / dkilo; + sprintf(suffix, "T"); + } else if (val >= giga) { + val /= mega; + dval = (double)val / dkilo; + sprintf(suffix, "G"); + } else if (val >= mega) { + val /= kilo; + dval = (double)val / dkilo; + sprintf(suffix, "M"); + } else if (val >= kilo) { + dval = (double)val / dkilo; + sprintf(suffix, "K"); + } else + dval = val; + + if (!sigdigits) + sprintf(buf, "%d%s", (unsigned int)dval, suffix); + else + sprintf(buf, "%-*.*g%s", sigdigits + 1, sigdigits, dval, suffix); +} + static void get_statline(char *buf, struct cgpu_info *cgpu) { - double displayed_hashes, displayed_rolling = cgpu->rolling; - bool mhash_base = true; + char displayed_hashes[16], displayed_rolling[16]; + uint64_t dh64, dr64; - displayed_hashes = cgpu->total_mhashes / total_secs; - if (displayed_hashes < 1) { - displayed_hashes *= 1000; - displayed_rolling *= 1000; - mhash_base = false; - } + dh64 = (double)cgpu->total_mhashes / total_secs * 1000000ull; + dr64 = (double)cgpu->rolling * 1000000ull; + suffix_string(dh64, displayed_hashes, 4); + suffix_string(dr64, displayed_rolling, 4); sprintf(buf, "%s%d ", cgpu->api->name, cgpu->device_id); if (cgpu->api->get_statline_before) cgpu->api->get_statline_before(buf, cgpu); else tailsprintf(buf, " | "); - tailsprintf(buf, "(%ds):%.1f (avg):%.1f %sh/s | A:%d R:%d HW:%d U:%.1f/m", + tailsprintf(buf, "(%ds):%s (avg):%sh/s | A:%d R:%d HW:%d U:%.1f/m", opt_log_interval, displayed_rolling, displayed_hashes, - mhash_base ? "M" : "K", cgpu->accepted, cgpu->rejected, cgpu->hw_errors, @@ -1484,12 +1581,16 @@ static void curses_print_status(void) global_queued(), total_staged(), total_stale, total_discarded, new_blocks, local_work, total_go, total_ro, total_diff1 / total_secs * 60); wclrtoeol(statuswin); - if ((pool_strategy == POOL_LOADBALANCE || pool_strategy == POOL_BALANCE) && total_pools > 1) + if (pool->has_stratum) { + mvwprintw(statuswin, 4, 0, " Connected to %s with stratum as user %s", + pool->stratum_url, pool->rpc_user); + } else if ((pool_strategy == POOL_LOADBALANCE || pool_strategy == POOL_BALANCE) && total_pools > 1) { mvwprintw(statuswin, 4, 0, " Connected to multiple pools with%s LP", have_longpoll ? "": "out"); - else + } else { mvwprintw(statuswin, 4, 0, " Connected to %s with%s LP as user %s", pool->rpc_url, have_longpoll ? "": "out", pool->rpc_user); + } wclrtoeol(statuswin); mvwprintw(statuswin, 5, 0, " Block: %s... Started: %s", current_hash, blocktime); mvwhline(statuswin, 6, 0, '-', 80); @@ -1510,9 +1611,9 @@ static void curses_print_devstatus(int thr_id) { static int awidth = 1, rwidth = 1, hwwidth = 1, uwidth = 1; struct cgpu_info *cgpu = thr_info[thr_id].cgpu; - double displayed_hashes, displayed_rolling; - bool mhash_base = true; char logline[255]; + char displayed_hashes[16], displayed_rolling[16]; + uint64_t dh64, dr64; if (devcursor + cgpu->cgminer_id > LINES - 2) return; @@ -1529,13 +1630,10 @@ static void curses_print_devstatus(int thr_id) else wprintw(statuswin, " | "); - displayed_hashes = cgpu->total_mhashes / total_secs; - displayed_rolling = cgpu->rolling; - if (displayed_hashes < 1) { - displayed_hashes *= 1000; - displayed_rolling *= 1000; - mhash_base = false; - } + dh64 = (double)cgpu->total_mhashes / total_secs * 1000000ull; + dr64 = (double)cgpu->rolling * 1000000ull; + suffix_string(dh64, displayed_hashes, 4); + suffix_string(dr64, displayed_rolling, 4); if (cgpu->status == LIFE_DEAD) wprintw(statuswin, "DEAD "); @@ -1546,15 +1644,14 @@ static void curses_print_devstatus(int thr_id) else if (cgpu->deven == DEV_RECOVER) wprintw(statuswin, "REST "); else - wprintw(statuswin, "%5.1f", displayed_rolling); + wprintw(statuswin, "%6s", displayed_rolling); adj_width(cgpu->accepted, &awidth); adj_width(cgpu->rejected, &rwidth); adj_width(cgpu->hw_errors, &hwwidth); adj_width(cgpu->utility, &uwidth); - wprintw(statuswin, "/%5.1f%sh/s | A:%*d R:%*d HW:%*d U:%*.2f/m", + wprintw(statuswin, "/%6sh/s | A:%*d R:%*d HW:%*d U:%*.2f/m", displayed_hashes, - mhash_base ? "M" : "K", awidth, cgpu->accepted, rwidth, cgpu->rejected, hwwidth, cgpu->hw_errors, @@ -1758,10 +1855,142 @@ static void reject_pool(struct pool *pool) pool->enabled = POOL_REJECTING; } +/* Theoretically threads could race when modifying accepted and + * rejected values but the chance of two submits completing at the + * same time is zero so there is no point adding extra locking */ +static void +share_result(json_t *val, json_t *res, json_t *err, const struct work *work, + char *hashshow, bool resubmit, char *worktime) +{ + struct pool *pool = work->pool; + struct cgpu_info *cgpu = thr_info[work->thr_id].cgpu; + + if (json_is_true(res)) { + cgpu->accepted++; + total_accepted++; + pool->accepted++; + cgpu->diff_accepted += work->work_difficulty; + total_diff_accepted += work->work_difficulty; + pool->diff_accepted += work->work_difficulty; + pool->seq_rejects = 0; + cgpu->last_share_pool = pool->pool_no; + cgpu->last_share_pool_time = time(NULL); + cgpu->last_share_diff = work->work_difficulty; + pool->last_share_time = cgpu->last_share_pool_time; + pool->last_share_diff = work->work_difficulty; + applog(LOG_DEBUG, "PROOF OF WORK RESULT: true (yay!!!)"); + if (!QUIET) { + if (total_pools > 1) + applog(LOG_NOTICE, "Accepted %s %s %d pool %d %s%s", + hashshow, cgpu->api->name, cgpu->device_id, work->pool->pool_no, resubmit ? "(resubmit)" : "", worktime); + else + applog(LOG_NOTICE, "Accepted %s %s %d %s%s", + hashshow, cgpu->api->name, cgpu->device_id, resubmit ? "(resubmit)" : "", worktime); + } + sharelog("accept", work); + if (opt_shares && total_accepted >= opt_shares) { + applog(LOG_WARNING, "Successfully mined %d accepted shares as requested and exiting.", opt_shares); + kill_work(); + return; + } + + /* Detect if a pool that has been temporarily disabled for + * continually rejecting shares has started accepting shares. + * This will only happen with the work returned from a + * longpoll */ + if (unlikely(pool->enabled == POOL_REJECTING)) { + applog(LOG_WARNING, "Rejecting pool %d now accepting shares, re-enabling!", pool->pool_no); + enable_pool(pool); + switch_pools(NULL); + } + } else { + cgpu->rejected++; + total_rejected++; + pool->rejected++; + cgpu->diff_rejected += work->work_difficulty; + total_diff_rejected += work->work_difficulty; + pool->diff_rejected += work->work_difficulty; + pool->seq_rejects++; + applog(LOG_DEBUG, "PROOF OF WORK RESULT: false (booooo)"); + if (!QUIET) { + char where[17]; + char disposition[36] = "reject"; + char reason[32]; + + strcpy(reason, ""); + if (total_pools > 1) + sprintf(where, "pool %d", work->pool->pool_no); + else + strcpy(where, ""); + + res = json_object_get(val, "reject-reason"); + if (res) { + const char *reasontmp = json_string_value(res); + + size_t reasonLen = strlen(reasontmp); + if (reasonLen > 28) + reasonLen = 28; + reason[0] = ' '; reason[1] = '('; + memcpy(2 + reason, reasontmp, reasonLen); + reason[reasonLen + 2] = ')'; reason[reasonLen + 3] = '\0'; + memcpy(disposition + 7, reasontmp, reasonLen); + disposition[6] = ':'; disposition[reasonLen + 7] = '\0'; + } else if (work->stratum && err && json_is_array(err)) { + json_t *reason_val = json_array_get(err, 1); + char *reason_str; + + if (reason_val && json_is_string(reason_val)) { + reason_str = (char *)json_string_value(reason_val); + snprintf(reason, 31, " (%s)", reason_str); + } + } + + applog(LOG_NOTICE, "Rejected %s %s %d %s%s %s%s", + hashshow, cgpu->api->name, cgpu->device_id, where, reason, resubmit ? "(resubmit)" : "", worktime); + sharelog(disposition, work); + } + + /* Once we have more than a nominal amount of sequential rejects, + * at least 10 and more than 3 mins at the current utility, + * disable the pool because some pool error is likely to have + * ensued. Do not do this if we know the share just happened to + * be stale due to networking delays. + */ + if (pool->seq_rejects > 10 && !work->stale && opt_disable_pool && enabled_pools > 1) { + double utility = total_accepted / total_secs * 60; + + if (pool->seq_rejects > utility * 3) { + applog(LOG_WARNING, "Pool %d rejected %d sequential shares, disabling!", + pool->pool_no, pool->seq_rejects); + reject_pool(pool); + if (pool == current_pool()) + switch_pools(NULL); + pool->seq_rejects = 0; + } + } + } +} + +static uint64_t share_diff(const struct work *work) +{ + const uint64_t h64 = 0xFFFF000000000000ull; + uint64_t *data64, d64; + char rhash[33]; + uint64_t ret; + + swab256(rhash, work->hash); + data64 = (uint64_t *)(rhash + 4); + d64 = be64toh(*data64); + if (unlikely(!d64)) + d64 = 1; + ret = h64 / d64; + return ret; +} + static bool submit_upstream_work(const struct work *work, CURL *curl, bool resubmit) { char *hexstr = NULL; - json_t *val, *res; + json_t *val, *res, *err; char s[345], sd[345]; bool rc = false; int thr_id = work->thr_id; @@ -1770,7 +1999,7 @@ static bool submit_upstream_work(const struct work *work, CURL *curl, bool resub int rolltime; uint32_t *hash32; struct timeval tv_submit, tv_submit_reply; - char hashshow[64+1] = ""; + char hashshow[64 + 1] = ""; char worktime[200] = ""; #ifdef __BIG_ENDIAN__ @@ -1812,6 +2041,7 @@ static bool submit_upstream_work(const struct work *work, CURL *curl, bool resub applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); res = json_object_get(val, "result"); + err = json_object_get(val, "error"); if (!QUIET) { hash32 = (uint32_t *)(work->hash); @@ -1819,8 +2049,12 @@ static bool submit_upstream_work(const struct work *work, CURL *curl, bool resub sprintf(hashshow, "%08lx.%08lx", (unsigned long)(hash32[7]), (unsigned long)(hash32[6])); else { int intdiff = round(work->work_difficulty); + uint64_t sharediff = share_diff(work); + char diffdisp[16]; + + suffix_string(sharediff, diffdisp, 0); - sprintf(hashshow, "%08lx Diff %d%s", (unsigned long)(hash32[6]), intdiff, + sprintf(hashshow, "%08lx Diff %s/%d%s", (unsigned long)(hash32[6]), diffdisp, intdiff, work->block? " BLOCK!" : ""); } @@ -1866,105 +2100,7 @@ static bool submit_upstream_work(const struct work *work, CURL *curl, bool resub } } - /* Theoretically threads could race when modifying accepted and - * rejected values but the chance of two submits completing at the - * same time is zero so there is no point adding extra locking */ - if (json_is_true(res)) { - cgpu->accepted++; - total_accepted++; - pool->accepted++; - cgpu->diff_accepted += work->work_difficulty; - total_diff_accepted += work->work_difficulty; - pool->diff_accepted += work->work_difficulty; - pool->seq_rejects = 0; - cgpu->last_share_pool = pool->pool_no; - cgpu->last_share_pool_time = time(NULL); - cgpu->last_share_diff = work->work_difficulty; - pool->last_share_time = cgpu->last_share_pool_time; - pool->last_share_diff = work->work_difficulty; - applog(LOG_DEBUG, "PROOF OF WORK RESULT: true (yay!!!)"); - if (!QUIET) { - if (total_pools > 1) - applog(LOG_NOTICE, "Accepted %s %s %d pool %d %s%s", - hashshow, cgpu->api->name, cgpu->device_id, work->pool->pool_no, resubmit ? "(resubmit)" : "", worktime); - else - applog(LOG_NOTICE, "Accepted %s %s %d %s%s", - hashshow, cgpu->api->name, cgpu->device_id, resubmit ? "(resubmit)" : "", worktime); - } - sharelog("accept", work); - if (opt_shares && total_accepted >= opt_shares) { - applog(LOG_WARNING, "Successfully mined %d accepted shares as requested and exiting.", opt_shares); - kill_work(); - goto out; - } - - /* Detect if a pool that has been temporarily disabled for - * continually rejecting shares has started accepting shares. - * This will only happen with the work returned from a - * longpoll */ - if (unlikely(pool->enabled == POOL_REJECTING)) { - applog(LOG_WARNING, "Rejecting pool %d now accepting shares, re-enabling!", pool->pool_no); - enable_pool(pool); - switch_pools(NULL); - } - } else { - cgpu->rejected++; - total_rejected++; - pool->rejected++; - cgpu->diff_rejected += work->work_difficulty; - total_diff_rejected += work->work_difficulty; - pool->diff_rejected += work->work_difficulty; - pool->seq_rejects++; - applog(LOG_DEBUG, "PROOF OF WORK RESULT: false (booooo)"); - if (!QUIET) { - char where[17]; - char disposition[36] = "reject"; - char reason[32]; - - if (total_pools > 1) - sprintf(where, "pool %d", work->pool->pool_no); - else - strcpy(where, ""); - - res = json_object_get(val, "reject-reason"); - if (res) { - const char *reasontmp = json_string_value(res); - - size_t reasonLen = strlen(reasontmp); - if (reasonLen > 28) - reasonLen = 28; - reason[0] = ' '; reason[1] = '('; - memcpy(2 + reason, reasontmp, reasonLen); - reason[reasonLen + 2] = ')'; reason[reasonLen + 3] = '\0'; - memcpy(disposition + 7, reasontmp, reasonLen); - disposition[6] = ':'; disposition[reasonLen + 7] = '\0'; - } else - strcpy(reason, ""); - - applog(LOG_NOTICE, "Rejected %s %s %d %s%s %s%s", - hashshow, cgpu->api->name, cgpu->device_id, where, reason, resubmit ? "(resubmit)" : "", worktime); - sharelog(disposition, work); - } - - /* Once we have more than a nominal amount of sequential rejects, - * at least 10 and more than 3 mins at the current utility, - * disable the pool because some pool error is likely to have - * ensued. Do not do this if we know the share just happened to - * be stale due to networking delays. - */ - if (pool->seq_rejects > 10 && !work->stale && opt_disable_pool && enabled_pools > 1) { - double utility = total_accepted / total_secs * 60; - - if (pool->seq_rejects > utility * 3) { - applog(LOG_WARNING, "Pool %d rejected %d sequential shares, disabling!", - pool->pool_no, pool->seq_rejects); - reject_pool(pool); - if (pool == current_pool()) - switch_pools(NULL); - pool->seq_rejects = 0; - } - } - } + share_result(val, res, err, work, hashshow, resubmit, worktime); cgpu->utility = cgpu->accepted / total_secs * 60; @@ -2047,19 +2183,22 @@ static double DIFFEXACTONE = 269599466671506397946670150870196306736371444225405 /* * Calculate the work share difficulty */ -static void calc_diff(struct work *work) +static void calc_diff(struct work *work, int known) { struct cgminer_pool_stats *pool_stats = &(work->pool->cgminer_pool_stats); double targ; int i; - targ = 0; - for (i = 31; i >= 0; i--) { - targ *= 256; - targ += work->target[i]; - } + if (!known) { + targ = 0; + for (i = 31; i >= 0; i--) { + targ *= 256; + targ += work->target[i]; + } - work->work_difficulty = DIFFEXACTONE / (targ ? : DIFFEXACTONE); + work->work_difficulty = DIFFEXACTONE / (targ ? : DIFFEXACTONE); + } else + work->work_difficulty = known; pool_stats->last_diff = work->work_difficulty; @@ -2093,7 +2232,7 @@ static void get_benchmark_work(struct work *work) gettimeofday(&(work->tv_getwork), NULL); memcpy(&(work->tv_getwork_reply), &(work->tv_getwork), sizeof(struct timeval)); work->getwork_mode = GETWORK_MODE_BENCHMARK; - calc_diff(work); + calc_diff(work, 0); } static bool get_upstream_work(struct work *work, CURL *curl) @@ -2141,7 +2280,7 @@ static bool get_upstream_work(struct work *work, CURL *curl) work->pool = pool; work->longpoll = false; work->getwork_mode = GETWORK_MODE_POOL; - calc_diff(work); + calc_diff(work, 0); total_getworks++; pool->getwork_requested++; @@ -2421,7 +2560,7 @@ static inline bool should_roll(struct work *work) * reject blocks as invalid. */ static inline bool can_roll(struct work *work) { - return (work->pool && work->rolltime && !work->clone && + return (!work->stratum && work->pool && work->rolltime && !work->clone && work->rolls < 7000 && !stale_work(work, false)); } @@ -2504,19 +2643,43 @@ static void pool_died(struct pool *pool) } } +static void gen_stratum_work(struct pool *pool, struct work *work); + static void *get_work_thread(void *userdata) { struct workio_cmd *wc = (struct workio_cmd *)userdata; - struct pool *pool = current_pool(); struct work *ret_work= NULL; struct curl_ent *ce = NULL; + struct pool *pool; pthread_detach(pthread_self()); applog(LOG_DEBUG, "Creating extra get work thread"); +retry: pool = wc->pool; + if (pool->has_stratum) { + while (!pool->stratum_active) { + struct pool *altpool = select_pool(true); + + if (altpool != pool) { + wc->pool = altpool; + goto retry; + } + sleep(5); + } + ret_work = make_work(); + gen_stratum_work(pool, ret_work); + if (unlikely(!stage_work(ret_work))) { + applog(LOG_ERR, "Failed to stage stratum work in get_work_thread"); + kill_work(); + free(ret_work); + } + dec_queued(pool); + goto out; + } + if (clone_available()) { dec_queued(pool); goto out; @@ -2633,7 +2796,7 @@ static void check_solve(struct work *work) work->pool->solved++; found_blocks++; work->mandatory = true; - applog(LOG_NOTICE, "Found block for pool %d!", work->pool); + applog(LOG_NOTICE, "Found block for pool %d!", work->pool->pool_no); } #endif } @@ -2669,6 +2832,40 @@ static void *submit_work_thread(void *userdata) work->stale = true; } + if (work->stratum) { + struct stratum_share *sshare = calloc(sizeof(struct stratum_share), 1); + uint32_t *hash32 = (uint32_t *)work->hash, nonce; + char *noncehex; + char s[1024]; + + memcpy(&sshare->work, work, sizeof(struct work)); + + /* Give the stratum share a unique id */ + mutex_lock(&sshare_lock); + sshare->id = swork_id++; + HASH_ADD_INT(stratum_shares, id, sshare); + mutex_unlock(&sshare_lock); + + nonce = *((uint32_t *)(work->data + 76)); + noncehex = bin2hex((const unsigned char *)&nonce, 4); + + memset(s, 0, 1024); + sprintf(s, "{\"params\": [\"%s\", \"%s\", \"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}", + pool->rpc_user, work->job_id, work->nonce2, work->ntime, noncehex, sshare->id); + free(noncehex); + + applog(LOG_INFO, "Submitting share %08lx to pool %d", (unsigned long)(hash32[6]), pool->pool_no); + + if (unlikely(!stratum_send(pool, s, strlen(s)))) { + mutex_lock(&sshare_lock); + HASH_DEL(stratum_shares, sshare); + mutex_unlock(&sshare_lock); + free(sshare); + } + + goto out; + } + ce = pop_curl_entry(pool); /* submit solution to bitcoin via JSON-RPC */ while (!submit_upstream_work(work, ce->curl, resubmit)) { @@ -2929,7 +3126,7 @@ static bool block_exists(char *hexstr) /* Tests if this work is from a block that has been seen before */ static inline bool from_existing_block(struct work *work) { - char *hexstr = bin2hex(work->data, 18); + char *hexstr = bin2hex(work->data + 8, 18); bool ret; if (unlikely(!hexstr)) { @@ -2946,17 +3143,18 @@ static int block_sort(struct block *blocka, struct block *blockb) return blocka->block_no - blockb->block_no; } -static void test_work_current(struct work *work) +static bool test_work_current(struct work *work) { + bool ret = true; char *hexstr; if (work->mandatory) - return; + return ret; - hexstr = bin2hex(work->data, 18); + hexstr = bin2hex(work->data + 8, 18); if (unlikely(!hexstr)) { applog(LOG_ERR, "stage_thread OOM"); - return; + return ret; } /* Search to see if this block exists yet and if not, consider it a @@ -2964,6 +3162,7 @@ static void test_work_current(struct work *work) if (!block_exists(hexstr)) { struct block *s = calloc(sizeof(struct block), 1); int deleted_block = 0; + ret = false; if (unlikely(!s)) quit (1, "test_work_current OOM"); @@ -2992,14 +3191,16 @@ static void test_work_current(struct work *work) work_block++; - if (work->longpoll) { - applog(LOG_NOTICE, "LONGPOLL from pool %d detected new block", - work->pool->pool_no); - work->longpoll = false; - } else if (have_longpoll) - applog(LOG_NOTICE, "New block detected on network before longpoll"); - else - applog(LOG_NOTICE, "New block detected on network"); + if (!work->stratum) { + if (work->longpoll) { + applog(LOG_NOTICE, "LONGPOLL from pool %d detected new block", + work->pool->pool_no); + work->longpoll = false; + } else if (have_longpoll) + applog(LOG_NOTICE, "New block detected on network before longpoll"); + else + applog(LOG_NOTICE, "New block detected on network"); + } restart_threads(); } else if (work->longpoll) { work->longpoll = false; @@ -3012,6 +3213,7 @@ static void test_work_current(struct work *work) } out_free: free(hexstr); + return ret; } static int tv_sort(struct work *worka, struct work *workb) @@ -3821,7 +4023,7 @@ static inline void thread_reportout(struct thr_info *thr) } static void hashmeter(int thr_id, struct timeval *diff, - unsigned long long hashes_done) + uint64_t hashes_done) { struct timeval temp_tv_end, total_diff; double secs; @@ -3829,9 +4031,10 @@ static void hashmeter(int thr_id, struct timeval *diff, double utility, efficiency = 0.0; static double local_mhashes_done = 0; static double rolling = 0; - double local_mhashes, displayed_hashes, displayed_rolling; - bool mhash_base = true; + double local_mhashes; bool showlog = false; + char displayed_hashes[16], displayed_rolling[16]; + uint64_t dh64, dr64; local_mhashes = (double)hashes_done / 1000000.0; /* Update the last time this thread reported in */ @@ -3849,7 +4052,7 @@ static void hashmeter(int thr_id, struct timeval *diff, double thread_rolling = 0.0; int i; - applog(LOG_DEBUG, "[thread %d: %llu hashes, %.1f khash/sec]", + applog(LOG_DEBUG, "[thread %d: %"PRIu64" hashes, %.1f khash/sec]", thr_id, hashes_done, hashes_done / 1000 / secs); /* Rolling average for each thread and each device */ @@ -3909,17 +4112,14 @@ static void hashmeter(int thr_id, struct timeval *diff, utility = total_accepted / total_secs * 60; efficiency = total_getworks ? total_accepted * 100.0 / total_getworks : 0.0; - displayed_hashes = total_mhashes_done / total_secs; - displayed_rolling = rolling; - if (displayed_hashes < 1) { - displayed_hashes *= 1000; - displayed_rolling *= 1000; - mhash_base = false; - } + dh64 = (double)total_mhashes_done / total_secs * 1000000ull; + dr64 = (double)rolling * 1000000ull; + suffix_string(dh64, displayed_hashes, 4); + suffix_string(dr64, displayed_rolling, 4); - sprintf(statusline, "%s(%ds):%.1f (avg):%.1f %sh/s | Q:%d A:%d R:%d HW:%d E:%.0f%% U:%.1f/m", + sprintf(statusline, "%s(%ds):%s (avg):%sh/s | Q:%d A:%d R:%d HW:%d E:%.0f%% U:%.1f/m", want_per_device_stats ? "ALL " : "", - opt_log_interval, displayed_rolling, displayed_hashes, mhash_base ? "M" : "K", + opt_log_interval, displayed_rolling, displayed_hashes, total_getworks, total_accepted, total_rejected, hw_errors, efficiency, utility); @@ -3935,8 +4135,180 @@ out_unlock: } } +static void stratum_share_result(json_t *val, json_t *res_val, json_t *err_val, + struct stratum_share *sshare) +{ + struct work *work = &sshare->work; + uint64_t sharediff = share_diff(work); + char hashshow[65]; + uint32_t *hash32; + char diffdisp[16]; + int intdiff; + + hash32 = (uint32_t *)(work->hash); + intdiff = round(work->work_difficulty); + suffix_string(sharediff, diffdisp, 0); + sprintf(hashshow, "%08lx Diff %s/%d%s", (unsigned long)(hash32[6]), diffdisp, intdiff, + work->block? " BLOCK!" : ""); + share_result(val, res_val, err_val, work, hashshow, false, ""); +} + +/* Parses stratum json responses and tries to find the id that the request + * matched to and treat it accordingly. */ +static bool parse_stratum_response(char *s) +{ + json_t *val = NULL, *err_val, *res_val, *id_val; + struct stratum_share *sshare; + json_error_t err; + bool ret = false; + int id; + + val = JSON_LOADS(s, &err); + if (!val) { + applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text); + goto out; + } + + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + id_val = json_object_get(val, "id"); + + if (json_is_null(id_val) || !id_val) { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + + applog(LOG_INFO, "JSON-RPC non method decode failed: %s", ss); + + free(ss); + + goto out; + } + + id = json_integer_value(id_val); + mutex_lock(&sshare_lock); + HASH_FIND_INT(stratum_shares, &id, sshare); + if (sshare) + HASH_DEL(stratum_shares, sshare); + mutex_unlock(&sshare_lock); + if (!sshare) { + if (json_is_true(res_val)) + applog(LOG_NOTICE, "Accepted untracked stratum share"); + else + applog(LOG_NOTICE, "Rejected untracked stratum share"); + goto out; + } + stratum_share_result(val, res_val, err_val, sshare); + free(sshare); + + ret = true; +out: + if (val) + json_decref(val); + + return ret; +} + +static void pool_resus(struct pool *pool); + +/* One stratum thread per pool that has stratum waits on the socket checking + * for new messages and for the integrity of the socket connection. We reset + * the connection based on the integrity of the receive side only as the send + * side will eventually expire data it fails to send. */ +static void *stratum_thread(void *userdata) +{ + struct pool *pool = (struct pool *)userdata; + + pthread_detach(pthread_self()); + + while (42) { + struct timeval timeout; + fd_set rd; + char *s; + + if (unlikely(pool->removed)) + break; + + FD_ZERO(&rd); + FD_SET(pool->sock, &rd); + timeout.tv_sec = 120; + timeout.tv_usec = 0; + + /* The protocol specifies that notify messages should be sent + * every minute so if we fail to receive any for 2 minutes we + * assume the connection has been dropped and treat this pool + * as dead */ + select(pool->sock + 1, &rd, NULL, NULL, &timeout); + s = recv_line(pool); + if (!s) { + applog(LOG_INFO, "Stratum connection to pool %d interrupted", pool->pool_no); + pool->getfail_occasions++; + total_go++; + + if (initiate_stratum(pool) && auth_stratum(pool)) + continue; + + pool_died(pool); + while (!initiate_stratum(pool) || !auth_stratum(pool)) { + if (pool->removed) + goto out; + sleep(30); + } + applog(LOG_INFO, "Stratum connection to pool %d resumed", pool->pool_no); + pool_resus(pool); + continue; + } + + if (!parse_method(pool, s) && !parse_stratum_response(s)) + applog(LOG_INFO, "Unknown stratum msg: %s", s); + free(s); + if (pool->swork.clean) { + struct work work; + + /* Generate a single work item to update the current + * block database */ + pool->swork.clean = false; + gen_stratum_work(pool, &work); + if (test_work_current(&work)) { + /* Only accept a work restart if this stratum + * connection is from the current pool */ + if (pool == current_pool()) { + restart_threads(); + applog(LOG_NOTICE, "Stratum from pool %d requested work restart", pool->pool_no); + } + } else + applog(LOG_NOTICE, "Stratum from pool %d detected new block", pool->pool_no); + } + + } + +out: + return NULL; +} + +static void init_stratum_thread(struct pool *pool) +{ + if (unlikely(pthread_create(&pool->stratum_thread, NULL, stratum_thread, (void *)pool))) + quit(1, "Failed to create stratum thread"); +} + static void *longpoll_thread(void *userdata); +static bool stratum_works(struct pool *pool) +{ + applog(LOG_INFO, "Testing pool %d stratum %s", pool->pool_no, pool->stratum_url); + if (!extract_sockaddr(pool, pool->stratum_url)) + return false; + + if (!initiate_stratum(pool)) + return false; + + return true; +} + static bool pool_active(struct pool *pool, bool pinging) { struct timeval tv_getwork, tv_getwork_reply; @@ -3945,18 +4317,50 @@ static bool pool_active(struct pool *pool, bool pinging) CURL *curl; int rolltime; + applog(LOG_INFO, "Testing pool %s", pool->rpc_url); + + /* This is the central point we activate stratum when we can */ +retry_stratum: + if (pool->has_stratum) { + /* We create the stratum thread for each pool just after + * successful authorisation. Once the auth flag has been set + * we never unset it and the stratum thread is responsible for + * setting/unsetting the active flag */ + if (pool->stratum_auth) + return pool->stratum_active; + if (!pool->stratum_active && !initiate_stratum(pool)) + return false; + if (!auth_stratum(pool)) + return false; + pool->stratum_auth = true; + pool->idle = false; + init_stratum_thread(pool); + return true; + } + curl = curl_easy_init(); if (unlikely(!curl)) { applog(LOG_ERR, "CURL initialisation failed"); return false; } - applog(LOG_INFO, "Testing pool %s", pool->rpc_url); gettimeofday(&tv_getwork, NULL); val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, rpc_req, true, false, &rolltime, pool, false); gettimeofday(&tv_getwork_reply, NULL); + /* Detect if a http getwork pool has an X-Stratum header at startup, + * and if so, switch to that in preference to getwork if it works */ + if (pool->stratum_url && !opt_fix_protocol && stratum_works(pool)) { + applog(LOG_NOTICE, "Switching pool %d %s to %s", pool->pool_no, pool->rpc_url, pool->stratum_url); + if (!pool->rpc_url) + pool->rpc_url = strdup(pool->stratum_url); + pool->has_stratum = true; + curl_easy_cleanup(curl); + + goto retry_stratum; + } + if (val) { struct work *work = make_work(); bool rc; @@ -3970,7 +4374,7 @@ static bool pool_active(struct pool *pool, bool pinging) memcpy(&(work->tv_getwork), &tv_getwork, sizeof(struct timeval)); memcpy(&(work->tv_getwork_reply), &tv_getwork_reply, sizeof(struct timeval)); work->getwork_mode = GETWORK_MODE_TESTPOOL; - calc_diff(work); + calc_diff(work, 0); applog(LOG_DEBUG, "Pushing pooltest work to base pool"); tq_push(thr_info[stage_thr_id].q, work); @@ -4020,6 +4424,12 @@ static bool pool_active(struct pool *pool, bool pinging) quit(1, "Failed to create pool longpoll thread"); } } else { + /* If we failed to parse a getwork, this could be a stratum + * url without the prefix stratum+tcp:// so let's check it */ + if (initiate_stratum(pool)) { + pool->has_stratum = true; + goto retry_stratum; + } applog(LOG_DEBUG, "FAILED to retrieve work from pool %u %s", pool->pool_no, pool->rpc_url); if (!pinging) @@ -4124,8 +4534,16 @@ static struct work *hash_pop(const struct timespec *abstime) return work; } -static bool reuse_work(struct work *work) +static bool reuse_work(struct work *work, struct pool *pool) { + if (pool->has_stratum) { + if (!pool->stratum_active) + return false; + applog(LOG_DEBUG, "Reusing stratum work"); + gen_stratum_work(pool, work);; + return true; + } + if (can_roll(work) && should_roll(work)) { roll_work(work); return true; @@ -4171,6 +4589,139 @@ static struct work *clone_work(struct work *work) return work; } +static void gen_hash(unsigned char *data, unsigned char *hash, int len) +{ + unsigned char hash1[33]; + + sha2(data, len, hash1, false); + sha2(hash1, 32, hash, false); +} + +/* Diff 1 is a 256 bit unsigned integer of + * 0x00000000ffff0000000000000000000000000000000000000000000000000000 + * so we use a big endian 64 bit unsigned integer centred on the 5th byte to + * cover a huge range of difficulty targets, though not all 256 bits' worth */ +static void set_work_target(struct work *work, int diff) +{ + unsigned char rtarget[33], target[33]; + uint64_t *data64, h64; + + h64 = 0xFFFF000000000000ull; + h64 /= (uint64_t)diff; + memset(rtarget, 0, 32); + data64 = (uint64_t *)(rtarget + 4); + *data64 = htobe64(h64); + swab256(target, rtarget); + if (opt_debug) { + char *htarget = bin2hex(target, 32); + + if (likely(htarget)) { + applog(LOG_DEBUG, "Generated target %s", htarget); + free(htarget); + } + } + memcpy(work->target, target, 32); +} + +/* Generates stratum based work based on the most recent notify information + * from the pool. This will keep generating work while a pool is down so we use + * other means to detect when the pool has died in stratum_thread */ +static void gen_stratum_work(struct pool *pool, struct work *work) +{ + unsigned char *coinbase, merkle_root[33], merkle_sha[65], *merkle_hash; + char header[257], hash1[129], *nonce2; + int len, cb1_len, n1_len, cb2_len, i; + uint32_t *data32, *swap32; + + memset(work->job_id, 0, 64); + memset(work->nonce2, 0, 64); + memset(work->ntime, 0, 16); + + mutex_lock(&pool->pool_lock); + + /* Generate coinbase */ + nonce2 = bin2hex((const unsigned char *)&pool->nonce2, pool->n2size); + if (unlikely(!nonce2)) + quit(1, "Failed to convert nonce2 in gen_stratum_work"); + pool->nonce2++; + cb1_len = strlen(pool->swork.coinbase1) / 2; + n1_len = strlen(pool->nonce1) / 2; + cb2_len = strlen(pool->swork.coinbase2) / 2; + len = cb1_len + n1_len + pool->n2size + cb2_len; + coinbase = alloca(len + 1); + hex2bin(coinbase, pool->swork.coinbase1, cb1_len); + hex2bin(coinbase + cb1_len, pool->nonce1, n1_len); + hex2bin(coinbase + cb1_len + n1_len, nonce2, pool->n2size); + hex2bin(coinbase + cb1_len + n1_len + pool->n2size, pool->swork.coinbase2, cb2_len); + + /* Generate merkle root */ + gen_hash(coinbase, merkle_root, len); + memcpy(merkle_sha, merkle_root, 32); + for (i = 0; i < pool->swork.merkles; i++) { + unsigned char merkle_bin[33]; + + hex2bin(merkle_bin, pool->swork.merkle[i], 32); + memcpy(merkle_sha + 32, merkle_bin, 32); + gen_hash(merkle_sha, merkle_root, 64); + memcpy(merkle_sha, merkle_root, 32); + } + data32 = (uint32_t *)merkle_sha; + swap32 = (uint32_t *)merkle_root; + for (i = 0; i < 32 / 4; i++) + swap32[i] = swab32(data32[i]); + merkle_hash = (unsigned char *)bin2hex((const unsigned char *)merkle_root, 32); + if (unlikely(!merkle_hash)) + quit(1, "Failed to conver merkle_hash in gen_stratum_work"); + + sprintf(header, "%s", pool->swork.bbversion); + strcat(header, pool->swork.prev_hash); + strcat(header, (char *)merkle_hash); + strcat(header, pool->swork.ntime); + strcat(header, pool->swork.nbit); + strcat(header, "00000000"); /* nonce */ + strcat(header, "000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"); + + /* Store the stratum work diff to check it still matches the pool's + * stratum diff when submitting shares */ + work->sdiff = pool->swork.diff; + + /* Copy parameters required for share submission */ + sprintf(work->job_id, "%s", pool->swork.job_id); + sprintf(work->nonce2, "%s", nonce2); + sprintf(work->ntime, "%s", pool->swork.ntime); + free(nonce2); + + mutex_unlock(&pool->pool_lock); + + applog(LOG_DEBUG, "Generated stratum merkle %s", merkle_hash); + applog(LOG_DEBUG, "Generated stratum header %s", header); + applog(LOG_DEBUG, "Work job_id %s nonce2 %s ntime %s", work->job_id, work->nonce2, work->ntime); + + free(merkle_hash); + + /* Convert hex data to binary data for work */ + if (unlikely(!hex2bin(work->data, header, 128))) + quit(1, "Failed to convert header to data in gen_stratum_work"); + calc_midstate(work); + sprintf(hash1, "00000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000010000"); + if (unlikely(!hex2bin(work->hash1, hash1, 64))) + quit(1, "Failed to convert hash1 in gen_stratum_work"); + + set_work_target(work, work->sdiff); + + local_work++; + work->pool = pool; + work->stratum = true; + work->blk.nonce = 0; + work->id = total_work++; + work->longpoll = false; + work->getwork_mode = GETWORK_MODE_STRATUM; + work->work_block = work_block; + calc_diff(work, work->sdiff); + + gettimeofday(&work->tv_staged, NULL); +} + static void get_work(struct work *work, struct thr_info *thr, const int thr_id) { struct timespec abstime = {0, 0}; @@ -4190,9 +4741,16 @@ static void get_work(struct work *work, struct thr_info *thr, const int thr_id) retry: pool = current_pool(); - if (reuse_work(work)) + if (reuse_work(work, pool)) goto out; + /* If we were unable to reuse work from a stratum pool, it implies the + * pool is inactive and unless we have another pool to grab work from + * we can only wait till it comes alive or another pool comes online */ + if (pool->has_stratum) { + sleep(5); + goto retry; + } if (!pool->lagging && !total_staged() && global_queued() >= mining_threads + opt_queue) { struct cgpu_info *cgpu = thr->cgpu; bool stalled = true; @@ -4285,7 +4843,7 @@ err_out: return false; } -static bool hashtest(struct thr_info *thr, const struct work *work) +static bool hashtest(struct thr_info *thr, struct work *work) { uint32_t *data32 = (uint32_t *)(work->data); unsigned char swap[128]; @@ -4293,7 +4851,8 @@ static bool hashtest(struct thr_info *thr, const struct work *work) unsigned char hash1[32]; unsigned char hash2[32]; uint32_t *hash2_32 = (uint32_t *)hash2; - int i; + struct pool *pool = work->pool; + int i, diff; for (i = 0; i < 80 / 4; i++) swap32[i] = swab32(data32[i]); @@ -4314,6 +4873,17 @@ static bool hashtest(struct thr_info *thr, const struct work *work) return false; } + if (work->stratum) { + mutex_lock(&pool->pool_lock); + diff = pool->swork.diff; + mutex_unlock(&pool->pool_lock); + + if (unlikely(work->sdiff != diff)) { + applog(LOG_DEBUG, "Share needs retargetting to match pool"); + set_work_target(work, diff); + } + } + bool test = fulltest(work->hash, work->target); if (!test) applog(LOG_INFO, "Share below target"); @@ -4586,7 +5156,7 @@ static void convert_to_work(json_t *val, int rolltime, struct pool *pool, struct memcpy(&(work->tv_getwork), tv_lp, sizeof(struct timeval)); memcpy(&(work->tv_getwork_reply), tv_lp_reply, sizeof(struct timeval)); work->getwork_mode = GETWORK_MODE_LP; - calc_diff(work); + calc_diff(work, 0); if (pool->enabled == POOL_REJECTING) work->mandatory = true; @@ -4793,7 +5363,8 @@ static void *watchpool_thread(void __maybe_unused *userdata) if (!opt_benchmark) reap_curl(pool); - if (pool->enabled == POOL_DISABLED) + + if (pool->enabled == POOL_DISABLED || pool->has_stratum) continue; /* Test pool is idle once every minute */ @@ -5155,12 +5726,8 @@ char *curses_input(const char *query) } #endif -void add_pool_details(bool live, char *url, char *user, char *pass) +void add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass) { - struct pool *pool; - - pool = add_pool(); - url = get_proxy(url, pool); pool->rpc_url = url; @@ -5171,9 +5738,13 @@ void add_pool_details(bool live, char *url, char *user, char *pass) quit(1, "Failed to malloc userpass"); sprintf(pool->rpc_userpass, "%s:%s", pool->rpc_user, pool->rpc_pass); + enable_pool(pool); + + /* Prevent noise on startup */ + pool->lagging = true; + /* Test the pool is not idle if we're live running, otherwise * it will be tested separately */ - enable_pool(pool); if (live && !pool_active(pool, false)) pool->idle = true; } @@ -5182,6 +5753,7 @@ void add_pool_details(bool live, char *url, char *user, char *pass) static bool input_pool(bool live) { char *url = NULL, *user = NULL, *pass = NULL; + struct pool *pool; bool ret = false; immedok(logwin, true); @@ -5191,8 +5763,19 @@ static bool input_pool(bool live) if (!url) goto out; - if (strncmp(url, "http://", 7) && - strncmp(url, "https://", 8)) { + user = curses_input("Username"); + if (!user) + goto out; + + pass = curses_input("Password"); + if (!pass) + goto out; + + pool = add_pool(); + + if (detect_stratum(pool, url)) + url = strdup(pool->stratum_url); + else if (strncmp(url, "http://", 7) && strncmp(url, "https://", 8)) { char *httpinput; httpinput = malloc(255); @@ -5204,15 +5787,7 @@ static bool input_pool(bool live) url = httpinput; } - user = curses_input("Username"); - if (!user) - goto out; - - pass = curses_input("Password"); - if (!pass) - goto out; - - add_pool_details(live, url, user, pass); + add_pool_details(pool, live, url, user, pass); ret = true; out: immedok(logwin, false); @@ -5421,6 +5996,7 @@ int main(int argc, char *argv[]) mutex_init(&control_lock); mutex_init(&sharelog_lock); mutex_init(&ch_lock); + mutex_init(&sshare_lock); rwlock_init(&blk_lock); rwlock_init(&netacc_lock); @@ -5443,7 +6019,9 @@ int main(int argc, char *argv[]) sigemptyset(&handler.sa_mask); sigaction(SIGTERM, &handler, &termhandler); sigaction(SIGINT, &handler, &inthandler); - +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif opt_kernel_path = alloca(PATH_MAX); strcpy(opt_kernel_path, CGMINER_PREFIX); cgminer_path = alloca(PATH_MAX); @@ -5686,14 +6264,6 @@ int main(int argc, char *argv[]) if (!pool->rpc_userpass) quit(1, "Failed to malloc userpass"); sprintf(pool->rpc_userpass, "%s:%s", pool->rpc_user, pool->rpc_pass); - } else { - pool->rpc_user = malloc(strlen(pool->rpc_userpass) + 1); - if (!pool->rpc_user) - quit(1, "Failed to malloc user"); - strcpy(pool->rpc_user, pool->rpc_userpass); - pool->rpc_user = strtok(pool->rpc_user, ":"); - if (!pool->rpc_user) - quit(1, "Failed to find colon delimiter in userpass"); } } /* Set the currentpool to pool 0 */ diff --git a/configure.ac b/configure.ac index fe618e02..0d77b979 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ ##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## ##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## m4_define([v_maj], [2]) -m4_define([v_min], [7]) -m4_define([v_mic], [6]) +m4_define([v_min], [8]) +m4_define([v_mic], [3]) ##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## m4_define([v_ver], [v_maj.v_min.v_mic]) m4_define([lt_rev], m4_eval(v_maj + v_min)) @@ -92,6 +92,7 @@ case $target in PTHREAD_FLAGS="" DLOPEN_FLAGS="" WS2_LIBS="-lws2_32" + AC_DEFINE([_WIN32_WINNT], [0x0501], "WinNT version for XP+ support") ;; powerpc-*-darwin*) CFLAGS="$CFLAGS -faltivec" @@ -351,8 +352,7 @@ fi PKG_PROG_PKG_CONFIG() -PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.15.6], [AC_DEFINE([CURL_HAS_SOCKOPT], [1], [Defined if version of curl supports sockopts.])], -[PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.10.1], ,[AC_MSG_ERROR([Missing required libcurl dev >= 7.10.1])])]) +PKG_CHECK_MODULES([LIBCURL], [libcurl >= 7.18.2], ,[AC_MSG_ERROR([Missing required libcurl dev >= 7.18.2])]) AC_SUBST(LIBCURL_LIBS) dnl CCAN wants to know a lot of vars. diff --git a/driver-bitforce.c b/driver-bitforce.c index ded9e923..ae9fa01c 100644 --- a/driver-bitforce.c +++ b/driver-bitforce.c @@ -154,7 +154,7 @@ static int bitforce_autodetect_ftdi(void) char **bufptrs; char *buf; int found = 0; - int i; + DWORD i; FT_STATUS ftStatus; DWORD numDevs; @@ -410,6 +410,11 @@ static bool bitforce_get_temp(struct cgpu_info *bitforce) if ((!strncasecmp(pdevbuf, "TEMP", 4)) && (s = strchr(pdevbuf + 4, ':'))) { float temp = strtof(s + 1, NULL); + /* Cope with older software that breaks and reads nonsense + * values */ + if (temp > 100) + temp = strtod(s + 1, NULL); + if (temp > 0) { bitforce->temp = temp; if (unlikely(bitforce->cutofftemp > 0 && temp > bitforce->cutofftemp)) { diff --git a/driver-icarus.c b/driver-icarus.c index 4214c31b..c013b5dd 100644 --- a/driver-icarus.c +++ b/driver-icarus.c @@ -29,6 +29,8 @@ * nonce range is completely calculated. */ +#include "config.h" + #include #include #include @@ -223,6 +225,11 @@ static void rev(unsigned char *s, size_t l) #define icarus_open2(devpath, baud, purge) serial_open(devpath, baud, ICARUS_READ_FAULT_DECISECONDS, purge) #define icarus_open(devpath, baud) icarus_open2(devpath, baud, false) +#define ICA_GETS_ERROR -1 +#define ICA_GETS_OK 0 +#define ICA_GETS_RESTART 1 +#define ICA_GETS_TIMEOUT 2 + static int icarus_gets(unsigned char *buf, int fd, struct timeval *tv_finish, struct thr_info *thr, int read_count) { ssize_t ret = 0; @@ -233,12 +240,14 @@ static int icarus_gets(unsigned char *buf, int fd, struct timeval *tv_finish, st // Read reply 1 byte at a time to get earliest tv_finish while (true) { ret = read(fd, buf, 1); + if (ret < 0) + return ICA_GETS_ERROR; if (first) gettimeofday(tv_finish, NULL); if (ret >= read_amount) - return 0; + return ICA_GETS_OK; if (ret > 0) { buf += ret; @@ -254,16 +263,16 @@ static int icarus_gets(unsigned char *buf, int fd, struct timeval *tv_finish, st "Icarus Read: No data in %.2f seconds", (float)rc/(float)TIME_FACTOR); } - return 1; + return ICA_GETS_TIMEOUT; } - if (thr->work_restart) { + if (thr && thr->work_restart) { if (opt_debug) { applog(LOG_DEBUG, "Icarus Read: Work restart at %.2f seconds", (float)(rc)/(float)TIME_FACTOR); } - return 1; + return ICA_GETS_RESTART; } } } @@ -281,6 +290,13 @@ static int icarus_write(int fd, const void *buf, size_t bufLen) #define icarus_close(fd) close(fd) +static void do_icarus_close(struct thr_info *thr) +{ + struct cgpu_info *icarus = thr->cgpu; + icarus_close(icarus->device_fd); + icarus->device_fd = -1; +} + static const char *timing_mode_str(enum timing_mode timing_mode) { switch(timing_mode) { @@ -533,10 +549,7 @@ static bool icarus_detect_one(const char *devpath) gettimeofday(&tv_start, NULL); memset(nonce_bin, 0, sizeof(nonce_bin)); - struct thr_info dummy = { - .work_restart = false, - }; - icarus_gets(nonce_bin, fd, &tv_finish, &dummy, 1); + icarus_gets(nonce_bin, fd, &tv_finish, NULL, 1); icarus_close(fd); @@ -563,6 +576,7 @@ static bool icarus_detect_one(const char *devpath) icarus = calloc(1, sizeof(struct cgpu_info)); icarus->api = &icarus_api; icarus->device_path = strdup(devpath); + icarus->device_fd = -1; icarus->threads = 1; add_cgpu(icarus); icarus_info = realloc(icarus_info, sizeof(struct ICARUS_INFO *) * (total_devices + 1)); @@ -607,6 +621,8 @@ static bool icarus_prepare(struct thr_info *thr) struct timeval now; + icarus->device_fd = -1; + int fd = icarus_open(icarus->device_path, icarus_info[icarus->device_id]->baud); if (unlikely(-1 == fd)) { applog(LOG_ERR, "Failed to open Icarus on %s", @@ -653,6 +669,17 @@ static int64_t icarus_scanhash(struct thr_info *thr, struct work *work, elapsed.tv_sec = elapsed.tv_usec = 0; icarus = thr->cgpu; + if (icarus->device_fd == -1) + if (!icarus_prepare(thr)) { + applog(LOG_ERR, "ICA%i: Comms error", icarus->device_id); + icarus->device_last_not_well = time(NULL); + icarus->device_not_well_reason = REASON_DEV_COMMS_ERROR; + icarus->dev_comms_error_count++; + + // fail the device if the reopen attempt fails + return -1; + } + fd = icarus->device_fd; memset(ob_bin, 0, sizeof(ob_bin)); @@ -664,8 +691,14 @@ static int64_t icarus_scanhash(struct thr_info *thr, struct work *work, tcflush(fd, TCOFLUSH); #endif ret = icarus_write(fd, ob_bin, sizeof(ob_bin)); - if (ret) - return -1; /* This should never happen */ + if (ret) { + do_icarus_close(thr); + applog(LOG_ERR, "ICA%i: Comms error", icarus->device_id); + icarus->device_last_not_well = time(NULL); + icarus->device_not_well_reason = REASON_DEV_COMMS_ERROR; + icarus->dev_comms_error_count++; + return 0; /* This should never happen */ + } gettimeofday(&tv_start, NULL); @@ -682,12 +715,19 @@ static int64_t icarus_scanhash(struct thr_info *thr, struct work *work, memset(nonce_bin, 0, sizeof(nonce_bin)); info = icarus_info[icarus->device_id]; ret = icarus_gets(nonce_bin, fd, &tv_finish, thr, info->read_count); + if (ret == ICA_GETS_ERROR) { + do_icarus_close(thr); + applog(LOG_ERR, "ICA%i: Comms error", icarus->device_id); + icarus->device_last_not_well = time(NULL); + icarus->device_not_well_reason = REASON_DEV_COMMS_ERROR; + icarus->dev_comms_error_count++; + return 0; + } work->blk.nonce = 0xffffffff; - memcpy((char *)&nonce, nonce_bin, sizeof(nonce_bin)); // aborted before becoming idle, get new work - if (nonce == 0 && ret) { + if (ret == ICA_GETS_TIMEOUT || ret == ICA_GETS_RESTART) { timersub(&tv_finish, &tv_start, &elapsed); // ONLY up to just when it aborted @@ -709,6 +749,8 @@ static int64_t icarus_scanhash(struct thr_info *thr, struct work *work, return estimate_hashes; } + memcpy((char *)&nonce, nonce_bin, sizeof(nonce_bin)); + #if !defined (__BIG_ENDIAN__) && !defined(MIPSEB) nonce = swab32(nonce); #endif @@ -717,6 +759,10 @@ static int64_t icarus_scanhash(struct thr_info *thr, struct work *work, submit_nonce(thr, work, nonce); was_hw_error = (curr_hw_errors > icarus->hw_errors); + // Force a USB close/reopen on any hw error + if (was_hw_error) + do_icarus_close(thr); + hash_count = (nonce & info->nonce_mask); hash_count++; hash_count *= info->fpga_count; @@ -862,8 +908,7 @@ static struct api_data *icarus_api_stats(struct cgpu_info *cgpu) static void icarus_shutdown(struct thr_info *thr) { - struct cgpu_info *icarus = thr->cgpu; - icarus_close(icarus->device_fd); + do_icarus_close(thr); } struct device_api icarus_api = { diff --git a/driver-modminer.c b/driver-modminer.c index d15f2d88..f052d43a 100644 --- a/driver-modminer.c +++ b/driver-modminer.c @@ -16,6 +16,7 @@ #include "logging.h" #include "miner.h" #include "fpgautils.h" +#include "util.h" #define BITSTREAM_FILENAME "fpgaminer_top_fixed7_197MHz.ncd" #define BISTREAM_USER_ID "\2\4$B" @@ -48,7 +49,7 @@ modminer_detect_one(const char *devpath) bailout(LOG_DEBUG, "ModMiner detect: failed to open %s", devpath); char buf[0x100]; - size_t len; + ssize_t len; // Sending 45 noops, just in case the device was left in "start job" reading (void)(write(fd, "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", 45) ?:0); @@ -128,6 +129,7 @@ modminer_detect() } while(0) #define status_read(eng) do { \ +FD_ZERO(&fds); \ FD_SET(fd, &fds); \ select(fd+1, &fds, NULL, NULL, NULL); \ if (1 != read(fd, buf, 1)) \ @@ -139,7 +141,7 @@ select(fd+1, &fds, NULL, NULL, NULL); \ static bool modminer_fpga_upload_bitstream(struct cgpu_info*modminer) { -fd_set fds; + fd_set fds; char buf[0x100]; unsigned char *ubuf = (unsigned char*)buf; unsigned long len; @@ -185,7 +187,7 @@ fd_set fds; len = ((unsigned long)ubuf[0] << 24) | ((unsigned long)ubuf[1] << 16) | (ubuf[2] << 8) | ubuf[3]; applog(LOG_DEBUG, " Bitstream size: %lu", len); - int fd = modminer->device_fd; + SOCKETTYPE fd = modminer->device_fd; applog(LOG_WARNING, "%s %u: Programming %s... DO NOT EXIT CGMINER UNTIL COMPLETE", modminer->api->name, modminer->device_id, modminer->device_path); buf[0] = '\x05'; // Program Bitstream @@ -375,7 +377,7 @@ fd_set fds; struct cgpu_info*modminer = thr->cgpu; struct modminer_fpga_state *state = thr->cgpu_data; char fpgaid = thr->device_thread; - int fd = modminer->device_fd; + SOCKETTYPE fd = modminer->device_fd; char buf[1]; diff --git a/driver-opencl.c b/driver-opencl.c index 82cbc6bb..d558a820 100644 --- a/driver-opencl.c +++ b/driver-opencl.c @@ -1585,14 +1585,19 @@ static int64_t opencl_scanhash(struct thr_info *thr, struct work *work, gpu_us = us_tdiff(&tv_gpuend, &gpu->tv_gpumid); if (gpu_us > 0 && ++gpu->hit > 4) { gpu_us = us_tdiff(&tv_gpuend, &gpu->tv_gpustart) / gpu->intervals; + /* Very rarely we may get an overflow so put an upper + * limit on the detected time */ + if (unlikely(gpu->gpu_us_average > 0 && gpu_us > gpu->gpu_us_average * 4)) + gpu_us = gpu->gpu_us_average * 4; + gpu->gpu_us_average = (gpu->gpu_us_average + gpu_us * 0.63) / 1.63; /* Try to not let the GPU be out for longer than * opt_dynamic_interval in ms, but increase * intensity when the system is idle in dynamic mode */ - if (gpu_us > dynamic_us) { + if (gpu->gpu_us_average > dynamic_us) { if (gpu->intensity > MIN_INTENSITY) --gpu->intensity; - } else if (gpu_us < dynamic_us / 2) { + } else if (gpu->gpu_us_average < dynamic_us / 2) { if (gpu->intensity < MAX_INTENSITY) ++gpu->intensity; } diff --git a/findnonce.c b/findnonce.c index 788835d9..2bfc1ef8 100644 --- a/findnonce.c +++ b/findnonce.c @@ -252,6 +252,16 @@ static void *postcalc_hash(void *userdata) pthread_detach(pthread_self()); + /* To prevent corrupt values in FOUND from trying to read beyond the + * end of the res[] array */ + if (unlikely(pcd->res[FOUND] & ~FOUND)) { + applog(LOG_WARNING, "%s%d: invalid nonce count - HW error", + thr->cgpu->api->name, thr->cgpu->device_id); + hw_errors++; + thr->cgpu->hw_errors++; + pcd->res[FOUND] &= FOUND; + } + for (entry = 0; entry < pcd->res[FOUND]; entry++) { uint32_t nonce = pcd->res[entry]; diff --git a/fpgautils.c b/fpgautils.c index de9d93e7..4c5829a1 100644 --- a/fpgautils.c +++ b/fpgautils.c @@ -74,7 +74,7 @@ int serial_autodetect_udev(__maybe_unused detectone_func_t detectone, __maybe_un } #endif -int serial_autodetect_devserial(detectone_func_t detectone, const char*prodname) +int serial_autodetect_devserial(__maybe_unused detectone_func_t detectone, __maybe_unused const char*prodname) { #ifndef WIN32 DIR *D; diff --git a/logging.c b/logging.c index 47d1970d..afc70089 100644 --- a/logging.c +++ b/logging.c @@ -7,6 +7,8 @@ * any later version. See COPYING for more details. */ +#include "config.h" + #include #include "logging.h" diff --git a/miner.h b/miner.h index 169c9ee1..5e982447 100644 --- a/miner.h +++ b/miner.h @@ -12,6 +12,7 @@ #include "elist.h" #include "uthash.h" #include "logging.h" +#include "util.h" #ifdef HAVE_OPENCL #ifdef __APPLE_CC__ @@ -51,6 +52,19 @@ void *alloca (size_t); # endif #endif +#ifdef __MINGW32__ +#include +#include +static inline int fsync (int fd) +{ + return (FlushFileBuffers ((HANDLE) _get_osfhandle (fd))) ? 0 : -1; +} + +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0x1000000 +#endif +#endif /* __MINGW32__ */ + #if defined (__linux) #ifndef LINUX #define LINUX @@ -125,14 +139,19 @@ void *alloca (size_t); #endif #endif /* !defined(__GLXBYTEORDER_H__) */ -/* This assumes htobe32 is a macro in endian.h */ +/* This assumes htobe32 is a macro in endian.h, and if it doesn't exist, then + * htobe64 also won't exist */ #ifndef htobe32 # if __BYTE_ORDER == __LITTLE_ENDIAN # define be32toh(x) bswap_32(x) +# define be64toh(x) bswap_64(x) # define htobe32(x) bswap_32(x) +# define htobe64(x) bswap_64(x) # elif __BYTE_ORDER == __BIG_ENDIAN # define be32toh(x) (x) +# define be64toh(x) (x) # define htobe32(x) (x) +# define htobe64(x) (x) #else #error UNKNOWN BYTE ORDER #endif @@ -378,11 +397,12 @@ struct cgpu_info { #ifdef USE_SCRYPT int opt_lg, lookup_gap; - int opt_tc, thread_concurrency; - int shaders; + size_t opt_tc, thread_concurrency; + size_t shaders; #endif struct timeval tv_gpustart; struct timeval tv_gpumid; + double gpu_us_average; int intervals, hit; #endif @@ -510,6 +530,21 @@ static inline void swap256(void *dest_p, const void *src_p) dest[7] = src[0]; } +static inline void swab256(void *dest_p, const void *src_p) +{ + uint32_t *dest = dest_p; + const uint32_t *src = src_p; + + dest[0] = swab32(src[7]); + dest[1] = swab32(src[6]); + dest[2] = swab32(src[5]); + dest[3] = swab32(src[4]); + dest[4] = swab32(src[3]); + dest[5] = swab32(src[2]); + dest[6] = swab32(src[1]); + dest[7] = swab32(src[0]); +} + extern void quit(int status, const char *format, ...); static inline void mutex_lock(pthread_mutex_t *lock) @@ -594,6 +629,7 @@ extern bool opt_worktime; #ifdef USE_BITFORCE extern bool opt_bfl_noncerange; #endif +extern int swork_id; extern pthread_rwlock_t netacc_lock; @@ -645,7 +681,9 @@ extern void api(int thr_id); extern struct pool *current_pool(void); extern int enabled_pools; -extern void add_pool_details(bool live, char *url, char *user, char *pass); +extern bool detect_stratum(struct pool *pool, char *url); +extern struct pool *add_pool(void); +extern void add_pool_details(struct pool *pool, bool live, char *url, char *user, char *pass); #define MAX_GPUDEVICES 16 @@ -748,6 +786,24 @@ enum pool_enable { POOL_REJECTING, }; +struct stratum_work { + char *job_id; + char *prev_hash; + char *coinbase1; + char *coinbase2; + char **merkle; + char *bbversion; + char *nbit; + char *ntime; + bool clean; + + int merkles; + int diff; +}; + +#define RECVSIZE 8191 +#define RBUFSIZE (RECVSIZE + 1) + struct pool { int pool_no; int prio; @@ -810,12 +866,31 @@ struct pool { struct cgminer_stats cgminer_stats; struct cgminer_pool_stats cgminer_pool_stats; + + /* Stratum variables */ + char *stratum_url; + char *stratum_port; + CURL *stratum_curl; + SOCKETTYPE sock; + char sockbuf[RBUFSIZE]; + struct sockaddr_in *server, client; + char *sockaddr_url; /* stripped url used for sockaddr */ + char *nonce1; + uint32_t nonce2; + int n2size; + bool has_stratum; + bool stratum_active; + bool stratum_auth; + struct stratum_work swork; + pthread_t stratum_thread; + pthread_mutex_t stratum_lock; }; #define GETWORK_MODE_TESTPOOL 'T' #define GETWORK_MODE_POOL 'P' #define GETWORK_MODE_LP 'L' #define GETWORK_MODE_BENCHMARK 'B' +#define GETWORK_MODE_STRATUM 'S' struct work { unsigned char data[128]; @@ -845,6 +920,14 @@ struct work { bool block; bool queued; + bool stratum; + /* These are arbitrary lengths as it is too hard to keep track of + * dynamically allocated ram in work structs */ + char job_id[64]; + char nonce2[64]; + char ntime[16]; + int sdiff; + unsigned int work_block; int id; UT_hash_handle hh; diff --git a/ocl.c b/ocl.c index 450a2d61..5b56e003 100644 --- a/ocl.c +++ b/ocl.c @@ -541,7 +541,7 @@ _clState *initCl(unsigned int gpu, char *name, size_t nameSize) strcat(binaryfilename, "g"); if (opt_scrypt) { #ifdef USE_SCRYPT - sprintf(numbuf, "lg%dtc%d", cgpu->lookup_gap, cgpu->thread_concurrency); + sprintf(numbuf, "lg%utc%u", cgpu->lookup_gap, (unsigned int)cgpu->thread_concurrency); strcat(binaryfilename, numbuf); #endif } else { @@ -614,7 +614,7 @@ build: #ifdef USE_SCRYPT if (opt_scrypt) sprintf(CompilerOptions, "-D LOOKUP_GAP=%d -D CONCURRENT_THREADS=%d -D WORKSIZE=%d", - cgpu->lookup_gap, cgpu->thread_concurrency, (int)clState->wsize); + cgpu->lookup_gap, (unsigned int)cgpu->thread_concurrency, (int)clState->wsize); else #endif { @@ -810,8 +810,8 @@ built: /* Use the max alloc value which has been rounded to a power of * 2 greater >= required amount earlier */ if (bufsize > cgpu->max_alloc) { - applog(LOG_WARNING, "Maximum buffer memory device %d supports says %u, your scrypt settings come to %u", - gpu, cgpu->max_alloc, bufsize); + applog(LOG_WARNING, "Maximum buffer memory device %d supports says %u", gpu, cgpu->max_alloc); + applog(LOG_WARNING, "Your scrypt settings come to %u", bufsize); } else bufsize = cgpu->max_alloc; applog(LOG_DEBUG, "Creating scrypt buffer sized %d", bufsize); diff --git a/util.c b/util.c index 3a0dbc18..fe9501c6 100644 --- a/util.c +++ b/util.c @@ -26,20 +26,17 @@ # include # include # include +# include #else # include # include +# include #endif #include "miner.h" #include "elist.h" #include "compat.h" - -#if JANSSON_MAJOR_VERSION >= 2 -#define JSON_LOADS(str, err_ptr) json_loads((str), 0, (err_ptr)) -#else -#define JSON_LOADS(str, err_ptr) json_loads((str), (err_ptr)) -#endif +#include "util.h" bool successful_connect = false; struct timeval nettime; @@ -58,6 +55,7 @@ struct header_info { char *lp_path; int rolltime; char *reason; + char *stratum_url; bool hadrolltime; bool canroll; bool hadexpire; @@ -187,13 +185,17 @@ static size_t resp_hdr_cb(void *ptr, size_t size, size_t nmemb, void *user_data) val = NULL; } + if (!strcasecmp("X-Stratum", key)) { + hi->stratum_url = val; + val = NULL; + } + out: free(key); free(val); return ptrlen; } -#ifdef CURL_HAS_SOCKOPT int json_rpc_call_sockopt_cb(void __maybe_unused *userdata, curl_socket_t fd, curlsocktype __maybe_unused purpose) { @@ -241,7 +243,6 @@ int json_rpc_call_sockopt_cb(void __maybe_unused *userdata, curl_socket_t fd, return 0; } -#endif static void last_nettime(struct timeval *last) { @@ -265,7 +266,7 @@ json_t *json_rpc_call(CURL *curl, const char *url, { long timeout = longpoll ? (60 * 60) : 60; struct data_buffer all_data = {NULL, 0}; - struct header_info hi = {NULL, 0, NULL, false, false, false}; + struct header_info hi = {NULL, 0, NULL, NULL, false, false, false}; char len_hdr[64], user_agent_hdr[128]; char curl_err_str[CURL_ERROR_SIZE]; struct curl_slist *headers = NULL; @@ -316,10 +317,8 @@ json_t *json_rpc_call(CURL *curl, const char *url, curl_easy_setopt(curl, CURLOPT_USERPWD, userpass); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); } -#ifdef CURL_HAS_SOCKOPT if (longpoll) curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, json_rpc_call_sockopt_cb); -#endif curl_easy_setopt(curl, CURLOPT_POST, 1); if (opt_protocol) @@ -392,9 +391,19 @@ json_t *json_rpc_call(CURL *curl, const char *url, pool->hdr_path = hi.lp_path; } else pool->hdr_path = NULL; - } else if (hi.lp_path) { - free(hi.lp_path); - hi.lp_path = NULL; + if (hi.stratum_url) { + pool->stratum_url = hi.stratum_url; + hi.stratum_url = NULL; + } + } else { + if (hi.lp_path) { + free(hi.lp_path); + hi.lp_path = NULL; + } + if (hi.stratum_url) { + free(hi.stratum_url); + hi.stratum_url = NULL; + } } *rolltime = hi.rolltime; @@ -794,3 +803,578 @@ double tdiff(struct timeval *end, struct timeval *start) { return end->tv_sec - start->tv_sec + (end->tv_usec - start->tv_usec) / 1000000.0; } + +bool extract_sockaddr(struct pool *pool, char *url) +{ + char *url_begin, *url_end, *port_start = NULL; + char url_address[256], port[6]; + struct addrinfo hints, *res; + int url_len, port_len = 0; + + url_begin = strstr(url, "//"); + if (!url_begin) + url_begin = url; + else + url_begin += 2; + url_end = strstr(url_begin, ":"); + if (url_end) { + url_len = url_end - url_begin; + port_len = strlen(url_begin) - url_len - 1; + if (port_len < 1) + return false; + port_start = url_end + 1; + } else + url_len = strlen(url_begin); + + if (url_len < 1) + return false; + + sprintf(url_address, "%.*s", url_len, url_begin); + + if (port_len) + snprintf(port, 6, "%.*s", port_len, port_start); + else + strcpy(port, "80"); + + pool->stratum_port = strdup(port); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo(url_address, port, &hints, &res)) { + applog(LOG_DEBUG, "Failed to extract sock addr"); + return false; + } + + pool->server = (struct sockaddr_in *)res->ai_addr; + pool->sockaddr_url = strdup(url_address); + + return true; +} + +/* Send a single command across a socket, appending \n to it */ +bool stratum_send(struct pool *pool, char *s, ssize_t len) +{ + ssize_t ssent = 0; + bool ret = false; + + if (opt_protocol) + applog(LOG_DEBUG, "SEND: %s", s); + + strcat(s, "\n"); + len++; + + mutex_lock(&pool->stratum_lock); + while (len > 0 ) { + size_t sent = 0; + + if (curl_easy_send(pool->stratum_curl, s + ssent, len, &sent) != CURLE_OK) { + applog(LOG_DEBUG, "Failed to curl_easy_send in stratum_send"); + ret = false; + goto out_unlock; + } + ssent += sent; + len -= ssent; + } + ret = true; +out_unlock: + mutex_unlock(&pool->stratum_lock); + return ret;; +} + +#define RECVSIZE 8191 +#define RBUFSIZE (RECVSIZE + 1) + +static void clear_sock(struct pool *pool) +{ + SOCKETTYPE sock = pool->sock; + + recv(sock, pool->sockbuf, RECVSIZE, MSG_DONTWAIT); + strcpy(pool->sockbuf, ""); +} + +/* Check to see if Santa's been good to you */ +static bool sock_full(struct pool *pool, bool wait) +{ + SOCKETTYPE sock = pool->sock; + struct timeval timeout; + fd_set rd; + + if (strlen(pool->sockbuf)) + return true; + + FD_ZERO(&rd); + FD_SET(sock, &rd); + timeout.tv_usec = 0; + if (wait) + timeout.tv_sec = 60; + else + timeout.tv_sec = 0; + if (select(sock + 1, &rd, NULL, NULL, &timeout) > 0) + return true; + return false; +} + +/* Peeks at a socket to find the first end of line and then reads just that + * from the socket and returns that as a malloced char */ +char *recv_line(struct pool *pool) +{ + ssize_t len, buflen; + char *tok, *sret = NULL; + size_t n; + + if (!strstr(pool->sockbuf, "\n")) { + char s[RBUFSIZE]; + CURLcode rc; + + if (!sock_full(pool, true)) { + applog(LOG_DEBUG, "Timed out waiting for data on sock_full"); + goto out; + } + memset(s, 0, RBUFSIZE); + + mutex_lock(&pool->stratum_lock); + rc = curl_easy_recv(pool->stratum_curl, s, RECVSIZE, &n); + mutex_unlock(&pool->stratum_lock); + + if (rc != CURLE_OK) { + applog(LOG_DEBUG, "Failed to recv sock in recv_line"); + goto out; + } + strcat(pool->sockbuf, s); + } + + buflen = strlen(pool->sockbuf); + tok = strtok(pool->sockbuf, "\n"); + if (!tok) { + applog(LOG_DEBUG, "Failed to parse a \\n terminated string in recv_line"); + goto out; + } + sret = strdup(tok); + len = strlen(sret); + + /* Copy what's left in the buffer after the \n, including the + * terminating \0 */ + if (buflen > len + 1) + memmove(pool->sockbuf, pool->sockbuf + len + 1, buflen - len + 1); + else + strcpy(pool->sockbuf, ""); +out: + if (!sret) + clear_sock(pool); + else if (opt_protocol) + applog(LOG_DEBUG, "RECVD: %s", sret); + return sret; +} + +/* Extracts a string value from a json array with error checking. To be used + * when the value of the string returned is only examined and not to be stored. + * See json_array_string below */ +static char *__json_array_string(json_t *val, unsigned int entry) +{ + json_t *arr_entry; + + if (json_is_null(val)) + return NULL; + if (!json_is_array(val)) + return NULL; + if (entry > json_array_size(val)) + return NULL; + arr_entry = json_array_get(val, entry); + if (!json_is_string(arr_entry)) + return NULL; + + return (char *)json_string_value(arr_entry); +} + +/* Creates a freshly malloced dup of __json_array_string */ +static char *json_array_string(json_t *val, unsigned int entry) +{ + char *buf = __json_array_string(val, entry); + + if (buf) + return strdup(buf); + return NULL; +} + +static bool parse_notify(struct pool *pool, json_t *val) +{ + char *job_id, *prev_hash, *coinbase1, *coinbase2, *bbversion, *nbit, *ntime; + int merkles, i; + json_t *arr; + bool clean; + + arr = json_array_get(val, 4); + if (!arr || !json_is_array(arr)) + return false; + + merkles = json_array_size(arr); + + job_id = json_array_string(val, 0); + prev_hash = json_array_string(val, 1); + coinbase1 = json_array_string(val, 2); + coinbase2 = json_array_string(val, 3); + bbversion = json_array_string(val, 5); + nbit = json_array_string(val, 6); + ntime = json_array_string(val, 7); + clean = json_is_true(json_array_get(val, 8)); + + if (!job_id || !prev_hash || !coinbase1 || !coinbase2 || !bbversion || !nbit || !ntime) { + /* Annoying but we must not leak memory */ + if (job_id) + free(job_id); + if (prev_hash) + free(prev_hash); + if (coinbase1) + free(coinbase1); + if (coinbase2) + free(coinbase2); + if (bbversion) + free(bbversion); + if (nbit) + free(nbit); + if (ntime) + free(ntime); + return false; + } + + mutex_lock(&pool->pool_lock); + pool->swork.job_id = job_id; + pool->swork.prev_hash = prev_hash; + pool->swork.coinbase1 = coinbase1; + pool->swork.coinbase2 = coinbase2; + pool->swork.bbversion = bbversion; + pool->swork.nbit = nbit; + pool->swork.ntime = ntime; + pool->swork.clean = clean; + for (i = 0; i < pool->swork.merkles; i++) + free(pool->swork.merkle[i]); + if (merkles) { + pool->swork.merkle = realloc(pool->swork.merkle, sizeof(char *) * merkles + 1); + for (i = 0; i < merkles; i++) + pool->swork.merkle[i] = json_array_string(arr, i); + } + pool->swork.merkles = merkles; + if (clean) + pool->nonce2 = 0; + mutex_unlock(&pool->pool_lock); + + if (opt_protocol) { + applog(LOG_DEBUG, "job_id: %s", job_id); + applog(LOG_DEBUG, "prev_hash: %s", prev_hash); + applog(LOG_DEBUG, "coinbase1: %s", coinbase1); + applog(LOG_DEBUG, "coinbase2: %s", coinbase2); + for (i = 0; i < merkles; i++) + applog(LOG_DEBUG, "merkle%d: %s", i, pool->swork.merkle[i]); + applog(LOG_DEBUG, "bbversion: %s", bbversion); + applog(LOG_DEBUG, "nbit: %s", nbit); + applog(LOG_DEBUG, "ntime: %s", ntime); + applog(LOG_DEBUG, "clean: %s", clean ? "yes" : "no"); + } + + /* A notify message is the closest stratum gets to a getwork */ + pool->getwork_requested++; + total_getworks++; + return true; +} + +static bool parse_diff(struct pool *pool, json_t *val) +{ + int diff; + + diff = json_integer_value(json_array_get(val, 0)); + if (diff < 1) + return false; + + mutex_lock(&pool->pool_lock); + pool->swork.diff = diff; + mutex_unlock(&pool->pool_lock); + + applog(LOG_DEBUG, "Pool %d difficulty set to %d", pool->pool_no, diff); + + return true; +} + +static bool parse_reconnect(struct pool *pool, json_t *val) +{ + char *url, *port, address[256]; + + memset(address, 0, 255); + url = (char *)json_string_value(json_array_get(val, 0)); + if (!url) + url = pool->sockaddr_url; + + port = (char *)json_string_value(json_array_get(val, 1)); + if (!port) + port = pool->stratum_port; + + sprintf(address, "%s:%s", url, port); + + if (!extract_sockaddr(pool, address)) + return false; + + pool->stratum_url = pool->sockaddr_url; + + applog(LOG_NOTICE, "Reconnect requested from pool %d to %s", pool->pool_no, address); + + if (!initiate_stratum(pool) || !auth_stratum(pool)) + return false; + + return true; +} + +static bool send_version(struct pool *pool, json_t *val) +{ + char s[RBUFSIZE]; + int id = json_integer_value(json_object_get(val, "id")); + + if (!id) + return false; + + sprintf(s, "{\"id\": %d, \"result\": \""PACKAGE"/"VERSION"\", \"error\": null}", id); + if (!stratum_send(pool, s, strlen(s))) + return false; + + return true; +} + +bool parse_method(struct pool *pool, char *s) +{ + json_t *val = NULL, *method, *err_val, *params; + json_error_t err; + bool ret = false; + char *buf; + + if (!s) + goto out; + + val = JSON_LOADS(s, &err); + if (!val) { + applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text); + goto out; + } + + method = json_object_get(val, "method"); + if (!method) + goto out; + err_val = json_object_get(val, "error"); + params = json_object_get(val, "params"); + + if (err_val && !json_is_null(err_val)) { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + + applog(LOG_INFO, "JSON-RPC method decode failed: %s", ss); + + free(ss); + + goto out; + } + + buf = (char *)json_string_value(method); + if (!buf) + goto out; + + if (!strncasecmp(buf, "mining.notify", 13) && parse_notify(pool, params)) { + ret = true; + goto out; + } + + if (!strncasecmp(buf, "mining.set_difficulty", 21) && parse_diff(pool, params)) { + ret = true; + goto out; + } + + if (!strncasecmp(buf, "client.reconnect", 16) && parse_reconnect(pool, params)) { + ret = true; + goto out; + } + + if (!strncasecmp(buf, "client.get_version", 18) && send_version(pool, val)) { + ret = true; + goto out; + } +out: + if (val) + json_decref(val); + + return ret; +} + +bool auth_stratum(struct pool *pool) +{ + json_t *val = NULL, *res_val, *err_val; + char s[RBUFSIZE], *sret = NULL; + json_error_t err; + bool ret = false; + + sprintf(s, "{\"id\": %d, \"method\": \"mining.authorize\", \"params\": [\"%s\", \"%s\"]}", + swork_id++, pool->rpc_user, pool->rpc_pass); + + /* Parse all data prior sending auth request */ + while (sock_full(pool, false)) { + sret = recv_line(pool); + if (!parse_method(pool, sret)) { + clear_sock(pool); + applog(LOG_INFO, "Failed to parse stratum buffer"); + free(sret); + return ret; + } + free(sret); + } + + if (!stratum_send(pool, s, strlen(s))) + goto out; + + sret = recv_line(pool); + if (!sret) + goto out; + val = JSON_LOADS(sret, &err); + free(sret); + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + + if (!res_val || json_is_false(res_val) || (err_val && !json_is_null(err_val))) { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + applog(LOG_WARNING, "JSON stratum auth failed: %s", ss); + free(ss); + + goto out; + } + ret = true; + applog(LOG_INFO, "Stratum authorisation success for pool %d", pool->pool_no); +out: + if (val) + json_decref(val); + + return ret; +} + +bool initiate_stratum(struct pool *pool) +{ + json_t *val = NULL, *res_val, *err_val; + char curl_err_str[CURL_ERROR_SIZE]; + char s[RBUFSIZE], *sret = NULL; + CURL *curl = NULL; + json_error_t err; + bool ret = false; + + if (!pool->stratum_curl) { + pool->stratum_curl = curl_easy_init(); + if (unlikely(!pool->stratum_curl)) + quit(1, "Failed to curl_easy_init in initiate_stratum"); + } + curl = pool->stratum_curl; + + /* Create a http url for use with curl */ + memset(s, 0, RBUFSIZE); + sprintf(s, "http://%s:%s", pool->sockaddr_url, pool->stratum_port); + + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 60); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_URL, s); + curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1); + curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY); + if (pool->rpc_proxy) { + curl_easy_setopt(curl, CURLOPT_PROXY, pool->rpc_proxy); + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, pool->rpc_proxytype); + } else if (opt_socks_proxy) { + curl_easy_setopt(curl, CURLOPT_PROXY, opt_socks_proxy); + curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); + } + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1); + if (curl_easy_perform(curl)) { + applog(LOG_INFO, "Stratum connect failed to pool %d: %s", pool->pool_no, curl_err_str); + goto out; + } + curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, (long *)&pool->sock); + + sprintf(s, "{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": []}", swork_id++); + + if (!stratum_send(pool, s, strlen(s))) { + applog(LOG_DEBUG, "Failed to send s in initiate_stratum"); + goto out; + } + + if (!sock_full(pool, true)) { + applog(LOG_DEBUG, "Timed out waiting for response in initiate_stratum"); + goto out; + } + + sret = recv_line(pool); + if (!sret) + goto out; + + val = JSON_LOADS(sret, &err); + free(sret); + if (!val) { + applog(LOG_INFO, "JSON decode failed(%d): %s", err.line, err.text); + goto out; + } + + res_val = json_object_get(val, "result"); + err_val = json_object_get(val, "error"); + + if (!res_val || json_is_null(res_val) || + (err_val && !json_is_null(err_val))) { + char *ss; + + if (err_val) + ss = json_dumps(err_val, JSON_INDENT(3)); + else + ss = strdup("(unknown reason)"); + + applog(LOG_INFO, "JSON-RPC decode failed: %s", ss); + + free(ss); + + goto out; + } + + pool->nonce1 = json_array_string(res_val, 1); + if (!pool->nonce1) { + applog(LOG_INFO, "Failed to get nonce1 in initiate_stratum"); + goto out; + } + pool->n2size = json_integer_value(json_array_get(res_val, 2)); + if (!pool->n2size) { + applog(LOG_INFO, "Failed to get n2size in initiate_stratum"); + goto out; + } + + ret = true; +out: + if (val) + json_decref(val); + + if (ret) { + if (!pool->stratum_url) + pool->stratum_url = pool->sockaddr_url; + pool->stratum_active = true; + pool->swork.diff = 1; + if (opt_protocol) { + applog(LOG_DEBUG, "Pool %d confirmed mining.subscribe with extranonce1 %s extran2size %d", + pool->pool_no, pool->nonce1, pool->n2size); + } + } else { + pool->stratum_active = false; + if (curl) { + curl_easy_cleanup(curl); + pool->stratum_curl = NULL; + } + } + + return ret; +} diff --git a/util.h b/util.h new file mode 100644 index 00000000..d5ac54c3 --- /dev/null +++ b/util.h @@ -0,0 +1,53 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#if defined(unix) || defined(__APPLE__) + #include + #include + #include + #include + + #define SOCKETTYPE long + #define SOCKETFAIL(a) ((a) < 0) + #define INVSOCK -1 + #define INVINETADDR -1 + #define CLOSESOCKET close + + #define SOCKERRMSG strerror(errno) +#elif defined WIN32 + #include + #include + + #define SOCKETTYPE SOCKET + #define SOCKETFAIL(a) ((int)(a) == SOCKET_ERROR) + #define INVSOCK INVALID_SOCKET + #define INVINETADDR INADDR_NONE + #define CLOSESOCKET closesocket + + extern char *WSAErrorMsg(void); + #define SOCKERRMSG WSAErrorMsg() + + #ifndef SHUT_RDWR + #define SHUT_RDWR SD_BOTH + #endif + + #ifndef in_addr_t + #define in_addr_t uint32_t + #endif +#endif + +#if JANSSON_MAJOR_VERSION >= 2 +#define JSON_LOADS(str, err_ptr) json_loads((str), 0, (err_ptr)) +#else +#define JSON_LOADS(str, err_ptr) json_loads((str), (err_ptr)) +#endif + +struct pool; +bool stratum_send(struct pool *pool, char *s, ssize_t len); +char *recv_line(struct pool *pool); +bool parse_method(struct pool *pool, char *s); +bool extract_sockaddr(struct pool *pool, char *url); +bool auth_stratum(struct pool *pool); +bool initiate_stratum(struct pool *pool); + +#endif /* __UTIL_H__ */