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();
}