diff --git a/src/datum_api.c b/src/datum_api.c index ba54565c..4490a20e 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -90,7 +90,9 @@ static void html_leading_zeros(char * const buffer, const size_t buffer_size, co } static void datum_api_format_share_counts(char *buffer, size_t buffer_size, uint64_t count, uint64_t diff) { - snprintf(buffer, buffer_size, "%llu (%llu diff)", (unsigned long long)count, (unsigned long long)diff); + char diffstr[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + datum_format_difficulty(diffstr, sizeof(diffstr), datum_pdiff_to_diff(diff)); + snprintf(buffer, buffer_size, "%llu (%s diff)", (unsigned long long)count, diffstr); } void datum_api_var_STRATUM_SHARES_ACCEPTED(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { @@ -171,7 +173,7 @@ void datum_api_var_DATUM_MINER_TAG(char *buffer, size_t buffer_size, const T_DAT buffer[i+2] = 0; } void datum_api_var_DATUM_POOL_DIFF(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { - snprintf(buffer, buffer_size, "%llu", (unsigned long long)datum_config.override_vardiff_min); + datum_format_difficulty(buffer, buffer_size, datum_pdiff_to_diff(datum_config.override_vardiff_min)); } void datum_api_var_DATUM_POOL_PUBKEY(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { snprintf(buffer, buffer_size, "%s", datum_config.datum_pool_pubkey); @@ -228,7 +230,8 @@ void datum_api_var_STRATUM_JOB_WITNESS(char *buffer, size_t buffer_size, const T snprintf(buffer, buffer_size, "%s", vardata->sjob->block_template->default_witness_commitment); } void datum_api_var_STRATUM_JOB_DIFF(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { - snprintf(buffer, buffer_size, "%.3Lf", calc_network_difficulty(vardata->sjob->nbits)); + datum_format_difficulty(buffer, buffer_size, + calc_network_difficulty_blake2b(vardata->sjob->nbits_uint)); } void datum_api_var_STRATUM_JOB_VERSION(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { snprintf(buffer, buffer_size, "%s (%u)", vardata->sjob->version, (unsigned)vardata->sjob->version_uint); @@ -902,6 +905,7 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { double hr; unsigned char astat; double thr = 0.0; + char difficulty[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; const int max_threads = global_stratum_app ? global_stratum_app->max_threads : 0; @@ -946,14 +950,17 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { sz += snprintf(&output[sz], max_sz-1-sz, "N/A"); } - sz += snprintf(&output[sz], max_sz-1-sz, "%"PRIu64"", m->current_diff); - sz += snprintf(&output[sz], max_sz-1-sz, "%"PRIu64" (%"PRIu64")", m->share_diff_accepted, m->share_count_accepted); + datum_format_difficulty(difficulty, sizeof(difficulty), datum_pdiff_to_diff(m->current_diff)); + sz += snprintf(&output[sz], max_sz-1-sz, "%s", difficulty); + datum_format_difficulty(difficulty, sizeof(difficulty), datum_pdiff_to_diff(m->share_diff_accepted)); + sz += snprintf(&output[sz], max_sz-1-sz, "%s (%"PRIu64")", difficulty, m->share_count_accepted); hr = 0.0; if (m->share_diff_accepted > 0) { hr = ((double)m->share_diff_rejected / (double)(m->share_diff_accepted + m->share_diff_rejected))*100.0; } - sz += snprintf(&output[sz], max_sz-1-sz, "%"PRIu64" (%"PRIu64") %.2f%%", m->share_diff_rejected, m->share_count_rejected, hr); + datum_format_difficulty(difficulty, sizeof(difficulty), datum_pdiff_to_diff(m->share_diff_rejected)); + sz += snprintf(&output[sz], max_sz-1-sz, "%s (%"PRIu64") %.2f%%", difficulty, m->share_count_rejected, hr); astat = m->stats.active_index?0:1; // inverted hr = 0.0; @@ -1082,6 +1089,13 @@ size_t datum_api_fill_config_var(const char *var_start, const size_t var_name_le DLOG_ERROR("%s: %s not implemented", __func__, "DATUM_CONF_USERNAME_MODS"); break; } + case DATUM_CONF_DIFFICULTY: { + val = *((int *)item->ptr); + if (!colon_pos) { + return datum_format_difficulty(replacement, replacement_max_len, datum_pdiff_to_diff(val)); + } + break; + } } } else { DLOG_ERROR("%s: '%.*s' not implemented", __func__, (int)(var_end - var_start_2), var_start_2); diff --git a/src/datum_conf.c b/src/datum_conf.c index 399b6176..12b1b4bb 100644 --- a/src/datum_conf.c +++ b/src/datum_conf.c @@ -36,6 +36,8 @@ // Custom configurator and help output generator #include +#include +#include #include #include #include @@ -58,6 +60,7 @@ const char *datum_conf_var_type_text[] = { "string", "string_array", "{\"modname\":{\"address\":proportion,...},...}", + "difficulty", }; const T_DATUM_CONFIG_ITEM datum_config_options[] = { @@ -93,7 +96,7 @@ const T_DATUM_CONFIG_ITEM datum_config_options[] = { .required = false, .ptr = &datum_config.stratum_v1_max_clients, .default_int = 1024 }, { .var_type = DATUM_CONF_INT, .category = "stratum", .name = "trust_proxy", .description = "Enable support for the PROXY protocol, trusting up to the specified number of levels deep of proxies (-1 to disable entirely)", .required = false, .ptr = &datum_config.stratum_v1_trust_proxy, .default_int = -1 }, - { .var_type = DATUM_CONF_INT, .category = "stratum", .name = "vardiff_min", .description = "Work difficulty floor", + { .var_type = DATUM_CONF_DIFFICULTY, .category = "stratum", .name = "vardiff_min", .description = "Work difficulty floor (integer or quoted decimal with SI suffix)", .required = false, .ptr = &datum_config.stratum_v1_vardiff_min, .default_int = 1024 }, { .var_type = DATUM_CONF_INT, .category = "stratum", .name = "vardiff_target_shares_min",.description = "Adjust work difficulty to target this many shares per minute", .required = false, .ptr = &datum_config.stratum_v1_vardiff_target_shares_min, .default_int = 8 }, @@ -261,6 +264,11 @@ void datum_config_set_default(const T_DATUM_CONFIG_ITEM *c) { *umods_p = NULL; break; } + + case DATUM_CONF_DIFFICULTY: { + *((int *)c->ptr) = c->default_int; + break; + } } } @@ -363,6 +371,110 @@ struct datum_username_mod *datum_username_mods_find(struct datum_username_mod *u return NULL; } +static int datum_config_compare_difficulty(const char *value, const char *end, int exponent, uint64_t boundary) { + // Removing the decimal point gives an integer coefficient in units of 10^exponent. + // Compare it with the hash boundary rounded to those units (the input's half-unit tolerance). + if (exponent > 0) { + for (int i = 1; i < exponent && boundary; ++i) boundary /= 10; + boundary = boundary / 10 + (boundary % 10 >= 5); + if (!boundary) return 1; // The validated input is positive. + exponent = 0; + } + int comparison = 0; + while (end > value) { + const char digit = *--end; + if (digit == '.') continue; + const int value_digit = digit - '0'; + int boundary_digit; + if (exponent < 0) { + boundary_digit = 0; + ++exponent; // Consume a virtual trailing zero without multiplying the boundary. + } else { + boundary_digit = boundary % 10; + boundary /= 10; + } + if (value_digit != boundary_digit) { + comparison = value_digit > boundary_digit ? 1 : -1; + } + } + return boundary ? -1 : comparison; +} + +int datum_config_parse_difficulty(int * const out, json_t * const item) { + switch (json_typeof(item)) { + case JSON_INTEGER: { + const json_int_t value = json_integer_value(item); + if (value < 1) return -1; + if (value <= INT_MAX) { + *out = roundDownToPowerOfTwo_64(value); + return (*out == value) ? 3 : -1; + } + const uint64_t hashes = value; + const uint64_t minimum = (hashes >> 32) + !!(hashes & UINT32_MAX); + const uint64_t max = roundDownToPowerOfTwo_64(INT_MAX); + if (minimum > max) return -1; + *out = minimum <= 1 ? 1 : roundDownToPowerOfTwo_64(minimum - 1) << 1; + return (((uint64_t)*out << 32) > hashes) ? 2 : 1; + } + case JSON_REAL: + case JSON_STRING: + break; + default: + return -1; + } + + const char * const value_str = json_string_value(item); + char *end = NULL; + int exponent = 0; + bitcoin_difficulty_typ requested; + if (value_str) { + errno = 0; + requested = strtold(value_str, &end); + if (errno || end == value_str) return -1; + + int decimal_places = 0; + for (const char *p = value_str; p < end; ++p) { + if (*p == '.') { + if (end - p - 1 > INT_MAX) return -1; + decimal_places = (end - p) - 1; + } else if (!isdigit((unsigned char)*p)) { + // Don't allow explicit exponents, whitespace, sign, etc + return -1; + } + } + + int decimal_exponent = 0; + if (end[0]) { + static const char suffixes[] = "KMGTPEZYRQ"; + const char * const suffix = strchr(suffixes, toupper((unsigned char)end[0])); + if (end[1] || !suffix) return -1; + decimal_exponent = ((suffix - suffixes) + 1) * 3; + requested *= powl(10.0L, decimal_exponent); + } + exponent = decimal_exponent - decimal_places; + } else { + requested = json_real_value(item); + } + + if (!isfinite(requested) || requested <= 0) return -1; + const uint64_t max = roundDownToPowerOfTwo_64(INT_MAX); + // Estimate the next power of two; only the original digits decide which boundary is admissible. + const uint64_t minimum = ceill(fminl(requested / datum_pdiff_to_diff(1), max)); + *out = minimum <= 1 ? 1 : roundDownToPowerOfTwo_64(minimum - 1) << 1; + if (!value_str) { + if (requested > datum_pdiff_to_diff(max)) return -1; + return datum_pdiff_to_diff(*out) > requested ? 2 : 1; + } + // A positive decimal's half-unit tolerance cannot extend below half its value. + if (*out > 1 && datum_config_compare_difficulty(value_str, end, exponent, (uint64_t)(*out / 2) << 32) <= 0) { + *out /= 2; + } else if (datum_config_compare_difficulty(value_str, end, exponent, (uint64_t)*out << 32) > 0) { + if ((uint64_t)*out >= max) return -1; + *out *= 2; + } + return datum_config_compare_difficulty(value_str, end, exponent, (uint64_t)*out << 32) < 0 ? 2 : 1; +} + int datum_config_parse_value(const T_DATUM_CONFIG_ITEM *c, json_t *item) { switch(c->var_type) { case DATUM_CONF_INT: { @@ -426,6 +538,25 @@ int datum_config_parse_value(const T_DATUM_CONFIG_ITEM *c, json_t *item) { case DATUM_CONF_USERNAME_MODS: { return datum_config_parse_username_mods(c->ptr, item, true); } + + case DATUM_CONF_DIFFICULTY: { + int * const out = c->ptr; + const int result = datum_config_parse_difficulty(out, item); + if (result < 0) return result; + if (result > 1) { + char diffstr[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + datum_format_difficulty(diffstr, sizeof(diffstr), datum_pdiff_to_diff(*out)); + switch (result) { + case 2: + DLOG_WARN("%s.%s rounded up to %s", c->category, c->name, diffstr); + break; + case 3: + DLOG_WARN("%s.%s uses legacy integer syntax; use \"%s\" instead", c->category, c->name, diffstr); + break; + } + } + return 1; + } } return -1; @@ -589,17 +720,17 @@ int datum_read_config(const char *conffile) { return 0; } + if (datum_config.stratum_v1_vardiff_min < 1) { + DLOG_FATAL("Stratum server stratum.vardiff_min must be at least 1 (suggest at least 1024, but more likely 32768)"); + return 0; + } + if (roundDownToPowerOfTwo_64(datum_config.stratum_v1_vardiff_min) != datum_config.stratum_v1_vardiff_min) { const int nv = roundDownToPowerOfTwo_64(datum_config.stratum_v1_vardiff_min); DLOG_WARN("stratum.vardiff_min MUST be a power of two. adjusting from %d to %d", datum_config.stratum_v1_vardiff_min, nv); datum_config.stratum_v1_vardiff_min = nv; } - if (datum_config.stratum_v1_vardiff_min < 1) { - DLOG_FATAL("Stratum server stratum.vardiff_min must be at least 1 (suggest at least 1024, but more likely 32768)"); - return 0; - } - if (datum_config.stratum_v1_max_clients > (datum_config.stratum_v1_max_clients_per_thread*datum_config.stratum_v1_max_threads)) { DLOG_FATAL("Stratum server configuration error. Max clients too high for thread settings"); return 0; @@ -677,6 +808,13 @@ void datum_gateway_help(const char * const argv0) { break; } + case DATUM_CONF_DIFFICULTY: { + char difficulty[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + datum_format_difficulty(difficulty, sizeof(difficulty), datum_pdiff_to_diff(opt->default_int)); + printf(", default: \"%s\")\n", difficulty); + break; + } + default: { puts(")"); break; @@ -737,6 +875,13 @@ void datum_gateway_example_conf(void) { puts("{}"); break; } + + case DATUM_CONF_DIFFICULTY: { + char difficulty[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + datum_format_difficulty(difficulty, sizeof(difficulty), datum_pdiff_to_diff(opt->default_int)); + printf("\"%s\"", difficulty); + break; + } } } } diff --git a/src/datum_conf.h b/src/datum_conf.h index 85f014e2..a0f5f154 100644 --- a/src/datum_conf.h +++ b/src/datum_conf.h @@ -55,6 +55,7 @@ enum datum_conf_vartype { DATUM_CONF_STRING, DATUM_CONF_STRING_ARRAY, DATUM_CONF_USERNAME_MODS, + DATUM_CONF_DIFFICULTY, }; typedef struct { @@ -80,6 +81,7 @@ typedef struct { const T_DATUM_CONFIG_ITEM *datum_config_get_option_info(const char *category, size_t category_len, const char *name, size_t name_len); const T_DATUM_CONFIG_ITEM *datum_config_get_option_info2(const char *category, const char *name); +int datum_config_parse_difficulty(int *out, json_t *item); struct datum_addr_range { char *addr; diff --git a/src/datum_conf_tests.c b/src/datum_conf_tests.c index 682c0829..70b94802 100644 --- a/src/datum_conf_tests.c +++ b/src/datum_conf_tests.c @@ -43,6 +43,78 @@ #include "datum_jsonrpc.h" #include "datum_utils.h" +static void datum_conf_test_difficulty_(json_t * const input, const int expected_ret, const int expected_pdiff, + const char * const expr, const unsigned int line, const char * const func) { + assert(input); + char err[0x100]; + int pdiff; + const int rv = datum_config_parse_difficulty(&pdiff, input); + if (rv != expected_ret) { + snprintf(err, sizeof(err), "return value for %s was %d", expr, rv); + datum_test_fail_(err, __FILE__, line, func); + } + if (expected_ret > 0 && pdiff != expected_pdiff) { + snprintf(err, sizeof(err), "output value for %s was %d", expr, pdiff); + datum_test_fail_(err, __FILE__, line, func); + } + json_decref(input); +} +#define datum_conf_test_difficulty(input, ...) \ + datum_conf_test_difficulty_(input, __VA_ARGS__, #input, __LINE__, __func__) + +static void datum_conf_difficulty_tests(void) { + datum_conf_test_difficulty(json_integer(1023), -1, 0); + datum_conf_test_difficulty(json_string("1023"), 2, 1); + datum_conf_test_difficulty(json_integer(1024), 3, 1024); + datum_conf_test_difficulty(json_string("1024"), 2, 1); + datum_conf_test_difficulty(json_integer(4398046511104ULL), 1, 1024); + datum_conf_test_difficulty(json_string("4398046511104"), 1, 1024); + datum_conf_test_difficulty(json_integer(4400000000000ULL), 2, 2048); + datum_conf_test_difficulty(json_integer(18014398509481984LL), 1, 1 << 22); + datum_conf_test_difficulty(json_integer(18014398509481985LL), 2, 1 << 23); + datum_conf_test_difficulty(json_string("18014398509481983"), 2, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481984"), 1, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481985"), 2, 1 << 23); + datum_conf_test_difficulty(json_string("00018014398509481984.000"), 1, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481983.999"), 2, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481984.001"), 2, 1 << 23); + datum_conf_test_difficulty(json_string("18014398509481984.0000000000000000000000000000000000000000000000000"), 1, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481985.0000000000000000000000000000000000000000000000000"), 2, 1 << 23); + datum_conf_test_difficulty(json_string("18.014398509481984P"), 1, 1 << 22); + datum_conf_test_difficulty(json_string("18.014398509481985P"), 2, 1 << 23); + datum_conf_test_difficulty(json_string("18.014398509481983P"), 2, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481.98k"), 1, 1 << 22); + datum_conf_test_difficulty(json_string("18014398509481.99k"), 2, 1 << 23); + datum_conf_test_difficulty(json_string("4611686018427387904"), 1, 1 << 30); + datum_conf_test_difficulty(json_string("4611686018427387903"), 2, 1 << 30); + datum_conf_test_difficulty(json_string("4611686018427387905"), -1, 0); + datum_conf_test_difficulty(json_string("4.611686018427387904E"), 1, 1 << 30); + datum_conf_test_difficulty(json_string("4.611686018427387905E"), -1, 0); + datum_conf_test_difficulty(json_real(4398046511104.0), 1, 1024); + datum_conf_test_difficulty(json_real(18014398509481984.0), 1, 1 << 22); + datum_conf_test_difficulty(json_real(18014398509481988.0), 2, 1 << 23); + datum_conf_test_difficulty(json_real(0.0), -1, 0); + datum_conf_test_difficulty(json_real(-1.0), -1, 0); + datum_conf_test_difficulty(json_real(1e20), -1, 0); + datum_conf_test_difficulty(json_string("4G"), 1, 1); + datum_conf_test_difficulty(json_string("4.4T"), 1, 1024); + datum_conf_test_difficulty(json_string("4.40T"), 1, 1024); + datum_conf_test_difficulty(json_string("4.400T"), 2, 2048); + datum_conf_test_difficulty(json_string("4.5T"), 2, 2048); + datum_conf_test_difficulty(json_string("2T"), 1, 512); + datum_conf_test_difficulty(json_string("1T"), 1, 128); + datum_conf_test_difficulty(json_string("1E"), 1, 1 << 27); + datum_conf_test_difficulty(json_string("0.0000000000000000044Q"), 1, 1024); + datum_conf_test_difficulty(json_string("4.6E"), 1, 1 << 30); + datum_conf_test_difficulty(json_string("1.2Z"), -1, 0); + datum_conf_test_difficulty(json_string("1Q"), -1, 0); + datum_conf_test_difficulty(json_string("4.4X"), -1, 0); + datum_conf_test_difficulty(json_string("4.4\xc3\xa9"), -1, 0); + datum_conf_test_difficulty(json_string("4e12"), -1, 0); + datum_conf_test_difficulty(json_string("4.4e12"), -1, 0); + datum_conf_test_difficulty(json_string("0x1p42"), -1, 0); +} + struct datum_test_username_mods_range { const char *addr; uint16_t max; @@ -207,5 +279,6 @@ void datum_conf_username_mods_tests() { } void datum_conf_tests(void) { + datum_conf_difficulty_tests(); datum_conf_username_mods_tests(); } diff --git a/src/datum_protocol.c b/src/datum_protocol.c index 1d9356d3..2b31214b 100644 --- a/src/datum_protocol.c +++ b/src/datum_protocol.c @@ -1421,11 +1421,21 @@ int datum_protocol_client_configure(int len, unsigned char *data) { datum_config.override_mining_coinbase_tag_primary[a] = 0; if (i + 8 > len) goto err; - datum_config.override_vardiff_min = upk_u64le(data, i); i+=8; - if (datum_config.override_vardiff_min != roundDownToPowerOfTwo_64(datum_config.override_vardiff_min)) { - DLOG_WARN("Server specified a minimum difficulty that is not a power of two! Is your client up to date? Rounding up to a power of two! (%"PRIu64" to %"PRIu64")", datum_config.override_vardiff_min, roundDownToPowerOfTwo_64(datum_config.override_vardiff_min)<<1); - datum_config.override_vardiff_min = roundDownToPowerOfTwo_64(datum_config.override_vardiff_min)<<1; + uint64_t server_vardiff_min = upk_u64le(data, i); i+=8; + { + uint64_t server_vardiff_min_rounded = server_vardiff_min ? roundDownToPowerOfTwo_64(server_vardiff_min) : 1; + if (server_vardiff_min != server_vardiff_min_rounded) { + if (server_vardiff_min > 0 && server_vardiff_min < DATUM_MAX_PDIFF) { + server_vardiff_min_rounded <<= 1; + } + char requested[DATUM_FORMAT_DIFFICULTY_OUT_SZ], effective[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + datum_format_difficulty(requested, sizeof(requested), datum_pdiff_to_diff(server_vardiff_min)); + datum_format_difficulty(effective, sizeof(effective), datum_pdiff_to_diff(server_vardiff_min_rounded)); + DLOG_WARN("Server specified a minimum difficulty that is not a power of two! Is your client up to date? Adjusting to a supported value! (%s to %s)", requested, effective); + server_vardiff_min = server_vardiff_min_rounded; + } } + datum_config.override_vardiff_min = server_vardiff_min; if (i + 2 > len) goto err; const unsigned char config_flags = data[i]; @@ -1456,7 +1466,12 @@ int datum_protocol_client_configure(int len, unsigned char *data) { DLOG_DEBUG("DATUM Pool Payout Script: (len %u) %s", (unsigned)datum_config.override_mining_pool_scriptpubkey_len, msg); DLOG_DEBUG("DATUM Pool Coinbase Tag: \"%s\"",datum_config.override_mining_coinbase_tag_primary); DLOG_DEBUG("DATUM Pool Prime ID: %16.16"PRIx64, datum_config.prime_id); - DLOG_DEBUG("DATUM Pool Min Diff: %"PRIu64,datum_config.override_vardiff_min); + { + const bitcoin_difficulty_typ diff = datum_pdiff_to_diff(datum_config.override_vardiff_min); + char diffstr[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + datum_format_difficulty(diffstr, sizeof(diffstr), diff); + DLOG_DEBUG("DATUM Pool Min Diff: %"PRIdiff" (%s)", diff, diffstr); + } DLOG_DEBUG("DATUM Pool ABW: %s", pool_abw_enabled ? "enabled" : "disabled"); diff --git a/src/datum_protocol_tests.c b/src/datum_protocol_tests.c index eb0f82ea..3cc36fc1 100644 --- a/src/datum_protocol_tests.c +++ b/src/datum_protocol_tests.c @@ -179,6 +179,7 @@ static void datum_protocol_config_v3_tests(void) { i += 40; payload[i++] = 3; memcpy(payload + i, "tag", 3); i += 3; + const size_t vardiff_min = i; pk_u64le(payload, i, 1024); i += 8; const size_t config_flags = i; payload[i++] = 0; @@ -191,6 +192,16 @@ static void datum_protocol_config_v3_tests(void) { datum_test(datum_config.override_mining_pool_scriptpubkey[0] == 0x51); datum_test(!strcmp(datum_config.override_mining_coinbase_tag_primary, "tag")); datum_test(datum_config.override_vardiff_min == 1024); + pk_u64le(payload, vardiff_min, 0); + datum_test(datum_protocol_client_configure((int)i, payload)); + datum_test(datum_config.override_vardiff_min == 1); + pk_u64le(payload, vardiff_min, DATUM_MAX_PDIFF); + datum_test(datum_protocol_client_configure((int)i, payload)); + datum_test(datum_config.override_vardiff_min == DATUM_MAX_PDIFF); + pk_u64le(payload, vardiff_min, UINT64_MAX); + datum_test(datum_protocol_client_configure((int)i, payload)); + datum_test(datum_config.override_vardiff_min == DATUM_MAX_PDIFF); + pk_u64le(payload, vardiff_min, 1024); datum_test(!datum_protocol_is_active()); unsigned char notice[36] = { DATUM_ABW_DRAFT_REVISION, DATUM_ABW_ASSIGNMENT_ACTIVE, 0, diff --git a/src/datum_stratum.c b/src/datum_stratum.c index 89b4654f..44000cc4 100644 --- a/src/datum_stratum.c +++ b/src/datum_stratum.c @@ -851,8 +851,15 @@ void stratum_update_vardiff(T_DATUM_CLIENT_DATA *c, bool no_quick) { // reusing this var... // try to set the difficulty quickly to a value that makes some sense based on how many shares we just saw - delta_tsms = roundDownToPowerOfTwo_64((target_ms_share / ms_per_share) * m->current_diff); - if (delta_tsms < (m->current_diff << 2)) { + const uint64_t diff_factor = target_ms_share / ms_per_share; + if (diff_factor > DATUM_MAX_PDIFF / m->current_diff) { + delta_tsms = DATUM_MAX_PDIFF; + } else { + delta_tsms = roundDownToPowerOfTwo_64(diff_factor * m->current_diff); + } + if (m->current_diff > DATUM_MAX_PDIFF >> 2) { + delta_tsms = DATUM_MAX_PDIFF; + } else if (delta_tsms < (m->current_diff << 2)) { delta_tsms = (m->current_diff << 2); } @@ -888,7 +895,7 @@ void stratum_update_vardiff(T_DATUM_CLIENT_DATA *c, bool no_quick) { if (ms_per_share < (target_ms_share/2)) { // adjust diff upward a tick - m->current_diff = m->current_diff << 1; + if (m->current_diff < DATUM_MAX_PDIFF) m->current_diff <<= 1; reset_vardiff_stats(c); return; } diff --git a/src/datum_utils.c b/src/datum_utils.c index 9d296d9d..9fad5271 100644 --- a/src/datum_utils.c +++ b/src/datum_utils.c @@ -607,31 +607,27 @@ bool strncpy_uachars(char *out, const char *in, size_t maxlen) { return true; } -long double calc_network_difficulty(const char *bits_hex) { - // given a share solution in hex, calculate the network difficulty - // Postgres code for this (with hex_to_int added function) - // (pow(10, ( (29-tpower_val)*2.4082399653118495617099111577959 ) + log( (65535 / tvalue_val) ) ) ) as network_difficulty - - char tpower[3]; - char tvalue[7]; - unsigned char tpower_val; - unsigned long tvalue_val; - char *ep; - int i; - long double d; - signed short s; - - tpower[0] = bits_hex[0]; - tpower[1] = bits_hex[1]; - tpower[2] = 0; - - for(i=0;i<6;i++) tvalue[i] = bits_hex[2+i]; - tvalue[6] = 0; - tpower_val = (unsigned char)strtoul(tpower, &ep, 16); - tvalue_val = strtoul(tvalue, &ep, 16); - s = (signed short)29 - (signed short)tpower_val; - d = powl(10.0,(double)s*(long double)2.4082399653118495617099111577959 + log10(65535.0 / (double)tvalue_val)); - return d; +bitcoin_difficulty_typ calc_network_difficulty_blake2b(uint32_t nbits) { + const unsigned int exponent = nbits >> 24; + const uint32_t mantissa = nbits & UINT32_C(0x00ffffff); + + return ldexpl(1.0L, 280 - 8 * (int)exponent) / mantissa; +} + +int datum_format_difficulty(char * const out, const size_t outsz, bitcoin_difficulty_typ diff) { + if (diff < 1000) { + return snprintf(out, outsz, "%"PRIdiff, diff); + } + + static const char suffixes[] = "kMGTPEZYRQ"; + const char *suffix = &suffixes[0]; + + diff /= 1000; + while (diff >= 999.95L && suffix[1]) { + diff /= 1000; + ++suffix; + } + return snprintf(out, outsz, "%.1Lf%c", diff, suffix[0]); } #define SIPHASH_ROTATE(a, b) ((uint64_t)(((a)<<(b))|((a)>>(64-(b))))) diff --git a/src/datum_utils.h b/src/datum_utils.h index ff4ac339..7de47737 100644 --- a/src/datum_utils.h +++ b/src/datum_utils.h @@ -39,6 +39,7 @@ #include #include #include +#include #include "datum_logger.h" void datum_utils_init(void); @@ -60,10 +61,20 @@ bool my_sha256(void *digest, const void *buffer, size_t length); void nbits_to_target(uint32_t nbits, uint8_t *target); int compare_hashes(const uint8_t *hash1, const uint8_t *hash2); +typedef long double bitcoin_difficulty_typ; +_Static_assert(LDBL_MANT_DIG >= 23, "long double must have at least 23 bits of precision for compact targets"); +#define PRIdiff ".0Lf" + +#define DATUM_MAX_PDIFF (UINT64_C(1) << 63) + static inline long double datum_pdiff_to_bdiff(uint64_t n) { return ((long double)n) * 65535.0L / 65536.0L; } +static inline bitcoin_difficulty_typ datum_pdiff_to_diff(uint64_t n) { + return (bitcoin_difficulty_typ)n * 4294967296.0L; +} + unsigned long long block_reward(unsigned int block_height); int append_bitcoin_varint_hex(uint64_t n, char *s); int append_UNum_hex(uint64_t n, char *s); @@ -80,7 +91,9 @@ void uchar_to_hex(char *s, const unsigned char b); int get_bitcoin_varint_len_bytes(uint64_t n); bool strncpy_uachars(char *out, const char *in, size_t maxlen); bool strncpy_workerchars(char *out, const char *in, size_t maxlen); -long double calc_network_difficulty(const char *bits_hex); +bitcoin_difficulty_typ calc_network_difficulty_blake2b(uint32_t nbits); +#define DATUM_FORMAT_DIFFICULTY_OUT_SZ 8 +int datum_format_difficulty(char *out, size_t out_size, bitcoin_difficulty_typ difficulty); unsigned char floorPoT(uint64_t x); uint64_t datum_siphash(const void *src, uint64_t sz, const unsigned char key[16]); uint64_t datum_siphash_mod8(const void *src, uint64_t sz, const unsigned char key[16]); diff --git a/src/datum_utils_tests.c b/src/datum_utils_tests.c index 58820b07..d561a1d1 100644 --- a/src/datum_utils_tests.c +++ b/src/datum_utils_tests.c @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -141,9 +142,28 @@ static void datum_utils_tests_pdiff_to_bdiff(void) { datum_test(datum_pdiff_to_bdiff(16) == 15.999755859375L); } +static void datum_utils_tests_network_difficulty_blake2b(void) { + const bitcoin_difficulty_typ difficulty = calc_network_difficulty_blake2b(UINT32_C(0x1702c4e4)); + char formatted[DATUM_FORMAT_DIFFICULTY_OUT_SZ]; + + datum_test(fabsl(difficulty / 4.3657653085953146e23L - 1.0L) < 1e-15L); + datum_test(datum_pdiff_to_diff(1) == 4294967296.0L); + datum_test(datum_pdiff_to_diff(DATUM_MAX_PDIFF) == ldexpl(1.0L, 95)); + datum_test(datum_pdiff_to_diff(UINT64_MAX) > (bitcoin_difficulty_typ)UINT64_MAX); + datum_test(datum_format_difficulty(formatted, sizeof(formatted), datum_pdiff_to_diff(DATUM_MAX_PDIFF)) > 0); + datum_test(!strcmp(formatted, "39.6R")); + datum_test(datum_format_difficulty(formatted, sizeof(formatted), datum_pdiff_to_diff(1024)) > 0); + datum_test(!strcmp(formatted, "4.4T")); + datum_test(datum_format_difficulty(formatted, sizeof(formatted), 999950) > 0); + datum_test(!strcmp(formatted, "1.0M")); + datum_test(datum_format_difficulty(formatted, sizeof(formatted), difficulty) > 0); + datum_test(!strcmp(formatted, "436.6Z")); +} + void datum_utils_tests(void) { datum_utils_tests_hex(); datum_utils_tests_secure_strequals(); datum_utils_tests_scriptnum(); datum_utils_tests_pdiff_to_bdiff(); + datum_utils_tests_network_difficulty_blake2b(); }