diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ce6d8b..9db35d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,9 +25,9 @@ install(TARGETS datum_gateway DESTINATION bin) set(WEB_RESOURCES www/home.html - www/clients_top.html - www/coinbaser_top.html - www/threads_top.html + www/clients.html + www/coinbaser.html + www/threads.html www/foot.html www/assets/style.css www/assets/icons/datum_logo.svg diff --git a/src/datum_api.c b/src/datum_api.c index 646c6e3..867d57b 100644 --- a/src/datum_api.c +++ b/src/datum_api.c @@ -66,6 +66,17 @@ const char *cbnames[] = { "Antmain2" }; +static void leading_zeros(char * const buffer, const size_t buffer_size, const char * const numstr) { + int zeros = 0; + while (numstr[zeros] == '0') { + ++zeros; + } + if (zeros) { + snprintf(buffer, buffer_size, "%.*s%s", zeros, numstr, &numstr[zeros]); + } +} + + static void html_leading_zeros(char * const buffer, const size_t buffer_size, const char * const numstr) { int zeros = 0; while (numstr[zeros] == '0') { @@ -148,10 +159,10 @@ void datum_api_var_STRATUM_JOB_BLOCK_VALUE(char *buffer, size_t buffer_size, con snprintf(buffer, buffer_size, "%.8f BTC", (double)vardata->sjob->block_template->coinbasevalue / (double)100000000.0); } void datum_api_var_STRATUM_JOB_TARGET(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { - html_leading_zeros(buffer, buffer_size, vardata->sjob->block_template->block_target_hex); + leading_zeros(buffer, buffer_size, vardata->sjob->block_template->block_target_hex); } void datum_api_var_STRATUM_JOB_PREVBLOCK(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { - html_leading_zeros(buffer, buffer_size, vardata->sjob->block_template->previousblockhash); + leading_zeros(buffer, buffer_size, vardata->sjob->block_template->previousblockhash); } void datum_api_var_STRATUM_JOB_WITNESS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) { snprintf(buffer, buffer_size, "%s", vardata->sjob->block_template->default_witness_commitment); @@ -228,6 +239,10 @@ DATUM_API_VarFunc datum_api_find_var_func(const char *var_name) { return NULL; // Variable not found } +void datum_api_use_webresource(const char *input, char *output, size_t max_output_size) { + +} + void datum_api_fill_vars(const char *input, char *output, size_t max_output_size, const T_DATUM_API_DASH_VARS *vardata) { const char* p = input; size_t output_len = 0; @@ -453,43 +468,9 @@ int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) { int datum_api_coinbaser(struct MHD_Connection *connection) { struct MHD_Response *response; - T_DATUM_STRATUM_JOB *sjob; - int j,i,max_sz = 0,sz=0,ret; - char tempaddr[256]; - uint64_t tv = 0; - char *output = NULL; - - pthread_rwlock_rdlock(&stratum_global_job_ptr_lock); - j = global_latest_stratum_job_index; - sjob = (j >= 0 && j < MAX_STRATUM_JOBS) ? global_cur_stratum_jobs[j] : NULL; - pthread_rwlock_unlock(&stratum_global_job_ptr_lock); - - if (!sjob) return MHD_NO; - - max_sz = www_coinbaser_top_html_sz + www_foot_html_sz + (sjob->available_coinbase_outputs_count * 512) + 2048; // approximate max size of each row - output = calloc(max_sz+16,1); - if (!output) { - return MHD_NO; - } - - sz = snprintf(output, max_sz-1-sz, "%s", www_coinbaser_top_html); - sz += snprintf(&output[sz], max_sz-1-sz, ""); - - for(i=0;iavailable_coinbase_outputs_count;i++) { - output_script_2_addr(sjob->available_coinbase_outputs[i].output_script, sjob->available_coinbase_outputs[i].output_script_len, tempaddr); - sz += snprintf(&output[sz], max_sz-1-sz, "", (double)sjob->available_coinbase_outputs[i].value_sats / (double)100000000.0, tempaddr); - tv += sjob->available_coinbase_outputs[i].value_sats; - } - - if (tv < sjob->coinbase_value) { - output_script_2_addr(sjob->pool_addr_script, sjob->pool_addr_script_len, tempaddr); - sz += snprintf(&output[sz], max_sz-1-sz, "", (double)(sjob->coinbase_value - tv) / (double)100000000.0, tempaddr); - } - - sz += snprintf(&output[sz], max_sz-1-sz, "
Value Address
%.8f BTC%s
%.8f BTC%s
"); - sz += snprintf(&output[sz], max_sz-1-sz, "%s", www_foot_html); - - response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); + int ret; + + response = MHD_create_response_from_buffer (strlen(www_coinbaser_html), (void *) www_coinbaser_html, MHD_RESPMEM_MUST_COPY); MHD_add_response_header(response, "Content-Type", "text/html"); http_resp_prevent_caching(response); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); @@ -499,157 +480,9 @@ int datum_api_coinbaser(struct MHD_Connection *connection) { int datum_api_thread_dashboard(struct MHD_Connection *connection) { struct MHD_Response *response; - int sz=0, ret, max_sz = 0, j, ii; - char *output = NULL; - T_DATUM_MINER_DATA *m = NULL; - uint64_t tsms; - double hr; - unsigned char astat; - double thr = 0.0; - int subs,conns; - - max_sz = www_threads_top_html_sz + www_foot_html_sz + (global_stratum_app->max_threads * 512) + 2048; // approximate max size of each row - output = calloc(max_sz+16,1); - if (!output) { - return MHD_NO; - } - - tsms = current_time_millis(); - - sz = snprintf(output, max_sz-1-sz, "%s", www_threads_top_html); - sz += snprintf(&output[sz], max_sz-1-sz, ""); - for(j=0;jmax_threads;j++) { - thr = 0.0; - subs = 0; - conns = 0; - - for(ii=0;iimax_clients_thread;ii++) { - if (global_stratum_app->datum_threads[j].client_data[ii].fd > 0) { - conns++; - m = (T_DATUM_MINER_DATA *)global_stratum_app->datum_threads[j].client_data[ii].app_client_data; - if (m->subscribed) { - subs++; - astat = m->stats.active_index?0:1; // inverted - hr = 0.0; - if ((m->stats.last_swap_ms > 0) && (m->stats.diff_accepted[astat] > 0)) { - hr = ((double)m->stats.diff_accepted[astat] / (double)((double)m->stats.last_swap_ms/1000.0)) * 0.004294967296; // Th/sec based on shares/sec - } - if (((double)(tsms - m->stats.last_swap_tsms)/1000.0) < 180.0) { - thr += hr; - } - } - } - } - if (conns) { - sz += snprintf(&output[sz], max_sz-1-sz, "", j, conns, subs, thr, j); - } - } - sz += snprintf(&output[sz], max_sz-1-sz, "
TID Connection Count Sub Count Approx. Hashrate Command
%d %d %d %.2f Th/s
"); - sz += snprintf(&output[sz], max_sz-1-sz, ""); - sz += snprintf(&output[sz], max_sz-1-sz, "%s", www_foot_html); - - response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); - MHD_add_response_header(response, "Content-Type", "text/html"); - http_resp_prevent_caching(response); - ret = MHD_queue_response (connection, MHD_HTTP_OK, response); - MHD_destroy_response (response); - return ret; -} + int ret; -int datum_api_client_dashboard(struct MHD_Connection *connection) { - struct MHD_Response *response; - int connected_clients = 0; - int i,sz=0,ret,max_sz = 0,j,ii; - char *output = NULL; - T_DATUM_MINER_DATA *m = NULL; - uint64_t tsms; - double hr; - unsigned char astat; - double thr = 0.0; - - for(i=0;imax_threads;i++) { - connected_clients+=global_stratum_app->datum_threads[i].connected_clients; - } - - max_sz = www_clients_top_html_sz + www_foot_html_sz + (connected_clients * 1024) + 2048; // approximate max size of each row - output = calloc(max_sz+16,1); - if (!output) { - return MHD_NO; - } - - tsms = current_time_millis(); - - sz = snprintf(output, max_sz-1-sz, "%s", www_clients_top_html); - sz += snprintf(&output[sz], max_sz-1-sz, ""); - - for(j=0;jmax_threads;j++) { - for(ii=0;iimax_clients_thread;ii++) { - if (global_stratum_app->datum_threads[j].client_data[ii].fd > 0) { - m = (T_DATUM_MINER_DATA *)global_stratum_app->datum_threads[j].client_data[ii].app_client_data; - sz += snprintf(&output[sz], max_sz-1-sz, "", j,ii); - - sz += snprintf(&output[sz], max_sz-1-sz, "", global_stratum_app->datum_threads[j].client_data[ii].rem_host); - - sz += snprintf(&output[sz], max_sz-1-sz, ""); - - if (m->subscribed) { - sz += snprintf(&output[sz], max_sz-1-sz, "", m->sid, (double)(tsms - m->subscribe_tsms)/1000.0); - - if (m->stats.last_share_tsms) { - sz += snprintf(&output[sz], max_sz-1-sz, "", (double)(tsms - m->stats.last_share_tsms)/1000.0); - } else { - sz += snprintf(&output[sz], max_sz-1-sz, ""); - } - - sz += snprintf(&output[sz], max_sz-1-sz, "", m->current_diff); - sz += snprintf(&output[sz], max_sz-1-sz, "", m->share_diff_accepted, 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, "", m->share_diff_rejected, m->share_count_rejected, hr); - - astat = m->stats.active_index?0:1; // inverted - hr = 0.0; - if ((m->stats.last_swap_ms > 0) && (m->stats.diff_accepted[astat] > 0)) { - hr = ((double)m->stats.diff_accepted[astat] / (double)((double)m->stats.last_swap_ms/1000.0)) * 0.004294967296; // Th/sec based on shares/sec - } - if (((double)(tsms - m->stats.last_swap_tsms)/1000.0) < 180.0) { - thr += hr; - } - if (m->share_diff_accepted > 0) { - sz += snprintf(&output[sz], max_sz-1-sz, "", hr, (double)(tsms - m->stats.last_swap_tsms)/1000.0); - } else { - sz += snprintf(&output[sz], max_sz-1-sz, ""); - } - - if (m->coinbase_selection < (sizeof(cbnames) / sizeof(cbnames[0]))) { - sz += snprintf(&output[sz], max_sz-1-sz, "", cbnames[m->coinbase_selection]); - } else { - sz += snprintf(&output[sz], max_sz-1-sz, ""); - } - - sz += snprintf(&output[sz], max_sz-1-sz, ""); - } else { - sz += snprintf(&output[sz], max_sz-1-sz, ""); - } - - sz += snprintf(&output[sz], max_sz-1-sz, "", j, ii); - } - } - } - - sz += snprintf(&output[sz], max_sz-1-sz, "
TID/CID RemHost Auth Username Subbed Last Accepted VDiff DiffA (A) DiffR (R) Hashrate (age) Coinbase UserAgent Command
%d/%d%s"); - sz += strncpy_html_escape(&output[sz], m->last_auth_username, max_sz-1-sz); - sz += snprintf(&output[sz], max_sz-1-sz, " %4.4x %.1fs%.1fsN/A%"PRIu64"%"PRIu64" (%"PRIu64")%"PRIu64" (%"PRIu64") %.2f%%%.2f Th/s (%.1fs)N/A%sUnknown"); - sz += strncpy_html_escape(&output[sz], m->useragent, max_sz-1-sz); - sz += snprintf(&output[sz], max_sz-1-sz, "Not Subscribed

Total active hashrate estimate: %.2f Th/s
", thr); - sz += snprintf(&output[sz], max_sz-1-sz, ""); - sz += snprintf(&output[sz], max_sz-1-sz, "%s", www_foot_html); - - // return the home page with some data and such - response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_FREE); + response = MHD_create_response_from_buffer (strlen(www_threads_html), (void *) www_threads_html, MHD_RESPMEM_MUST_COPY); MHD_add_response_header(response, "Content-Type", "text/html"); http_resp_prevent_caching(response); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); @@ -657,37 +490,214 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) { return ret; } -int datum_api_homepage(struct MHD_Connection *connection) { +int datum_api_json_response(struct MHD_Connection *connection, const char *url, const char *method) { struct MHD_Response *response; - char output[DATUM_API_HOMEPAGE_MAX_SIZE]; - int j, k = 0, kk = 0, ii, ret; - T_DATUM_MINER_DATA *m; + enum MHD_Result ret; T_DATUM_API_DASH_VARS vardata; - unsigned char astat; - double thr = 0.0; - double hr; - uint64_t tsms; - + T_DATUM_MINER_DATA *m; + memset(&vardata, 0, sizeof(T_DATUM_API_DASH_VARS)); + char *json_output; + int j,i,sz = 0; + + char buffer[1024]; // Adjust size as needed + + int k = 0, kk = 0, ii; pthread_rwlock_rdlock(&stratum_global_job_ptr_lock); j = global_latest_stratum_job_index; vardata.sjob = (j >= 0 && j < MAX_STRATUM_JOBS) ? global_cur_stratum_jobs[j] : NULL; pthread_rwlock_unlock(&stratum_global_job_ptr_lock); - tsms = current_time_millis(); - - if (global_stratum_app) { - k = 0; - kk = 0; - for(j=0;jmax_threads;j++) { - k+=global_stratum_app->datum_threads[j].connected_clients; - for(ii=0;iimax_clients_thread;ii++) { + // Helper macro to reduce repetition + #define ADD_JSON_VALUE(obj, key, func, type) \ + do { \ + buffer[0] = 0; \ + func(buffer, sizeof(buffer), &vardata); \ + if (buffer[0] != 0) { \ + json_t *json_value; \ + if (strcmp(type, "string") == 0) { \ + json_value = json_string(buffer); \ + } else if (strcmp(type, "integer") == 0) { \ + json_value = json_integer(atoll(buffer)); \ + } else if (strcmp(type, "boolean") == 0) { \ + json_value = json_boolean(atoi(buffer)); \ + } else { \ + json_value = json_string(buffer); /* Default to string */ \ + } \ + if (json_value) { \ + json_object_set_new(obj, key, json_value); \ + } \ + } \ + } while(0) + + + if (!strcmp(url, "/api/v1/client_stats")) { + json_t *datum = json_object(); + + json_object_set_new(datum, "IS_ACTIVE", json_boolean(datum_protocol_is_active())); + json_object_set_new(datum, "POOL_TAG", json_string(datum_protocol_is_active()?datum_config.override_mining_coinbase_tag_primary:datum_config.mining_coinbase_tag_primary)); + json_object_set_new(datum, "MINER_TAG", json_string(datum_config.mining_coinbase_tag_secondary)); + ADD_JSON_VALUE(datum, "SHARES_ACCEPTED", datum_api_var_DATUM_SHARES_ACCEPTED, "integer"); + ADD_JSON_VALUE(datum, "SHARES_REJECTED", datum_api_var_DATUM_SHARES_REJECTED, "integer"); + json_object_set_new(datum, "IS_ACTIVE", json_boolean(datum_protocol_is_active())); + ADD_JSON_VALUE(datum, "POOL_HOST", datum_api_var_DATUM_POOL_HOST, "string"); + ADD_JSON_VALUE(datum, "POOL_DIFF", datum_api_var_DATUM_POOL_DIFF, "integer"); + ADD_JSON_VALUE(datum, "POOL_PUBKEY", datum_api_var_DATUM_POOL_PUBKEY, "string"); + + char *json_output = json_dumps(datum, JSON_COMPACT); + json_decref(datum); + + if (!json_output) { + // Handle error: JSON encoding failed + return MHD_NO; + } + + response = MHD_create_response_from_buffer( + strlen(json_output), + (void*)json_output, + MHD_RESPMEM_MUST_FREE + ); + + + } else if (!strcmp(url, "/api/v1/stratum_server_info")) { + uint64_t tsms = current_time_millis(); + unsigned char astat; + double thr = 0.0; + double hr; + + if (global_stratum_app) { + k = 0; + kk = 0; + for(j=0;jmax_threads;j++) { + k+=global_stratum_app->datum_threads[j].connected_clients; + for(ii=0;iimax_clients_thread;ii++) { + if (global_stratum_app->datum_threads[j].client_data[ii].fd > 0) { + m = (T_DATUM_MINER_DATA *)global_stratum_app->datum_threads[j].client_data[ii].app_client_data; + if (m->subscribed) { + kk++; + astat = m->stats.active_index?0:1; // inverted + hr = 0.0; + if ((m->stats.last_swap_ms > 0) && (m->stats.diff_accepted[astat] > 0)) { + hr = ((double)m->stats.diff_accepted[astat] / (double)((double)m->stats.last_swap_ms/1000.0)) * 0.004294967296; // Th/sec based on shares/sec + } + if (((double)(tsms - m->stats.last_swap_tsms)/1000.0) < 180.0) { + thr += hr; + } + } + } + } + } + vardata.STRATUM_ACTIVE_THREADS = global_stratum_app->datum_active_threads; + vardata.STRATUM_TOTAL_CONNECTIONS = k; + vardata.STRATUM_TOTAL_SUBSCRIPTIONS = kk; + vardata.STRATUM_HASHRATE_ESTIMATE = thr; + } else { + vardata.STRATUM_ACTIVE_THREADS = 0; + vardata.STRATUM_TOTAL_CONNECTIONS = 0; + vardata.STRATUM_TOTAL_SUBSCRIPTIONS = 0; + vardata.STRATUM_HASHRATE_ESTIMATE = 0.0; + } + + json_t *stratum = json_object(); + + ADD_JSON_VALUE(stratum, "ACTIVE_THREADS", datum_api_var_STRATUM_ACTIVE_THREADS, "integer"); + ADD_JSON_VALUE(stratum, "TOTAL_CONNECTIONS", datum_api_var_STRATUM_TOTAL_CONNECTIONS, "integer"); + ADD_JSON_VALUE(stratum, "TOTAL_SUBSCRIPTIONS", datum_api_var_STRATUM_TOTAL_SUBSCRIPTIONS, "integer"); + ADD_JSON_VALUE(stratum, "HASHRATE_ESTIMATE", datum_api_var_STRATUM_HASHRATE_ESTIMATE, "string"); + char *json_output = json_dumps(stratum, JSON_COMPACT); + json_decref(stratum); + + if (!json_output) { + // Handle error: JSON encoding failed + return MHD_NO; + } + + response = MHD_create_response_from_buffer( + strlen(json_output), + (void*)json_output, + MHD_RESPMEM_MUST_FREE + ); + } else if (!strcmp(url, "/api/v1/current_stratum_job")) { + json_t *stratum_job = json_object(); + + ADD_JSON_VALUE(stratum_job, "INFO", datum_api_var_STRATUM_JOB_INFO, "string"); + ADD_JSON_VALUE(stratum_job, "BLOCK_HEIGHT", datum_api_var_STRATUM_JOB_BLOCK_HEIGHT, "integer"); + ADD_JSON_VALUE(stratum_job, "BLOCK_VALUE", datum_api_var_STRATUM_JOB_BLOCK_VALUE, "string"); + ADD_JSON_VALUE(stratum_job, "PREVBLOCK", datum_api_var_STRATUM_JOB_PREVBLOCK, "string"); + ADD_JSON_VALUE(stratum_job, "TARGET", datum_api_var_STRATUM_JOB_TARGET, "string"); + ADD_JSON_VALUE(stratum_job, "WITNESS", datum_api_var_STRATUM_JOB_WITNESS, "string"); + ADD_JSON_VALUE(stratum_job, "DIFF", datum_api_var_STRATUM_JOB_DIFF, "string"); + ADD_JSON_VALUE(stratum_job, "VERSION", datum_api_var_STRATUM_JOB_VERSION, "string"); + ADD_JSON_VALUE(stratum_job, "BITS", datum_api_var_STRATUM_JOB_BITS, "string"); + ADD_JSON_VALUE(stratum_job, "TIMEINFO", datum_api_var_STRATUM_JOB_TIMEINFO, "string"); + ADD_JSON_VALUE(stratum_job, "LIMITINFO", datum_api_var_STRATUM_JOB_LIMITINFO, "string"); + ADD_JSON_VALUE(stratum_job, "SIZE", datum_api_var_STRATUM_JOB_SIZE, "integer"); + ADD_JSON_VALUE(stratum_job, "WEIGHT", datum_api_var_STRATUM_JOB_WEIGHT, "integer"); + ADD_JSON_VALUE(stratum_job, "SIGOPS", datum_api_var_STRATUM_JOB_SIGOPS, "integer"); + ADD_JSON_VALUE(stratum_job, "TXNCOUNT", datum_api_var_STRATUM_JOB_TXNCOUNT, "integer"); + + char *json_output = json_dumps(stratum_job, JSON_COMPACT); + json_decref(stratum_job); + + if (!json_output) { + // Handle error: JSON encoding failed + return MHD_NO; + } + + response = MHD_create_response_from_buffer( + strlen(json_output), + (void*)json_output, + MHD_RESPMEM_MUST_FREE + ); + } else if (!strcmp(url, "/api/v1/clients")) { + int connected_clients = 0; + for(i=0;imax_threads;i++) { + connected_clients+=global_stratum_app->datum_threads[i].connected_clients; + } + + json_t *root = json_object(); + json_t *clients = json_array(); + uint64_t tsms = current_time_millis(); + unsigned char astat; + double thr = 0.0; + double hr; + + for (int j = 0; j < global_stratum_app->max_threads; j++) { + for (int ii = 0; ii < global_stratum_app->max_clients_thread; ii++) { if (global_stratum_app->datum_threads[j].client_data[ii].fd > 0) { - m = (T_DATUM_MINER_DATA *)global_stratum_app->datum_threads[j].client_data[ii].app_client_data; + T_DATUM_MINER_DATA *m = (T_DATUM_MINER_DATA *)global_stratum_app->datum_threads[j].client_data[ii].app_client_data; + json_t *client = json_object(); + + json_object_set_new(client, "tid", json_integer(j)); + json_object_set_new(client, "cid", json_integer(ii)); + json_object_set_new(client, "rem_host", json_string(global_stratum_app->datum_threads[j].client_data[ii].rem_host)); + json_object_set_new(client, "auth_username", json_string(m->last_auth_username)); + json_object_set_new(client, "subbed", json_boolean(m->subscribed)); + if (m->subscribed) { - kk++; - astat = m->stats.active_index?0:1; // inverted + json_object_set_new(client, "sid", json_integer(m->sid)); + json_object_set_new(client, "subscribe_age", json_real((double)(tsms - m->subscribe_tsms)/1000.0)); + + if (m->stats.last_share_tsms) { + json_object_set_new(client, "last_accepted", json_real((double)(tsms - m->stats.last_share_tsms)/1000.0)); + } else { + json_object_set_new(client, "last_accepted", json_null()); + } + + json_object_set_new(client, "v_diff", json_integer(m->current_diff)); + json_object_set_new(client, "diff_A", json_integer(m->share_diff_accepted)); + json_object_set_new(client, "shares_A", json_integer(m->share_count_accepted)); + json_object_set_new(client, "diff_R", json_integer(m->share_diff_rejected)); + json_object_set_new(client, "shares_R", json_integer(m->share_count_rejected)); + + double 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; + } + json_object_set_new(client, "reject_rate", json_real(hr)); + + int astat = m->stats.active_index ? 0 : 1; // inverted hr = 0.0; if ((m->stats.last_swap_ms > 0) && (m->stats.diff_accepted[astat] > 0)) { hr = ((double)m->stats.diff_accepted[astat] / (double)((double)m->stats.last_swap_ms/1000.0)) * 0.004294967296; // Th/sec based on shares/sec @@ -695,26 +705,184 @@ int datum_api_homepage(struct MHD_Connection *connection) { if (((double)(tsms - m->stats.last_swap_tsms)/1000.0) < 180.0) { thr += hr; } + json_object_set_new(client, "hashrate", json_real(hr)); + json_object_set_new(client, "last_updated", json_real((double)(tsms - m->stats.last_swap_tsms)/1000.0)); + + if (m->coinbase_selection < (sizeof(cbnames) / sizeof(cbnames[0]))) { + json_object_set_new(client, "coinbase", json_string(cbnames[m->coinbase_selection])); + } else { + json_object_set_new(client, "coinbase", json_string("Unknown")); + } + + json_object_set_new(client, "useragent", json_string(m->useragent)); + } + + json_array_append_new(clients, client); + } + } + } + + json_object_set_new(root, "clients", clients); + json_object_set_new(root, "total_hashrate", json_real(thr)); + + char *json_output = json_dumps(root, JSON_COMPACT); + json_decref(root); + + if (!json_output) { + // Handle error: JSON encoding failed + return MHD_NO; + } + + response = MHD_create_response_from_buffer( + strlen(json_output), + (void*)json_output, + MHD_RESPMEM_MUST_FREE + ); + } else if (!strcmp(url, "/api/v1/threads")) { + json_t *root = json_object(); + json_t *threads = json_array(); + uint64_t tsms = current_time_millis(); + + for (int j = 0; j < global_stratum_app->max_threads; j++) { + double thr = 0.0; + int subs = 0; + int conns = 0; + + for (int ii = 0; ii < global_stratum_app->max_clients_thread; ii++) { + if (global_stratum_app->datum_threads[j].client_data[ii].fd > 0) { + conns++; + T_DATUM_MINER_DATA *m = (T_DATUM_MINER_DATA *)global_stratum_app->datum_threads[j].client_data[ii].app_client_data; + if (m->subscribed) { + subs++; + unsigned char astat = m->stats.active_index ? 0 : 1; // inverted + double hr = 0.0; + if ((m->stats.last_swap_ms > 0) && (m->stats.diff_accepted[astat] > 0)) { + hr = ((double)m->stats.diff_accepted[astat] / (double)((double)m->stats.last_swap_ms/1000.0)) * 0.004294967296; // Th/sec based on shares/sec + } + if (((double)(tsms - m->stats.last_swap_tsms)/1000.0) < 180.0) { + thr += hr; + } } } } + + if (conns) { + json_t *thread = json_object(); + json_object_set_new(thread, "tid", json_integer(j)); + json_object_set_new(thread, "connection_count", json_integer(conns)); + json_object_set_new(thread, "sub_count", json_integer(subs)); + json_object_set_new(thread, "approx_hashrate", json_real(thr)); + json_array_append_new(threads, thread); + } } - vardata.STRATUM_ACTIVE_THREADS = global_stratum_app->datum_active_threads; - vardata.STRATUM_TOTAL_CONNECTIONS = k; - vardata.STRATUM_TOTAL_SUBSCRIPTIONS = kk; - vardata.STRATUM_HASHRATE_ESTIMATE = thr; + +// json_object_set_new(root, "threads", threads); + + char *json_output = json_dumps(threads, JSON_COMPACT); + json_decref(root); + + if (!json_output) { + // Handle error: JSON encoding failed + return MHD_NO; + } + + response = MHD_create_response_from_buffer( + strlen(json_output), + (void*)json_output, + MHD_RESPMEM_MUST_FREE + ); + } else if (!strcmp(url, "/api/v1/coinbaser")) { + T_DATUM_STRATUM_JOB *sjob; + + char tempaddr[256]; + uint64_t tv = 0; + + pthread_rwlock_rdlock(&stratum_global_job_ptr_lock); + j = global_latest_stratum_job_index; + sjob = (j >= 0 && j < MAX_STRATUM_JOBS) ? global_cur_stratum_jobs[j] : NULL; + pthread_rwlock_unlock(&stratum_global_job_ptr_lock); + + if (!sjob) { + // Handle error: no valid job available + return MHD_NO; + } + + json_t *root = json_array(); + + for (int i = 0; i < sjob->available_coinbase_outputs_count; i++) { + output_script_2_addr(sjob->available_coinbase_outputs[i].output_script, + sjob->available_coinbase_outputs[i].output_script_len, + tempaddr); + + json_t *output = json_object(); + json_object_set_new(output, "address", json_string(tempaddr)); + json_object_set_new(output, "value", json_real((double)sjob->available_coinbase_outputs[i].value_sats / 100000000.0)); + json_array_append_new(root, output); + + tv += sjob->available_coinbase_outputs[i].value_sats; + } + + if (tv < sjob->coinbase_value) { + output_script_2_addr(sjob->pool_addr_script, sjob->pool_addr_script_len, tempaddr); + + json_t *output = json_object(); + json_object_set_new(output, "address", json_string(tempaddr)); + json_object_set_new(output, "value", json_real((double)(sjob->coinbase_value - tv) / 100000000.0)); + json_array_append_new(root, output); + } + + char *json_output = json_dumps(root, JSON_COMPACT); + json_decref(root); + + if (!json_output) { + // Handle error: JSON encoding failed + return MHD_NO; + } + + response = MHD_create_response_from_buffer(strlen(json_output), + (void *)json_output, + MHD_RESPMEM_MUST_FREE); + } else { - vardata.STRATUM_ACTIVE_THREADS = 0; - vardata.STRATUM_TOTAL_CONNECTIONS = 0; - vardata.STRATUM_TOTAL_SUBSCRIPTIONS = 0; - vardata.STRATUM_HASHRATE_ESTIMATE = 0.0; + const char *error_response = "{\"error\": \"Not found\"}"; + response = MHD_create_response_from_buffer(strlen(error_response), (void *)error_response, MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, "Content-Type", "application/json"); + ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response); + MHD_destroy_response (response); + return ret; } - - output[0] = 0; - datum_api_fill_vars(www_home_html, output, DATUM_API_HOMEPAGE_MAX_SIZE, &vardata); + + if (!response) { + free(json_output); + return MHD_NO; + } + + MHD_add_response_header(response, "Content-Type", "application/json"); + http_resp_prevent_caching(response); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + + +int datum_api_client_dashboard(struct MHD_Connection *connection) { + struct MHD_Response *response; + int ret; // return the home page with some data and such - response = MHD_create_response_from_buffer (strlen(output), (void *) output, MHD_RESPMEM_MUST_COPY); + response = MHD_create_response_from_buffer (strlen(www_clients_html), (void *) www_clients_html, MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(response, "Content-Type", "text/html"); + http_resp_prevent_caching(response); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + +int datum_api_homepage(struct MHD_Connection *connection) { + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen(www_home_html), (void *) www_home_html, MHD_RESPMEM_MUST_COPY); MHD_add_response_header(response, "Content-Type", "text/html"); http_resp_prevent_caching(response); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); @@ -859,7 +1027,9 @@ enum MHD_Result datum_api_answer(void *cls, struct MHD_Connection *connection, c } case 'a': { - if (!strcmp(url, "/assets/icons/datum_logo.svg")) { + if (strstr(url, "/api/") != NULL) { + return datum_api_json_response(connection, url, method); + } else if (!strcmp(url, "/assets/icons/datum_logo.svg")) { return datum_api_asset(connection, "image/svg+xml", www_assets_icons_datum_logo_svg, www_assets_icons_datum_logo_svg_sz); } else if (!strcmp(url, "/assets/icons/favicon.ico")) { return datum_api_asset(connection, "image/x-icon", www_assets_icons_favicon_ico, www_assets_icons_favicon_ico_sz); diff --git a/www/clients.html b/www/clients.html new file mode 100644 index 0000000..fa34b01 --- /dev/null +++ b/www/clients.html @@ -0,0 +1,114 @@ + + + + + DATUM Gateway Status + + + + +
+
+

+ (DATUM Logo) + DATUM GATEWAY +

+
+ +
+ +
+
+
+

Stratum Client List

+ + + + + + + + + + + + + + + + + + + + +
TID/CIDRemHostAuth UsernameSubbedLast AcceptedVDiffDiffA (A)DiffR (R)Hashrate (age)CoinbaseUserAgentCommand
+
Total active hashrate estimate: 0.00 Th/s
+ +
+
+
+ + diff --git a/www/clients_top.html b/www/clients_top.html deleted file mode 100644 index 7acbd16..0000000 --- a/www/clients_top.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - DATUM Gateway Status - - - - -
-
-

(DATUM Logo) DATUM GATEWAY

-
- -
- -
-
-
-

Stratum Client List

diff --git a/www/coinbaser.html b/www/coinbaser.html new file mode 100644 index 0000000..1f3ce12 --- /dev/null +++ b/www/coinbaser.html @@ -0,0 +1,87 @@ + + + + + DATUM Gateway Status + + + + + +
+
+

(DATUM Logo) DATUM GATEWAY

+
+ +
+ +
+
+
+

Current Coinbaser

+ + + + + +
ValueAddress
+ +
+
+
+ + diff --git a/www/coinbaser_top.html b/www/coinbaser_top.html deleted file mode 100644 index 31c6d96..0000000 --- a/www/coinbaser_top.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - DATUM Gateway Status - - - - - -
-
-

(DATUM Logo) DATUM GATEWAY

-
- -
- -
-
-
-

Current Coinbaser

diff --git a/www/foot.html b/www/foot.html index 8059634..99e245e 100644 --- a/www/foot.html +++ b/www/foot.html @@ -1,7 +1,5 @@
-
-
Note: This page does not automatically refresh
diff --git a/www/home.html b/www/home.html index 93e0487..483bcf3 100644 --- a/www/home.html +++ b/www/home.html @@ -54,8 +54,8 @@

Decentralized Client Stats

${DATUM_SHARES_REJECTED} - Status: - ${DATUM_CONNECTION_STATUS} + Is Active: + ${DATUM_IS_ACTIVE} Pool Host: @@ -171,6 +171,57 @@

Current Stratum Job


-
Note: This page does not automatically refresh
+ diff --git a/www/threads.html b/www/threads.html new file mode 100644 index 0000000..14c9ab2 --- /dev/null +++ b/www/threads.html @@ -0,0 +1,91 @@ + + + + + DATUM Gateway Status + + + + + +
+
+

(DATUM Logo) DATUM GATEWAY

+
+ +
+ +
+
+
+

Thread Stats

+ + + + + + + + +
TIDConnection CountSub CountApprox. HashrateCommand
+ \ No newline at end of file diff --git a/www/threads_top.html b/www/threads_top.html deleted file mode 100644 index ffc5b84..0000000 --- a/www/threads_top.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - DATUM Gateway Status - - - - - -
-
-

(DATUM Logo) DATUM GATEWAY

-
- -
- -
-
-
-

Thread Stats