forked from Ocean/datum_gateway
Initial import of DATUM codebase
Co-authored-by: Luke Dashjr <luke-jr+git@utopios.org>
This commit is contained in:
parent
28330dece7
commit
c4a1845a53
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
|
||||
git_version.h
|
||||
web_resources.h
|
||||
datum_gateway
|
||||
|
||||
*.conf
|
||||
*.json
|
||||
.DS_Store
|
||||
*.orig
|
||||
*.rej
|
||||
*~
|
96
CMakeLists.txt
Normal file
96
CMakeLists.txt
Normal file
@ -0,0 +1,96 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(DATUM VERSION 0.2 LANGUAGES C)
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
add_executable(datum_gateway
|
||||
src/datum_api.c
|
||||
src/datum_blocktemplates.c
|
||||
src/datum_coinbaser.c
|
||||
src/datum_conf.c
|
||||
src/datum_gateway.c
|
||||
src/datum_jsonrpc.c
|
||||
src/datum_logger.c
|
||||
src/datum_protocol.c
|
||||
src/datum_queue.c
|
||||
src/datum_sockets.c
|
||||
src/datum_stratum.c
|
||||
src/datum_stratum_dupes.c
|
||||
src/datum_submitblock.c
|
||||
src/datum_utils.c
|
||||
src/thirdparty_base58.c
|
||||
src/thirdparty_segwit_addr.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/web_resources.h
|
||||
)
|
||||
|
||||
set(WEB_RESOURCES
|
||||
www/home.html
|
||||
www/clients_top.html
|
||||
www/coinbaser_top.html
|
||||
www/threads_top.html
|
||||
www/foot.html
|
||||
www/assets/style.css
|
||||
www/assets/icons/datum_logo.svg
|
||||
www/assets/icons/favicon.ico
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(CURL REQUIRED libcurl)
|
||||
pkg_check_modules(JANSSON REQUIRED jansson)
|
||||
pkg_check_modules(MICROHTTPD REQUIRED libmicrohttpd)
|
||||
pkg_check_modules(SODIUM REQUIRED libsodium)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
set(POW_LIBS "")
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM)
|
||||
if(LIBM)
|
||||
list(APPEND POW_LIBS "m")
|
||||
endif()
|
||||
|
||||
add_custom_target(generate_git_version
|
||||
BYPRODUCTS ${PROJECT_BINARY_DIR}/git_version.h
|
||||
COMMAND ${CMAKE_COMMAND} -DBUILD_INFO_HEADER_PATH=${PROJECT_BINARY_DIR}/git_version.h -DSOURCE_DIR=${PROJECT_SOURCE_DIR} -P ${PROJECT_SOURCE_DIR}/cmake/script/GenerateBuildInfo.cmake
|
||||
DEPENDS cmake/script/GenerateBuildInfo.cmake
|
||||
COMMENT "Generating git_version.h"
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(datum_gateway generate_git_version)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT web_resources.h
|
||||
COMMAND ${CMAKE_COMMAND} "-DINPUT_FILES=${WEB_RESOURCES}" -DOUTPUT_FILE=web_resources.h -DSOURCE_DIR=${PROJECT_SOURCE_DIR} -P ${PROJECT_SOURCE_DIR}/cmake/script/EmbedResources.cmake
|
||||
DEPENDS ${WEB_RESOURCES} cmake/script/EmbedResources.cmake
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
target_include_directories(datum_gateway
|
||||
PRIVATE
|
||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${MICROHTTPD_INCLUDE_DIRS}
|
||||
${JANSSON_INCLUDE_DIRS}
|
||||
${SODIUM_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_directories(datum_gateway
|
||||
PUBLIC
|
||||
${CURL_LIBRARY_DIRS}
|
||||
${MICROHTTPD_LIBRARY_DIRS}
|
||||
${JANSSON_LIBRARY_DIRS}
|
||||
${SODIUM_LIBRARY_DIRS}
|
||||
)
|
||||
target_link_libraries(datum_gateway
|
||||
PUBLIC
|
||||
${POW_LIBS}
|
||||
Threads::Threads
|
||||
${CURL_LIBRARIES} ${CURL_LDFLAGS} ${CURL_LDFLAGS_OTHER}
|
||||
${MICROHTTPD_LIBRARIES} ${MICROHTTPD_LDFLAGS} ${MICROHTTPD_LDFLAGS_OTHER}
|
||||
${JANSSON_LIBRARIES} ${JANSSON_LDFLAGS} ${JANSSON_LDFLAGS_OTHER}
|
||||
${SODIUM_LIBRARIES} ${SODIUM_LDFLAGS} ${SODIUM_LDFLAGS_OTHER}
|
||||
)
|
||||
target_compile_options(datum_gateway
|
||||
PUBLIC
|
||||
${CURL_CFLAGS} ${CURL_CFLAGS_OTHER}
|
||||
${MICROHTTPD_CFLAGS} ${MICROHTTPD_CFLAGS_OTHER}
|
||||
${JANSSON_CFLAGS} ${JANSSON_CFLAGS_OTHER}
|
||||
${SODIUM_CFLAGS} ${SODIUM_CFLAGS_OTHER}
|
||||
)
|
26
LICENSE
Normal file
26
LICENSE
Normal file
@ -0,0 +1,26 @@
|
||||
Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
This copyright license for the DATUM Gateway does not include any
|
||||
express or implied right or license to use the DATUM trademark and any
|
||||
trademarks, logos, service marks, or trade names of Mummolin, Inc.,
|
||||
Bitcoin OCEAN, LLC, or any other contributors to OCEAN's open source
|
||||
projects.
|
122
README.md
Normal file
122
README.md
Normal file
@ -0,0 +1,122 @@
|
||||
# DATUM Gateway
|
||||
**Decentralized Alternative Templates for Universal Mining**
|
||||
(c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
|
||||
The DATUM Gateway implements lightweight efficient client side decentralized block template creation for true solo mining.
|
||||
|
||||
It reaches out to a local Bitcoin node for block templates, generates and distributes work for mining hardware, and submits solved blocks to the network directly.
|
||||
|
||||
For miners wanting to pool rewards, it facilitates communication with a DATUM-supporting pool in addition to the above. The pool is responsible for coordinating the block reward split based on work done for the pool by the miner, but does not create work for the miner.
|
||||
|
||||
The work provided by the gateway to mining hardware is generated only from the local node generating templates for the miner. The real miner is always whoever is running the Bitcoin node. With DATUM, that's not the pool. As the protocol is intended solely for mining of decentralized block templates, the DATUM protocol has no mechanisms for the pool providing the information needed to construct work or a block template.
|
||||
|
||||
Currently the DATUM Gateway supports communication with mining hardware using the Stratum v1 protocol with version rolling extensions (aka "ASICBoost"). Communication with the Bitcoin node is via RPC and must support GBT ("getblocktemplate"). Finally, communication with the pool is via the DATUM protocol.
|
||||
|
||||
**Using Bitcoin Knots is highly recommended**. This gives miners fine controls over how they wish to construct their block templates. Other node implementations that support GBT can also be used. This includes Bitcoin Core, but it is severely lacking in template control options. That is unfortunately a centralizing force which partly defeats the purpose of decentralizing block template creation in the first place.
|
||||
|
||||
The DATUM Gateway only supports mining Bitcoin. Modifying the code to support non-Bitcoin is not straightforward, as many optimizations and design considerations are tightly tied to Bitcoin-specific restraints for efficiency.
|
||||
|
||||
## DATUM Protocol
|
||||
The DATUM Gateway's communication with the mining pool is via the DATUM Protocol. This is an encrypted communication link between the DATUM Gateway (client) and DATUM Prime (pool side).
|
||||
|
||||
The protocol itself was made from the ground up as a custom protocol. Its specification is evolving, subject to change, and will be published elsewhere.
|
||||
|
||||
The core concepts of the protocol:
|
||||
|
||||
- Encrypt communications between the Gateway and pool
|
||||
- Obfuscate the communications somewhat so a MITM is unable to glean useful or accurate insight into the miner's operation via analysis of the still-ciphered communications.
|
||||
- Retrieve proper generation transaction payout splits from the pool for locally constructed templates
|
||||
- Submit work to the pool with sufficient data to efficiently validate and accept the work for proper rewards
|
||||
- Communicate minimal guardrails and requirements for a valid template to earn pooled rewards
|
||||
|
||||
With the current version of the protocol, the pool does block validation after coordinating with the miner. This is strictly to ensure miners are not accidentally creating invalid blocks while DATUM is still undergoing testing. In a future version of the protocol, the pool will not be in charge of this function and will be almost completely blinded to the contents of the miner's block template.
|
||||
|
||||
The protocol is not specific to a pooled reward system, as the Gateway coordinates the appropriate generation transaction with the pool. However, in the spirit of maximum decentralization, the pool should implement rewarding miners directly from generated payouts, such as with OCEAN's TIDES reward system.
|
||||
|
||||
## Requirements
|
||||
|
||||
- 64-bit AMD or Intel system. Other systems may work, but at this time it is at your own risk.
|
||||
- Linux-based operating system. Other OSs will be supported in the future.
|
||||
- Bitcoin full node ([Bitcoin Knots](https://bitcoinknots.org/) recommended) fully synced with the Bitcoin network.
|
||||
- Fast storage recommended for the Bitcoin node.
|
||||
- Stable internet connection for both the Bitcoin node and Gateway's communication with pool.
|
||||
- CPU powerful enough to run the Bitcoin node without validation delays.
|
||||
- Approximately 1GB/RAM, plus 1GB/RAM per 1000 Stratum clients, plus Bitcoin node RAM requirements.
|
||||
- Bitcoin mining hardware able to reach the system running the DATUM Gateway.
|
||||
|
||||
This list is not extensive, but the main goal is the have a stable system for your Bitcoin node and the Gateway such that your node is processing new incoming blocks and getting templates to the Gateway as quickly as possible. While this may all work on relatively low end hardware, your mileage may vary.
|
||||
|
||||
No modifications to the Bitcoin node source code is required for the Gateway, as it uses the standard GBT mechanism for template fetch.
|
||||
|
||||
The following external libraries are required:
|
||||
- libcurl
|
||||
- libjansson
|
||||
- libmicrohttpd
|
||||
- libsodium
|
||||
|
||||
## Node Configuation
|
||||
Your Bitcoin node must be configured to construct blocks as you desire. Bitcoin Knots provides many options for configuring your node's policy and is highly recommended.
|
||||
|
||||
At this time, you must also reserve some block space for the pool's generation transaction. The following options are currently recommended:
|
||||
|
||||
blockmaxsize=3985000
|
||||
blockmaxweight=3985000
|
||||
|
||||
Note: This reservation requirement will be removed for Bitcoin Knots users in a future version of the DATUM Gateway thanks to support for on-the-fly specification of these metrics by the client in Knots (as of version 27.1).
|
||||
|
||||
You must also configure a "*blocknotify*" setting to alert the Gateway of new blocks. See Installation section.
|
||||
|
||||
Finally, the Gateway must have RPC access to your node, and you must add an RPC user to your configuration to facilitate this, as well as ensuring the service running the Gateway is whitelisted for RPC access (if not on the same machine).
|
||||
|
||||
Some additional recommendations:
|
||||
|
||||
maxmempool=1000
|
||||
blockreconstructionextratxn=1000000
|
||||
|
||||
As a true miner, you'll most likely want as many valid transactions as possible in your mempool which meet your node's policies.
|
||||
|
||||
## Installation
|
||||
Install and fully sync your Bitcoin full node. Instructions for this are beyond the scope of this document.
|
||||
|
||||
Configure your node to create block templates as you desire. Be sure to reserve some space for the generation transaction, otherwise your work will not be able to fit a reward split. See node configuration recommendations above.
|
||||
|
||||
Install the required libraries and development packages for dependencies: cmake, jansson, libmicrohttpd, libsodium, and libcurl.
|
||||
|
||||
Compile DATUM by running:
|
||||
|
||||
cmake . && make
|
||||
|
||||
Run the datum_gateway executable with the -? flag for detailed configuration information, descriptions, and required options. Then construct a configuration file (defaults to "datum_gateway_config.json" in the current working directory). Be sure to also set your coinbase tags. The primary tag setting is unused in pooled mining, however the secondary tag is intended to show on things like block explorers when you mine a block.
|
||||
|
||||
To avoid mining stale work, you will need to ensure the DATUM Gateway receives new block notifications from your node. It is suggested you run the DATUM Gateway as the same user as your full node and utilize the following configuration line in your bitcoin.conf:
|
||||
|
||||
blocknotify=killall -USR1 datum_gateway
|
||||
|
||||
Ensure you have "killall" installed on your system (*psmisc* package on Debian-like OSs).
|
||||
|
||||
If the node and Gateway are on different systems, you may need to utilize the "NOTIFY" endpoint on the Gateway's dashboard/API instead.
|
||||
|
||||
## Template/Share Requirements for Pooled Mining
|
||||
|
||||
- Must be a valid block and conform to current Bitcoin consensus rules
|
||||
- Submitted work must be for the current latest block height, valid time, etc
|
||||
- Must include generation transaction outputs provided by the pool in the order provided
|
||||
- Must include the primary coinbase tag as provided by the pool
|
||||
- Must include the unique identifier provided by the pool
|
||||
- Work must include the work target and meet/exceed that target
|
||||
- Any additional requirements by pool documentation
|
||||
|
||||
## Notes/Known Issues/Limitations
|
||||
|
||||
- By default, if the connection with the pool is lost and fails to reconnect, the Gateway will disconnect all stratum clients. This way miners can use their built-in failover and switch to non-DATUM mining, or an alternate/backup Gateway.
|
||||
- Accepted/rejected share counts on mining hardware may not perfectly match with the pool. The delta may vary depending on the Gateway's configuration. This is because shares are first accepted or rejected as valid for your local template based on your local node, and then again accepted or rejected based on the pool's requirements, latency to the pool (stale work), latency between your node and the network (stale work), etc. Stratum v1 has no mechanism to report back to the miner that previously accepted work is now rejected, and it doesn't make sense to wait for the pool before responding, either.
|
||||
|
||||
**Most importantly**, please note that this is currently a public **BETA** release. While best efforts have been made to ensure this software is as stable and as useful as possible, you may still encounter issues.
|
||||
|
||||
This software is likely to undergo rapid development and revisions up until a v1.0 stable release. Some of these revisions may include changes, such as protocol changes, that require upgrading to the latest version with short or even no notice in order to continue using the software with a DATUM pool. Be sure to watch for important updates!
|
||||
|
||||
Be sure you have failover settings on your miners. As a best practice, when mining on a DATUM pool, set your miner's failover to use that pool's Stratum endpoint.
|
||||
|
||||
## License
|
||||
|
||||
The DATUM Gateway (including the DATUM Protocol) is free open source software and released under the terms of the MIT license. See LICENSE.
|
16
cmake/script/EmbedResources.cmake
Normal file
16
cmake/script/EmbedResources.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
set(OUTPUT_CONTENT "#include <stddef.h>\n")
|
||||
foreach(INPUT_FILE ${INPUT_FILES})
|
||||
file(READ ${SOURCE_DIR}/${INPUT_FILE} INPUT_DATA_HEX HEX)
|
||||
|
||||
string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" OUTPUT_VAR ${INPUT_FILE})
|
||||
|
||||
string(LENGTH ${INPUT_DATA_HEX} INPUT_DATA_LEN)
|
||||
math(EXPR INPUT_DATA_LEN "${INPUT_DATA_LEN} / 2")
|
||||
|
||||
string(REGEX REPLACE "................" "\\0\n" INPUT_DATA_C_ARRAY "${INPUT_DATA_HEX}")
|
||||
string(REGEX REPLACE "[^\n][^\n]" "0x\\0," INPUT_DATA_C_ARRAY "${INPUT_DATA_C_ARRAY}")
|
||||
|
||||
string(APPEND OUTPUT_CONTENT "\nstatic const char ${OUTPUT_VAR}[]={\n${INPUT_DATA_C_ARRAY}0};\nstatic const size_t ${OUTPUT_VAR}_sz=${INPUT_DATA_LEN};\n")
|
||||
endforeach()
|
||||
|
||||
file(WRITE ${OUTPUT_FILE} "${OUTPUT_CONTENT}")
|
@ -1,8 +1,34 @@
|
||||
# DATUM Gateway
|
||||
# Decentralized Alternative Templates for Universal Mining
|
||||
#
|
||||
# This file is part of OCEAN's Bitcoin mining decentralization
|
||||
# project, DATUM.
|
||||
#
|
||||
# https://ocean.xyz
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# Copyright (c) 2024 Bitcoin Ocean, LLC & Luke Dashjr
|
||||
# Copyright (c) 2023-present The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://opensource.org/license/mit/.
|
||||
|
||||
# This script is a multiplatform port of the share/genbuild.sh shell script.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
macro(fatal_error)
|
||||
message(FATAL_ERROR "\n"
|
||||
@ -14,7 +40,7 @@ endmacro()
|
||||
|
||||
if(DEFINED BUILD_INFO_HEADER_PATH AND IS_ABSOLUTE "${BUILD_INFO_HEADER_PATH}")
|
||||
if(EXISTS "${BUILD_INFO_HEADER_PATH}")
|
||||
file(STRINGS ${BUILD_INFO_HEADER_PATH} INFO LIMIT_COUNT 1)
|
||||
file(STRINGS ${BUILD_INFO_HEADER_PATH} INFO)
|
||||
endif()
|
||||
else()
|
||||
fatal_error()
|
||||
@ -40,7 +66,6 @@ if(NOT "$ENV{BITCOIN_GENBUILD_NO_GIT}" STREQUAL "1")
|
||||
WORKING_DIRECTORY ${WORKING_DIR}
|
||||
OUTPUT_VARIABLE IS_INSIDE_WORK_TREE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(IS_INSIDE_WORK_TREE)
|
||||
# Clean 'dirty' status of touched files that haven't been modified.
|
||||
@ -101,12 +126,17 @@ if(NOT "$ENV{BITCOIN_GENBUILD_NO_GIT}" STREQUAL "1")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(GIT_TAG)
|
||||
set(NEWINFO "#define BUILD_GIT_TAG \"${GIT_TAG}\"")
|
||||
elseif(GIT_COMMIT)
|
||||
set(NEWINFO "#define BUILD_GIT_COMMIT \"${GIT_COMMIT}\"")
|
||||
if(HEAD_COMMIT)
|
||||
if(IS_DIRTY)
|
||||
string(APPEND HEAD_COMMIT "+")
|
||||
endif()
|
||||
else()
|
||||
set(NEWINFO "// No build information available")
|
||||
set(HEAD_COMMIT UNKNOWN_GIT_HASH)
|
||||
endif()
|
||||
|
||||
set(NEWINFO "#define GIT_COMMIT_HASH \"${HEAD_COMMIT}\"")
|
||||
if(GIT_TAG)
|
||||
string(APPEND NEWINFO "\n#define BUILD_GIT_TAG \"${GIT_TAG}\"")
|
||||
endif()
|
||||
|
||||
# Only update the header if necessary.
|
||||
|
963
src/datum_api.c
Normal file
963
src/datum_api.c
Normal file
@ -0,0 +1,963 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// This is quick and dirty for now. Will be improved over time.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <microhttpd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "datum_api.h"
|
||||
#include "datum_conf.h"
|
||||
#include "datum_utils.h"
|
||||
#include "datum_stratum.h"
|
||||
#include "datum_sockets.h"
|
||||
#include "datum_protocol.h"
|
||||
|
||||
#include "web_resources.h"
|
||||
|
||||
const char * const homepage_html_end = "</body></html>";
|
||||
|
||||
#define DATUM_API_HOMEPAGE_MAX_SIZE 128000
|
||||
|
||||
const char *cbnames[] = {
|
||||
"Blank",
|
||||
"Tiny",
|
||||
"Default",
|
||||
"Respect",
|
||||
"Yuge",
|
||||
"Antmain2"
|
||||
};
|
||||
|
||||
static void html_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, "<span class='leading_zeros'>%.*s</span>%s", zeros, numstr, &numstr[zeros]);
|
||||
}
|
||||
}
|
||||
|
||||
void datum_api_var_DATUM_SHARES_ACCEPTED(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%llu (%llu diff)", (unsigned long long)datum_accepted_share_count, (unsigned long long)datum_accepted_share_diff);
|
||||
}
|
||||
void datum_api_var_DATUM_SHARES_REJECTED(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%llu (%llu diff)", (unsigned long long)datum_rejected_share_count, (unsigned long long)datum_rejected_share_diff);
|
||||
}
|
||||
void datum_api_var_DATUM_CONNECTION_STATUS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
const char *colour = "lime";
|
||||
const char *s;
|
||||
if (datum_protocol_is_active()) {
|
||||
s = "Connected and Ready";
|
||||
} else if (datum_config.datum_pooled_mining_only && datum_config.datum_pool_host[0]) {
|
||||
colour = "red";
|
||||
s = "Not Ready";
|
||||
} else {
|
||||
if (datum_config.datum_pool_host[0]) {
|
||||
colour = "yellow";
|
||||
}
|
||||
s = "Non-Pooled Mode";
|
||||
}
|
||||
snprintf(buffer, buffer_size, "<svg viewBox='0 0 100 100' role='img' style='width:1em;height:1em'><circle cx='50' cy='60' r='35' style='fill:%s' /></svg> %s", colour, s);
|
||||
}
|
||||
void datum_api_var_DATUM_POOL_HOST(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
if (datum_config.datum_pool_host[0]) {
|
||||
snprintf(buffer, buffer_size, "%s:%u", datum_config.datum_pool_host, (unsigned)datum_config.datum_pool_port);
|
||||
} else {
|
||||
snprintf(buffer, buffer_size, "N/A");
|
||||
}
|
||||
}
|
||||
void datum_api_var_DATUM_POOL_TAG(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
size_t i;
|
||||
buffer[0] = '"';
|
||||
i = strncpy_html_escape(&buffer[1], datum_protocol_is_active()?datum_config.override_mining_coinbase_tag_primary:datum_config.mining_coinbase_tag_primary, buffer_size-3);
|
||||
buffer[i+1] = '"';
|
||||
buffer[i+2] = 0;
|
||||
}
|
||||
void datum_api_var_DATUM_MINER_TAG(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
size_t i;
|
||||
buffer[0] = '"';
|
||||
i = strncpy_html_escape(&buffer[1], datum_config.mining_coinbase_tag_secondary, buffer_size-3);
|
||||
buffer[i+1] = '"';
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
void datum_api_var_STRATUM_ACTIVE_THREADS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%d", vardata->STRATUM_ACTIVE_THREADS);
|
||||
}
|
||||
void datum_api_var_STRATUM_TOTAL_CONNECTIONS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%d", vardata->STRATUM_TOTAL_CONNECTIONS);
|
||||
}
|
||||
void datum_api_var_STRATUM_TOTAL_SUBSCRIPTIONS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%d", vardata->STRATUM_TOTAL_SUBSCRIPTIONS);
|
||||
}
|
||||
void datum_api_var_STRATUM_HASHRATE_ESTIMATE(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%.2f Th/sec", vardata->STRATUM_HASHRATE_ESTIMATE);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_INFO(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
if (!vardata->sjob) return;
|
||||
snprintf(buffer, buffer_size, "%s (%d) @ %.3f", vardata->sjob->job_id, vardata->sjob->global_index, (double)vardata->sjob->tsms / 1000.0);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_BLOCK_HEIGHT(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%llu", (unsigned long long)vardata->sjob->block_template->height);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_BLOCK_VALUE(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_BITS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%s", vardata->sjob->nbits);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_TIMEINFO(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "Current: %llu / Min: %llu", (unsigned long long)vardata->sjob->block_template->curtime, (unsigned long long)vardata->sjob->block_template->mintime);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_LIMITINFO(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "Size: %lu, Weight: %lu, SigOps: %lu", (unsigned long)vardata->sjob->block_template->sizelimit, (unsigned long)vardata->sjob->block_template->weightlimit, (unsigned long)vardata->sjob->block_template->sigoplimit);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_SIZE(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%lu", (unsigned long)vardata->sjob->block_template->txn_total_size);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_WEIGHT(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%lu", (unsigned long)vardata->sjob->block_template->txn_total_weight);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_SIGOPS(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%lu", (unsigned long)vardata->sjob->block_template->txn_total_sigops);
|
||||
}
|
||||
void datum_api_var_STRATUM_JOB_TXNCOUNT(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata) {
|
||||
snprintf(buffer, buffer_size, "%u", (unsigned)vardata->sjob->block_template->txn_count);
|
||||
}
|
||||
|
||||
|
||||
DATUM_API_VarEntry var_entries[] = {
|
||||
{"DATUM_SHARES_ACCEPTED", datum_api_var_DATUM_SHARES_ACCEPTED},
|
||||
{"DATUM_SHARES_REJECTED", datum_api_var_DATUM_SHARES_REJECTED},
|
||||
{"DATUM_CONNECTION_STATUS", datum_api_var_DATUM_CONNECTION_STATUS},
|
||||
{"DATUM_POOL_HOST", datum_api_var_DATUM_POOL_HOST},
|
||||
{"DATUM_POOL_TAG", datum_api_var_DATUM_POOL_TAG},
|
||||
{"DATUM_MINER_TAG", datum_api_var_DATUM_MINER_TAG},
|
||||
{"DATUM_POOL_DIFF", datum_api_var_DATUM_POOL_DIFF},
|
||||
{"DATUM_POOL_PUBKEY", datum_api_var_DATUM_POOL_PUBKEY},
|
||||
|
||||
{"STRATUM_ACTIVE_THREADS", datum_api_var_STRATUM_ACTIVE_THREADS},
|
||||
{"STRATUM_TOTAL_CONNECTIONS", datum_api_var_STRATUM_TOTAL_CONNECTIONS},
|
||||
{"STRATUM_TOTAL_SUBSCRIPTIONS", datum_api_var_STRATUM_TOTAL_SUBSCRIPTIONS},
|
||||
{"STRATUM_HASHRATE_ESTIMATE", datum_api_var_STRATUM_HASHRATE_ESTIMATE},
|
||||
|
||||
{"STRATUM_JOB_INFO", datum_api_var_STRATUM_JOB_INFO},
|
||||
{"STRATUM_JOB_BLOCK_HEIGHT", datum_api_var_STRATUM_JOB_BLOCK_HEIGHT},
|
||||
{"STRATUM_JOB_BLOCK_VALUE", datum_api_var_STRATUM_JOB_BLOCK_VALUE},
|
||||
{"STRATUM_JOB_PREVBLOCK", datum_api_var_STRATUM_JOB_PREVBLOCK},
|
||||
{"STRATUM_JOB_TARGET", datum_api_var_STRATUM_JOB_TARGET},
|
||||
{"STRATUM_JOB_WITNESS", datum_api_var_STRATUM_JOB_WITNESS},
|
||||
{"STRATUM_JOB_DIFF", datum_api_var_STRATUM_JOB_DIFF},
|
||||
{"STRATUM_JOB_VERSION", datum_api_var_STRATUM_JOB_VERSION},
|
||||
{"STRATUM_JOB_BITS", datum_api_var_STRATUM_JOB_BITS},
|
||||
{"STRATUM_JOB_TIMEINFO", datum_api_var_STRATUM_JOB_TIMEINFO},
|
||||
{"STRATUM_JOB_LIMITINFO", datum_api_var_STRATUM_JOB_LIMITINFO},
|
||||
{"STRATUM_JOB_SIZE", datum_api_var_STRATUM_JOB_SIZE},
|
||||
{"STRATUM_JOB_WEIGHT", datum_api_var_STRATUM_JOB_WEIGHT},
|
||||
{"STRATUM_JOB_SIGOPS", datum_api_var_STRATUM_JOB_SIGOPS},
|
||||
{"STRATUM_JOB_TXNCOUNT", datum_api_var_STRATUM_JOB_TXNCOUNT},
|
||||
|
||||
{NULL, NULL} // Mark the end of the array
|
||||
};
|
||||
|
||||
DATUM_API_VarFunc datum_api_find_var_func(const char *var_name) {
|
||||
for (int i = 0; var_entries[i].var_name != NULL; i++) {
|
||||
if (strcmp(var_entries[i].var_name, var_name) == 0) {
|
||||
return var_entries[i].func;
|
||||
}
|
||||
}
|
||||
return NULL; // Variable not found
|
||||
}
|
||||
|
||||
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;
|
||||
size_t var_name_len = 0;
|
||||
char var_name[256];
|
||||
char replacement[256];
|
||||
size_t replacement_len;
|
||||
size_t remaining;
|
||||
size_t to_copy;
|
||||
const char *var_start;
|
||||
const char *var_end;
|
||||
size_t total_var_len;
|
||||
char temp_var[260];
|
||||
|
||||
while (*p && output_len < max_output_size - 1) {
|
||||
if (strncmp(p, "${", 2) == 0) {
|
||||
p += 2; // Skip "${"
|
||||
var_start = p;
|
||||
var_end = strchr(p, '}');
|
||||
if (!var_end) {
|
||||
// No closing '}', copy rest of the input to output
|
||||
remaining = strlen(p);
|
||||
to_copy = (remaining < max_output_size - output_len - 1) ? remaining : max_output_size - output_len - 1;
|
||||
strncpy(&output[output_len], p, to_copy);
|
||||
output_len += to_copy;
|
||||
break;
|
||||
}
|
||||
var_name_len = var_end - var_start;
|
||||
|
||||
if (var_name_len >= sizeof(var_name)-1) {
|
||||
output[output_len] = 0;
|
||||
return;
|
||||
}
|
||||
strncpy(var_name, var_start, var_name_len);
|
||||
var_name[var_name_len] = 0;
|
||||
|
||||
DATUM_API_VarFunc func = datum_api_find_var_func(var_name);
|
||||
if (func) {
|
||||
replacement[0] = 0;
|
||||
func(replacement, sizeof(replacement), vardata);
|
||||
replacement_len = strlen(replacement);
|
||||
if (replacement_len) {
|
||||
to_copy = (replacement_len < max_output_size - output_len - 1) ? replacement_len : max_output_size - output_len - 1;
|
||||
strncpy(&output[output_len], replacement, to_copy);
|
||||
output_len += to_copy;
|
||||
}
|
||||
output[output_len] = 0;
|
||||
} else {
|
||||
// Not sure what this is... so just leave it
|
||||
total_var_len = var_name_len + 3;
|
||||
snprintf(temp_var, sizeof(temp_var), "${%s}", var_name);
|
||||
to_copy = (total_var_len < max_output_size - output_len - 1) ? total_var_len : max_output_size - output_len - 1;
|
||||
strncpy(&output[output_len], temp_var, to_copy);
|
||||
output_len += to_copy;
|
||||
output[output_len] = 0;
|
||||
}
|
||||
p = var_end + 1; // Move past '}'
|
||||
} else {
|
||||
output[output_len++] = *p++;
|
||||
output[output_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
output[output_len] = 0;
|
||||
}
|
||||
|
||||
size_t strncpy_html_escape(char *dest, const char *src, size_t n) {
|
||||
size_t i = 0;
|
||||
|
||||
while (*src && i < n) {
|
||||
switch (*src) {
|
||||
case '&':
|
||||
if (i + 5 <= n) { // &
|
||||
dest[i++] = '&';
|
||||
dest[i++] = 'a';
|
||||
dest[i++] = 'm';
|
||||
dest[i++] = 'p';
|
||||
dest[i++] = ';';
|
||||
} else {
|
||||
return i; // Stop if there's not enough space
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
if (i + 4 <= n) { // <
|
||||
dest[i++] = '&';
|
||||
dest[i++] = 'l';
|
||||
dest[i++] = 't';
|
||||
dest[i++] = ';';
|
||||
} else {
|
||||
return i; // Stop if there's not enough space
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if (i + 4 <= n) { // >
|
||||
dest[i++] = '&';
|
||||
dest[i++] = 'g';
|
||||
dest[i++] = 't';
|
||||
dest[i++] = ';';
|
||||
} else {
|
||||
return i; // Stop if there's not enough space
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
if (i + 6 <= n) { // "
|
||||
dest[i++] = '&';
|
||||
dest[i++] = 'q';
|
||||
dest[i++] = 'u';
|
||||
dest[i++] = 'o';
|
||||
dest[i++] = 't';
|
||||
dest[i++] = ';';
|
||||
} else {
|
||||
return i; // Stop if there's not enough space
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dest[i++] = *src;
|
||||
break;
|
||||
}
|
||||
src++;
|
||||
}
|
||||
|
||||
// Null-terminate the destination string if there's space
|
||||
if (i < n) {
|
||||
dest[i] = '\0';
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void http_resp_prevent_caching(struct MHD_Response * const response) {
|
||||
MHD_add_response_header(response, "Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
MHD_add_response_header(response, "Pragma", "no-cache");
|
||||
MHD_add_response_header(response, "Expires", "0");
|
||||
}
|
||||
|
||||
static int datum_api_asset(struct MHD_Connection * const connection, const char * const mimetype, const char * const data, const size_t datasz) {
|
||||
struct MHD_Response * const response = MHD_create_response_from_buffer(datasz, (void*)data, MHD_RESPMEM_PERSISTENT);
|
||||
MHD_add_response_header(response, "Content-Type", mimetype);
|
||||
const int ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
||||
MHD_destroy_response (response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void datum_api_cmd_empty_thread(int tid) {
|
||||
if ((tid >= 0) && (tid < global_stratum_app->max_threads)) {
|
||||
DLOG_WARN("API Request to empty stratum thread %d!", tid);
|
||||
global_stratum_app->datum_threads[tid].empty_request = true;
|
||||
}
|
||||
}
|
||||
|
||||
void datum_api_cmd_kill_client(int tid, int cid) {
|
||||
if ((tid >= 0) && (tid < global_stratum_app->max_threads)) {
|
||||
if ((cid >= 0) && (cid < global_stratum_app->max_clients_thread)) {
|
||||
DLOG_WARN("API Request to disconnect stratum client %d/%d!", tid, cid);
|
||||
global_stratum_app->datum_threads[tid].client_data[cid].kill_request = true;
|
||||
global_stratum_app->datum_threads[tid].has_client_kill_request = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int datum_api_cmd(struct MHD_Connection *connection, char *post, int len) {
|
||||
struct MHD_Response *response;
|
||||
char output[1024];
|
||||
int ret, sz=0;
|
||||
json_t *root, *cmd, *param;
|
||||
json_error_t error;
|
||||
const char *cstr;
|
||||
int tid,cid;
|
||||
|
||||
if ((len) && (post)) {
|
||||
DLOG_DEBUG("POST DATA: %s", post);
|
||||
|
||||
if (post[0] == '{') {
|
||||
// attempt to parse JSON command
|
||||
root = json_loadb(post, len, 0, &error);
|
||||
if (root) {
|
||||
if (json_is_object(root) && (cmd = json_object_get(root, "cmd"))) {
|
||||
if (json_is_string(cmd)) {
|
||||
cstr = json_string_value(cmd);
|
||||
DLOG_DEBUG("JSON CMD: %s",cstr);
|
||||
switch(cstr[0]) {
|
||||
case 'e': {
|
||||
if (!strcmp(cstr,"empty_thread")) {
|
||||
param = json_object_get(root, "tid");
|
||||
if (json_is_integer(param)) {
|
||||
datum_api_cmd_empty_thread(json_integer_value(param));
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'k': {
|
||||
if (!strcmp(cstr,"kill_client")) {
|
||||
param = json_object_get(root, "tid");
|
||||
if (json_is_integer(param)) {
|
||||
tid = json_integer_value(param);
|
||||
param = json_object_get(root, "cid");
|
||||
if (json_is_integer(param)) {
|
||||
cid = json_integer_value(param);
|
||||
datum_api_cmd_kill_client(tid,cid);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(output, "{}");
|
||||
response = MHD_create_response_from_buffer (sz, (void *) output, MHD_RESPMEM_MUST_COPY);
|
||||
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_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, "<TABLE><TR><TD><U>Value</U></TD> <TD><U>Address</U></TD></TR>");
|
||||
|
||||
for(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);
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TR><TD>%.8f BTC</TD><TD>%s</TD></TR>", (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, "<TR><TD>%.8f BTC</TD><TD>%s</TD></TR>", (double)(sjob->coinbase_value - tv) / (double)100000000.0, tempaddr);
|
||||
}
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "</TABLE>");
|
||||
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 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, "<TABLE><TR><TD><U>TID</U></TD> <TD><U>Connection Count</U></TD> <TD><U>Sub Count</U></TD> <TD><U>Approx. Hashrate</U></TD> <TD><U>Command</U></TD></TR>");
|
||||
for(j=0;j<global_stratum_app->max_threads;j++) {
|
||||
thr = 0.0;
|
||||
subs = 0;
|
||||
conns = 0;
|
||||
|
||||
for(ii=0;ii<global_stratum_app->max_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, "<TR><TD>%d</TD> <TD>%d</TD> <TD>%d</TD> <TD>%.2f Th/s</TD> <TD><button onclick=\"sendPostRequest('/cmd', {cmd:'empty_thread',tid:%d})\">Disconnect All</button></TD></TR>", j, conns, subs, thr, j);
|
||||
}
|
||||
}
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "</TABLE>");
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<script>function sendPostRequest(url, data){fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});}</script>");
|
||||
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 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;i<global_stratum_app->max_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, "<TABLE><TR><TD><U>TID/CID</U></TD> <TD><U>RemHost</U></TD> <TD><U>Auth Username</U></TD> <TD><U>Subbed</U></TD> <TD><U>Last Accepted</U></TD> <TD><U>VDiff</U></TD> <TD><U>DiffA (A)</U></TD> <TD><U>DiffR (R)</U></TD> <TD><U>Hashrate (age)</U></TD> <TD><U>Coinbase</U></TD> <TD><U>UserAgent</U> </TD><TD><U>Command</U></TD></TR>");
|
||||
|
||||
for(j=0;j<global_stratum_app->max_threads;j++) {
|
||||
for(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;
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TR><TD>%d/%d</TD>", j,ii);
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>%s</TD>", global_stratum_app->datum_threads[j].client_data[ii].rem_host);
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>");
|
||||
sz += strncpy_html_escape(&output[sz], m->last_auth_username, max_sz-1-sz);
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "</TD>");
|
||||
|
||||
if (m->subscribed) {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD> <span style=\"font-family: monospace;\">%4.4x</span> %.1fs</TD>", m->sid, (double)(tsms - m->subscribe_tsms)/1000.0);
|
||||
|
||||
if (m->stats.last_share_tsms) {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>%.1fs</TD>", (double)(tsms - m->stats.last_share_tsms)/1000.0);
|
||||
} else {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>N/A</TD>");
|
||||
}
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>%"PRIu64"</TD>", m->current_diff);
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>%"PRIu64" (%"PRIu64")</TD>", 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, "<TD>%"PRIu64" (%"PRIu64") %.2f%%</TD>", 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, "<TD>%.2f Th/s (%.1fs)</TD>", hr, (double)(tsms - m->stats.last_swap_tsms)/1000.0);
|
||||
} else {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>N/A</TD>");
|
||||
}
|
||||
|
||||
if (m->coinbase_selection < (sizeof(cbnames) / sizeof(cbnames[0]))) {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>%s</TD>", cbnames[m->coinbase_selection]);
|
||||
} else {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>Unknown</TD>");
|
||||
}
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD>");
|
||||
sz += strncpy_html_escape(&output[sz], m->useragent, max_sz-1-sz);
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "</TD>");
|
||||
} else {
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD COLSPAN=\"8\">Not Subscribed</TD>");
|
||||
}
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<TD><button onclick=\"sendPostRequest('/cmd', {cmd:'kill_client',tid:%d,cid:%d})\">Kick</button></TD></TR>", j, ii);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "</TABLE><BR><CENTER>Total active hashrate estimate: %.2f Th/s</CENTER>", thr);
|
||||
sz += snprintf(&output[sz], max_sz-1-sz, "<script>function sendPostRequest(url, data){fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});}</script>");
|
||||
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);
|
||||
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;
|
||||
char output[DATUM_API_HOMEPAGE_MAX_SIZE];
|
||||
int j, k = 0, kk = 0, ii, ret;
|
||||
T_DATUM_MINER_DATA *m;
|
||||
T_DATUM_API_DASH_VARS vardata;
|
||||
unsigned char astat;
|
||||
double thr = 0.0;
|
||||
double hr;
|
||||
uint64_t tsms;
|
||||
|
||||
memset(&vardata, 0, sizeof(T_DATUM_API_DASH_VARS));
|
||||
|
||||
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;j<global_stratum_app->max_threads;j++) {
|
||||
k+=global_stratum_app->datum_threads[j].connected_clients;
|
||||
for(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;
|
||||
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;
|
||||
}
|
||||
|
||||
output[0] = 0;
|
||||
datum_api_fill_vars(www_home_html, output, DATUM_API_HOMEPAGE_MAX_SIZE, &vardata);
|
||||
|
||||
// return the home page with some data and such
|
||||
response = MHD_create_response_from_buffer (strlen(output), (void *) output, 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_OK(struct MHD_Connection *connection) {
|
||||
enum MHD_Result ret;
|
||||
struct MHD_Response *response;
|
||||
const char *ok_response = "OK";
|
||||
response = MHD_create_response_from_buffer(strlen(ok_response), (void *)ok_response, MHD_RESPMEM_PERSISTENT);
|
||||
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;
|
||||
}
|
||||
|
||||
struct ConnectionInfo {
|
||||
char *data;
|
||||
size_t data_size;
|
||||
};
|
||||
|
||||
static void datum_api_request_completed(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
|
||||
struct ConnectionInfo *con_info = *con_cls;
|
||||
|
||||
if (con_info != NULL) {
|
||||
if (con_info->data != NULL) free(con_info->data);
|
||||
free(con_info);
|
||||
}
|
||||
*con_cls = NULL;
|
||||
}
|
||||
|
||||
enum MHD_Result datum_api_answer(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) {
|
||||
char *user;
|
||||
char *pass;
|
||||
enum MHD_Result ret;
|
||||
struct MHD_Response *response;
|
||||
struct ConnectionInfo *con_info = *con_cls;
|
||||
int int_method = 0;
|
||||
int uds = 0;
|
||||
const char *time_str;
|
||||
|
||||
if (strcmp(method, "GET") == 0) {
|
||||
int_method = 1;
|
||||
}
|
||||
|
||||
if (strcmp(method, "POST") == 0) {
|
||||
int_method = 2;
|
||||
}
|
||||
|
||||
if (!int_method) {
|
||||
const char *error_response = "<H1>Method not allowed.</H1>";
|
||||
response = MHD_create_response_from_buffer(strlen(error_response), (void *)error_response, MHD_RESPMEM_PERSISTENT);
|
||||
MHD_add_response_header(response, "Content-Type", "text/html");
|
||||
ret = MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);
|
||||
MHD_destroy_response(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (int_method == 2) {
|
||||
if (!con_info) {
|
||||
// Allocate memory for connection info
|
||||
con_info = malloc(sizeof(struct ConnectionInfo));
|
||||
if (!con_info) {
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
con_info->data = calloc(16384,1);
|
||||
con_info->data_size = 0;
|
||||
|
||||
if (!con_info->data) {
|
||||
free(con_info);
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
*con_cls = (void *)con_info;
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
if (*upload_data_size) {
|
||||
// Accumulate data
|
||||
|
||||
// max 1 MB? seems reasonable
|
||||
if (con_info->data_size + *upload_data_size > (1024*1024)) return MHD_NO;
|
||||
|
||||
con_info->data = realloc(con_info->data, con_info->data_size + *upload_data_size + 1);
|
||||
if (!con_info->data) {
|
||||
return MHD_NO;
|
||||
}
|
||||
memcpy(&(con_info->data[con_info->data_size]), upload_data, *upload_data_size);
|
||||
con_info->data_size += *upload_data_size;
|
||||
con_info->data[con_info->data_size] = '\0';
|
||||
*upload_data_size = 0;
|
||||
|
||||
return MHD_YES;
|
||||
} else if (!con_info->data_size) {
|
||||
const char *error_response = "<H1>Invalid request.</H1>";
|
||||
response = MHD_create_response_from_buffer(strlen(error_response), (void *)error_response, MHD_RESPMEM_PERSISTENT);
|
||||
MHD_add_response_header(response, "Content-Type", "text/html");
|
||||
ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response);
|
||||
MHD_destroy_response(response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uds = *upload_data_size;
|
||||
}
|
||||
|
||||
const union MHD_ConnectionInfo *conn_info = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
|
||||
char *client_ip = inet_ntoa(((struct sockaddr_in*)conn_info->client_addr)->sin_addr);
|
||||
|
||||
DLOG_DEBUG("REQUEST: %s, %s, %s, %d", client_ip, method, url, uds);
|
||||
|
||||
pass = NULL;
|
||||
user = MHD_basic_auth_get_username_password (connection, &pass);
|
||||
|
||||
/////////////////////////
|
||||
// TODO: Implement API key or auth or something similar
|
||||
|
||||
if (user) MHD_free(user);
|
||||
if (pass) MHD_free(pass);
|
||||
|
||||
while (!global_stratum_app) {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if (int_method == 1 && url[0] == '/' && url[1] == 0) {
|
||||
// homepage
|
||||
return datum_api_homepage(connection);
|
||||
}
|
||||
|
||||
switch (url[1]) {
|
||||
case 'N': {
|
||||
if (!strcmp(url, "/NOTIFY")) {
|
||||
// TODO: Implement faster notifies with hash+height
|
||||
datum_blocktemplates_notifynew(NULL, 0);
|
||||
return datum_api_OK(connection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'a': {
|
||||
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);
|
||||
} else if (!strcmp(url, "/assets/style.css")) {
|
||||
return datum_api_asset(connection, "text/css", www_assets_style_css, www_assets_style_css_sz);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'c': {
|
||||
if (!strcmp(url, "/clients")) {
|
||||
return datum_api_client_dashboard(connection);
|
||||
}
|
||||
if (!strcmp(url, "/coinbaser")) {
|
||||
return datum_api_coinbaser(connection);
|
||||
}
|
||||
if ((int_method==2) && (!strcmp(url, "/cmd"))) {
|
||||
if (con_info) {
|
||||
return datum_api_cmd(connection, con_info->data, con_info->data_size);
|
||||
} else {
|
||||
return MHD_NO;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'f': {
|
||||
if (!strcmp(url, "/favicon.ico")) {
|
||||
return datum_api_asset(connection, "image/x-icon", www_assets_icons_favicon_ico, www_assets_icons_favicon_ico_sz);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 't': {
|
||||
if (!strcmp(url, "/threads")) {
|
||||
return datum_api_thread_dashboard(connection);
|
||||
}
|
||||
if (!strcmp(url, "/testnet_fastforward")) {
|
||||
// Get the time parameter from the URL query
|
||||
time_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "ts");
|
||||
|
||||
uint32_t t = -1000;
|
||||
if (time_str != NULL) {
|
||||
// Convert the time parameter to uint32_t
|
||||
t = (int)strtoul(time_str, NULL, 10);
|
||||
}
|
||||
|
||||
datum_blocktemplates_notifynew("T", t);
|
||||
return datum_api_OK(connection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
const char *error_response = "<H1>Not found</H1>";
|
||||
response = MHD_create_response_from_buffer(strlen(error_response), (void *)error_response, MHD_RESPMEM_PERSISTENT);
|
||||
MHD_add_response_header(response, "Content-Type", "text/html");
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_NOT_FOUND, response);
|
||||
MHD_destroy_response (response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *datum_api_thread(void *ptr) {
|
||||
struct MHD_Daemon *daemon;
|
||||
|
||||
if (!datum_config.api_listen_port) {
|
||||
DLOG_INFO("No API port configured. API disabled.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD, datum_config.api_listen_port, NULL, NULL, &datum_api_answer, NULL,
|
||||
MHD_OPTION_CONNECTION_LIMIT, 128,
|
||||
MHD_OPTION_NOTIFY_COMPLETED, datum_api_request_completed, NULL,
|
||||
MHD_OPTION_END);
|
||||
|
||||
if (!daemon) {
|
||||
DLOG_FATAL("Unable to start daemon for API");
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DLOG_INFO("API listening on port %d", datum_config.api_listen_port);
|
||||
|
||||
while(1) {
|
||||
sleep(3);
|
||||
}
|
||||
}
|
||||
|
||||
int datum_api_init(void) {
|
||||
pthread_t pthread_datum_api_thread;
|
||||
|
||||
if (!datum_config.api_listen_port) {
|
||||
DLOG_INFO("INFO: No API port configured. API disabled.");
|
||||
return 0;
|
||||
}
|
||||
pthread_create(&pthread_datum_api_thread, NULL, datum_api_thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
61
src/datum_api.h
Normal file
61
src/datum_api.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_API_H_
|
||||
#define _DATUM_API_H_
|
||||
|
||||
#include "datum_stratum.h"
|
||||
|
||||
typedef struct {
|
||||
int STRATUM_ACTIVE_THREADS;
|
||||
int STRATUM_TOTAL_CONNECTIONS;
|
||||
int STRATUM_TOTAL_SUBSCRIPTIONS;
|
||||
double STRATUM_HASHRATE_ESTIMATE;
|
||||
|
||||
T_DATUM_STRATUM_JOB *sjob;
|
||||
} T_DATUM_API_DASH_VARS;
|
||||
|
||||
typedef void (*DATUM_API_VarFunc)(char *buffer, size_t buffer_size, const T_DATUM_API_DASH_VARS *vardata);
|
||||
|
||||
typedef struct {
|
||||
const char *var_name;
|
||||
DATUM_API_VarFunc func;
|
||||
} DATUM_API_VarEntry;
|
||||
|
||||
|
||||
int datum_api_init(void);
|
||||
size_t strncpy_html_escape(char *dest, const char *src, size_t n);
|
||||
|
||||
#endif
|
560
src/datum_blocktemplates.c
Normal file
560
src/datum_blocktemplates.c
Normal file
@ -0,0 +1,560 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <jansson.h>
|
||||
#include <inttypes.h>
|
||||
#include <curl/curl.h>
|
||||
#include <stdatomic.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "datum_gateway.h"
|
||||
#include "datum_jsonrpc.h"
|
||||
#include "datum_utils.h"
|
||||
#include "datum_blocktemplates.h"
|
||||
#include "datum_conf.h"
|
||||
#include "datum_stratum.h"
|
||||
|
||||
volatile sig_atomic_t new_notify = 0;
|
||||
volatile char new_notify_blockhash[256] = { 0 };
|
||||
volatile int new_notify_height = 0;
|
||||
|
||||
void datum_blocktemplates_notifynew(const char *prevhash, int height) {
|
||||
new_notify = 1;
|
||||
if (prevhash) {
|
||||
if (prevhash[0] > 0) {
|
||||
strncpy((char *)new_notify_blockhash, prevhash, 66);
|
||||
if (height > new_notify_height) {
|
||||
new_notify_height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T_DATUM_TEMPLATE_DATA *template_data = NULL;
|
||||
|
||||
int next_template_index = 0;
|
||||
|
||||
int datum_template_init(void) {
|
||||
char *temp = NULL, *ptr = NULL;
|
||||
int i,j;
|
||||
|
||||
template_data = (T_DATUM_TEMPLATE_DATA *)calloc(sizeof(T_DATUM_TEMPLATE_DATA),MAX_TEMPLATES_IN_MEMORY+1);
|
||||
if (!template_data) {
|
||||
DLOG_FATAL("Could not allocate RAM for in-memory template data. :( (1)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: Be smarter about dependent RAM data and size
|
||||
// we're storing both binary and ascii hex versions of all txns for both processing and submitblock speedups
|
||||
j = (sizeof(T_DATUM_TEMPLATE_TXN)*16384) + (MAX_BLOCK_SIZE_BYTES*3) + 2000000;
|
||||
temp = calloc(j, MAX_TEMPLATES_IN_MEMORY);
|
||||
if (!temp) {
|
||||
DLOG_FATAL("ERROR: Could not allocate RAM for in-memory template data. :( (2)");
|
||||
return -2;
|
||||
}
|
||||
|
||||
ptr = temp;
|
||||
for(i=0;i<MAX_TEMPLATES_IN_MEMORY;i++) {
|
||||
template_data[i].local_data = ptr;
|
||||
ptr+=j;
|
||||
template_data[i].local_data_size = j;
|
||||
template_data[i].local_index = i;
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Allocated %d MB of RAM for template memory", (j*MAX_TEMPLATES_IN_MEMORY)/(1024*1024));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void datum_template_clear(T_DATUM_TEMPLATE_DATA* p) {
|
||||
p->coinbasevalue = 0;
|
||||
p->txn_count = 0;
|
||||
p->txn_total_size = 0;
|
||||
p->txn_data_offset = 0;
|
||||
p->txn_total_weight = 0;
|
||||
p->txn_total_sigops = 0;
|
||||
p->txns = p->local_data;
|
||||
}
|
||||
|
||||
T_DATUM_TEMPLATE_DATA *get_next_template_ptr(void) {
|
||||
T_DATUM_TEMPLATE_DATA *p;
|
||||
|
||||
if (!template_data) return NULL;
|
||||
|
||||
p = &template_data[next_template_index];
|
||||
|
||||
datum_template_clear(p);
|
||||
|
||||
next_template_index++;
|
||||
if (next_template_index >= MAX_TEMPLATES_IN_MEMORY) {
|
||||
next_template_index = 0;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
T_DATUM_TEMPLATE_DATA *datum_gbt_parser(json_t *gbt) {
|
||||
T_DATUM_TEMPLATE_DATA *tdata;
|
||||
const char *s;
|
||||
int i,j;
|
||||
json_t *tx_array;
|
||||
|
||||
tdata = get_next_template_ptr();
|
||||
if (!tdata) {
|
||||
DLOG_ERROR("Could not get a template pointer.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->height = json_integer_value(json_object_get(gbt, "height"));
|
||||
if (!tdata->height) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (height)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->coinbasevalue = json_integer_value(json_object_get(gbt, "coinbasevalue"));
|
||||
if (!tdata->coinbasevalue) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (coinbasevalue)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->mintime = json_integer_value(json_object_get(gbt, "mintime"));
|
||||
if (!tdata->mintime) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (mintime)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->sigoplimit = json_integer_value(json_object_get(gbt, "sigoplimit"));
|
||||
if (!tdata->sigoplimit) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (sigoplimit)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->curtime = json_integer_value(json_object_get(gbt, "curtime"));
|
||||
if (!tdata->curtime) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (curtime)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->sizelimit = json_integer_value(json_object_get(gbt, "sizelimit"));
|
||||
if (!tdata->sizelimit) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (sizelimit)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->weightlimit = json_integer_value(json_object_get(gbt, "weightlimit"));
|
||||
if (!tdata->weightlimit) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (weightlimit)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->version = json_integer_value(json_object_get(gbt, "version"));
|
||||
if (!tdata->version) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (version)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = json_string_value(json_object_get(gbt, "bits"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (bits)");
|
||||
return NULL;
|
||||
}
|
||||
if (strlen(s) != 8) {
|
||||
DLOG_ERROR("Wrong bits length from GBT JSON");
|
||||
return NULL;
|
||||
}
|
||||
strcpy(tdata->bits, s);
|
||||
|
||||
s = json_string_value(json_object_get(gbt, "previousblockhash"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (previousblockhash)");
|
||||
return NULL;
|
||||
}
|
||||
strncpy(tdata->previousblockhash, s, 71);
|
||||
|
||||
s = json_string_value(json_object_get(gbt, "target"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (target)");
|
||||
return NULL;
|
||||
}
|
||||
strncpy(tdata->block_target_hex, s, 71);
|
||||
|
||||
s = json_string_value(json_object_get(gbt, "default_witness_commitment"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (default_witness_commitment)");
|
||||
return NULL;
|
||||
}
|
||||
strncpy(tdata->default_witness_commitment, s, 95);
|
||||
|
||||
// "20000000", "192e17d5", "66256be5"
|
||||
// version, bits, time
|
||||
// 192e17d5 // gbt format matches stratum for bits
|
||||
|
||||
// stash useful binary versions of prevblockhash and nbits
|
||||
for(i=0;i<64;i+=2) {
|
||||
tdata->previousblockhash_bin[31-(i>>1)] = hex2bin_uchar(&tdata->previousblockhash[i]);
|
||||
}
|
||||
for(i=0;i<4;i++) {
|
||||
tdata->bits_bin[3-i] = hex2bin_uchar(&tdata->bits[i<<1]);
|
||||
}
|
||||
tdata->bits_uint = upk_u32le(tdata->bits_bin, 0);
|
||||
nbits_to_target(tdata->bits_uint, tdata->block_target);
|
||||
|
||||
// store binary default witness commitment
|
||||
j = strlen(tdata->default_witness_commitment);
|
||||
for(i=0;i<j;i+=2) {
|
||||
tdata->default_witness_commitment_bin[(i>>1)] = hex2bin_uchar(&tdata->default_witness_commitment[i]);
|
||||
}
|
||||
|
||||
// Get the txns
|
||||
tx_array = json_object_get(gbt, "transactions");
|
||||
if (!json_is_array(tx_array)) {
|
||||
DLOG_ERROR("Missing data from GBT JSON (transactions)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tdata->txn_count = json_array_size(tx_array);
|
||||
if (tdata->txn_count > 0) {
|
||||
tdata->txn_data_offset = sizeof(T_DATUM_TEMPLATE_TXN)*tdata->txn_count;
|
||||
|
||||
for(i=0;i<tdata->txn_count;i++) {
|
||||
json_t *tx = json_array_get(tx_array, i);
|
||||
if (!tx) {
|
||||
DLOG_ERROR("transaction %d not found!", i);
|
||||
return NULL;
|
||||
}
|
||||
if (!json_is_object(tx)) {
|
||||
DLOG_ERROR("transaction %d is not an object!", i);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// index (1 based, like GBT depends)
|
||||
tdata->txns[i].index_raw = i+1;
|
||||
|
||||
// txid
|
||||
s = json_string_value(json_object_get(tx, "txid"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON transactions[%d] (txid)",i);
|
||||
return NULL;
|
||||
}
|
||||
strcpy(tdata->txns[i].txid_hex, s);
|
||||
hex_to_bin_le(tdata->txns[i].txid_hex, tdata->txns[i].txid_bin);
|
||||
|
||||
// hash
|
||||
s = json_string_value(json_object_get(tx, "hash"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON transactions[%d] (hash)",i);
|
||||
return NULL;
|
||||
}
|
||||
strcpy(tdata->txns[i].hash_hex, s);
|
||||
hex_to_bin_le(tdata->txns[i].hash_hex, tdata->txns[i].hash_bin);
|
||||
|
||||
// fee
|
||||
tdata->txns[i].fee_sats = json_integer_value(json_object_get(tx, "fee"));
|
||||
|
||||
// sigops
|
||||
tdata->txns[i].sigops = json_integer_value(json_object_get(tx, "sigops"));
|
||||
|
||||
// weight
|
||||
tdata->txns[i].weight = json_integer_value(json_object_get(tx, "weight"));
|
||||
|
||||
// data
|
||||
s = json_string_value(json_object_get(tx, "data"));
|
||||
if (!s) {
|
||||
DLOG_ERROR("Missing data from GBT JSON transactions[%d] (data)",i);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// size
|
||||
tdata->txns[i].size = strlen(s)>>1;
|
||||
|
||||
// raw txn data
|
||||
tdata->txns[i].txn_data_binary = &((uint8_t *)tdata->local_data)[tdata->txn_data_offset];
|
||||
tdata->txn_data_offset += tdata->txns[i].size+1;
|
||||
tdata->txns[i].txn_data_hex = &((char *)tdata->local_data)[tdata->txn_data_offset];
|
||||
tdata->txn_data_offset += (tdata->txns[i].size*2)+2;
|
||||
if (tdata->txn_data_offset >= tdata->local_data_size) {
|
||||
DLOG_ERROR("Exceeded template local size with txn data!");
|
||||
return NULL;
|
||||
}
|
||||
strcpy(tdata->txns[i].txn_data_hex, s);
|
||||
hex_to_bin(s, tdata->txns[i].txn_data_binary);
|
||||
|
||||
// tallies
|
||||
tdata->txn_total_weight+=tdata->txns[i].weight;
|
||||
tdata->txn_total_size+=tdata->txns[i].size;
|
||||
tdata->txn_total_sigops+=tdata->txns[i].sigops;
|
||||
}
|
||||
}
|
||||
|
||||
return tdata;
|
||||
}
|
||||
|
||||
void *datum_gateway_fallback_notifier(void *args) {
|
||||
CURL *tcurl = NULL;
|
||||
char userpass[512];
|
||||
char req[512];
|
||||
char p1[72];
|
||||
p1[0] = 0;
|
||||
json_t *gbbh, *res_val;
|
||||
const char *s;
|
||||
|
||||
tcurl = curl_easy_init();
|
||||
if (!tcurl) {
|
||||
DLOG_FATAL("Could not initialize cURL");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
DLOG_DEBUG("Fallback notifier thread ready.");
|
||||
|
||||
sprintf(userpass, "%s:%s", datum_config.bitcoind_rpcuser, datum_config.bitcoind_rpcpassword);
|
||||
|
||||
while(1) {
|
||||
sprintf(req, "{\"jsonrpc\":\"1.0\",\"id\":\"%"PRIu64"\",\"method\":\"getbestblockhash\",\"params\":[]}", current_time_millis());
|
||||
gbbh = json_rpc_call(tcurl, datum_config.bitcoind_rpcurl, userpass, req);
|
||||
if (gbbh) {
|
||||
res_val = json_object_get(gbbh, "result");
|
||||
if (!res_val) {
|
||||
DLOG_ERROR("ERROR: Could not decode getbestblockhash result!");
|
||||
} else {
|
||||
s = json_string_value(res_val);
|
||||
if (s) {
|
||||
if (strlen(s) == 64) {
|
||||
if (p1[0] == 0) {
|
||||
strncpy(p1,s,70);
|
||||
} else {
|
||||
if (strcmp(s, p1) != 0) {
|
||||
// new block?!?!?!
|
||||
datum_blocktemplates_notifynew(s,0);
|
||||
strncpy(p1,s,70);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
json_decref(gbbh);
|
||||
gbbh = NULL;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void *datum_gateway_template_thread(void *args) {
|
||||
CURL *tcurl = NULL;
|
||||
json_t *gbt = NULL, *res_val;
|
||||
uint64_t i = 0;
|
||||
char gbt_req[1024];
|
||||
int j;
|
||||
char userpass[512];
|
||||
T_DATUM_TEMPLATE_DATA *t;
|
||||
bool was_notified = false;
|
||||
int wnc = 0;
|
||||
uint64_t last_block_change = 0;
|
||||
pthread_t pthread_datum_gateway_fallback_notifier;
|
||||
tcurl = curl_easy_init();
|
||||
if (!tcurl) {
|
||||
DLOG_FATAL("Could not initialize cURL");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
|
||||
if (datum_template_init() < 1) {
|
||||
DLOG_FATAL("Couldn't setup template processor.");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
|
||||
if (datum_config.bitcoind_notify_fallback) {
|
||||
// start getbestblockhash poller thread as a backup for notifications
|
||||
DLOG_DEBUG("Starting fallback block notifier");
|
||||
pthread_create(&pthread_datum_gateway_fallback_notifier, NULL, datum_gateway_fallback_notifier, NULL);
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Template fetcher thread ready.");
|
||||
|
||||
char p1[72];
|
||||
p1[0] = 0;
|
||||
|
||||
sprintf(userpass, "%s:%s", datum_config.bitcoind_rpcuser, datum_config.bitcoind_rpcpassword);
|
||||
|
||||
while(1) {
|
||||
i++;
|
||||
|
||||
// fetch latest template
|
||||
sprintf(gbt_req, "{\"method\":\"getblocktemplate\",\"params\":[{\"rules\":[\"segwit\"]}],\"id\":%"PRIu64"}",(uint64_t)((uint64_t)time(NULL)<<(uint64_t)8)|(uint64_t)(i&255));
|
||||
gbt = json_rpc_call(tcurl, datum_config.bitcoind_rpcurl, userpass, gbt_req);
|
||||
|
||||
if (!gbt) {
|
||||
DLOG_ERROR("Could not fetch new template!");
|
||||
sleep(1);
|
||||
continue;
|
||||
} else {
|
||||
res_val = json_object_get(gbt, "result");
|
||||
if (!res_val) {
|
||||
DLOG_ERROR("ERROR: Could not decode GBT result!");
|
||||
} else {
|
||||
DLOG_DEBUG("DEBUG: calling datum_gbt_parser (new=%d)", was_notified?1:0);
|
||||
t = datum_gbt_parser(res_val);
|
||||
|
||||
if (t) {
|
||||
DLOG_DEBUG("height: %d / value: %"PRIu64,t->height, t->coinbasevalue);
|
||||
DLOG_DEBUG("--- prevhash: %s", t->previousblockhash);
|
||||
DLOG_DEBUG("--- txn_count: %u / sigops: %u / weight: %u / size: %u", t->txn_count, t->txn_total_sigops, t->txn_total_weight, t->txn_total_size);
|
||||
|
||||
// If the previous block hash changed, we should push clean work
|
||||
if (strcmp(t->previousblockhash, p1)) {
|
||||
last_block_change = current_time_millis();
|
||||
update_stratum_job(t,true,JOB_STATE_EMPTY_PLUS);
|
||||
strcpy(p1, t->previousblockhash);
|
||||
was_notified = false;
|
||||
DLOG_INFO("NEW NETWORK BLOCK: %s (%d)",t->previousblockhash,t->height);
|
||||
|
||||
// sleep for a milisecond
|
||||
// this will let other threads churn for a moment. we wont get all the empty jobs blasted out in a milisecond anyway
|
||||
usleep(1000);
|
||||
|
||||
// wait for the empties to complete
|
||||
DLOG_DEBUG("Waiting on empty work send completion...");
|
||||
for(j=0;j<4000;j++) {
|
||||
if (stratum_latest_empty_check_ready_for_full()) break;
|
||||
usleep(1001);
|
||||
}
|
||||
DLOG_DEBUG("Empty sends done!");
|
||||
|
||||
// use this template to setup for a coinbaser wait job while the empty + full w/blank jobs are blasted
|
||||
// then this job will get blasted when its ready.
|
||||
i = datum_stratum_v1_global_subscriber_count();
|
||||
DLOG_INFO("Updating priority stratum job for block %d: %.8f BTC, %d txns, %d bytes (Sent to %d stratum client%s)", t->height, (double)t->coinbasevalue / (double)100000000.0, t->txn_count, t->txn_total_size, i, (i!=1)?"s":"");
|
||||
update_stratum_job(t,false,JOB_STATE_FULL_PRIORITY_WAIT_COINBASER);
|
||||
} else {
|
||||
if (was_notified) {
|
||||
// we got a notification of a new block, but there doesn't seem to actually be a new block.
|
||||
// we should quickly check again instead of actually updating the stratum job
|
||||
|
||||
if ((new_notify_blockhash[0] > 0) && (!strcmp(t->previousblockhash,(char *)new_notify_blockhash))) {
|
||||
// we got notified for work we already knew about
|
||||
if (new_notify_height <= 0) {
|
||||
was_notified = false;
|
||||
wnc = 0;
|
||||
} else {
|
||||
if (new_notify_height == t->height) {
|
||||
was_notified = false;
|
||||
wnc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!was_notified) {
|
||||
DLOG_DEBUG("Multi notified for block we knew details about. (%s)", new_notify_blockhash);
|
||||
} else {
|
||||
DLOG_DEBUG("Notified, however new = %s, t->previousblockhash = %s, t->height = %d, new_notify_height = %d", new_notify_blockhash, t->previousblockhash, t->height, new_notify_height);
|
||||
|
||||
// Sometimes we call GBT before we get the signal from a blocknotify. It's a bit of a race condition.
|
||||
// Instead of freaking out, we'll just ignore things when we get a signal that results in the same block if it was
|
||||
// within 2.5s of a previous block change.
|
||||
// absolute worst case scenario here is that there's a reverse race condition of some kind where we get our notify early and GBT is still
|
||||
// returning the old block data... then we'd be one work change delay behind things.
|
||||
// that shouldn't be possible, though, if the notify comes from the same bitcoind that we're getting our templates from
|
||||
if ((current_time_millis()-2500) < last_block_change) {
|
||||
DLOG_DEBUG("This is probably a duplicate signal, since we just changed blocks less than 2.5s ago");
|
||||
was_notified = false;
|
||||
}
|
||||
|
||||
if (((t->height < 800000) || (t->height > 2980000)) && (new_notify_blockhash[0] == 'T')) { // some hardcoded guardrails that should last for quite some time for testnet3 and testnet4
|
||||
DLOG_DEBUG("DEBUG: TESTNET FAST FORWARD HACK!!!");
|
||||
|
||||
// set diff 1
|
||||
strcpy(t->bits, "1d00ffff");
|
||||
for(j=0;j<4;j++) {
|
||||
t->bits_bin[3-j] = hex2bin_uchar(&t->bits[j<<1]);
|
||||
}
|
||||
t->bits_uint = upk_u32le(t->bits_bin, 0);
|
||||
nbits_to_target(t->bits_uint, t->block_target);
|
||||
// ff 20 min
|
||||
if (new_notify_height > t->curtime) {
|
||||
t->curtime = new_notify_height;
|
||||
new_notify_height = -1;
|
||||
} else {
|
||||
t->curtime += 1200;
|
||||
}
|
||||
|
||||
DLOG_DEBUG("t->curtime = %d",t->curtime);
|
||||
|
||||
update_stratum_job(t,true,JOB_STATE_FULL_PRIORITY_WAIT_COINBASER);
|
||||
new_notify_blockhash[0] = 0;
|
||||
was_notified = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
i = datum_stratum_v1_global_subscriber_count();
|
||||
DLOG_INFO("Updating standard stratum job for block %d: %.8f BTC, %d txns, %d bytes (Sent to %d stratum client%s)", t->height, (double)t->coinbasevalue / (double)100000000.0, t->txn_count, t->txn_total_size, i, (i!=1)?"s":"");
|
||||
update_stratum_job(t,false,JOB_STATE_FULL_NORMAL_WAIT_COINBASER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
json_decref(gbt);
|
||||
}
|
||||
gbt = NULL;
|
||||
|
||||
if ((!was_notified) || (new_notify)) {
|
||||
for(i=0;i<(((uint64_t)datum_config.bitcoind_work_update_seconds*(uint64_t)1000000)/(uint64_t)2500);i++) {
|
||||
usleep(2500);
|
||||
if (new_notify) {
|
||||
new_notify = 0;
|
||||
was_notified = 1;
|
||||
wnc = 0;
|
||||
DLOG_INFO("NEW NETWORK BLOCK NOTIFICATION RECEIVED");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
usleep(250000);
|
||||
wnc++;
|
||||
if (wnc > 16) { // 4 seconds
|
||||
// something is weird.
|
||||
DLOG_WARN("We received a new block notification, however after 16 attempts we did not see a new block.");
|
||||
was_notified = false;
|
||||
wnc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// this thread is never intended to exit unless the application dies
|
||||
|
||||
// TODO: Clean things up
|
||||
}
|
194
src/datum_blocktemplates.h
Normal file
194
src/datum_blocktemplates.h
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_BLOCKTEMPLATE_H_
|
||||
#define _DATUM_BLOCKTEMPLATE_H_
|
||||
|
||||
#ifndef uint64_t
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#ifndef json_t
|
||||
#include <jansson.h>
|
||||
#endif
|
||||
|
||||
#ifndef bool
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "datum_gateway.h"
|
||||
|
||||
#define MAX_TEMPLATES_IN_MEMORY 32 // 32*30 seconds = 16 minutes of work remembered
|
||||
|
||||
// consensus rules:
|
||||
// --- max sigops = 80000
|
||||
// --- max size = 4000000
|
||||
// --- max weight = 4000000
|
||||
|
||||
#define MAX_BLOCK_SIZE_BYTES 4000000
|
||||
|
||||
// Assumption notes
|
||||
|
||||
// max possible transactions = 16394-ish .. close enough to say 16384, since we're just not going to be idiots
|
||||
// max non-segwit data = 1000000
|
||||
|
||||
// Maximum possible inputs spent in a block = ~24400
|
||||
// 80 byte block header
|
||||
// 1 byte txcount
|
||||
// coinbase txn ~150 bytes
|
||||
// --- one giant transaction
|
||||
// 4 bytes version
|
||||
// 3 byte input count (up to 65535)
|
||||
// INPUT: 36 byte outpoint, 1 byte scriptsiglen (0), no scriptsig (segwit), 4 byte sequence
|
||||
//
|
||||
// 1 byte output count
|
||||
// OUTPUT: 8 byte value, 1 byte scriptlen, 22 bytes script
|
||||
// 4 bytes lock time
|
||||
// txn wo/inputs = 4+3+1+8+1+22+4 = 43 bytes
|
||||
// block with coinbase + txn wo/inputs = 80+1+150+43 = 274 bytes
|
||||
// remaining bytes (1000000-274) / input size (41) = ~24383
|
||||
|
||||
// Maximum possible transactions in a block = ~16400
|
||||
// 80 byte block header
|
||||
// 1 byte txcount
|
||||
// coinbase txn ~150 bytes
|
||||
// --- one giant transaction
|
||||
// 4 bytes version
|
||||
// 1 byte input count (1)
|
||||
// INPUT: 36 byte outpoint, 1 byte scriptsiglen (0), no scriptsig (segwit/OP_TRUE), 4 byte sequence
|
||||
// 1 byte output count
|
||||
// OUTPUT: 8 byte value, 1 byte scriptlen, OP_TRUE (1 byte)
|
||||
// 4 bytes lock time
|
||||
// txn size = 4+1+36+1+4+1+8+1+1+4 = 61 bytes
|
||||
// remaining bytes (1000000-80-1-150) / 61 = ~16390
|
||||
|
||||
// maximum possible UTXOs in a block = ~100000
|
||||
// 80 byte block header
|
||||
// 1 byte txcount
|
||||
// coinbase txn ~150 bytes
|
||||
// --- one giant transaction
|
||||
// 4 bytes version
|
||||
// 1 byte input count (1)
|
||||
// INPUT: 36 byte outpoint, 1 byte scriptsiglen (0), no scriptsig (segwit/OP_TRUE), 4 byte sequence
|
||||
// 3 byte output count
|
||||
// OUTPUT: 8 byte value, 1 byte scriptlen, OP_TRUE (1 byte)
|
||||
// 4 bytes lock time
|
||||
// txn size wo/outputs = 4+1+36+1+4+2+4 = 52 bytes
|
||||
// remaining bytes (1000000-80-1-150-52) / 10 = ~99972
|
||||
|
||||
// txn struct = ~264 bytes
|
||||
|
||||
typedef struct T_DATUM_TEMPLATE_TXN {
|
||||
// *1-based* index of this transaction in the original GBT call
|
||||
// max txns in a block is about 16,400 (see notes above)
|
||||
uint16_t index_raw;
|
||||
|
||||
// transaction ID in ASCII hex + calc'd NBO, as provided by GBT
|
||||
char txid_hex[72]; // big endian hex
|
||||
uint8_t txid_bin[32]; // little endian binary
|
||||
|
||||
// "hash" (segwit) ID in ASCII hex + calc'd NBO, as provided by GBT
|
||||
char hash_hex[72]; // big endian hex
|
||||
uint8_t hash_bin[32]; // little endian binary
|
||||
|
||||
// size of the transaction in bytes
|
||||
uint32_t size;
|
||||
|
||||
// "weight" of the transaction
|
||||
// vBytes = weight>>2
|
||||
uint32_t weight;
|
||||
|
||||
// transaction fee paid, in sats
|
||||
uint64_t fee_sats;
|
||||
|
||||
// signature operations
|
||||
uint32_t sigops;
|
||||
|
||||
// binary of the raw transaction data, as provided by GBT
|
||||
// --- this should point to data allocated as a chunk in the base template
|
||||
uint8_t *txn_data_binary;
|
||||
char *txn_data_hex;
|
||||
|
||||
// Info on dependancies returned by GBT
|
||||
// Who do I depend on?
|
||||
// if depends_on_count > 0, then this txn relies on some other txn in the GBT
|
||||
// it's possible those txns have other txns they depend on as well
|
||||
//uint16_t depends_on_count;
|
||||
//uint16_t *depends_on_list;
|
||||
} T_DATUM_TEMPLATE_TXN;
|
||||
|
||||
typedef struct {
|
||||
uint16_t local_index; // tie to stratum work
|
||||
|
||||
uint64_t coinbasevalue; //
|
||||
uint64_t mintime; //
|
||||
uint64_t curtime; //
|
||||
uint64_t sizelimit; //
|
||||
uint64_t weightlimit; //
|
||||
uint32_t height; //
|
||||
uint32_t version; //
|
||||
uint32_t sigoplimit; //
|
||||
|
||||
char bits[9]; //
|
||||
char dummy[7]; // unused, possibly for alignment
|
||||
uint8_t bits_bin[4]; //
|
||||
uint32_t bits_uint; //
|
||||
char previousblockhash[72]; //
|
||||
uint8_t previousblockhash_bin[32]; //
|
||||
char default_witness_commitment[96]; //
|
||||
uint8_t default_witness_commitment_bin[48]; //
|
||||
|
||||
char block_target_hex[72]; //
|
||||
uint8_t block_target[32]; // calculated from bits
|
||||
|
||||
uint32_t txn_count;
|
||||
uint32_t txn_total_weight;
|
||||
uint32_t txn_total_size;
|
||||
uint32_t txn_total_sigops;
|
||||
|
||||
T_DATUM_TEMPLATE_TXN *txns;
|
||||
uint32_t txn_data_offset;
|
||||
|
||||
// Pointer to allocated data for this particular template copy
|
||||
void *local_data;
|
||||
uint32_t local_data_size;
|
||||
} T_DATUM_TEMPLATE_DATA;
|
||||
|
||||
int datum_template_init(void);
|
||||
T_DATUM_TEMPLATE_DATA *datum_gbt_parser(json_t *gbt);
|
||||
void *datum_gateway_template_thread(void *args);
|
||||
void datum_blocktemplates_notifynew(const char *prevhash, int height);
|
||||
|
||||
#endif
|
879
src/datum_coinbaser.c
Normal file
879
src/datum_coinbaser.c
Normal file
@ -0,0 +1,879 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// TODO: Empty work speedup work and an actual empty template will cause duplicate work.
|
||||
// It's kind of unlikely that a sane miner would be purposefully providing empty templates, but
|
||||
// this is a low priority bug to address nonetheless.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <curl/curl.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "datum_conf.h"
|
||||
#include "datum_utils.h"
|
||||
#include "datum_stratum.h"
|
||||
#include "datum_jsonrpc.h"
|
||||
#include "datum_protocol.h"
|
||||
#include "datum_coinbaser.h"
|
||||
|
||||
CURL *coinbaser_curl = NULL;
|
||||
|
||||
const char *cbstart_hex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff"; // 82 len hex, 41 bytes
|
||||
|
||||
#define MAX_COINBASE_TAG_SPACE 86 // leaves space for BIP34 height, extranonces, datum prime tag, etc.
|
||||
|
||||
int generate_coinbase_input(int height, char *cb, int *target_pot_index) {
|
||||
int cb_input_sz = 0;
|
||||
int tag_len[2] = { 0, 0 };
|
||||
int k, m, i;
|
||||
int excess;
|
||||
bool datum_active = false;
|
||||
|
||||
// let's figure out our coinbase tags w/BIP34 height
|
||||
i = append_UNum_hex(height, &cb[0]);
|
||||
cb_input_sz += i>>1;
|
||||
|
||||
datum_active = datum_protocol_is_active();
|
||||
|
||||
// Handle coinbase tagging
|
||||
// The first push after the height should be:
|
||||
// PUSHBYTES X, Primary tag, 0x0F, Secondary tag, 0x0F, Tertiary tag, 0x00
|
||||
// We should then push a unique entropy tag (push + 2 bytes = 3 bytes)
|
||||
if (!datum_active) {
|
||||
tag_len[0] = strlen(datum_config.mining_coinbase_tag_primary);
|
||||
} else {
|
||||
tag_len[0] = strlen(datum_config.override_mining_coinbase_tag_primary);
|
||||
}
|
||||
tag_len[1] = strlen(datum_config.mining_coinbase_tag_secondary);
|
||||
k = tag_len[0] + tag_len[1] + 2;
|
||||
if (!tag_len[1]) {
|
||||
k--;
|
||||
if (!tag_len[0]) {
|
||||
k--;
|
||||
}
|
||||
}
|
||||
|
||||
if (k > MAX_COINBASE_TAG_SPACE) {
|
||||
// something still needs truncating
|
||||
excess = k - MAX_COINBASE_TAG_SPACE;
|
||||
if (tag_len[1] > excess) {
|
||||
// truncating tag1 is enough to cover us
|
||||
tag_len[1] -= excess;
|
||||
k = MAX_COINBASE_TAG_SPACE;
|
||||
} else {
|
||||
// not enough, so need to remove this tag entirely
|
||||
if (tag_len[1]) {
|
||||
tag_len[1] = 0;
|
||||
k-=tag_len[1]+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (k > MAX_COINBASE_TAG_SPACE) {
|
||||
// one tag should never exceed 64 bytes, so we're going to panic here.
|
||||
DLOG_FATAL("Could not fit coinbase primary tag alone somehow. This is probably a bug. Panicking. :(");
|
||||
panic_from_thread(__LINE__);
|
||||
sleep(1000000);
|
||||
}
|
||||
|
||||
if (k > 0) {
|
||||
// ok, we have one or more coinbase tags with a total len of k
|
||||
if (k <= 75) {
|
||||
// OP_PUSHBYTES (1 byte, 1 to 75)
|
||||
uchar_to_hex(&cb[i], (unsigned char)k); i+=2; cb_input_sz++;
|
||||
} else {
|
||||
// OP_PUSHBYTES (2 byte, 76 to 94)
|
||||
uchar_to_hex(&cb[i], 0x4C); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], (unsigned char)k); i+=2; cb_input_sz++;
|
||||
}
|
||||
|
||||
if (tag_len[0]) {
|
||||
if (datum_active) {
|
||||
for(m=0;m<tag_len[0];m++) {
|
||||
uchar_to_hex(&cb[i], (unsigned char)datum_config.override_mining_coinbase_tag_primary[m]); i+=2; cb_input_sz++;
|
||||
}
|
||||
} else {
|
||||
for(m=0;m<tag_len[0];m++) {
|
||||
uchar_to_hex(&cb[i], (unsigned char)datum_config.mining_coinbase_tag_primary[m]); i+=2; cb_input_sz++;
|
||||
}
|
||||
}
|
||||
if (!tag_len[1]) {
|
||||
uchar_to_hex(&cb[i], 0x00); i+=2; cb_input_sz++;
|
||||
} else {
|
||||
uchar_to_hex(&cb[i], 0x0F); i+=2; cb_input_sz++;
|
||||
}
|
||||
} else {
|
||||
// we wouldn't be here if there wasn't at least one other
|
||||
if (tag_len[1]) {
|
||||
uchar_to_hex(&cb[i], 0x0F); i+=2; cb_input_sz++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag_len[1]) {
|
||||
for(m=0;m<tag_len[1];m++) {
|
||||
uchar_to_hex(&cb[i], (unsigned char)datum_config.mining_coinbase_tag_secondary[m]); i+=2; cb_input_sz++;
|
||||
}
|
||||
uchar_to_hex(&cb[i], 0x00); i+=2; cb_input_sz++;
|
||||
}
|
||||
} else {
|
||||
// we'll push a null char to be consistent, and to not parse the UID as if it were a pool name
|
||||
uchar_to_hex(&cb[i], 0x01); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], 0x00); i+=2; cb_input_sz++;
|
||||
}
|
||||
|
||||
// append the coinbase unique ID tag
|
||||
if ((datum_config.prime_id == 0) && (!datum_active)) {
|
||||
uchar_to_hex(&cb[i], 0x03); i+=2; cb_input_sz++;
|
||||
if (target_pot_index != NULL) *target_pot_index = cb_input_sz;
|
||||
uchar_to_hex(&cb[i], 0xFF); i+=2; cb_input_sz++; // placehodler for PoT target
|
||||
uchar_to_hex(&cb[i], (datum_config.coinbase_unique_id&0xFF)); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], ((datum_config.coinbase_unique_id>>8)&0xFF)); i+=2; cb_input_sz++;
|
||||
} else {
|
||||
uchar_to_hex(&cb[i], 0x07); i+=2; cb_input_sz++;
|
||||
if (target_pot_index != NULL) *target_pot_index = cb_input_sz;
|
||||
uchar_to_hex(&cb[i], 0xFF); i+=2; cb_input_sz++; // placeholder for PoT target
|
||||
uchar_to_hex(&cb[i], (datum_config.coinbase_unique_id&0xFF)); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], ((datum_config.coinbase_unique_id>>8)&0xFF)); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], (datum_config.prime_id&0xFF)); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], ((datum_config.prime_id>>8)&0xFF)); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], ((datum_config.prime_id>>16)&0xFF)); i+=2; cb_input_sz++;
|
||||
uchar_to_hex(&cb[i], ((datum_config.prime_id>>24)&0xFF)); i+=2; cb_input_sz++;
|
||||
}
|
||||
|
||||
return cb_input_sz;
|
||||
}
|
||||
|
||||
void generate_coinbase_txns_for_stratum_job_subtypebysize(T_DATUM_STRATUM_JOB *s, int coinbase_index, int remaining_size, bool space_for_en_in_coinbase, int *cb1idx, int *cb2idx, bool special_coinb1) {
|
||||
// This function finishes off the stratum coinb1+coinb2 using the available outputs in the job and other flags specified.
|
||||
// it does not attempt to maximize coinb1's size to any specific size
|
||||
|
||||
int i, j, k, m, i2 = 0, c1cnt = 0;
|
||||
uint64_t mval = 0;
|
||||
bool c1full = false;
|
||||
bool en_done = false;
|
||||
// chicken and egg problem. we need to know the output count before we can close off coinb1 if !space_for_en_in_coinbase
|
||||
// either way, we want to start out coinb2 with outputs
|
||||
i = remaining_size;
|
||||
j = remaining_size;
|
||||
if (special_coinb1) {
|
||||
i2 = (300 - cb1idx[coinbase_index])>>1;
|
||||
if (i2 < 0) i2 = 0;
|
||||
space_for_en_in_coinbase = false;
|
||||
}
|
||||
m = 0;
|
||||
mval = 0;
|
||||
// technically an output script could be > 0x4B, meaning an extra byte would be eaten here... but that's not currently the standard
|
||||
// this needs to match the loop lower in this function, as the count will get thrown off if it does not.
|
||||
|
||||
// TODO: Enforce max sigops! Note: This is not currently enforced in eloipool, either, so punting for now and will monitor network stats to determine priority.
|
||||
for(k=0;k<s->available_coinbase_outputs_count;k++) {
|
||||
if (((s->available_coinbase_outputs[k].output_script_len+9) <= i) && ((mval + s->available_coinbase_outputs[k].value_sats) <= s->coinbase_value)) {
|
||||
if ((special_coinb1) && (!c1full) && ((s->available_coinbase_outputs[k].output_script_len+9) <= i2)) {
|
||||
i2 -= (s->available_coinbase_outputs[k].output_script_len+9);
|
||||
c1cnt++;
|
||||
} else {
|
||||
c1full = true;
|
||||
}
|
||||
|
||||
i -= (s->available_coinbase_outputs[k].output_script_len+9);
|
||||
m++;
|
||||
mval += s->available_coinbase_outputs[k].value_sats;
|
||||
if (i < 30) break;
|
||||
if (mval >= s->coinbase_value) break;
|
||||
}
|
||||
}
|
||||
|
||||
// "m" outputs fit
|
||||
if (space_for_en_in_coinbase) {
|
||||
// we'll start the empty coinb2 with the "sequence"
|
||||
m+=2; // pool addr + witness
|
||||
pk_u64le(s->coinbase[coinbase_index].coinb2, cb2idx[coinbase_index], 0x6666666666666666ULL); // "ffffffff"
|
||||
cb2idx[coinbase_index] = 8;
|
||||
cb2idx[coinbase_index] += append_bitcoin_varint_hex(m, &s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]]); // us, witness, and "m" outputs
|
||||
} else {
|
||||
m+=3;
|
||||
cb1idx[coinbase_index] += append_bitcoin_varint_hex(m, &s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]]); // extranonce, us, witness commit, and "m" outputs
|
||||
|
||||
if (!special_coinb1) {
|
||||
// append extranonce op_return
|
||||
cb1idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]], "0000000000000000106a0e%04" PRIx16, s->enprefix);
|
||||
en_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// append "m" payouts. find them the same way we did before
|
||||
mval = 0;
|
||||
for(k=0;k<s->available_coinbase_outputs_count;k++) {
|
||||
if (((s->available_coinbase_outputs[k].output_script_len+9) <= j) && ((mval + s->available_coinbase_outputs[k].value_sats) <= s->coinbase_value)) {
|
||||
j -= (s->available_coinbase_outputs[k].output_script_len+9);
|
||||
m--;
|
||||
|
||||
mval += s->available_coinbase_outputs[k].value_sats;
|
||||
|
||||
if ((special_coinb1) && (k < c1cnt)) {
|
||||
// put in coinb1
|
||||
cb1idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]], "%016llx", (unsigned long long)__builtin_bswap64(s->available_coinbase_outputs[k].value_sats)); // TODO: Profile a faster way to do this
|
||||
cb1idx[coinbase_index] += append_bitcoin_varint_hex(s->available_coinbase_outputs[k].output_script_len, &s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]]); // Append script length
|
||||
for(i=0;i<s->available_coinbase_outputs[k].output_script_len;i++) {
|
||||
uchar_to_hex(&s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]], s->available_coinbase_outputs[k].output_script[i]);
|
||||
cb1idx[coinbase_index]+=2;
|
||||
}
|
||||
} else {
|
||||
if ((special_coinb1) && (k == c1cnt)) {
|
||||
// append extranonce op_return
|
||||
cb1idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]], "0000000000000000106a0e%04" PRIx16, s->enprefix);
|
||||
en_done = true;
|
||||
}
|
||||
|
||||
// put in coinb2
|
||||
cb2idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], "%016llx", (unsigned long long)__builtin_bswap64(s->available_coinbase_outputs[k].value_sats)); // TODO: Profile a faster way to do this
|
||||
cb2idx[coinbase_index] += append_bitcoin_varint_hex(s->available_coinbase_outputs[k].output_script_len, &s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]]); // Append script length
|
||||
for(i=0;i<s->available_coinbase_outputs[k].output_script_len;i++) {
|
||||
uchar_to_hex(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], s->available_coinbase_outputs[k].output_script[i]);
|
||||
cb2idx[coinbase_index]+=2;
|
||||
}
|
||||
}
|
||||
if (!m) break;
|
||||
if (j < 30) break;
|
||||
if (mval >= s->coinbase_value) break;
|
||||
}
|
||||
}
|
||||
|
||||
// this should never happen, but...
|
||||
if (mval > s->coinbase_value) {
|
||||
DLOG_ERROR("Attempting to pay more than we have available in the generation txn! --- %"PRIu64" sats available, %"PRIu64" sats to miners", s->coinbase_value, mval);
|
||||
}
|
||||
|
||||
if ((!space_for_en_in_coinbase) && (!en_done)) {
|
||||
cb1idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb1[cb1idx[coinbase_index]], "0000000000000000106a0e%04" PRIx16, s->enprefix);
|
||||
en_done = true;
|
||||
}
|
||||
|
||||
if (s->coinbase_value > mval) {
|
||||
// append our payout output value and script, since there are leftover funds
|
||||
cb2idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], "%016llx", (unsigned long long)__builtin_bswap64(s->coinbase_value - mval)); // TODO: Profile a faster way to do this
|
||||
cb2idx[coinbase_index] += append_bitcoin_varint_hex(s->pool_addr_script_len, &s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]]); // Append script length
|
||||
for(i=0;i<s->pool_addr_script_len;i++) {
|
||||
uchar_to_hex(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], s->pool_addr_script[i]);
|
||||
cb2idx[coinbase_index]+=2;
|
||||
}
|
||||
} else {
|
||||
// We paid every sat of the coinbase to miners... and saved an output.
|
||||
// HOWEVER....... we already locked in a number of outputs that presumes we would have a pool output
|
||||
// so tack on a dead output, sadly.
|
||||
// TODO: Make code smarter above, don't waste an output if we don't need it.
|
||||
// This is quite unlikely in practice, but, just in case let's make this a prunable OP_RETURN
|
||||
cb2idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], "0000000000000000036a0100"); // TODO: Is a naked OP_RETURN without any bytes after safe? Above TODO is probably better than investigating.
|
||||
}
|
||||
|
||||
// witness commit output costs 46 bytes
|
||||
// append the default_witness_commitment
|
||||
cb2idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], "0000000000000000%2.2x%s", (unsigned char)strlen(s->block_template->default_witness_commitment)>>1, s->block_template->default_witness_commitment);
|
||||
// lock time
|
||||
cb2idx[coinbase_index] += sprintf(&s->coinbase[coinbase_index].coinb2[cb2idx[coinbase_index]], "00000000");
|
||||
}
|
||||
|
||||
int datum_stratum_coinbase_fit_to_template(int max_sz, int fixed_bytes, T_DATUM_STRATUM_JOB *s) {
|
||||
int j,i,msz1;
|
||||
|
||||
i = fixed_bytes + max_sz;
|
||||
msz1 = max_sz+fixed_bytes;
|
||||
|
||||
if ((i+s->block_template->txn_total_size+85+36) > s->block_template->sizelimit) {
|
||||
j = s->block_template->sizelimit - (s->block_template->txn_total_size+85+36) - fixed_bytes;
|
||||
if (j < 0) return 0;
|
||||
msz1 = j;
|
||||
}
|
||||
|
||||
if (((i<<2)+s->block_template->txn_total_weight+340+36) > s->block_template->weightlimit) {
|
||||
j = ((s->block_template->weightlimit - (s->block_template->txn_total_weight+340+36))>>2) - fixed_bytes;
|
||||
if (j < 0) return 0;
|
||||
msz1 = j;
|
||||
}
|
||||
|
||||
if (msz1 < 0) {
|
||||
msz1 = 0;
|
||||
}
|
||||
|
||||
if (msz1 < (max_sz - fixed_bytes)) {
|
||||
return msz1;
|
||||
} else {
|
||||
return max_sz - fixed_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
void generate_base_coinbase_txns_for_stratum_job(T_DATUM_STRATUM_JOB *s, bool new_block) {
|
||||
char cb[512];
|
||||
int cb_input_sz = 0;
|
||||
bool space_for_en_in_coinbase = false;
|
||||
int i, j, k;
|
||||
int cb1idx[1] = { 0 };
|
||||
int cb2idx[1] = { 0 };
|
||||
int target_pot_index;
|
||||
|
||||
if (datum_protocol_is_active()) {
|
||||
// DATUM
|
||||
s->pool_addr_script_len = datum_config.override_mining_pool_scriptsig_len;
|
||||
memcpy(&s->pool_addr_script[0], datum_config.override_mining_pool_scriptsig, datum_config.override_mining_pool_scriptsig_len);
|
||||
s->is_datum_job = true;
|
||||
} else {
|
||||
// No pool
|
||||
s->pool_addr_script_len = addr_2_output_script(datum_config.mining_pool_address, &s->pool_addr_script[0], 64);
|
||||
s->is_datum_job = false;
|
||||
}
|
||||
if (!s->pool_addr_script_len) {
|
||||
DLOG_FATAL("Could not generate output script for pool addr! Perhaps invalid? This is bad.");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
// copy beginning of the generation txn to the appropriate outputs
|
||||
j = strlen(cbstart_hex);
|
||||
memcpy(&s->coinbase[0].coinb1[0], cbstart_hex, j);
|
||||
cb1idx[0] = j;
|
||||
|
||||
cb_input_sz = generate_coinbase_input(s->height, &cb[0], &target_pot_index);
|
||||
i = cb_input_sz << 1;
|
||||
|
||||
// null terminate... probably not needed
|
||||
cb[i] = 0;
|
||||
|
||||
if (cb_input_sz <= 85) {
|
||||
space_for_en_in_coinbase = true;
|
||||
}
|
||||
|
||||
if (space_for_en_in_coinbase) {
|
||||
cb1idx[0] += append_bitcoin_varint_hex(cb_input_sz+15, &s->coinbase[0].coinb1[cb1idx[0]]); // 15 bytes for extranonce+uid push + data
|
||||
} else {
|
||||
cb1idx[0] += append_bitcoin_varint_hex(cb_input_sz, &s->coinbase[0].coinb1[cb1idx[0]]);
|
||||
}
|
||||
memcpy(&s->coinbase[0].coinb1[cb1idx[0]], &cb[0], cb_input_sz*2);
|
||||
s->target_pot_index = target_pot_index + (cb1idx[0]>>1); // adjust for placement in the txn. always safe for all types, since the varint will always be 1 byte.
|
||||
cb1idx[0] += cb_input_sz*2;
|
||||
|
||||
if (space_for_en_in_coinbase) {
|
||||
// if we are doing extranonce in the coinbase, then this is ALMOST the end of coinbase1
|
||||
// we need a PUSH 14 and our enprefix in the coinbase
|
||||
uchar_to_hex(&s->coinbase[0].coinb1[cb1idx[0]], 0x0E);
|
||||
cb1idx[0]+=2;
|
||||
// TODO: Profile a faster way to do this
|
||||
cb1idx[0] += sprintf(&s->coinbase[0].coinb1[cb1idx[0]], "%04" PRIx16, s->enprefix);
|
||||
} else {
|
||||
// if we are not, then we need to append the "sequence"
|
||||
pk_u64le(s->coinbase[0].coinb1, cb1idx[0], 0x6666666666666666ULL); // "ffffffff"
|
||||
cb1idx[0] += 8;
|
||||
}
|
||||
|
||||
s->coinbase[0].coinb1[cb1idx[0]] = 0;
|
||||
|
||||
/////////////////////////////
|
||||
// 0 / EMPTY
|
||||
// empty should be easy. lets start there
|
||||
if (space_for_en_in_coinbase) {
|
||||
// we'll start the empty coinb2 with the "sequence"
|
||||
pk_u64le(s->coinbase[0].coinb2, 0, 0x6666666666666666ULL); // "ffffffff"
|
||||
cb2idx[0] = 8;
|
||||
cb2idx[0] += append_bitcoin_varint_hex(2, &s->coinbase[0].coinb2[cb2idx[0]]); // us and witness commit
|
||||
|
||||
if (new_block) {
|
||||
// copy the beginning to the subsidy-only
|
||||
memcpy(&s->subsidy_only_coinbase.coinb1[0], &s->coinbase[0].coinb1[0], cb1idx[0]);
|
||||
pk_u64le(s->subsidy_only_coinbase.coinb2, 0, 0x6666666666666666ULL); // "ffffffff"
|
||||
append_bitcoin_varint_hex(1, &s->subsidy_only_coinbase.coinb2[8]); // just us!
|
||||
}
|
||||
} else {
|
||||
// we're already at the point in coinb1 where we need an output count, which will be 3
|
||||
if (new_block) {
|
||||
j = cb1idx[0];
|
||||
}
|
||||
cb1idx[0] += append_bitcoin_varint_hex(3, &s->coinbase[0].coinb1[cb1idx[0]]); // extranonce, us, and witness commit
|
||||
|
||||
// append extranonce op_return
|
||||
cb1idx[0] += sprintf(&s->coinbase[0].coinb1[cb1idx[0]], "0000000000000000106a0e%04" PRIx16, s->enprefix);
|
||||
|
||||
if (new_block) {
|
||||
// copy the beginning to the subsidy-only
|
||||
memcpy(&s->subsidy_only_coinbase.coinb1[0], &s->coinbase[0].coinb1[0], cb1idx[0]);
|
||||
k = append_bitcoin_varint_hex(2, &s->subsidy_only_coinbase.coinb1[j]); // extranonce and us
|
||||
s->subsidy_only_coinbase.coinb1[j+k] = s->coinbase[0].coinb1[j+k];
|
||||
}
|
||||
}
|
||||
// finish off "empty" coinbase
|
||||
|
||||
// append our payout output value and script
|
||||
if (new_block) {
|
||||
j = cb2idx[0];
|
||||
}
|
||||
|
||||
cb2idx[0] += sprintf(&s->coinbase[0].coinb2[cb2idx[0]], "%016llx", (unsigned long long)__builtin_bswap64(s->coinbase_value)); // TODO: Profile a faster way to do this
|
||||
cb2idx[0] += append_bitcoin_varint_hex(s->pool_addr_script_len, &s->coinbase[0].coinb2[cb2idx[0]]); // Append script length
|
||||
for(i=0;i<s->pool_addr_script_len;i++) {
|
||||
uchar_to_hex(&s->coinbase[0].coinb2[cb2idx[0]], s->pool_addr_script[i]);
|
||||
cb2idx[0]+=2;
|
||||
}
|
||||
|
||||
if (new_block) {
|
||||
k = cb2idx[0];
|
||||
}
|
||||
|
||||
// witness commit output costs 46 bytes
|
||||
// append the default_witness_commitment
|
||||
cb2idx[0] += sprintf(&s->coinbase[0].coinb2[cb2idx[0]], "0000000000000000%2.2x%s", (unsigned char)strlen(s->block_template->default_witness_commitment)>>1, s->block_template->default_witness_commitment);
|
||||
// lock time
|
||||
cb2idx[0] += sprintf(&s->coinbase[0].coinb2[cb2idx[0]], "00000000");
|
||||
|
||||
if (new_block) {
|
||||
// Append the subsidy-only payout to the subsidy_only_coinbase
|
||||
sprintf(&s->subsidy_only_coinbase.coinb2[j], "%016llx", (unsigned long long)__builtin_bswap64(block_reward(s->height))); // subsidy calc for height
|
||||
memcpy(&s->subsidy_only_coinbase.coinb2[j+16], &s->coinbase[0].coinb2[j+16], k-j-16);
|
||||
sprintf(&s->subsidy_only_coinbase.coinb2[k], "00000000");
|
||||
}
|
||||
|
||||
// End of 0 / Empty
|
||||
//////////////////////////////
|
||||
|
||||
// prep binary versions of the coinbase for speeding up later
|
||||
|
||||
i = strlen(s->coinbase[0].coinb1);
|
||||
s->coinbase[0].coinb1_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->coinbase[0].coinb1_bin[j>>1] = hex2bin_uchar(&s->coinbase[0].coinb1[j]);
|
||||
s->coinbase[0].coinb1_len++;
|
||||
}
|
||||
i = strlen(s->coinbase[0].coinb2);
|
||||
s->coinbase[0].coinb2_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->coinbase[0].coinb2_bin[j>>1] = hex2bin_uchar(&s->coinbase[0].coinb2[j]);
|
||||
s->coinbase[0].coinb2_len++;
|
||||
}
|
||||
|
||||
if (new_block) {
|
||||
i = strlen(s->subsidy_only_coinbase.coinb1);
|
||||
s->subsidy_only_coinbase.coinb1_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->subsidy_only_coinbase.coinb1_bin[j>>1] = hex2bin_uchar(&s->subsidy_only_coinbase.coinb1[j]);
|
||||
s->subsidy_only_coinbase.coinb1_len++;
|
||||
}
|
||||
i = strlen(s->subsidy_only_coinbase.coinb2);
|
||||
s->subsidy_only_coinbase.coinb2_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->subsidy_only_coinbase.coinb2_bin[j>>1] = hex2bin_uchar(&s->subsidy_only_coinbase.coinb2[j]);
|
||||
s->subsidy_only_coinbase.coinb2_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generate_coinbase_txns_for_stratum_job(T_DATUM_STRATUM_JOB *s, bool empty_only) {
|
||||
// Account for available vsize, sigops, size, weight, etc
|
||||
|
||||
// Note:
|
||||
// With a minimum payout of 10 TBC, the largest likely coinbase as of height 840000 is around 16 KB if we paid every miner the minimum to a long address type.
|
||||
// This seems highly unlikely. 16KB is more than sufficient.
|
||||
|
||||
int i, j, k;
|
||||
char cb[300];
|
||||
int target_pot_index;
|
||||
int cb_input_sz = 0;
|
||||
|
||||
bool space_for_en_in_coinbase = false;
|
||||
|
||||
int cb1idx[MAX_COINBASE_TYPES] = { 0,0,0,0,0,0 };
|
||||
int cb2idx[MAX_COINBASE_TYPES] = { 0,0,0,0,0,0 };
|
||||
|
||||
int cb_req_sz[MAX_COINBASE_TYPES] = { 0,0,0,0,0 };
|
||||
|
||||
////////////////
|
||||
|
||||
// Initial mainnet coinbaser
|
||||
if (datum_protocol_is_active()) {
|
||||
// DATUM
|
||||
s->pool_addr_script_len = datum_config.override_mining_pool_scriptsig_len;
|
||||
memcpy(&s->pool_addr_script[0], datum_config.override_mining_pool_scriptsig, datum_config.override_mining_pool_scriptsig_len);
|
||||
s->is_datum_job = true;
|
||||
if (s->available_coinbase_outputs_count == 0) {
|
||||
empty_only = true;
|
||||
}
|
||||
} else {
|
||||
// No pool
|
||||
s->pool_addr_script_len = addr_2_output_script(datum_config.mining_pool_address, &s->pool_addr_script[0], 64);
|
||||
s->is_datum_job = false;
|
||||
empty_only = true;
|
||||
}
|
||||
if (!s->pool_addr_script_len) {
|
||||
DLOG_FATAL("Could not generate output script for pool addr! Perhaps invalid? This is bad.");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
|
||||
// copy beginning of the generation txn to the appropriate outputs
|
||||
j = strlen(cbstart_hex);
|
||||
for(i=0;i<MAX_COINBASE_TYPES;i++) {
|
||||
memcpy(&s->coinbase[i].coinb1[0], cbstart_hex, j);
|
||||
cb1idx[i] = j;
|
||||
}
|
||||
|
||||
cb_input_sz = generate_coinbase_input(s->height, &cb[0], &target_pot_index);
|
||||
s->target_pot_index = target_pot_index;
|
||||
i = cb_input_sz << 1;
|
||||
|
||||
// null terminate... probably not needed
|
||||
cb[i] = 0;
|
||||
|
||||
// do we have space in the coinbase for the extranonce for types that can do it this way?
|
||||
// we need 1 byte for the push, 2 for the enprefix, 4 for en1 and 8 for en2 = 15 bytes
|
||||
// coinbase max is 100
|
||||
if (cb_input_sz <= 85) {
|
||||
space_for_en_in_coinbase = true;
|
||||
}
|
||||
|
||||
// multiple coinbase options
|
||||
// 0 = "empty" --- just pays pool addr, and possibly TIDES data. extranonce in coinbase if fits, or in first output if not.
|
||||
// 1 = "nicehash" --- roughly 500 bytes total... smaller than antminer... has nothing before the extranonce OP_RETURN (or no extranonce OP_RETURN if enough space in the coinbase)
|
||||
// 2 = "antminer" --- roughly 730 bytes max size, using a larger coinb1 and UART sync bits. This also works as a good default.
|
||||
// 3 = "whatsminer" --- max 6500 bytes tested. does not need the extranonce OP_RETURN unless there's no space in the coinbase itself after tags
|
||||
// 4 = "huge" --- max 16kB --- this is probably the most we should reasonably attempt to do in the coinbase... something like 380 to 530 outputs, depending on the type of output
|
||||
// 5 = "antminer2" --- max 2250 bytes --- latest S21s appear to support this
|
||||
|
||||
// only type 2 *needs* the OP_RETURN extranonce, unless the coinbase itself is too long
|
||||
// set the len, and copy over the rest of the coinbase
|
||||
for(i=0;i<MAX_COINBASE_TYPES;i++) {
|
||||
if ((i!=2) && (space_for_en_in_coinbase)) {
|
||||
cb1idx[i] += append_bitcoin_varint_hex(cb_input_sz+15, &s->coinbase[i].coinb1[cb1idx[i]]);
|
||||
} else {
|
||||
cb1idx[i] += append_bitcoin_varint_hex(cb_input_sz, &s->coinbase[i].coinb1[cb1idx[i]]);
|
||||
}
|
||||
memcpy(&s->coinbase[i].coinb1[cb1idx[i]], &cb[0], cb_input_sz*2);
|
||||
// save this and adjust for placement in the txn... this is always safe because the coinbase input is always < 0xFD len
|
||||
// little silly to set this multiple times, but it's fine for consistency.
|
||||
s->target_pot_index = target_pot_index + (cb1idx[i]>>1);
|
||||
cb1idx[i] += cb_input_sz*2;
|
||||
|
||||
if ((i!=2) && (space_for_en_in_coinbase)) {
|
||||
// if we are doing extranonce in the coinbase, then this is ALMOST the end of coinbase1
|
||||
// we need a PUSH 14 and our enprefix in the coinbase
|
||||
uchar_to_hex(&s->coinbase[i].coinb1[cb1idx[i]], 0x0E);
|
||||
cb1idx[i]+=2;
|
||||
// TODO: Profile a faster way to do this
|
||||
cb1idx[i] += sprintf(&s->coinbase[i].coinb1[cb1idx[i]], "%04" PRIx16, s->enprefix);
|
||||
} else {
|
||||
// if we are not, then we need to append the "sequence"
|
||||
pk_u64le(s->coinbase[i].coinb1, cb1idx[i], 0x6666666666666666ULL); // "ffffffff"
|
||||
cb1idx[i] += 8;
|
||||
}
|
||||
|
||||
s->coinbase[i].coinb1[cb1idx[i]] = 0;
|
||||
}
|
||||
|
||||
// extranonce ends up at the end of coinb1
|
||||
// for the antminer hack coinbaser, we want to cram an output or two in coinb1, which is tricky
|
||||
// would be much easier to just always use the OP_RETURN, but that's wasteful when not needed as it wastes 10 bytes (wastes 8 bytes for the value, 2 for the OP_RETURN and the PUSH...)
|
||||
// if extranonce in the coinbase, then we start coinb2 with the "sequence"
|
||||
// if extranonce not in the coinbase, then we already tacked the "sequence" on to coinb1 immediately
|
||||
|
||||
// we need to know the output count for each type so we can figure out what to stuff in each one
|
||||
// this may be a bit wasteful, but needs to be done. only needs to happen once per work update, and only when doing non-empty.
|
||||
|
||||
/////////////////////////////
|
||||
// 0 / EMPTY
|
||||
// empty should be easy. lets start there
|
||||
if (space_for_en_in_coinbase) {
|
||||
// we'll start the empty coinb2 with the "sequence"
|
||||
pk_u64le(s->coinbase[0].coinb2, 0, 0x6666666666666666ULL); // "ffffffff"
|
||||
cb2idx[0] = 8;
|
||||
cb2idx[0] += append_bitcoin_varint_hex(2, &s->coinbase[0].coinb2[cb2idx[0]]); // us and witness commit
|
||||
|
||||
if (empty_only) {
|
||||
// copy the beginning to the subsidy-only
|
||||
memcpy(&s->subsidy_only_coinbase.coinb1[0], &s->coinbase[0].coinb1[0], cb1idx[0]);
|
||||
pk_u64le(s->subsidy_only_coinbase.coinb2, 0, 0x6666666666666666ULL); // "ffffffff"
|
||||
append_bitcoin_varint_hex(1, &s->subsidy_only_coinbase.coinb2[8]); // just us!
|
||||
}
|
||||
} else {
|
||||
// we're already at the point in coinb1 where we need an output count, which will be 3
|
||||
if (empty_only) {
|
||||
j = cb1idx[0];
|
||||
}
|
||||
cb1idx[0] += append_bitcoin_varint_hex(3, &s->coinbase[0].coinb1[cb1idx[0]]); // extranonce, us, and witness commit
|
||||
|
||||
// append extranonce op_return
|
||||
cb1idx[0] += sprintf(&s->coinbase[0].coinb1[cb1idx[0]], "0000000000000000106a0e%04" PRIx16, s->enprefix);
|
||||
|
||||
if (empty_only) {
|
||||
// copy the beginning to the subsidy-only
|
||||
memcpy(&s->subsidy_only_coinbase.coinb1[0], &s->coinbase[0].coinb1[0], cb1idx[0]);
|
||||
k = append_bitcoin_varint_hex(2, &s->subsidy_only_coinbase.coinb1[j]); // extranonce and us
|
||||
s->subsidy_only_coinbase.coinb1[j+k] = s->coinbase[0].coinb1[j+k];
|
||||
}
|
||||
}
|
||||
// finish off "empty" coinbase
|
||||
|
||||
// append our payout output value and script
|
||||
if (empty_only) {
|
||||
j = cb2idx[0];
|
||||
}
|
||||
|
||||
cb2idx[0] += sprintf(&s->coinbase[0].coinb2[cb2idx[0]], "%016llx", (unsigned long long)__builtin_bswap64(s->coinbase_value)); // TODO: Profile a faster way to do this
|
||||
cb2idx[0] += append_bitcoin_varint_hex(s->pool_addr_script_len, &s->coinbase[0].coinb2[cb2idx[0]]); // Append script length
|
||||
for(i=0;i<s->pool_addr_script_len;i++) {
|
||||
uchar_to_hex(&s->coinbase[0].coinb2[cb2idx[0]], s->pool_addr_script[i]);
|
||||
cb2idx[0]+=2;
|
||||
}
|
||||
|
||||
if (empty_only) {
|
||||
k = cb2idx[0];
|
||||
}
|
||||
|
||||
// witness commit output costs 46 bytes
|
||||
// append the default_witness_commitment
|
||||
cb2idx[0] += sprintf(&s->coinbase[0].coinb2[cb2idx[0]], "0000000000000000%2.2x%s", (unsigned char)strlen(s->block_template->default_witness_commitment)>>1, s->block_template->default_witness_commitment);
|
||||
// lock time
|
||||
cb2idx[0] += sprintf(&s->coinbase[0].coinb2[cb2idx[0]], "00000000");
|
||||
|
||||
if (empty_only) {
|
||||
// Append the subsidy-only payout to the subsidy_only_coinbase
|
||||
sprintf(&s->subsidy_only_coinbase.coinb2[j], "%016llx", (unsigned long long)__builtin_bswap64(block_reward(s->height))); // subsidy calc for height
|
||||
memcpy(&s->subsidy_only_coinbase.coinb2[j+16], &s->coinbase[0].coinb2[j+16], k-j-16);
|
||||
sprintf(&s->subsidy_only_coinbase.coinb2[k], "00000000");
|
||||
}
|
||||
|
||||
// End of 0 / Empty
|
||||
//////////////////////////////
|
||||
|
||||
if (empty_only) {
|
||||
// copy empty coinbaser to the others
|
||||
for (i=1;i<MAX_COINBASE_TYPES;i++) {
|
||||
strcpy(s->coinbase[i].coinb1, s->coinbase[0].coinb1);
|
||||
strcpy(s->coinbase[i].coinb2, s->coinbase[0].coinb2);
|
||||
}
|
||||
} else {
|
||||
// ok, let's figure out how much space, if any, we have for miner payout outputs
|
||||
// we first need to figure out how much space we are using for each type after required data, so let's do that
|
||||
|
||||
// witness output = 46 bytes
|
||||
// pool output = pool_addr_script_len + 9
|
||||
// coinbase itself = cb_input_sz
|
||||
// coinbase len = 1
|
||||
// cbstart = 41 bytes
|
||||
// lock time = 4 bytes
|
||||
// "sequence" = 4 bytes
|
||||
// extranonce size = 15 bytes (w/len push needed for either coinbase or OP_RETURN formats)
|
||||
// output count... could technically be up to three bytes for types 3 + 4, most likely 1 byte for 0,1,2.
|
||||
// --- lets give ourselves the wiggle room and say 3 bytes
|
||||
//
|
||||
// total static bytes = 46+9+1+41+4+3+4+15 = 123 bytes
|
||||
// not-static bytes = pool_addr_script_len + cb_input_sz + (space_for_en_in_coinbase?0:10)
|
||||
// --- it costs 10 extra bytes to do the OP_RETURN based extranonce
|
||||
|
||||
if (!space_for_en_in_coinbase) {
|
||||
cb_req_sz[1] = cb_req_sz[2] = cb_req_sz[3] = cb_req_sz[4] = cb_req_sz[5] = 119 + s->pool_addr_script_len + cb_input_sz + 10;
|
||||
} else {
|
||||
cb_req_sz[1] = cb_req_sz[2] = cb_req_sz[3] = cb_req_sz[4] = cb_req_sz[5] = 119 + s->pool_addr_script_len + cb_input_sz;
|
||||
cb_req_sz[2] += 10; // always OP_RETURN extranonce for type 2
|
||||
}
|
||||
|
||||
// TYPE 1 - "Nicehash" friendly, max 500 bytes
|
||||
i = datum_stratum_coinbase_fit_to_template(500, cb_req_sz[1], s);
|
||||
generate_coinbase_txns_for_stratum_job_subtypebysize(s, 1, i, space_for_en_in_coinbase, cb1idx, cb2idx, false);
|
||||
|
||||
// TYPE 3 - "Whatsminer" friendly, max 6500 bytes
|
||||
i = datum_stratum_coinbase_fit_to_template(6500, cb_req_sz[3], s);
|
||||
generate_coinbase_txns_for_stratum_job_subtypebysize(s, 3, i, space_for_en_in_coinbase, cb1idx, cb2idx, false);
|
||||
|
||||
// TYPE 4 - "YUGE", max 16KB
|
||||
i = datum_stratum_coinbase_fit_to_template(16000, cb_req_sz[4], s);
|
||||
generate_coinbase_txns_for_stratum_job_subtypebysize(s, 4, i, space_for_en_in_coinbase, cb1idx, cb2idx, false);
|
||||
|
||||
// TYPE 5 - "Antminer 2", max 2250 bytes
|
||||
i = datum_stratum_coinbase_fit_to_template(2250, cb_req_sz[5], s);
|
||||
generate_coinbase_txns_for_stratum_job_subtypebysize(s, 5, i, space_for_en_in_coinbase, cb1idx, cb2idx, false);
|
||||
|
||||
// TYPE 2 - Older Antminer stock (S19)
|
||||
i = datum_stratum_coinbase_fit_to_template(755, cb_req_sz[2], s);
|
||||
generate_coinbase_txns_for_stratum_job_subtypebysize(s, 2, i, false, cb1idx, cb2idx, true);
|
||||
}
|
||||
|
||||
// prep binary versions of the coinbase for speeding up later
|
||||
for(k=0;k<MAX_COINBASE_TYPES;k++) {
|
||||
i = strlen(s->coinbase[k].coinb1);
|
||||
s->coinbase[k].coinb1_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->coinbase[k].coinb1_bin[j>>1] = hex2bin_uchar(&s->coinbase[k].coinb1[j]);
|
||||
s->coinbase[k].coinb1_len++;
|
||||
}
|
||||
i = strlen(s->coinbase[k].coinb2);
|
||||
s->coinbase[k].coinb2_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->coinbase[k].coinb2_bin[j>>1] = hex2bin_uchar(&s->coinbase[k].coinb2[j]);
|
||||
s->coinbase[k].coinb2_len++;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty_only) {
|
||||
i = strlen(s->subsidy_only_coinbase.coinb1);
|
||||
s->subsidy_only_coinbase.coinb1_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->subsidy_only_coinbase.coinb1_bin[j>>1] = hex2bin_uchar(&s->subsidy_only_coinbase.coinb1[j]);
|
||||
s->subsidy_only_coinbase.coinb1_len++;
|
||||
}
|
||||
i = strlen(s->subsidy_only_coinbase.coinb2);
|
||||
s->subsidy_only_coinbase.coinb2_len = 0;
|
||||
for(j=0;j<i;j+=2) {
|
||||
s->subsidy_only_coinbase.coinb2_bin[j>>1] = hex2bin_uchar(&s->subsidy_only_coinbase.coinb2[j]);
|
||||
s->subsidy_only_coinbase.coinb2_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int datum_coinbaser_v2_parse(T_DATUM_STRATUM_JOB *s, unsigned char *coinbaser, int cblen, bool must_free) {
|
||||
// parse raw outputs from DATUM connection into a useful coinbaser
|
||||
uint64_t outval = 0;
|
||||
uint64_t tally = 0;
|
||||
int cidx = 0;
|
||||
int slen = 0;
|
||||
int cbvalid = 0;
|
||||
int datum_id;
|
||||
|
||||
if (!coinbaser) {
|
||||
DLOG_WARN("Coinbaser is NULL Using default/empty");
|
||||
s->available_coinbase_outputs_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cblen < 9) {
|
||||
// 0 outputs possible
|
||||
DLOG_WARN("Coinbaser lentgh is invalid (too short). Using default/empty");
|
||||
s->available_coinbase_outputs_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Coinbaser v2 size %d", cblen);
|
||||
|
||||
datum_id = coinbaser[cidx]; cidx++;
|
||||
|
||||
while (cidx < cblen) {
|
||||
outval = upk_u64le(coinbaser, cidx); cidx+=8;
|
||||
if ((outval + tally) > s->coinbase_value) {
|
||||
// we can't include this value, since it would put us over our total available!
|
||||
// this shouldn't happen, however...
|
||||
break;
|
||||
}
|
||||
slen = coinbaser[cidx]; cidx++;
|
||||
if ((slen < 2) || (slen > 64)) {
|
||||
// invalid script len?!?
|
||||
break;
|
||||
}
|
||||
|
||||
tally += outval;
|
||||
memcpy(s->available_coinbase_outputs[cbvalid].output_script, &coinbaser[cidx], slen); cidx+=slen;
|
||||
// 64-bit value in sats is part of the output
|
||||
s->available_coinbase_outputs[cbvalid].value_sats = outval;
|
||||
if (s->available_coinbase_outputs[cbvalid].output_script[0] == 0x76) { // kludge for checking for P2PKH output
|
||||
s->available_coinbase_outputs[cbvalid].sigops = 4;
|
||||
} else {
|
||||
s->available_coinbase_outputs[cbvalid].sigops = 0;
|
||||
}
|
||||
|
||||
s->available_coinbase_outputs[cbvalid].output_script_len = slen;
|
||||
|
||||
cbvalid++;
|
||||
|
||||
if (cbvalid >= 512) break; // limitation of datum for now
|
||||
}
|
||||
|
||||
s->datum_coinbaser_id = datum_id;
|
||||
s->available_coinbase_outputs_count = cbvalid;
|
||||
if (coinbaser && must_free) free(coinbaser);
|
||||
return cbvalid;
|
||||
}
|
||||
|
||||
void *datum_coinbaser_thread(void *ptr) {
|
||||
int sjob = -1;
|
||||
T_DATUM_STRATUM_JOB *s = NULL;
|
||||
bool need_coinbaser = false;
|
||||
int i;
|
||||
|
||||
DLOG_DEBUG("Coinbaser thread active");
|
||||
|
||||
while(1) {
|
||||
// check if we need to fetch any new coinbasers
|
||||
// check if the stratum job has been updated
|
||||
pthread_rwlock_rdlock(&stratum_global_job_ptr_lock);
|
||||
if (global_latest_stratum_job_index != sjob) {
|
||||
s = global_cur_stratum_jobs[global_latest_stratum_job_index];
|
||||
if (s) {
|
||||
sjob = global_latest_stratum_job_index;
|
||||
if (s->need_coinbaser) {
|
||||
need_coinbaser = true;
|
||||
}
|
||||
} else {
|
||||
need_coinbaser = false;
|
||||
}
|
||||
}
|
||||
pthread_rwlock_unlock(&stratum_global_job_ptr_lock);
|
||||
|
||||
if (need_coinbaser) {
|
||||
// fetch remote coinbaser for job
|
||||
DLOG_DEBUG("Job %d needs a coinbaser!", sjob);
|
||||
if (datum_protocol_is_active()) {
|
||||
i = datum_protocol_coinbaser_fetch(s);
|
||||
} else {
|
||||
s->available_coinbase_outputs_count = 0;
|
||||
i = 0;
|
||||
}
|
||||
if (i>=0) {
|
||||
DLOG_DEBUG("Generating coinbases for up to %d outputs", i);
|
||||
generate_coinbase_txns_for_stratum_job(s, false);
|
||||
if (need_coinbaser_rwlocks_init_done) {
|
||||
pthread_rwlock_wrlock(&need_coinbaser_rwlocks[sjob]);
|
||||
s->need_coinbaser = false;
|
||||
pthread_rwlock_unlock(&need_coinbaser_rwlocks[sjob]);
|
||||
need_coinbaser = false;
|
||||
}
|
||||
DLOG_DEBUG("Generated and notified.");
|
||||
}
|
||||
}
|
||||
|
||||
usleep(12000);
|
||||
}
|
||||
}
|
||||
|
||||
int datum_coinbaser_init(void) {
|
||||
// TODO: Handle failed (rare, not priority)
|
||||
pthread_t pthread_datum_coinbaser_thread;
|
||||
pthread_create(&pthread_datum_coinbaser_thread, NULL, datum_coinbaser_thread, NULL);
|
||||
return 0;
|
||||
}
|
45
src/datum_coinbaser.h
Normal file
45
src/datum_coinbaser.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_COINBASE_H_
|
||||
#define _DATUM_COINBASE_H_
|
||||
|
||||
int datum_coinbaser_init(void);
|
||||
void generate_coinbase_txns_for_stratum_job_subtypebysize(T_DATUM_STRATUM_JOB *s, int coinbase_index, int remaining_size, bool space_for_en_in_coinbase, int *cb1idx, int *cb2idx, bool special_coinb1);
|
||||
void generate_coinbase_txns_for_stratum_job(T_DATUM_STRATUM_JOB *s, bool empty_only);
|
||||
void generate_base_coinbase_txns_for_stratum_job(T_DATUM_STRATUM_JOB *s, bool new_block);
|
||||
int datum_coinbaser_v2_parse(T_DATUM_STRATUM_JOB *s, unsigned char *coinbaser, int cblen, bool must_free);
|
||||
|
||||
#endif
|
432
src/datum_conf.c
Normal file
432
src/datum_conf.c
Normal file
@ -0,0 +1,432 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Custom configurator and help output generator
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "datum_conf.h"
|
||||
#include "datum_utils.h"
|
||||
#include "datum_sockets.h"
|
||||
|
||||
global_config_t datum_config;
|
||||
|
||||
const char *datum_conf_var_type_text[] = { "N/A", "boolean", "integer", "string", "string_array" };
|
||||
|
||||
const T_DATUM_CONFIG_ITEM datum_config_options[] = {
|
||||
// Bitcoind configs
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "bitcoind", .name = "rpcuser", .description = "RPC username for communication with local bitcoind.",
|
||||
.required = true, .ptr = datum_config.bitcoind_rpcuser, .max_string_len = 128 },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "bitcoind", .name = "rpcpassword", .description = "RPC password for communication with local bitcoind.",
|
||||
.required = true, .ptr = datum_config.bitcoind_rpcpassword, .max_string_len = 128 },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "bitcoind", .name = "rpcurl", .description = "RPC URL for communication with local bitcoind. (GBT Template Source)",
|
||||
.required = true, .ptr = datum_config.bitcoind_rpcurl, .max_string_len = 128 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "bitcoind", .name = "work_update_seconds", .description = "How many seconds between normal work updates? (5-120, 40 suggested)",
|
||||
.required = false, .ptr = &datum_config.bitcoind_work_update_seconds, .default_int = 40 },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "bitcoind", .name = "notify_fallback", .description = "Fall back to less efficient methods for new block notifications. Can disable if you use blocknotify.",
|
||||
.required = false, .ptr = &datum_config.bitcoind_notify_fallback, .default_bool = true },
|
||||
|
||||
// stratun v1 server configs
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "listen_port", .description = "Listening port for Stratum Gateway",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_listen_port, .default_int = 23334 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "max_clients_per_thread", .description = "Maximum clients per Stratum server thread",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_max_clients_per_thread, .default_int = 128 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "max_threads", .description = "Maximum Stratum server threads",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_max_threads, .default_int = 8 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "max_clients", .description = "Maximum total Stratum clients before rejecting connections",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_max_clients, .default_int = 1024 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "vardiff_min", .description = "Work difficulty floor",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_vardiff_min, .default_int = 16384 },
|
||||
{ .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 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "vardiff_quickdiff_count", .description = "How many shares before considering a quick diff update",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_vardiff_quickdiff_count, .default_int = 8 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "vardiff_quickdiff_delta", .description = "How many times faster than our target does the miner have to be before we enforce a quick diff bump",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_vardiff_quickdiff_delta, .default_int = 8 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "share_stale_seconds", .description = "How many seconds after a job is generated before a share submission is considered stale?",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_share_stale_seconds, .default_int = 120 },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "stratum", .name = "fingerprint_miners", .description = "Attempt to fingerprint miners for better use of coinbase space",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_fingerprint_miners, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "idle_timeout_no_subscribe",.description = "Seconds we allow a connection to be idle without seeing a work subscription? (0 disables)",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_idle_timeout_no_subscribe, .default_int = 15 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "idle_timeout_no_shares", .description = "Seconds we allow a subscribed connection to be idle without seeing at least one accepted share? (0 disables)",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_idle_timeout_no_share, .default_int = 7200 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "stratum", .name = "idle_timeout_max_last_work", .description = "Seconds we allow a subscribed connection to be idle since its last accepted share? (0 disables)",
|
||||
.required = false, .ptr = &datum_config.stratum_v1_idle_timeout_max_last_work, .default_int = 0 },
|
||||
|
||||
// mining settings
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "mining", .name = "pool_address", .description = "Bitcoin address used for mining rewards.",
|
||||
.required = true, .ptr = datum_config.mining_pool_address, .max_string_len = 128 },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "mining", .name = "coinbase_tag_primary", .description = "Text to have in the primary coinbase tag when not using pool (overridden by DATUM Pool)",
|
||||
.required = false, .ptr = datum_config.mining_coinbase_tag_primary, .default_string[0] = "DATUM Gateway", .max_string_len = 64 },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "mining", .name = "coinbase_tag_secondary", .description = "Text to have in the secondary coinbase tag (Short name/identifier)",
|
||||
.required = false, .ptr = datum_config.mining_coinbase_tag_secondary, .default_string[0] = "DATUM User", .max_string_len = 64 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "mining", .name = "coinbase_unique_id", .description = "A unique ID between 1 and 65535. This is appended to the coinbase. Make unique per instance of datum with the same coinbase tags.",
|
||||
.required = false, .ptr = &datum_config.coinbase_unique_id, .default_int = 4242 },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "mining", .name = "save_submitblocks_dir", .description = "Directory to save all submitted blocks to as submitblock JSON files",
|
||||
.required = false, .ptr = datum_config.mining_save_submitblocks_dir, .default_string[0] = "", .max_string_len = 256 },
|
||||
|
||||
// API/dashboard
|
||||
{ .var_type = DATUM_CONF_INT, .category = "api", .name = "listen_port", .description = "Port to listen for API/dashboard requests (0=disabled)",
|
||||
.required = false, .ptr = &datum_config.api_listen_port, .default_int = 0 },
|
||||
|
||||
// extra block submissions list
|
||||
{ .var_type = DATUM_CONF_STRING_ARRAY, .category = "extra_block_submissions", .name = "urls", .description = "Array of bitcoind RPC URLs to submit our blocks to directly. Include auth info: http://user:pass@IP",
|
||||
.required = false, .ptr = datum_config.extra_block_submissions_urls[0], .max_string_len = 512 },
|
||||
|
||||
// logger
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "logger", .name = "log_to_console", .description = "Enable logging of messages to the console",
|
||||
.required = false, .ptr = &datum_config.clog_to_console, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "logger", .name = "log_to_stderr", .description = "Log console messages to stderr *instead* of stdout",
|
||||
.required = false, .ptr = &datum_config.clog_to_stderr, .default_bool = false },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "logger", .name = "log_to_file", .description = "Enable logging of messages to a file",
|
||||
.required = false, .ptr = &datum_config.clog_to_file, .default_bool = false },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "logger", .name = "log_file", .description = "Path to file to write log messages, when enabled",
|
||||
.required = false, .ptr = datum_config.clog_file, .default_string[0] = "", .max_string_len = 1023 },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "logger", .name = "log_rotate_daily", .description = "Rotate the message log file at midnight",
|
||||
.required = false, .ptr = &datum_config.clog_rotate_daily, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "logger", .name = "log_calling_function", .description = "Log the name of the calling function when logging",
|
||||
.required = false, .ptr = &datum_config.clog_calling_function, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "logger", .name = "log_level_console", .description = "Minimum log level for console messages (0=All, 1=Debug, 2=Info, 3=Warn, 4=Error, 5=Fatal)",
|
||||
.required = false, .ptr = &datum_config.clog_level_console, .default_int = 2 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "logger", .name = "log_level_file", .description = "Minimum log level for log file messages (0=All, 1=Debug, 2=Info, 3=Warn, 4=Error, 5=Fatal)",
|
||||
.required = false, .ptr = &datum_config.clog_level_file, .default_int = 1 },
|
||||
|
||||
// datum options
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "datum", .name = "pool_host", .description = "Remote DATUM server host/ip to use for decentralized pooled mining (set to \"\" to disable pooled mining)",
|
||||
.required = false, .ptr = datum_config.datum_pool_host, .default_string[0] = "datum-beta1.mine.ocean.xyz", .max_string_len = 1023 },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "datum", .name = "pool_port", .description = "Remote DATUM server port",
|
||||
.required = false, .ptr = &datum_config.datum_pool_port, .default_int = 28915 },
|
||||
{ .var_type = DATUM_CONF_STRING, .category = "datum", .name = "pool_pubkey", .description = "Public key of the DATUM server for initiating encrypted connection. Get from secure location, or set to empty to auto-fetch.",
|
||||
.required = false, .ptr = datum_config.datum_pool_pubkey, .default_string[0] = "f21f2f0ef0aa1970468f22bad9bb7f4535146f8e4a8f646bebc93da3d89b1406f40d032f09a417d94dc068055df654937922d2c89522e3e8f6f0e649de473003", .max_string_len = 1023 },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "datum", .name = "pool_pass_workers", .description = "Pass stratum miner usernames as sub-worker names to the pool (pool_username.miner's username)",
|
||||
.required = false, .ptr = &datum_config.datum_pool_pass_workers, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "datum", .name = "pool_pass_full_users", .description = "Pass stratum miner usernames as raw usernames to the pool (use if putting multiple payout addresses on miners behind this gateway)",
|
||||
.required = false, .ptr = &datum_config.datum_pool_pass_full_users, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "datum", .name = "always_pay_self", .description = "Always include my datum.pool_username payout in my blocks if possible",
|
||||
.required = false, .ptr = &datum_config.datum_always_pay_self, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_BOOL, .category = "datum", .name = "pooled_mining_only", .description = "If the DATUM pool server becomes unavailable, terminate miner connections (otherwise, 100% of any blocks you find pay mining.pool_address)",
|
||||
.required = false, .ptr = &datum_config.datum_pooled_mining_only, .default_bool = true },
|
||||
{ .var_type = DATUM_CONF_INT, .category = "datum", .name = "protocol_global_timeout", .description = "If no valid messages are received from the DATUM server in this many seconds, give up and try to reconnect",
|
||||
.required = false, .ptr = &datum_config.datum_protocol_global_timeout, .default_int = 60 },
|
||||
};
|
||||
|
||||
#define NUM_CONFIG_ITEMS (sizeof(datum_config_options) / sizeof(datum_config_options[0]))
|
||||
|
||||
json_t *load_json_from_file(const char *file_path) {
|
||||
json_error_t error;
|
||||
json_t *root = json_load_file(file_path, 0, &error);
|
||||
|
||||
if(!root) {
|
||||
DLOG_ERROR("Error parsing JSON file: %s", error.text);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void datum_config_set_default(const T_DATUM_CONFIG_ITEM *c) {
|
||||
// set the default
|
||||
switch(c->var_type) {
|
||||
case DATUM_CONF_INT: {
|
||||
*((int *)c->ptr) = c->default_int;
|
||||
break;
|
||||
}
|
||||
|
||||
case DATUM_CONF_BOOL: {
|
||||
*((bool *)c->ptr) = c->default_bool;
|
||||
break;
|
||||
}
|
||||
|
||||
case DATUM_CONF_STRING: {
|
||||
strncpy((char *)c->ptr, c->default_string[0], c->max_string_len-1);
|
||||
((char *)c->ptr)[c->max_string_len-1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case DATUM_CONF_STRING_ARRAY: {
|
||||
// TODO: For now, these won't have a default. the first string will just be empty
|
||||
((char *)c->ptr)[0] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
int datum_config_parse_value(const T_DATUM_CONFIG_ITEM *c, json_t *item) {
|
||||
switch(c->var_type) {
|
||||
case DATUM_CONF_INT: {
|
||||
if (json_is_null(item)) {
|
||||
*((int *)c->ptr) = 0; // set to zero if "NULL" ... probably not ideal, but better than failing
|
||||
return 1;
|
||||
}
|
||||
if (!json_is_integer(item)) return -1;
|
||||
const json_int_t value = json_integer_value(item);
|
||||
if (value > INT_MAX || value < INT_MIN) return -1;
|
||||
*((int *)c->ptr) = value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
case DATUM_CONF_BOOL: {
|
||||
if (json_is_null(item)) {
|
||||
*((bool *)c->ptr) = false; // set to false if "NULL" ... probably not ideal, but better than failing
|
||||
return 1;
|
||||
}
|
||||
if (!json_is_boolean(item)) return -1;
|
||||
*((bool *)c->ptr) = json_boolean_value(item)?true:false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
case DATUM_CONF_STRING: {
|
||||
if (json_is_null(item)) {
|
||||
((char *)c->ptr)[0] = 0; // Set c->ptr to an empty string if the js is actually NULL
|
||||
return 1;
|
||||
}
|
||||
if (!json_is_string(item)) return -1;
|
||||
strncpy((char *)c->ptr, json_string_value(item), c->max_string_len-1);
|
||||
((char *)c->ptr)[c->max_string_len-1] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
case DATUM_CONF_STRING_ARRAY: {
|
||||
if (!json_is_array(item)) return -1;
|
||||
|
||||
size_t index;
|
||||
json_t *value;
|
||||
int i = 0;
|
||||
|
||||
json_array_foreach(item, index, value) {
|
||||
if (!json_is_string(value)) return -1;
|
||||
if (i < (DATUM_CONFIG_MAX_ARRAY_ENTRIES-1)) {
|
||||
strncpy(((char (*)[1024])c->ptr)[i], json_string_value(value), c->max_string_len-1);
|
||||
((char (*)[1024])c->ptr)[i][c->max_string_len-1] = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
((char (*)[1024])c->ptr)[i][0] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int datum_read_config(const char *conffile) {
|
||||
json_t *config = NULL;
|
||||
json_t *cat, *item;
|
||||
|
||||
unsigned int i;
|
||||
int j;
|
||||
|
||||
memset(&datum_config, 0, sizeof(global_config_t));
|
||||
|
||||
config = load_json_from_file(conffile);
|
||||
|
||||
if (!config) {
|
||||
DLOG_FATAL("Could not read configuration JSON file!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i=0;i<NUM_CONFIG_ITEMS;i++) {
|
||||
item = NULL; cat = NULL;
|
||||
cat = json_object_get(config, datum_config_options[i].category);
|
||||
if (cat) {
|
||||
item = json_object_get(cat, datum_config_options[i].name);
|
||||
}
|
||||
if ((!cat) || (!item)) {
|
||||
if (datum_config_options[i].required) {
|
||||
DLOG_ERROR("Required configuration option (%s.%s) not found in config file:", datum_config_options[i].category, datum_config_options[i].name);
|
||||
DLOG_ERROR("--- Config description: \"%s\"", datum_config_options[i].description);
|
||||
return 0;
|
||||
} else {
|
||||
datum_config_set_default(&datum_config_options[i]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// item might be valid
|
||||
j = datum_config_parse_value(&datum_config_options[i], item);
|
||||
if (j != 1) {
|
||||
DLOG_ERROR("Could not parse configuration option %s.%s. Type should be %s", datum_config_options[i].category, datum_config_options[i].name, (datum_config_options[i].var_type<DATUM_CONF_TYPES)?datum_conf_var_type_text[datum_config_options[i].var_type]:"UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
if (config) {
|
||||
json_decref(config);
|
||||
}
|
||||
|
||||
// pass configuration options to the logger
|
||||
datum_logger_config(datum_config.clog_to_file, datum_config.clog_to_console, datum_config.clog_level_console, datum_config.clog_level_file, datum_config.clog_calling_function, datum_config.clog_to_stderr, datum_config.clog_rotate_daily, datum_config.clog_file);
|
||||
|
||||
i = 0;
|
||||
for(i=0;i<DATUM_CONFIG_MAX_ARRAY_ENTRIES;i++) {
|
||||
if (datum_config.extra_block_submissions_urls[i][0] == 0) { break; }
|
||||
}
|
||||
datum_config.extra_block_submissions_count = i;
|
||||
|
||||
if (datum_config.bitcoind_work_update_seconds < 5) {
|
||||
datum_config.bitcoind_work_update_seconds = 5;
|
||||
}
|
||||
if (datum_config.bitcoind_work_update_seconds > 120) {
|
||||
datum_config.bitcoind_work_update_seconds = 120;
|
||||
}
|
||||
|
||||
if (datum_config.stratum_v1_max_threads > MAX_THREADS) {
|
||||
DLOG_FATAL("Maximum threads must be less than %d.", MAX_THREADS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (datum_config.stratum_v1_max_clients_per_thread > MAX_CLIENTS_THREAD) {
|
||||
DLOG_FATAL("Maximum clients per thread must be less than %d.",MAX_CLIENTS_THREAD);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((strlen(datum_config.mining_coinbase_tag_primary)+strlen(datum_config.mining_coinbase_tag_secondary)) > 88) {
|
||||
DLOG_FATAL("Length of coinbase tags can not exceed 88 bytes total.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((strlen(datum_config.mining_coinbase_tag_primary) > 60) || (strlen(datum_config.mining_coinbase_tag_secondary) > 60)) {
|
||||
DLOG_FATAL("Length of coinbase tags can not exceed 88 bytes total or 60 bytes each.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (datum_config.stratum_v1_vardiff_target_shares_min < 1) {
|
||||
DLOG_FATAL("Stratum server stratum.vardiff_target_shares_min must be at least 1");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (datum_config.stratum_v1_vardiff_quickdiff_count < 4) {
|
||||
DLOG_FATAL("Stratum server stratum.vardiff_quickdiff_count must be at least 4");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (datum_config.stratum_v1_vardiff_quickdiff_delta < 3) {
|
||||
DLOG_FATAL("Stratum server stratum.vardiff_quickdiff_delta must be at least 3");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (roundDownToPowerOfTwo_64(datum_config.stratum_v1_vardiff_min) != datum_config.stratum_v1_vardiff_min) {
|
||||
DLOG_WARN("stratum.stratum_v1_vardiff_min MUST be a power of two. adjusting from %d to %d", datum_config.stratum_v1_vardiff_min, roundDownToPowerOfTwo_64(datum_config.stratum_v1_vardiff_min));
|
||||
datum_config.stratum_v1_vardiff_min = roundDownToPowerOfTwo_64(datum_config.stratum_v1_vardiff_min);
|
||||
}
|
||||
|
||||
if (datum_config.stratum_v1_vardiff_min < 1) {
|
||||
DLOG_FATAL("Stratum server stratum.stratum_v1_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;
|
||||
}
|
||||
|
||||
if (datum_config.datum_protocol_global_timeout < (datum_config.bitcoind_work_update_seconds+5)) {
|
||||
DLOG_FATAL("DATUM protocol global timeout must be at least the work update interval plus 5 seconds.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Save some multiplication later
|
||||
datum_config.datum_protocol_global_timeout_ms = datum_config.datum_protocol_global_timeout * 1000;
|
||||
|
||||
// Just in case
|
||||
strcpy(datum_config.override_mining_coinbase_tag_primary, datum_config.mining_coinbase_tag_primary);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void datum_gateway_help(void) {
|
||||
unsigned int i;
|
||||
int p;
|
||||
char lastcat[512] = { 0 };
|
||||
char paddots[64];
|
||||
|
||||
strcpy(paddots, "...............................................................");
|
||||
|
||||
printf("usage: datum_gateway [OPTION...]\n\n");
|
||||
printf("Command line options:\n\n");
|
||||
printf(" -c, --config=FILE ..................... Path to configuration JSON file (default: ./datum_gateway_config.json)\n");
|
||||
printf(" -?, --help ............................ Print this help\n");
|
||||
printf(" --version ............................. Print this software's name and version\n");
|
||||
puts("");
|
||||
printf("Configuration file options:\n\n {\n");
|
||||
for (i=0;i<NUM_CONFIG_ITEMS;i++) {
|
||||
if (strcmp(datum_config_options[i].category, lastcat)) {
|
||||
if (i) { printf(" },\n"); }
|
||||
printf(" \"%s\":{\n", datum_config_options[i].category);
|
||||
strcpy(lastcat, datum_config_options[i].category);
|
||||
}
|
||||
p = 30 - strlen(datum_config_options[i].name);
|
||||
if (p < 0) p = 0;
|
||||
if (p > 62) p = 62;
|
||||
paddots[p] = 0;
|
||||
printf(" \"%s\": %s %s (%s", datum_config_options[i].name, paddots, datum_config_options[i].description, (datum_config_options[i].var_type<DATUM_CONF_TYPES)?datum_conf_var_type_text[datum_config_options[i].var_type]:"UNKNOWN");
|
||||
paddots[p] = '.';
|
||||
if (datum_config_options[i].required) {
|
||||
printf(", REQUIRED)\n");
|
||||
} else {
|
||||
switch(datum_config_options[i].var_type) {
|
||||
case DATUM_CONF_INT: {
|
||||
printf(", default: %d)\n",datum_config_options[i].default_int);
|
||||
break;
|
||||
}
|
||||
|
||||
case DATUM_CONF_BOOL: {
|
||||
printf(", default: %s)\n",datum_config_options[i].default_bool?"true":"false");
|
||||
break;
|
||||
}
|
||||
|
||||
case DATUM_CONF_STRING: {
|
||||
printf(", default: \"%s\")\n",datum_config_options[i].default_string[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
printf(")\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printf(" }\n }\n\n");
|
||||
}
|
133
src/datum_conf.h
Normal file
133
src/datum_conf.h
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_CONF_H_
|
||||
#define _DATUM_CONF_H_
|
||||
|
||||
#define DATUM_CONFIG_MAX_ARRAY_ENTRIES 32
|
||||
#define DATUM_MAX_BLOCK_SUBMITS DATUM_CONFIG_MAX_ARRAY_ENTRIES
|
||||
#define DATUM_CONFIG_MAX_STRING_ARRAY_LEN 1024
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define DATUM_CONF_BOOL 1
|
||||
#define DATUM_CONF_INT 2
|
||||
#define DATUM_CONF_STRING 3
|
||||
#define DATUM_CONF_STRING_ARRAY 4
|
||||
|
||||
#define DATUM_CONF_TYPES 5
|
||||
|
||||
typedef struct {
|
||||
char category[32];
|
||||
char name[64];
|
||||
char description[512];
|
||||
int var_type;
|
||||
int max_string_len;
|
||||
int default_int;
|
||||
bool default_bool;
|
||||
const char *default_string[DATUM_CONFIG_MAX_ARRAY_ENTRIES];
|
||||
|
||||
void *ptr;
|
||||
|
||||
bool required;
|
||||
} T_DATUM_CONFIG_ITEM;
|
||||
|
||||
// Globally accessable config options
|
||||
typedef struct {
|
||||
char bitcoind_rpcuser[256];
|
||||
char bitcoind_rpcpassword[256];
|
||||
char bitcoind_rpcurl[256];
|
||||
int bitcoind_work_update_seconds;
|
||||
bool bitcoind_notify_fallback;
|
||||
|
||||
int stratum_v1_listen_port;
|
||||
int stratum_v1_max_clients;
|
||||
int stratum_v1_max_threads;
|
||||
int stratum_v1_max_clients_per_thread;
|
||||
|
||||
int stratum_v1_vardiff_min;
|
||||
int stratum_v1_vardiff_target_shares_min;
|
||||
int stratum_v1_vardiff_quickdiff_count;
|
||||
int stratum_v1_vardiff_quickdiff_delta;
|
||||
int stratum_v1_share_stale_seconds;
|
||||
bool stratum_v1_fingerprint_miners;
|
||||
int stratum_v1_idle_timeout_no_subscribe;
|
||||
int stratum_v1_idle_timeout_no_share;
|
||||
int stratum_v1_idle_timeout_max_last_work;
|
||||
|
||||
char mining_pool_address[256];
|
||||
char mining_coinbase_tag_primary[256];
|
||||
char mining_coinbase_tag_secondary[256];
|
||||
char mining_save_submitblocks_dir[256];
|
||||
int coinbase_unique_id;
|
||||
|
||||
int api_listen_port;
|
||||
|
||||
int extra_block_submissions_count;
|
||||
char extra_block_submissions_urls[DATUM_MAX_BLOCK_SUBMITS][DATUM_CONFIG_MAX_STRING_ARRAY_LEN];
|
||||
|
||||
bool clog_to_file;
|
||||
bool clog_to_console;
|
||||
int clog_level_console;
|
||||
int clog_level_file;
|
||||
bool clog_calling_function;
|
||||
bool clog_to_stderr;
|
||||
bool clog_rotate_daily;
|
||||
char clog_file[1024];
|
||||
|
||||
char datum_pool_host[1024];
|
||||
int datum_pool_port;
|
||||
bool datum_pool_pass_workers;
|
||||
bool datum_pool_pass_full_users;
|
||||
bool datum_always_pay_self;
|
||||
bool datum_pooled_mining_only;
|
||||
char datum_pool_pubkey[1024];
|
||||
int datum_protocol_global_timeout;
|
||||
uint64_t datum_protocol_global_timeout_ms;
|
||||
|
||||
uint32_t prime_id;
|
||||
unsigned char override_mining_pool_scriptsig[256];
|
||||
int override_mining_pool_scriptsig_len;
|
||||
char override_mining_coinbase_tag_primary[256];
|
||||
uint64_t override_vardiff_min;
|
||||
} global_config_t;
|
||||
|
||||
extern global_config_t datum_config;
|
||||
|
||||
int datum_read_config(const char *conffile);
|
||||
void datum_gateway_help(void);
|
||||
|
||||
#endif
|
247
src/datum_gateway.c
Normal file
247
src/datum_gateway.c
Normal file
@ -0,0 +1,247 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// NOTE: Everything about this software assumes compilation for little endian underlying hardware operations.
|
||||
// This *will* break on big endian hardware and not perform expected operations correctly.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <jansson.h>
|
||||
#include <inttypes.h>
|
||||
#include <curl/curl.h>
|
||||
#include <argp.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "datum_gateway.h"
|
||||
#include "datum_jsonrpc.h"
|
||||
#include "datum_utils.h"
|
||||
#include "datum_blocktemplates.h"
|
||||
#include "datum_stratum.h"
|
||||
#include "datum_conf.h"
|
||||
#include "datum_sockets.h"
|
||||
#include "datum_api.h"
|
||||
#include "datum_coinbaser.h"
|
||||
#include "datum_protocol.h"
|
||||
|
||||
// ARGP stuff
|
||||
const char *argp_program_version = "datum_gateway " DATUM_PROTOCOL_VERSION;
|
||||
const char *argp_program_bug_address = "<jason@ocean.xyz>";
|
||||
static char doc[] = "Decentralized Alternative Templates for Universal Mining - Pool Gateway";
|
||||
static char args_doc[] = "";
|
||||
static struct argp_option options[] = {
|
||||
{"help", '?', 0, 0, "Show custom help", 0},
|
||||
{"usage", '?', 0, 0, "Show custom help", 0},
|
||||
{"config", 'c', "FILE", 0, "Configuration JSON file"},
|
||||
{0}
|
||||
};
|
||||
|
||||
struct arguments {
|
||||
char *config_file;
|
||||
};
|
||||
|
||||
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
|
||||
struct arguments *arguments = state->input;
|
||||
switch (key) {
|
||||
case '?': {
|
||||
datum_gateway_help();
|
||||
exit(0);
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
arguments->config_file = arg;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct argp argp = {options, parse_opt, args_doc, doc};
|
||||
// END ARGP Stuff
|
||||
|
||||
void handle_sigusr1(int sig) {
|
||||
datum_blocktemplates_notifynew(NULL, 0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct arguments arguments;
|
||||
pthread_t pthread_datum_stratum_v1;
|
||||
pthread_t pthread_datum_gateway_template;
|
||||
int i;
|
||||
int fail_retries=0;
|
||||
struct sigaction sa;
|
||||
uint64_t last_datum_protocol_connect_tsms = 0;
|
||||
bool rejecting_stratum = false;
|
||||
uint32_t next_reconnect_attempt_ms = 5000;
|
||||
|
||||
printf("\n **************************************************************************\n");
|
||||
printf(" * DATUM Gateway --- Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes *\n");
|
||||
printf(" * git commit: %-58s *\n", GIT_COMMIT_HASH);
|
||||
printf(" **************************************************************************\n\n");
|
||||
fflush(stdout);
|
||||
|
||||
// listen for block notifications
|
||||
// set this up early so a notification doesn't break our init
|
||||
sa.sa_handler = handle_sigusr1;
|
||||
sa.sa_flags = 0;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
|
||||
DLOG_FATAL("Could not setup signal handler!");
|
||||
perror("sigaction");
|
||||
usleep(100000);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
srand(time(NULL)); // Not used for anything secure, so this is fine.
|
||||
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
datum_utils_init();
|
||||
|
||||
arguments.config_file = "datum_gateway_config.json"; // Default config file
|
||||
if (argp_parse(&argp, argc, argv, 0, 0, &arguments) != 0) {
|
||||
DLOG_FATAL("Error parsing arguments. Check --help");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (datum_read_config(arguments.config_file) != 1) {
|
||||
DLOG_FATAL("Error reading config file. Check --help");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Initialize logger thread
|
||||
datum_logger_init();
|
||||
|
||||
if (datum_protocol_init()) {
|
||||
DLOG_FATAL("Error initializing the DATUM protocol!");
|
||||
usleep(100000);
|
||||
exit(1);
|
||||
}
|
||||
last_datum_protocol_connect_tsms = current_time_millis();
|
||||
|
||||
if (datum_api_init()) {
|
||||
DLOG_FATAL("Error initializing API interface");
|
||||
usleep(100000);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (datum_coinbaser_init()) {
|
||||
DLOG_FATAL("Error initializing coinbaser thread");
|
||||
usleep(100000);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Try to connect to the DATUM server, if setup to do so.
|
||||
if (datum_config.datum_pool_host[0] != 0) {
|
||||
while((current_time_millis()-15000 < last_datum_protocol_connect_tsms) && (!datum_protocol_is_active())) {
|
||||
DLOG_INFO("Waiting on DATUM server... %d", (last_datum_protocol_connect_tsms-(current_time_millis()-15000))/1000);
|
||||
sleep(1);
|
||||
if ((datum_config.datum_pool_host[0] != 0) && (!datum_protocol_thread_is_active())) {
|
||||
datum_protocol_start_connector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Churn and continue to try and connect while leaving the Stratum server down if pooled mining only
|
||||
if (datum_config.datum_pooled_mining_only && (!datum_protocol_is_active())) {
|
||||
DLOG_ERROR("DATUM server connection could not be established.");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Starting template fetcher thread");
|
||||
pthread_create(&pthread_datum_gateway_template, NULL, datum_gateway_template_thread, NULL);
|
||||
|
||||
// Note: The stratum thread will wait for a template to be available for some time before panicking.
|
||||
DLOG_DEBUG("Starting Stratum v1 server");
|
||||
pthread_create(&pthread_datum_stratum_v1, NULL, datum_stratum_v1_socket_server, NULL);
|
||||
|
||||
// Randomize the reconnect delay from 5 to 20 seconds to prevent hammering the server
|
||||
next_reconnect_attempt_ms = ( 5000 + (rand() % 15001) );
|
||||
|
||||
i=0;
|
||||
while(1) {
|
||||
if (panic_mode) {
|
||||
DLOG_FATAL("*** PANIC TRIGGERED: EXITING IMMEDIATELY ***");
|
||||
printf("PANIC EXIT.\n");
|
||||
sleep(1); // almost immediately, wait a second for the logger!
|
||||
fflush(stdout);
|
||||
usleep(2000);
|
||||
exit(1);
|
||||
}
|
||||
usleep(500000);
|
||||
i++;
|
||||
if (i>=600) { // Roughly every 5 minutes spit out some stats to the log
|
||||
i = datum_stratum_v1_global_subscriber_count();
|
||||
DLOG_INFO("Server stats: %d client%s / %.2f Th/s", i, (i!=1)?"s":"", datum_stratum_v1_est_total_th_sec());
|
||||
i=0;
|
||||
}
|
||||
|
||||
if (fail_retries > 0) {
|
||||
if (datum_protocol_is_active()) {
|
||||
fail_retries = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (datum_config.datum_pooled_mining_only && (fail_retries >= 2) && (!datum_protocol_is_active())) {
|
||||
if (!rejecting_stratum) {
|
||||
DLOG_WARN("Configured for pooled mining only, and connection lost to DATUM server! Shutting down Stratum v1 server until DATUM connection reestablished.");
|
||||
rejecting_stratum = true;
|
||||
datum_stratum_v1_shutdown_all();
|
||||
}
|
||||
} else {
|
||||
rejecting_stratum = false;
|
||||
}
|
||||
|
||||
if ((datum_config.datum_pool_host[0] != 0) && (!datum_protocol_thread_is_active())) {
|
||||
// DATUM thread is dead, and it shouldn't be.
|
||||
if (last_datum_protocol_connect_tsms < (current_time_millis()-next_reconnect_attempt_ms)) {
|
||||
datum_protocol_start_connector();
|
||||
last_datum_protocol_connect_tsms = current_time_millis();
|
||||
fail_retries++;
|
||||
// Randomize the reconnect delay from 5 to 20 seconds to prevent hammering the server
|
||||
next_reconnect_attempt_ms = ( 5000 + (rand() % 15001) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
58
src/datum_gateway.h
Normal file
58
src/datum_gateway.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_GATEWAY_H_
|
||||
#define _DATUM_GATEWAY_H_
|
||||
|
||||
#include "git_version.h"
|
||||
|
||||
#ifndef GIT_COMMIT_HASH
|
||||
#define GIT_COMMIT_HASH "UNKNOWN_GIT_HASH"
|
||||
#endif
|
||||
|
||||
// For SV1
|
||||
// client buffer must be large enough to hold entire coinbase in hex at max size
|
||||
// TODO: Make somewhat more dynamic without having to hammer [cm]alloc
|
||||
#define CLIENT_BUFFER ((16384*3)+1024)
|
||||
|
||||
// in ascii hex
|
||||
#define STRATUM_COINBASE1_MAX_LEN 1024
|
||||
#define STRATUM_COINBASE2_MAX_LEN 32768
|
||||
|
||||
#define MAX_COINBASE_TXN_SIZE_BYTES (((STRATUM_COINBASE1_MAX_LEN+STRATUM_COINBASE2_MAX_LEN)>>1)+64)
|
||||
|
||||
#define STRATUM_JOB_INDEX_XOR 0xC0DE
|
||||
|
||||
#endif
|
234
src/datum_jsonrpc.c
Normal file
234
src/datum_jsonrpc.c
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <curl/curl.h>
|
||||
#include <jansson.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "datum_jsonrpc.h"
|
||||
#include "datum_utils.h"
|
||||
|
||||
// TODO: Clean this up. Most of this is very old code from other parts of Eligius/OCEAN internal tools and needs
|
||||
// a solid makeover.
|
||||
// However, it's all quite functional, so not a top priority.
|
||||
|
||||
static void databuf_free(struct data_buffer *db) {
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
free(db->buf);
|
||||
memset(db, 0, sizeof(*db));
|
||||
}
|
||||
|
||||
static size_t all_data_cb(const void *ptr, size_t size, size_t nmemb, void *user_data) {
|
||||
struct data_buffer *db = user_data;
|
||||
size_t len, oldlen, newlen;
|
||||
void *newmem;
|
||||
|
||||
if (SIZE_MAX / size < nmemb) abort();
|
||||
len = size * nmemb;
|
||||
|
||||
oldlen = db->len;
|
||||
if (SIZE_MAX - oldlen < len) abort();
|
||||
newlen = oldlen + len;
|
||||
|
||||
newmem = realloc(db->buf, newlen + 1);
|
||||
if (!newmem) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
db->buf = newmem;
|
||||
db->len = newlen;
|
||||
memcpy(&((char *)db->buf)[oldlen], ptr, len);
|
||||
((char *)db->buf)[newlen] = 0;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t upload_data_cb(void *ptr, size_t size, size_t nmemb, void *user_data) {
|
||||
struct upload_buffer *ub = user_data;
|
||||
size_t len;
|
||||
if (SIZE_MAX / size < nmemb) nmemb = SIZE_MAX / size;
|
||||
len = size * nmemb;
|
||||
|
||||
if (len > ub->len) len = ub->len;
|
||||
|
||||
if (len) {
|
||||
memcpy(ptr, ub->buf, len);
|
||||
ub->buf = &((const char *)ub->buf)[len];
|
||||
ub->len -= len;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
char *basic_http_call(CURL *curl, const char *url) {
|
||||
CURLcode rc;
|
||||
struct data_buffer all_data = { };
|
||||
char curl_err_str[CURL_ERROR_SIZE];
|
||||
char *out;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_ENCODING, "");
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, all_data_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &all_data);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5); // quick timeout!
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); // quick timeout!
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
|
||||
rc = curl_easy_perform(curl);
|
||||
if (rc) {
|
||||
DLOG_DEBUG("HTTP request failed: %s", curl_err_str);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
out = calloc(strlen(all_data.buf)+20,1);
|
||||
if (!out) goto err_out;
|
||||
|
||||
strcpy(out, all_data.buf);
|
||||
|
||||
databuf_free(&all_data);
|
||||
curl_easy_reset(curl);
|
||||
return out;
|
||||
|
||||
err_out:
|
||||
databuf_free(&all_data);
|
||||
curl_easy_reset(curl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json_t *json_rpc_call_full(CURL *curl, const char *url, const char *userpass, const char *rpc_req, const char *extra_header) {
|
||||
json_t *val, *err_val, *res_val;
|
||||
CURLcode rc;
|
||||
struct data_buffer all_data = { };
|
||||
struct upload_buffer upload_data;
|
||||
json_error_t err = { };
|
||||
struct curl_slist *headers = NULL;
|
||||
char len_hdr[64];
|
||||
char curl_err_str[CURL_ERROR_SIZE];
|
||||
bool check_for_result = true;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_ENCODING, "");
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, all_data_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &all_data);
|
||||
curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_data_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_READDATA, &upload_data);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err_str);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5); // quick timeout!
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); // quick timeout!
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
|
||||
if (userpass) {
|
||||
curl_easy_setopt(curl, CURLOPT_USERPWD, userpass);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
||||
|
||||
upload_data.buf = rpc_req;
|
||||
upload_data.len = strlen(rpc_req);
|
||||
sprintf(len_hdr, "Content-Length: %lu",(unsigned long) upload_data.len);
|
||||
|
||||
headers = curl_slist_append(headers, "Content-type: application/json");
|
||||
headers = curl_slist_append(headers, len_hdr);
|
||||
headers = curl_slist_append(headers, "Expect:");
|
||||
|
||||
if (extra_header) {
|
||||
headers = curl_slist_append(headers, extra_header);
|
||||
check_for_result = false;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
rc = curl_easy_perform(curl);
|
||||
if (rc) {
|
||||
DLOG_DEBUG("json_rpc_call: HTTP request failed: %s", curl_err_str);
|
||||
DLOG_DEBUG("json_rpc_call: Request was: %s",rpc_req);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
val = JSON_LOADS(all_data.buf, &err);
|
||||
if (!val) {
|
||||
DLOG_DEBUG("JSON decode failed(%d): %s", err.line, err.text);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (check_for_result) {
|
||||
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 *s;
|
||||
|
||||
if (err_val) {
|
||||
s = json_dumps(err_val, JSON_INDENT(3));
|
||||
} else {
|
||||
s = strdup("(unknown reason)");
|
||||
}
|
||||
|
||||
DLOG_DEBUG("JSON-RPC call failed: %s", s);
|
||||
|
||||
free(s);
|
||||
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
|
||||
databuf_free(&all_data);
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_reset(curl);
|
||||
return val;
|
||||
|
||||
err_out:
|
||||
databuf_free(&all_data);
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_reset(curl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, const char *rpc_req) {
|
||||
return json_rpc_call_full(curl, url, userpass, rpc_req, NULL);
|
||||
}
|
67
src/datum_jsonrpc.h
Normal file
67
src/datum_jsonrpc.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_JSONRPC_H_
|
||||
#define _DATUM_JSONRPC_H_
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#ifndef JSON_INTEGER_IS_LONG_LONG
|
||||
# error "Jansson 2.0 with long long support required!"
|
||||
#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
|
||||
|
||||
// Legacy functions
|
||||
#define bitcoin_rpc_url global_config.bitcoind_url
|
||||
#define bitcoin_rpc_userpass global_config.bitcoind_userpass
|
||||
|
||||
struct data_buffer {
|
||||
void *buf;
|
||||
size_t len;
|
||||
};
|
||||
struct upload_buffer {
|
||||
const void *buf;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, const char *rpc_req);
|
||||
char *basic_http_call(CURL *curl, const char *url);
|
||||
|
||||
#endif
|
437
src/datum_logger.c
Normal file
437
src/datum_logger.c
Normal file
@ -0,0 +1,437 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Multithread-safe logger
|
||||
// TODO: Add additional output types such as for system logging.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "datum_logger.h"
|
||||
#include "datum_utils.h"
|
||||
|
||||
const char *level_text[] = { " ALL", "DEBUG", " INFO", " WARN", "ERROR", "FATAL" };
|
||||
|
||||
volatile bool datum_logger_initialized = false;
|
||||
|
||||
// configurable options
|
||||
bool log_to_file = false;
|
||||
bool log_to_console = true;
|
||||
int log_level_console = DLOG_LEVEL_INFO;
|
||||
int log_level_file = DLOG_LEVEL_ALL;
|
||||
bool log_calling_function = true;
|
||||
bool log_to_stderr = false;
|
||||
bool log_rotate_daily = true;
|
||||
char log_file[1024] = { 0 };
|
||||
|
||||
int dlog_queue_max_entries = 0;
|
||||
int msg_buf_maxsz = DLOG_MSG_BUF_SIZE;
|
||||
|
||||
pthread_rwlock_t dlog_active_buffer_rwlock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
int dlog_active_buffer = 0;
|
||||
uint64_t dlog_active_buffer_version = 0;
|
||||
|
||||
pthread_rwlock_t dlog_buffer_rwlock[2] = { PTHREAD_RWLOCK_INITIALIZER, PTHREAD_RWLOCK_INITIALIZER };
|
||||
DLOG_MSG *dlog_queue[2];
|
||||
int dlog_queue_next[2] = { 0, 0 };
|
||||
uint64_t dlog_queue_version[2] = { 0, 10 };
|
||||
|
||||
int msg_buf_idx[2] = { 0, 0 };
|
||||
char *msg_buffer[2] = { NULL, NULL };
|
||||
|
||||
void datum_logger_config(
|
||||
bool clog_to_file,
|
||||
bool clog_to_console,
|
||||
int clog_level_console,
|
||||
int clog_level_file,
|
||||
bool clog_calling_function,
|
||||
bool clog_to_stderr,
|
||||
bool clog_rotate_daily,
|
||||
char *clog_file
|
||||
) {
|
||||
log_to_file = clog_to_file;
|
||||
log_to_console = clog_to_console;
|
||||
log_level_console = clog_level_console;
|
||||
log_level_file = clog_level_file;
|
||||
log_calling_function = clog_calling_function;
|
||||
log_to_stderr = clog_to_stderr;
|
||||
log_rotate_daily = clog_rotate_daily;
|
||||
strncpy(log_file, clog_file, 1023);
|
||||
log_file[1023] = 0;
|
||||
|
||||
if (log_level_console < 0) log_level_console = 0;
|
||||
if (log_level_console > DLOG_LEVEL_FATAL) log_level_console = DLOG_LEVEL_FATAL;
|
||||
|
||||
if (log_level_file < 0) log_level_file = 0;
|
||||
if (log_level_file > DLOG_LEVEL_FATAL) log_level_file = DLOG_LEVEL_FATAL;
|
||||
}
|
||||
|
||||
int datum_logger_queue_msg(const char *func, int level, const char *format, ...) {
|
||||
int buffer_id, i;
|
||||
uint64_t buffer_version;
|
||||
DLOG_MSG *msg = NULL;
|
||||
uint64_t tsms;
|
||||
va_list args;
|
||||
struct timeval tv;
|
||||
struct tm tm_info;
|
||||
char time_buffer[40];
|
||||
|
||||
if ((level < log_level_console) && (level < log_level_file)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get timestamp before messing with locks and such
|
||||
gettimeofday(&tv, NULL);
|
||||
tsms = (tv.tv_sec * 1000LL) + (tv.tv_usec / 1000LL);
|
||||
|
||||
if (level > 5) level = 5;
|
||||
if (level < 0) level = 0;
|
||||
|
||||
if (__builtin_expect(!datum_logger_initialized,0)) {
|
||||
// not initialized yet, so we're just going to print this to console with default settings
|
||||
if ((log_to_console) && (level >= log_level_console)) {
|
||||
va_start(args, format);
|
||||
localtime_r(&tv.tv_sec, &tm_info);
|
||||
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", &tm_info);
|
||||
|
||||
if (log_calling_function) {
|
||||
fprintf(log_to_stderr?stderr:stdout, "%s.%03ld [%44s] %s: ", time_buffer, tv.tv_usec / 1000, func, level_text[level]);
|
||||
} else {
|
||||
fprintf(log_to_stderr?stderr:stdout, "%s.%03ld %s: ", time_buffer, tv.tv_usec / 1000, level_text[level]);
|
||||
}
|
||||
|
||||
vfprintf(log_to_stderr?stderr:stdout, format, args);
|
||||
fprintf(log_to_stderr?stderr:stdout, "\n");
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add the msg to the logger queue
|
||||
// this is probably overkill...
|
||||
for (i=0;i<10000000;i++) {
|
||||
if (i < 99999990) { // ensure we don't get the lock on the last try and forget to unlock and crash
|
||||
// get the active buffer ID
|
||||
pthread_rwlock_rdlock(&dlog_active_buffer_rwlock);
|
||||
buffer_id = dlog_active_buffer;
|
||||
buffer_version = dlog_active_buffer_version;
|
||||
pthread_rwlock_unlock(&dlog_active_buffer_rwlock);
|
||||
|
||||
// get a write lock for that buffer
|
||||
pthread_rwlock_wrlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
|
||||
// check for race condition on buffer swap
|
||||
if (buffer_version != dlog_queue_version[buffer_id]) {
|
||||
// Race condition!
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
} else {
|
||||
// no race condition, we're good
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= 10000000) {
|
||||
// we have no locks, but we also couldn't sync up on the rare race condition after 10000000 attempts
|
||||
// means something very bad is probably happening
|
||||
panic_from_thread(__LINE__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dlog_queue_next[buffer_id] >= dlog_queue_max_entries) {
|
||||
// TODO: Delay quickly, hope the writer thread catches up before panicking
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
panic_from_thread(__LINE__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// store the log data!
|
||||
msg = &dlog_queue[buffer_id][dlog_queue_next[buffer_id]];
|
||||
|
||||
// Construct and store the msg....
|
||||
memset(msg, 0, sizeof(DLOG_MSG));
|
||||
msg->level = level;
|
||||
msg->tsms = tsms;
|
||||
strncpy(msg->calling_function, func, 47);
|
||||
msg->calling_function[47] = 0;
|
||||
msg->msg = &msg_buffer[buffer_id][msg_buf_idx[buffer_id]];
|
||||
va_start(args, format);
|
||||
i = vsnprintf(msg->msg, 1023, format, args);
|
||||
msg->msg[i] = 0;
|
||||
va_end(args);
|
||||
|
||||
if (((msg_buf_idx[buffer_id]+i+2) > msg_buf_maxsz) || (dlog_queue_next[buffer_id] >= dlog_queue_max_entries)) {
|
||||
// this is ok, since we overallocate by more than 1KB on purpose
|
||||
// but not great for logging!
|
||||
|
||||
// we won't bump things, so the next line to the logger will overwrite this one
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
if ((log_to_console) && (level >= log_level_console)) {
|
||||
localtime_r(&tv.tv_sec, &tm_info);
|
||||
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", &tm_info);
|
||||
if (log_calling_function) {
|
||||
fprintf(log_to_stderr?stderr:stdout, "LOGGER OVERRUN:%s.%03ld [%44s] %s: %s\n", time_buffer, tv.tv_usec / 1000, func, level_text[level], msg->msg);
|
||||
} else {
|
||||
fprintf(log_to_stderr?stderr:stdout, "LOGGER OVERRUN:%s.%03ld %s: %s\n", time_buffer, tv.tv_usec / 1000, level_text[level], msg->msg);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg_buf_idx[buffer_id] += i+2; // increment the index
|
||||
dlog_queue_next[buffer_id]++; // bounds check is above, since we can potentially delay to wait for the writer instead of failing here
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t get_midnight_timestamp(void) {
|
||||
time_t now = time(NULL);
|
||||
struct tm *tm_now = localtime(&now);
|
||||
tm_now->tm_hour = 0;
|
||||
tm_now->tm_min = 0;
|
||||
tm_now->tm_sec = 0;
|
||||
tm_now->tm_mday += 1;
|
||||
time_t midnight = mktime(tm_now);
|
||||
return midnight;
|
||||
}
|
||||
|
||||
void * datum_logger_thread(void *ptr) {
|
||||
int buffer_id,offline_buffer_id;
|
||||
int i,j;
|
||||
uint64_t sts,ets,lflush;
|
||||
time_t seconds;
|
||||
int millis;
|
||||
struct tm tm_info_storage;
|
||||
struct tm *tm_info;
|
||||
DLOG_MSG *msg;
|
||||
char time_buffer[40];
|
||||
char log_line[1200];
|
||||
FILE *log_handle = NULL;
|
||||
time_t next_log_rotate = get_midnight_timestamp();
|
||||
time_t log_file_opened = time(NULL);
|
||||
|
||||
msg_buffer[0] = calloc((DLOG_MSG_BUF_SIZE * 2) + (1024*8),1);
|
||||
if (!msg_buffer[0]) {
|
||||
DLOG(DLOG_LEVEL_FATAL, "Could not allocate memory for logger queue!");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
// split the allocation in half for the double buffering
|
||||
msg_buffer[1] = &msg_buffer[0][DLOG_MSG_BUF_SIZE + (1024*4)];
|
||||
|
||||
dlog_queue_max_entries = (DLOG_MSG_BUF_SIZE / sizeof(DLOG_MSG)) - 1;
|
||||
if (dlog_queue_max_entries < 1024) dlog_queue_max_entries = 1024;
|
||||
dlog_queue[0] = calloc(dlog_queue_max_entries * 2 * sizeof(DLOG_MSG),1);
|
||||
if (!dlog_queue[0]) {
|
||||
DLOG(DLOG_LEVEL_FATAL, "Could not allocate memory for logger queue list!");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
dlog_queue[1] = &dlog_queue[0][dlog_queue_max_entries];
|
||||
|
||||
if ((log_to_file) && (log_file[0] != 0)) {
|
||||
log_handle = fopen(log_file,"a");
|
||||
if (!log_handle) {
|
||||
DLOG(DLOG_LEVEL_FATAL, "Could not open log file!");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// alert the masses.
|
||||
datum_logger_initialized = true;
|
||||
|
||||
DLOG(DLOG_LEVEL_DEBUG, "Logging thread started! (Approximately %d MB of RAM allocated for up to %d entries per cycle)", (DLOG_MSG_BUF_SIZE * 4)/1024/1024, dlog_queue_max_entries);
|
||||
lflush = 0;
|
||||
|
||||
while(1) {
|
||||
sts = current_time_micros();
|
||||
|
||||
// wait for there to be msgs to log, and log them!
|
||||
// We don't need to read lock to read this, as we're the only thread that writes to it.
|
||||
buffer_id = dlog_active_buffer;
|
||||
|
||||
pthread_rwlock_rdlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
i = dlog_queue_next[buffer_id];
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
|
||||
if (i) {
|
||||
// there are msgs to write.
|
||||
// switch the writers over to the other buffer, and then work on that
|
||||
// TODO: Think through the process here, ensure no race conditions with locking all at once.
|
||||
|
||||
// this lock prevents msgs from being queued and holds up all other threads
|
||||
// we need to release it ASAP
|
||||
pthread_rwlock_wrlock(&dlog_active_buffer_rwlock);
|
||||
|
||||
// we'll get a lock on writing to the current buffer.
|
||||
pthread_rwlock_wrlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
|
||||
// at this point we could have threads waiting on the buffer ID, and
|
||||
// we also could have threads waiting to write to the buffer we just got a
|
||||
// write lock on if the beat the race to lock the buffer_id
|
||||
// so we must increment the version of the current buffer, which will signal it's stale
|
||||
dlog_queue_version[buffer_id]++;
|
||||
|
||||
// no one should be waiting to write the other buffer
|
||||
offline_buffer_id = buffer_id?0:1;
|
||||
pthread_rwlock_wrlock(&dlog_buffer_rwlock[offline_buffer_id]);
|
||||
|
||||
// we now have write locks on everything
|
||||
// increment version again, just in case
|
||||
dlog_queue_version[offline_buffer_id]++;
|
||||
|
||||
// store the new offline buffer ID as the active
|
||||
dlog_active_buffer_version = dlog_queue_version[offline_buffer_id];
|
||||
|
||||
// make the offline buffer the active one
|
||||
dlog_active_buffer = offline_buffer_id;
|
||||
|
||||
// just in case
|
||||
dlog_queue_next[offline_buffer_id] = 0;
|
||||
|
||||
// release the lock on the offline
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[offline_buffer_id]);
|
||||
|
||||
// release the lock on the buffer index... which releases any threads waiting to write
|
||||
pthread_rwlock_unlock(&dlog_active_buffer_rwlock);
|
||||
|
||||
for(i=0;i<dlog_queue_next[buffer_id];i++) {
|
||||
// do things with msgs
|
||||
msg = &dlog_queue[buffer_id][i];
|
||||
seconds = msg->tsms / 1000;
|
||||
millis = msg->tsms % 1000;
|
||||
tm_info = localtime_r(&seconds, &tm_info_storage);
|
||||
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
if (log_calling_function) {
|
||||
j = snprintf(log_line, 1199, "%s.%03d [%44s] %s: %s\n", time_buffer, millis, msg->calling_function, level_text[msg->level], msg->msg);
|
||||
} else {
|
||||
j = snprintf(log_line, 1199, "%s.%03d %s: %s\n", time_buffer, millis, level_text[msg->level], msg->msg);
|
||||
}
|
||||
log_line[1199] = 0;
|
||||
|
||||
if ((log_to_console) && (msg->level >= log_level_console)) {
|
||||
fprintf(log_to_stderr?stderr:stdout, "%s", log_line);
|
||||
}
|
||||
|
||||
if ((log_to_file) && (msg->level >= log_level_file)) {
|
||||
if (log_handle) {
|
||||
// TODO: Error handling here.
|
||||
fwrite(log_line, j, 1, log_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all done
|
||||
msg_buf_idx[buffer_id] = 0;
|
||||
dlog_queue_next[buffer_id] = 0;
|
||||
pthread_rwlock_unlock(&dlog_buffer_rwlock[buffer_id]);
|
||||
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
ets = current_time_micros();
|
||||
|
||||
if ((log_to_file) && (log_handle)) {
|
||||
if ((sts - lflush) > 1000000) {
|
||||
fflush(log_handle);
|
||||
lflush = sts;
|
||||
}
|
||||
}
|
||||
|
||||
if ((log_rotate_daily) && (log_to_file) && (log_handle)) {
|
||||
if (next_log_rotate < (ets/1000000ULL)) {
|
||||
DLOG(DLOG_LEVEL_INFO, "Rotating log file!");
|
||||
|
||||
tm_info = localtime_r(&log_file_opened, &tm_info_storage);
|
||||
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d", tm_info);
|
||||
snprintf(log_line, 1199, "%s.%s", log_file, time_buffer);
|
||||
log_line[1199] = 0;
|
||||
|
||||
fclose(log_handle);
|
||||
if (rename(log_file, log_line) != 0) {
|
||||
DLOG(DLOG_LEVEL_ERROR, "Could not rename log file for rotation!");
|
||||
}
|
||||
|
||||
log_handle = fopen(log_file,"a");
|
||||
if (!log_handle) {
|
||||
DLOG(DLOG_LEVEL_FATAL, "Could not open log file after rotation!");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
|
||||
next_log_rotate = get_midnight_timestamp()+1;
|
||||
log_file_opened = time(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (ets > sts) {
|
||||
ets = ets - sts;
|
||||
} else {
|
||||
ets = 0;
|
||||
}
|
||||
|
||||
if (ets < 56999) {
|
||||
j = (57000 - ets) / 1000;
|
||||
j++;
|
||||
for(i=0;i<j;i++) {
|
||||
if (panic_mode) {
|
||||
i = j;
|
||||
} else {
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int datum_logger_init(void) {
|
||||
pthread_t pthread_datum_logger_thread;
|
||||
|
||||
pthread_create(&pthread_datum_logger_thread, NULL, datum_logger_thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
88
src/datum_logger.h
Normal file
88
src/datum_logger.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_LOGGER_H_
|
||||
#define _DATUM_LOGGER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// approximately this times 3 will be used
|
||||
// NOTE: With huge debug logging, this CAN potentially overrun and fail
|
||||
#define DLOG_MSG_BUF_SIZE (1024*1024*8)
|
||||
|
||||
typedef struct {
|
||||
int level;
|
||||
uint64_t tsms;
|
||||
char calling_function[48];
|
||||
char *msg;
|
||||
} DLOG_MSG;
|
||||
|
||||
#define DLOG_LEVEL_ALL 0
|
||||
#define DLOG_LEVEL_DEBUG 1
|
||||
#define DLOG_LEVEL_INFO 2
|
||||
#define DLOG_LEVEL_WARN 3
|
||||
#define DLOG_LEVEL_ERROR 4
|
||||
#define DLOG_LEVEL_FATAL 5
|
||||
|
||||
void datum_logger(const char *func, int level, const char *format, ...);
|
||||
int datum_logger_queue_msg(const char *func, int level, const char *format, ...);
|
||||
|
||||
// Generic for dynamic log level messages
|
||||
#define DLOG(level, format, ...) datum_logger_queue_msg(__func__, level, format __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
// for leftover logging code, default to debug level
|
||||
#define LOG_PRINTF(format, ...) datum_logger_queue_msg(__func__, DLOG_LEVEL_DEBUG, format __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
// macros for various log levels
|
||||
#define DLOG_DEBUG(format, ...) datum_logger_queue_msg(__func__, DLOG_LEVEL_DEBUG, format __VA_OPT__(,) __VA_ARGS__)
|
||||
#define DLOG_INFO(format, ...) datum_logger_queue_msg(__func__, DLOG_LEVEL_INFO, format __VA_OPT__(,) __VA_ARGS__)
|
||||
#define DLOG_WARN(format, ...) datum_logger_queue_msg(__func__, DLOG_LEVEL_WARN, format __VA_OPT__(,) __VA_ARGS__)
|
||||
#define DLOG_ERROR(format, ...) datum_logger_queue_msg(__func__, DLOG_LEVEL_ERROR, format __VA_OPT__(,) __VA_ARGS__)
|
||||
#define DLOG_FATAL(format, ...) datum_logger_queue_msg(__func__, DLOG_LEVEL_FATAL, format __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
int datum_logger_init(void);
|
||||
void datum_logger_config(
|
||||
bool clog_to_file,
|
||||
bool clog_to_console,
|
||||
int clog_level_console,
|
||||
int clog_level_file,
|
||||
bool clog_calling_function,
|
||||
bool clog_to_stderr,
|
||||
bool clog_rotate_daily,
|
||||
char *clog_file
|
||||
);
|
||||
|
||||
#endif
|
1937
src/datum_protocol.c
Normal file
1937
src/datum_protocol.c
Normal file
File diff suppressed because it is too large
Load Diff
172
src/datum_protocol.h
Normal file
172
src/datum_protocol.h
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_PROTOCOL_H_
|
||||
#define _DATUM_PROTOCOL_H_
|
||||
|
||||
#include <sodium.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "datum_stratum.h"
|
||||
|
||||
// This is a protocol limit! server will truncate down to 8, unless a new spec is done that permits more.
|
||||
// Works out to over 5 minutes of jobs at 30-40 second work change intervals. No miner should be holding on to work this long.
|
||||
#define MAX_DATUM_PROTOCOL_JOBS 8
|
||||
|
||||
#define DATUM_PROTOCOL_VERSION "v0.2-beta" // this is sent to the server as a UA
|
||||
#define DATUM_PROTOCOL_CONNECT_TIMEOUT 30
|
||||
|
||||
#define DATUM_PROTOCOL_MAX_CMD_DATA_SIZE 4194304 // 2^22 - protocol limit!
|
||||
#define DATUM_PROTOCOL_BUFFER_SIZE (DATUM_PROTOCOL_MAX_CMD_DATA_SIZE*3)
|
||||
|
||||
#define MAX_DATUM_CLIENT_EVENTS 32
|
||||
|
||||
// Header is only XOR'd with a rotating key. This is NOT 100% secure, and makes the cmd# and length of the handshake message decipherable.
|
||||
// This is not an issue for security, however, as all following packets use a negotiated XOR key.
|
||||
// It's likely possible to brute force the XOR key to break packets down into individual commands, but the contents and nature of the
|
||||
// cmd is still obfuscated and unrecoverable without the session keys.
|
||||
|
||||
typedef struct __attribute__((packed)) T_DATUM_PROTOCOL_HEADER {
|
||||
uint32_t cmd_len:22; // max cmd size is 2^22 (~4MB), which is roughly the max block size for a raw submission or a raw template validation
|
||||
uint8_t reserved:2; // save for later use
|
||||
bool is_signed:1;
|
||||
bool is_encrypted_pubkey:1;
|
||||
bool is_encrypted_channel:1;
|
||||
uint8_t proto_cmd:5; // 32 protocol level commands
|
||||
} T_DATUM_PROTOCOL_HEADER;
|
||||
|
||||
typedef struct {
|
||||
bool is_remote;
|
||||
|
||||
// ed25519 key pair (signing)
|
||||
unsigned char pk_ed25519[crypto_sign_PUBLICKEYBYTES];
|
||||
unsigned char sk_ed25519[crypto_sign_SECRETKEYBYTES];
|
||||
|
||||
// x25519 key pair (encyption)
|
||||
unsigned char pk_x25519[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char sk_x25519[crypto_box_SECRETKEYBYTES];
|
||||
} DATUM_ENC_KEYS;
|
||||
|
||||
typedef struct {
|
||||
DATUM_ENC_KEYS *local;
|
||||
DATUM_ENC_KEYS *remote;
|
||||
unsigned char precomp_remote[crypto_box_BEFORENMBYTES];
|
||||
} DATUM_ENC_PRECOMP;
|
||||
|
||||
typedef struct T_DATUM_PROTOCOL_JOB {
|
||||
unsigned char datum_job_id;
|
||||
T_DATUM_STRATUM_JOB *sjob;
|
||||
|
||||
bool server_has_merkle_branches;
|
||||
|
||||
bool server_has_coinbase[8];
|
||||
bool server_has_coinbase_empty;
|
||||
bool server_has_short_txnlist;
|
||||
|
||||
bool server_has_validated_block;
|
||||
} T_DATUM_PROTOCOL_JOB;
|
||||
|
||||
typedef struct {
|
||||
unsigned char datum_job_id;
|
||||
unsigned char extranonce[12];
|
||||
char username[384];
|
||||
unsigned char coinbase_id;
|
||||
bool subsidy_only;
|
||||
bool is_block;
|
||||
bool quickdiff;
|
||||
unsigned char target_byte;
|
||||
uint16_t target_byte_index;
|
||||
uint32_t ntime;
|
||||
uint32_t nonce;
|
||||
uint32_t version;
|
||||
} T_DATUM_PROTOCOL_POW;
|
||||
|
||||
int datum_protocol_init(void);
|
||||
int datum_encrypt_generate_keys(DATUM_ENC_KEYS *keys);
|
||||
bool datum_protocol_is_active(void);
|
||||
void datum_increment_session_nonce(void *s);
|
||||
int datum_protocol_fetch_coinbaser(uint64_t value);
|
||||
int datum_protocol_coinbaser_fetch(void *s);
|
||||
int datum_protocol_pow_submit(
|
||||
const T_DATUM_CLIENT_DATA *c,
|
||||
const T_DATUM_STRATUM_JOB *job,
|
||||
const char *username,
|
||||
const bool was_block,
|
||||
const bool subsidy_only,
|
||||
const bool quickdiff,
|
||||
const unsigned char *block_header,
|
||||
const uint64_t target_diff,
|
||||
const unsigned char *full_cb_tx,
|
||||
const T_DATUM_STRATUM_COINBASE *cb,
|
||||
unsigned char *extranonce,
|
||||
unsigned char coinbase_index
|
||||
);
|
||||
|
||||
bool datum_protocol_thread_is_active(void);
|
||||
void datum_protocol_start_connector(void);
|
||||
unsigned char datum_protocol_setup_new_job_idx(void *sx);
|
||||
|
||||
extern uint64_t datum_accepted_share_count;
|
||||
extern uint64_t datum_accepted_share_diff;
|
||||
extern uint64_t datum_rejected_share_count;
|
||||
extern uint64_t datum_rejected_share_diff;
|
||||
|
||||
#define DATUM_REJECT_BAD_JOB_ID 10
|
||||
#define DATUM_REJECT_BAD_COINBASE_ID 11
|
||||
#define DATUM_REJECT_BAD_EXTRANONCE_SIZE 12
|
||||
#define DATUM_REJECT_BAD_TARGET 13
|
||||
#define DATUM_REJECT_BAD_USERNAME 14
|
||||
#define DATUM_REJECT_BAD_COINBASER_ID 15
|
||||
#define DATUM_REJECT_BAD_MERKLE_COUNT 16
|
||||
#define DATUM_REJECT_BAD_COINBASE_TOO_LARGE 17
|
||||
#define DATUM_REJECT_COINBASE_MISSING 18
|
||||
#define DATUM_REJECT_TARGET_MISMATCH 19
|
||||
#define DATUM_REJECT_H_NOT_ZERO 20
|
||||
#define DATUM_REJECT_HIGH_HASH 21
|
||||
#define DATUM_REJECT_COINBASE_ID_MISMATCH 22
|
||||
#define DATUM_REJECT_BAD_NTIME 23
|
||||
#define DATUM_REJECT_BAD_VERSION 24
|
||||
#define DATUM_REJECT_STALE_BLOCK 25
|
||||
#define DATUM_REJECT_BAD_COINBASE 26
|
||||
#define DATUM_REJECT_BAD_COINBASE_OUTPUTS 27
|
||||
#define DATUM_REJECT_MISSING_POOL_TAG 28
|
||||
#define DATUM_REJECT_DUPLICATE_WORK 29
|
||||
#define DATUM_REJECT_OTHER 30
|
||||
|
||||
#define DATUM_POW_SHARE_RESPONSE_ACCEPTED 0x50
|
||||
#define DATUM_POW_SHARE_RESPONSE_ACCEPTED_TENTATIVELY 0x55
|
||||
#define DATUM_POW_SHARE_RESPONSE_REJECTED 0x66
|
||||
|
||||
#endif
|
252
src/datum_queue.c
Normal file
252
src/datum_queue.c
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Generic threaded queue implementation.
|
||||
// Used for DATUM Protocol share submissions.
|
||||
// TODO: Use for share logger?
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "datum_queue.h"
|
||||
#include "datum_logger.h"
|
||||
#include "datum_utils.h"
|
||||
|
||||
int datum_queue_free(DATUM_QUEUE *q) {
|
||||
if (!q->initialized) return -1;
|
||||
|
||||
pthread_rwlock_wrlock(&q->active_buffer_rwlock);
|
||||
pthread_rwlock_wrlock(&q->buffer_rwlock[0]);
|
||||
pthread_rwlock_wrlock(&q->buffer_rwlock[1]);
|
||||
|
||||
if (q->buffer[0]) {
|
||||
free(q->buffer[0]);
|
||||
}
|
||||
|
||||
q->initialized = false;
|
||||
q->buffer[0] = 0;
|
||||
q->buffer[0] = 0;
|
||||
|
||||
pthread_rwlock_unlock(&q->active_buffer_rwlock);
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[0]);
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[1]);
|
||||
|
||||
pthread_rwlock_destroy(&q->active_buffer_rwlock);
|
||||
pthread_rwlock_destroy(&q->buffer_rwlock[0]);
|
||||
pthread_rwlock_destroy(&q->buffer_rwlock[1]);
|
||||
|
||||
memset(q, 0, sizeof(DATUM_QUEUE));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int datum_queue_prep(DATUM_QUEUE *q, const int max_items, const int item_size, int (*item_handler)(void *)) {
|
||||
memset(q, 0, sizeof(DATUM_QUEUE));
|
||||
|
||||
q->initialized = false;
|
||||
|
||||
if (pthread_rwlock_init(&q->active_buffer_rwlock, NULL) != 0) {
|
||||
DLOG_FATAL("Could not initialize lock 1");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pthread_rwlock_init(&q->buffer_rwlock[0], NULL) != 0) {
|
||||
DLOG_FATAL("Could not initialize lock 2");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pthread_rwlock_init(&q->buffer_rwlock[1], NULL) != 0) {
|
||||
DLOG_FATAL("Could not initialize lock 3");
|
||||
return -1;
|
||||
}
|
||||
|
||||
q->max_entries = max_items;
|
||||
q->queue_version[1] = 10;
|
||||
|
||||
q->buffer[0] = calloc((max_items + 16)*2, item_size);
|
||||
if (!q->buffer[0]) {
|
||||
DLOG_FATAL("Could not allocate memory for queue items! (%d bytes)", (max_items + 16)*2*item_size);
|
||||
return -1;
|
||||
}
|
||||
q->buffer[1] = ((char *)q->buffer[0]) + ((max_items + 16) * item_size);
|
||||
|
||||
q->item_size = item_size;
|
||||
|
||||
// handler function pointer
|
||||
q->item_handler = item_handler;
|
||||
q->initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int datum_queue_add_item(DATUM_QUEUE *q, void *item) {
|
||||
int buffer_id, i;
|
||||
uint64_t buffer_version;
|
||||
void *out;
|
||||
|
||||
if (!q->initialized) return -1;
|
||||
|
||||
// Add the msg to the logger queue
|
||||
// this is probably overkill...
|
||||
for (i=0;i<10000000;i++) {
|
||||
if (i < 99999990) { // ensure we don't get the lock on the last try and forget to unlock and crash
|
||||
// get the active buffer ID
|
||||
pthread_rwlock_rdlock(&q->active_buffer_rwlock);
|
||||
buffer_id = q->active_buffer;
|
||||
buffer_version = q->active_buffer_version;
|
||||
pthread_rwlock_unlock(&q->active_buffer_rwlock);
|
||||
|
||||
// get a write lock for that buffer
|
||||
pthread_rwlock_wrlock(&q->buffer_rwlock[buffer_id]);
|
||||
|
||||
// check for race condition on buffer swap
|
||||
if (buffer_version != q->queue_version[buffer_id]) {
|
||||
// Race condition!
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[buffer_id]);
|
||||
} else {
|
||||
// no race condition, we're good
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= 10000000) {
|
||||
// we have no locks, but we also couldn't sync up on the rare race condition after 10000000 attempts
|
||||
// means something very bad is probably happening.
|
||||
DLOG_ERROR("Could not satisfy queue race condition. Is there anything consuming this queue? Likely a bug!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (q->queue_next[buffer_id] >= q->max_entries) {
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[buffer_id]);
|
||||
DLOG_ERROR("Queue overflow! Is there anything consuming this queue? Likely a bug!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
out = ((char *)q->buffer[buffer_id]) + (q->queue_next[buffer_id] * q->item_size);
|
||||
memcpy(out, item, q->item_size);
|
||||
q->queue_next[buffer_id]++; // bounds check is above, since we can potentially delay to wait for the writer instead of failing here
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[buffer_id]);
|
||||
//DLOG_DEBUG("QUEUE ADD @ %p", out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int datum_queue_process(DATUM_QUEUE *q) {
|
||||
// process any items in the specified queue
|
||||
// only one thread should ever call this, realistically.
|
||||
// if more than one thread needs to process a queue, this will need a good bit of modification.
|
||||
|
||||
int buffer_id,offline_buffer_id;
|
||||
int i;
|
||||
void *item;
|
||||
|
||||
if (!q->initialized) return -1;
|
||||
|
||||
// We don't need to read lock to read this, as we're the only thread that writes to it.
|
||||
buffer_id = q->active_buffer;
|
||||
|
||||
pthread_rwlock_rdlock(&q->buffer_rwlock[buffer_id]);
|
||||
i = q->queue_next[buffer_id];
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[buffer_id]);
|
||||
|
||||
if (!i) {
|
||||
// nothing in queue
|
||||
return 0;
|
||||
}
|
||||
|
||||
// there are msgs to write.
|
||||
// switch the writers over to the other buffer, and then work on that
|
||||
|
||||
// this lock prevents msgs from being queued and holds up all other threads
|
||||
// we need to release it ASAP
|
||||
pthread_rwlock_wrlock(&q->active_buffer_rwlock);
|
||||
|
||||
// we'll get a lock on writing to the current buffer.
|
||||
pthread_rwlock_wrlock(&q->buffer_rwlock[buffer_id]);
|
||||
|
||||
// at this point we could have threads waiting on the buffer ID, and
|
||||
// we also could have threads waiting to write to the buffer we just got a
|
||||
// write lock on if the beat the race to lock the buffer_id
|
||||
// so we must increment the version of the current buffer, which will signal it's stale
|
||||
q->queue_version[buffer_id]++;
|
||||
|
||||
// no one should be waiting to write the other buffer
|
||||
offline_buffer_id = buffer_id?0:1;
|
||||
pthread_rwlock_wrlock(&q->buffer_rwlock[offline_buffer_id]);
|
||||
|
||||
// we now have write locks on everything
|
||||
// increment version again, just in case
|
||||
q->queue_version[offline_buffer_id]++;
|
||||
|
||||
// store the new offline buffer ID as the active
|
||||
q->active_buffer_version = q->queue_version[offline_buffer_id];
|
||||
|
||||
// make the offline buffer the active one
|
||||
q->active_buffer = offline_buffer_id;
|
||||
|
||||
// just in case
|
||||
q->queue_next[offline_buffer_id] = 0;
|
||||
|
||||
// release the lock on the offline
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[offline_buffer_id]);
|
||||
|
||||
// release the lock on the buffer index... which releases any threads waiting to write
|
||||
pthread_rwlock_unlock(&q->active_buffer_rwlock);
|
||||
|
||||
for(i=0;i<q->queue_next[buffer_id];i++) {
|
||||
// process items
|
||||
item = ((char *)q->buffer[buffer_id]) + (i * q->item_size);
|
||||
q->item_handler(item);
|
||||
// TODO: Handle errors from handler?
|
||||
// If such a thing is needed in the future, implement it here so as not to break other things using these queues.
|
||||
}
|
||||
|
||||
// all done
|
||||
q->queue_next[buffer_id] = 0;
|
||||
pthread_rwlock_unlock(&q->buffer_rwlock[buffer_id]);
|
||||
|
||||
return i;
|
||||
}
|
63
src/datum_queue.h
Normal file
63
src/datum_queue.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_QUEUE_H_
|
||||
#define _DATUM_QUEUE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
volatile bool initialized;
|
||||
int max_entries;
|
||||
pthread_rwlock_t active_buffer_rwlock;
|
||||
int active_buffer;
|
||||
uint64_t active_buffer_version;
|
||||
pthread_rwlock_t buffer_rwlock[2];
|
||||
int queue_next[2];
|
||||
uint64_t queue_version[2];
|
||||
size_t item_size;
|
||||
int buf_idx[2];
|
||||
void *buffer[2];
|
||||
// pointer to processor function
|
||||
int (*item_handler)(void *);
|
||||
} DATUM_QUEUE;
|
||||
|
||||
int datum_queue_prep(DATUM_QUEUE *q, const int max_items, const int item_size, int (*item_handler)(void *));
|
||||
int datum_queue_process(DATUM_QUEUE *q);
|
||||
int datum_queue_add_item(DATUM_QUEUE *q, void *item);
|
||||
int datum_queue_free(DATUM_QUEUE *q);
|
||||
|
||||
#endif
|
719
src/datum_sockets.c
Normal file
719
src/datum_sockets.c
Normal file
@ -0,0 +1,719 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <stdatomic.h>
|
||||
#include <jansson.h>
|
||||
#include <inttypes.h>
|
||||
#include <curl/curl.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include "datum_conf.h"
|
||||
#include "datum_gateway.h"
|
||||
#include "datum_protocol.h"
|
||||
#include "datum_utils.h"
|
||||
#include "datum_sockets.h"
|
||||
|
||||
int datum_active_threads = 0;
|
||||
int datum_active_clients = 0;
|
||||
|
||||
int get_remote_ip(int fd, char *ip, size_t max_len) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
// Get the address of the peer
|
||||
if (getpeername(fd, (struct sockaddr*)&addr, &addr_len) == -1) {
|
||||
strncpy(ip, "0.0.0.0", max_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if the address is IPv4 or IPv6
|
||||
if (addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||
if (inet_ntop(AF_INET, &s->sin_addr, ip, max_len) == NULL) {
|
||||
strncpy(ip, "0.0.0.0", max_len);
|
||||
return -1;
|
||||
}
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
|
||||
if (inet_ntop(AF_INET6, &s->sin6_addr, ip, max_len) == NULL) {
|
||||
strncpy(ip, "0.0.0.0", max_len);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
strncpy(ip, "0.0.0.0", max_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *datum_threadpool_thread(void *arg) {
|
||||
T_DATUM_THREAD_DATA *my = (T_DATUM_THREAD_DATA *)arg;
|
||||
int i, nfds, n, cidx, j;
|
||||
size_t leftover = 0;
|
||||
|
||||
if (!my->app->client_cmd_func) {
|
||||
DLOG_FATAL("Thread pool thread started with no client command function pointer. :(");
|
||||
panic_from_thread(__LINE__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
my->epollfd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (my->epollfd < 0) {
|
||||
DLOG_FATAL("could not epoll_create!");
|
||||
panic_from_thread(__LINE__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Call application specific thread init
|
||||
if (my->app->init_func) my->app->init_func(my);
|
||||
|
||||
while(1) {
|
||||
pthread_mutex_lock(&my->thread_data_lock);
|
||||
|
||||
if (!my->connected_clients) {
|
||||
// no clients to serve
|
||||
// shutdown this thread after some kind of timeout?
|
||||
pthread_mutex_unlock(&my->thread_data_lock);
|
||||
|
||||
// the loop doesn't care if we have no clients...
|
||||
if (my->app->loop_func) my->app->loop_func(my);
|
||||
|
||||
my->has_client_kill_request = false;
|
||||
my->empty_request = false;
|
||||
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if any new clients, handle them if so
|
||||
if (my->has_new_clients) {
|
||||
for(i=0;i<my->app->max_clients_thread;i++) {
|
||||
if (my->client_data[i].new_connection) {
|
||||
my->client_data[i].new_connection = false;
|
||||
my->client_data[i].in_buf = 0;
|
||||
my->client_data[i].out_buf = 0;
|
||||
|
||||
// add to epoll for this thread
|
||||
my->ev.events = EPOLLIN | EPOLLONESHOT | EPOLLERR; // | EPOLLRDHUP
|
||||
my->ev.data.u64 = i; // store client index... duh
|
||||
if (epoll_ctl(my->epollfd, EPOLL_CTL_ADD, my->client_data[i].fd, &my->ev) < 0) {
|
||||
DLOG_ERROR("epoll_ctl add failed: %s", strerror(errno));
|
||||
close(my->client_data[i].fd); // Close the file descriptor on error
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[i], "epoll_ctl add failed @ new connection");
|
||||
|
||||
// we already have a lock on this thread's data here, so can (must) decrement wo/locking again
|
||||
datum_socket_thread_client_count_decrement(my, i, false);
|
||||
continue;
|
||||
}
|
||||
// call new client handler, if any
|
||||
if (my->app->new_client_func) my->app->new_client_func(&my->client_data[i]);
|
||||
}
|
||||
}
|
||||
my->has_new_clients = false;
|
||||
}
|
||||
pthread_mutex_unlock(&my->thread_data_lock);
|
||||
|
||||
if (__builtin_expect(my->empty_request,0)) {
|
||||
// We got a request to empty all clients from our thread!
|
||||
DLOG_WARN("Executing command to empty thread (%d clients)",my->connected_clients);
|
||||
for (j = 0; j < my->app->max_clients_thread; j++) {
|
||||
if (my->client_data[j].fd != 0) {
|
||||
epoll_ctl(my->epollfd, EPOLL_CTL_DEL, my->client_data[j].fd, NULL);
|
||||
close(my->client_data[j].fd);
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[j], "empty thread command");
|
||||
datum_socket_thread_client_count_decrement(my, j, true);
|
||||
}
|
||||
}
|
||||
} else if (__builtin_expect(my->has_client_kill_request,0)) {
|
||||
// the API has requested we kill a specific client
|
||||
for (j = 0; j < my->app->max_clients_thread; j++) {
|
||||
if ((my->client_data[j].fd != 0) && (my->client_data[j].kill_request)) {
|
||||
my->client_data[j].kill_request = false;
|
||||
epoll_ctl(my->epollfd, EPOLL_CTL_DEL, my->client_data[j].fd, NULL);
|
||||
close(my->client_data[j].fd);
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[j], "client kill command");
|
||||
datum_socket_thread_client_count_decrement(my, j, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
my->has_client_kill_request = false;
|
||||
my->empty_request = false;
|
||||
// Call application specific thread preloop
|
||||
if (my->app->loop_func) my->app->loop_func(my);
|
||||
|
||||
// TODO: make this smarter
|
||||
// See if there's anything to write for any of our clients before looping through all potential clients.
|
||||
// Will need profiling, as this is pretty cheap to do with reasonable max_clients_thread.
|
||||
// If there's data, attempt to send it.
|
||||
for (j = 0; j < my->app->max_clients_thread; j++) {
|
||||
if ((my->client_data[j].fd != 0) && (my->client_data[j].out_buf > 0)) {
|
||||
int sent = send(my->client_data[j].fd, my->client_data[j].w_buffer, my->client_data[j].out_buf, MSG_DONTWAIT);
|
||||
if (sent > 0) {
|
||||
if (sent < my->client_data[j].out_buf) {
|
||||
// not a full send. shift remaining data to beginning of w_buffer
|
||||
memmove(my->client_data[j].w_buffer, my->client_data[j].w_buffer + sent, my->client_data[j].out_buf - sent);
|
||||
}
|
||||
if (sent <= my->client_data[j].out_buf) {
|
||||
my->client_data[j].out_buf -= sent;
|
||||
} else {
|
||||
// should never happen
|
||||
my->client_data[j].out_buf = 0;
|
||||
}
|
||||
} else {
|
||||
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
|
||||
epoll_ctl(my->epollfd, EPOLL_CTL_DEL, my->client_data[j].fd, NULL);
|
||||
close(my->client_data[j].fd);
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[j], "send error");
|
||||
|
||||
datum_socket_thread_client_count_decrement(my, j, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if we have any data to read from any existing clients
|
||||
nfds = epoll_wait(my->epollfd, my->events, MAX_EVENTS, 7);
|
||||
if (nfds < 0) {
|
||||
if (errno != EINTR) {
|
||||
DLOG_ERROR("epoll_wait returned %d", nfds);
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (nfds) {
|
||||
for(i=0;i<nfds;i++) {
|
||||
cidx = my->events[i].data.u64;
|
||||
|
||||
if (cidx >= 0) {
|
||||
n = recv(my->client_data[cidx].fd, &my->client_data[cidx].buffer[my->client_data[cidx].in_buf], CLIENT_BUFFER - 1 - my->client_data[cidx].in_buf, MSG_DONTWAIT);
|
||||
if (n <= 0) {
|
||||
if ((n < 0) && ((errno == EAGAIN || errno == EWOULDBLOCK))) {
|
||||
// we epoll'd without edge triggering. this shouldn't happen!
|
||||
DLOG_DEBUG("recv returned would block or again! shouldn't happen?");
|
||||
continue; // continue for loop
|
||||
} else {
|
||||
// an error occurred or the client closed the connection
|
||||
DLOG_DEBUG("Thread %03d epoll --- Closing fd %d (n=%d) errno=%d (%s) (req bytes: %d)", my->thread_id, my->client_data[cidx].fd, n, errno, strerror(errno), CLIENT_BUFFER - 1 - my->client_data[cidx].in_buf);
|
||||
epoll_ctl(my->epollfd, EPOLL_CTL_DEL, my->client_data[cidx].fd, NULL);
|
||||
close(my->client_data[cidx].fd);
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[cidx], "client closed connection");
|
||||
|
||||
datum_socket_thread_client_count_decrement(my, cidx, true);
|
||||
}
|
||||
} else {
|
||||
// null terminate the buffer for simplicity
|
||||
// this set of functions is currently only used for stratum v1-like protocols, but can easily be adopted to others.
|
||||
my->client_data[cidx].buffer[my->client_data[cidx].in_buf+n] = 0;
|
||||
|
||||
char *start_line = my->client_data[cidx].buffer;
|
||||
char *end_line = strchr(start_line, '\n');
|
||||
|
||||
while (end_line != NULL) {
|
||||
*end_line = 0; // null terminate the line
|
||||
// this function can not be NULL
|
||||
j = my->app->client_cmd_func(&my->client_data[cidx], start_line);
|
||||
if (j < 0) {
|
||||
//LOG_PRINTF("Thread %03d --- Closing fd %d (client_cmd_func returned %d)", my->thread_id, my->client_data[cidx].fd, j);
|
||||
epoll_ctl(my->epollfd, EPOLL_CTL_DEL, my->client_data[cidx].fd, NULL);
|
||||
close(my->client_data[cidx].fd);
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[cidx], "client_cmd_func returned error");
|
||||
|
||||
datum_socket_thread_client_count_decrement(my, cidx, true);
|
||||
start_line[0] = 0;
|
||||
break;
|
||||
}
|
||||
start_line = end_line + 1;
|
||||
end_line = strchr(start_line, '\n');
|
||||
}
|
||||
|
||||
// If any data is leftover, shift it to the beginning of the buffer
|
||||
// TODO: Implement a buffer type that doesn't require memmove on a partial read
|
||||
if (start_line[0] != 0) {
|
||||
leftover = strlen(start_line); // we null terminate the buffer above
|
||||
if (leftover) {
|
||||
memmove(my->client_data[cidx].buffer, start_line, leftover+1); // we null terminated the read above, remember?
|
||||
}
|
||||
} else {
|
||||
leftover = 0;
|
||||
}
|
||||
my->client_data[cidx].in_buf = leftover;
|
||||
if (my->client_data[cidx].in_buf >= (CLIENT_BUFFER - 1)) {
|
||||
// buffer overrun. lose the data. will probably break things, so punt the client. this shouldn't happen with sane clients.
|
||||
my->client_data[cidx].in_buf = 0;
|
||||
my->client_data[cidx].buffer[0] = 0;
|
||||
|
||||
epoll_ctl(my->epollfd, EPOLL_CTL_DEL, my->client_data[cidx].fd, NULL);
|
||||
close(my->client_data[cidx].fd);
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[cidx], "read buffer overrun before client command break");
|
||||
|
||||
datum_socket_thread_client_count_decrement(my, cidx, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (my->client_data[cidx].fd > 0) {
|
||||
// re-add to epoll for this client
|
||||
my->ev.events = EPOLLIN | EPOLLONESHOT;
|
||||
my->ev.data.u64 = cidx; // store client index... duh
|
||||
if (epoll_ctl(my->epollfd, EPOLL_CTL_MOD, my->client_data[cidx].fd, &my->ev) < 0) {
|
||||
// if this fails, there's probably some bad things happening. In any case, we can't continue serving this client so we should punt them.
|
||||
DLOG_ERROR("epoll_ctl mod for client %d", cidx);
|
||||
close(my->client_data[cidx].fd); // Close the file descriptor on error
|
||||
|
||||
// call closed client function, if any
|
||||
if (my->app->closed_client_func) my->app->closed_client_func(&my->client_data[cidx], "epoll_ctl error re-upping client polling");
|
||||
|
||||
datum_socket_thread_client_count_decrement(my, cidx, true);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void clean_thread_data(T_DATUM_THREAD_DATA *d, T_DATUM_SOCKET_APP *app) {
|
||||
int i,ret;
|
||||
|
||||
// clean up clients, just in case
|
||||
for(i=0;i<app->max_clients_thread;i++) {
|
||||
d->client_data[i].new_connection = false;
|
||||
d->client_data[i].fd = 0;
|
||||
d->client_data[i].in_buf = 0;
|
||||
d->client_data[i].out_buf = 0;
|
||||
}
|
||||
|
||||
d->connected_clients = 0;
|
||||
d->next_open_client_index = 0;
|
||||
|
||||
d->has_new_clients = false;
|
||||
|
||||
// clear polling events
|
||||
// TODO: dynamic allocation of buffers
|
||||
memset(&d->ev, 0, sizeof(struct epoll_event));
|
||||
memset(d->events, 0, sizeof(struct epoll_event) * MAX_CLIENTS_THREAD*2);
|
||||
|
||||
// init the mutex
|
||||
ret = pthread_mutex_init(&d->thread_data_lock, NULL);
|
||||
if (ret) {
|
||||
DLOG_FATAL("Could not init mutex for thread data: %s", strerror(ret));
|
||||
panic_from_thread(__LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
// fix the app pointer
|
||||
d->app = app;
|
||||
}
|
||||
|
||||
int assign_to_thread(T_DATUM_SOCKET_APP *app, int fd) {
|
||||
// Only one thread will be calling this function for a particular "app"
|
||||
// under the current design. Safe to assume that multiple clients will
|
||||
// not cause overlap here.
|
||||
|
||||
// Check how many threads are active.
|
||||
// If < max, put the client on a new thread.
|
||||
// If all threads are active, then it should find the one with the fewest clients and place the new client there.
|
||||
|
||||
int i,j,ret,tc=0;
|
||||
|
||||
int tid=-1,cid=-1;
|
||||
|
||||
if (app->datum_active_threads < app->max_threads) {
|
||||
// we have not launched all threads yet, or somehow a thread has become inactive
|
||||
// place this connection on it's own new thread
|
||||
|
||||
// let's assume, for now, that if we don't have all threads active that we're not above max_clients
|
||||
|
||||
// find the first inactive thread
|
||||
for(i=0;i<app->max_threads;i++) {
|
||||
// safe to read this without locking, as we're the only one that should be updating it
|
||||
if (!app->datum_threads[i].is_active) {
|
||||
tid = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tid == -1) {
|
||||
DLOG_ERROR("Possible bug in thread handler. Could not find an inactive thread. datum_active_threads = %d; max_threads = %d", app->datum_active_threads, app->max_threads);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// clean up thread starting data
|
||||
clean_thread_data(&app->datum_threads[tid], app);
|
||||
|
||||
app->datum_threads[tid].thread_id = tid;
|
||||
app->datum_threads[tid].is_active = true;
|
||||
|
||||
if (pthread_create(&app->datum_threads[i].pthread, NULL, datum_threadpool_thread, &app->datum_threads[i]) != 0) {
|
||||
DLOG_ERROR("Could not start new thread for TID %d", tid);
|
||||
return 0;
|
||||
}
|
||||
app->datum_active_threads++;
|
||||
} else {
|
||||
// active threads are maxed already. find one with the fewest clients
|
||||
// in general, it should be safe to read the client count without locking, since
|
||||
// we don't particularly care _right here_ if it's higher than expected from a client
|
||||
// disconnection. We're the only one that increments it.
|
||||
|
||||
// TODO: Profile if locking/unlocking here is sufficiently slow to care or not on the performance side
|
||||
// We don't want to make a clean path to a DoS, even though this is intended as a local service for local miners.
|
||||
|
||||
j = app->max_clients_thread;
|
||||
|
||||
// find the thread with the lowest client count
|
||||
// also tally up the total clients
|
||||
for(i=0;i<app->max_threads;i++) {
|
||||
if (app->datum_threads[i].connected_clients < j) {
|
||||
j = app->datum_threads[i].connected_clients;
|
||||
tid = i;
|
||||
}
|
||||
tc+=app->datum_threads[i].connected_clients;
|
||||
}
|
||||
|
||||
if (tid == -1) {
|
||||
DLOG_INFO("All threads have max clients! Rejecting connection. :(");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tc >= app->max_clients) {
|
||||
DLOG_INFO("Sum of clients on all threads at configured global maximum (%d) Rejecting connection. :(", app->max_clients);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// lock the thread's data for a moment
|
||||
ret = pthread_mutex_lock(&app->datum_threads[tid].thread_data_lock);
|
||||
if (ret != 0) {
|
||||
DLOG_FATAL("Could not lock mutex for thread data on TID %d: %s", strerror(ret));
|
||||
panic_from_thread(__LINE__); // Is this panic worthy? should never happen
|
||||
return 0;
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (app->datum_threads[tid].connected_clients >= app->max_clients_thread) {
|
||||
pthread_mutex_unlock(&app->datum_threads[tid].thread_data_lock);
|
||||
DLOG_ERROR("Attempted to assign client to thread %d, which already has MAX CLIENTS %d >= %d", tid, app->datum_threads[tid].connected_clients, app->max_clients_thread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get the client's cid
|
||||
cid = app->datum_threads[tid].next_open_client_index;
|
||||
|
||||
// sanity check: confirm this cid is usable
|
||||
if (app->datum_threads[tid].client_data[cid].fd != 0) {
|
||||
DLOG_ERROR("Possible bug: Desync with next_open_client_index. Expected open client slot @ %d on non-maxed thread %d! (shows fd = %d)", cid, tid, app->datum_threads[tid].client_data[cid].fd);
|
||||
|
||||
// let's try the hard way to find an open slot
|
||||
cid = -1;
|
||||
for(i=0;i<app->max_clients_thread;i++) {
|
||||
if (app->datum_threads[tid].client_data[i].fd == 0) {
|
||||
cid = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cid != -1) {
|
||||
DLOG_ERROR("Possible bug: Found an open client slot the hard way. Recovering. TID=%d CID=%d", tid, cid);
|
||||
} else {
|
||||
DLOG_ERROR("Possible bug: Could not find an open client slot the hard way! Rejecting client for TID=%d (%d clients)", tid, app->datum_threads[tid].connected_clients);
|
||||
pthread_mutex_unlock(&app->datum_threads[tid].thread_data_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// prep the next open CID by finding the next open slot
|
||||
app->datum_threads[tid].next_open_client_index = cid + 1;
|
||||
if (app->datum_threads[tid].next_open_client_index == app->max_clients_thread) app->datum_threads[tid].next_open_client_index = 0;
|
||||
|
||||
// prep the next open CID
|
||||
for(i=app->datum_threads[tid].next_open_client_index; i != cid;) {
|
||||
if (app->datum_threads[tid].client_data[i].fd == 0) {
|
||||
// i is good
|
||||
app->datum_threads[tid].next_open_client_index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// loop i around
|
||||
i++;
|
||||
if (i >= app->max_clients_thread) i = 0;
|
||||
}
|
||||
|
||||
if (i == cid) {
|
||||
// we couldn't find an open client slot for the next client :(
|
||||
DLOG_DEBUG("Placing client on maxed out thread TID=%d CID=%d ... Thread is now FULL!",tid,cid);
|
||||
app->datum_threads[tid].next_open_client_index = app->max_clients_thread-1;
|
||||
}
|
||||
|
||||
// bump connected client count
|
||||
app->datum_threads[tid].connected_clients++;
|
||||
|
||||
// clear up and prep slot's client data without clobbering app_client_data
|
||||
app->datum_threads[tid].client_data[cid].fd = fd;
|
||||
app->datum_threads[tid].client_data[cid].cid = cid;
|
||||
app->datum_threads[tid].client_data[cid].new_connection = true;
|
||||
app->datum_threads[tid].client_data[cid].datum_thread = (void *)&app->datum_threads[tid];
|
||||
app->datum_threads[tid].client_data[cid].in_buf = 0;
|
||||
app->datum_threads[tid].client_data[cid].out_buf = 0;
|
||||
app->datum_threads[tid].has_new_clients = true;
|
||||
|
||||
pthread_mutex_unlock(&app->datum_threads[tid].thread_data_lock);
|
||||
|
||||
if (!tc) {
|
||||
// tally clients for our debug
|
||||
for(i=0;i<app->max_threads;i++) {
|
||||
tc+=app->datum_threads[i].connected_clients;
|
||||
}
|
||||
} else {
|
||||
tc++;
|
||||
}
|
||||
|
||||
get_remote_ip(fd, app->datum_threads[tid].client_data[cid].rem_host, DATUM_MAX_IP_LEN);
|
||||
|
||||
DLOG_DEBUG("New client (%s) on TID %d, CID %d with fd %d. clients: %d / clients on thread: %d", app->datum_threads[tid].client_data[cid].rem_host, tid, cid, fd, tc, app->datum_threads[tid].connected_clients);
|
||||
DLOG_DEBUG("app->datum_threads[tid].next_open_client_index = %d", app->datum_threads[tid].next_open_client_index);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *datum_gateway_listener_thread(void *arg) {
|
||||
struct sockaddr_in serveraddr;
|
||||
int i, ret;
|
||||
int reuse = 1;
|
||||
bool rejecting_now = false;
|
||||
uint64_t last_reject_msg_tsms = 0, curtime_tsms = 0;
|
||||
uint64_t reject_count = 0;
|
||||
|
||||
T_DATUM_SOCKET_APP *app = (T_DATUM_SOCKET_APP *)arg;
|
||||
|
||||
struct epoll_event ev, events[MAX_EVENTS];
|
||||
int listen_sock, conn_sock, nfds, epollfd;
|
||||
|
||||
if (!app) {
|
||||
DLOG_FATAL("Called without application data structure. :(");
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Setting up app '%s' on port %d. (T:%d/TC:%d/C:%d)", app->name, app->listen_port, app->max_threads, app->max_clients_thread, app->max_clients);
|
||||
|
||||
// we assume the caller sets up the thread data in some way
|
||||
// don't clobber those pointers
|
||||
for(i=0;i<app->max_threads;i++) {
|
||||
ret = pthread_mutex_init(&app->datum_threads[i].thread_data_lock, NULL);
|
||||
if (ret) {
|
||||
DLOG_FATAL("Could not init mutex for thread data: %s", strerror(ret));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// set app data pointer
|
||||
app->datum_threads[i].app = app;
|
||||
app->datum_threads[i].thread_id = i;
|
||||
app->datum_threads[i].connected_clients = 0;
|
||||
app->datum_threads[i].next_open_client_index = 0;
|
||||
}
|
||||
|
||||
app->datum_active_threads = 0;
|
||||
|
||||
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (!listen_sock) {
|
||||
DLOG_FATAL("Could get socket: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
datum_socket_setoptions(listen_sock);
|
||||
memset(&serveraddr, 0, sizeof(serveraddr));
|
||||
serveraddr.sin_family = AF_INET;
|
||||
serveraddr.sin_port = htons(app->listen_port);
|
||||
|
||||
// TODO: Add option to bind to specific IP per configuration!
|
||||
serveraddr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) {
|
||||
DLOG_FATAL("setsockopt(SO_REUSEADDR) failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(bind(listen_sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
|
||||
DLOG_FATAL("bind failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (listen(listen_sock, 10) < 0) {
|
||||
DLOG_FATAL("listen failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
epollfd = epoll_create1(0);
|
||||
if (epollfd < 0) {
|
||||
DLOG_FATAL("epoll_create1 failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = listen_sock;
|
||||
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev)<0) {
|
||||
DLOG_FATAL("epoll_ctl failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DLOG_INFO("DATUM Socket listener thread active for '%s'", app->name);
|
||||
|
||||
for (;;) {
|
||||
nfds = epoll_wait(epollfd, events, MAX_EVENTS, 100);
|
||||
if (nfds) {
|
||||
if (datum_config.datum_pooled_mining_only && (!datum_protocol_is_active())) {
|
||||
curtime_tsms = current_time_millis(); // we only need this if we're rejecting connections
|
||||
if (!rejecting_now) {
|
||||
last_reject_msg_tsms = curtime_tsms - 5000; // first disconnect triggers msg
|
||||
}
|
||||
rejecting_now = true;
|
||||
} else {
|
||||
rejecting_now = false;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < nfds; ++n) {
|
||||
if (events[n].data.fd == listen_sock) {
|
||||
conn_sock = accept(listen_sock, NULL, NULL);
|
||||
if (conn_sock < 0) {
|
||||
DLOG_ERROR("accept failed: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rejecting_now) {
|
||||
reject_count++;
|
||||
if ((curtime_tsms - last_reject_msg_tsms) > 5000) {
|
||||
DLOG_INFO("DATUM not connected and configured for pooled mining only! Rejecting connection. (%d connections rejected since last noted)", reject_count);
|
||||
last_reject_msg_tsms = curtime_tsms;
|
||||
reject_count = 0;
|
||||
}
|
||||
close(conn_sock);
|
||||
continue;
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Accepted socket to fd %d", conn_sock);
|
||||
datum_socket_setoptions(conn_sock);
|
||||
|
||||
// assign socket to a thread
|
||||
i = assign_to_thread(app, conn_sock);
|
||||
if (!i) {
|
||||
// error finding a thread (too many connections?)
|
||||
DLOG_DEBUG("Closing socket we couldn't assign %d", conn_sock);
|
||||
close(conn_sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void datum_socket_setoptions(int sock) {
|
||||
int opts;
|
||||
int flag = 1;
|
||||
|
||||
opts = fcntl(sock,F_GETFL);
|
||||
if (opts < 0) {
|
||||
DLOG_FATAL("fcntl(F_GETFL) failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
opts = (opts | O_NONBLOCK);
|
||||
if (fcntl(sock,F_SETFL,opts) < 0) {
|
||||
DLOG_FATAL("fcntl(F_SETFL) failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
|
||||
// Set the TCP_NODELAY option
|
||||
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)) < 0) {
|
||||
DLOG_FATAL("setsockopt(TCP_NODELAY) failed: %s", strerror(errno));
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
int datum_socket_send_string_to_client(T_DATUM_CLIENT_DATA *c, char *s) {
|
||||
int len = strlen(s);
|
||||
if (!len) return 0;
|
||||
if ((c->out_buf + len) >= CLIENT_BUFFER) return -1;
|
||||
strncpy(&c->w_buffer[c->out_buf], s, CLIENT_BUFFER-(c->out_buf)-1);
|
||||
c->out_buf += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
int datum_socket_send_chars_to_client(T_DATUM_CLIENT_DATA *c, char *s, int len) {
|
||||
if (!len) return 0;
|
||||
if ((c->out_buf + len) >= CLIENT_BUFFER) return -1;
|
||||
if (len > (CLIENT_BUFFER-(c->out_buf)-1)) {
|
||||
len = CLIENT_BUFFER-(c->out_buf)-1;
|
||||
}
|
||||
memcpy(&c->w_buffer[c->out_buf], s, len);
|
||||
c->out_buf += len;
|
||||
return len;
|
||||
}
|
188
src/datum_sockets.h
Normal file
188
src/datum_sockets.h
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_SOCKETS_H_
|
||||
#define _DATUM_SOCKETS_H_
|
||||
|
||||
#ifndef T_DATUM_TEMPLATE_DATA
|
||||
#include "datum_blocktemplates.h"
|
||||
#endif
|
||||
|
||||
#include <sys/epoll.h>
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct T_DATUM_THREAD_DATA T_DATUM_THREAD_DATA;
|
||||
typedef struct T_DATUM_CLIENT_DATA T_DATUM_CLIENT_DATA;
|
||||
|
||||
typedef void (*DATUM_ThreadPool_Init_Func)(T_DATUM_THREAD_DATA *);
|
||||
typedef void (*DATUM_ThreadPool_Loop_Func)(T_DATUM_THREAD_DATA *);
|
||||
typedef int (*DATUM_ThreadPool_ClientCmd_Func)(T_DATUM_CLIENT_DATA *, char *);
|
||||
|
||||
typedef void (*DATUM_ThreadPool_ClientClosed_Func)(T_DATUM_CLIENT_DATA *, const char *);
|
||||
typedef void (*DATUM_ThreadPool_ClientNew_Func)(T_DATUM_CLIENT_DATA *);
|
||||
|
||||
#define DATUM_MAX_IP_LEN 64
|
||||
|
||||
// TODO: Make these dynamic
|
||||
// These are hard coded buffer related values, not directly related to the config file values.
|
||||
// We avoid dynamic memory allocation to prevent fragmentation and other hassles, currently.
|
||||
#define MAX_CLIENTS_THREAD 4096
|
||||
#define MAX_THREADS 64
|
||||
#define MAX_EVENTS (MAX_CLIENTS_THREAD*2)
|
||||
|
||||
typedef struct T_DATUM_CLIENT_DATA {
|
||||
bool new_connection;
|
||||
int fd;
|
||||
int cid;
|
||||
|
||||
char buffer[CLIENT_BUFFER];
|
||||
int in_buf;
|
||||
|
||||
char w_buffer[CLIENT_BUFFER];
|
||||
int out_buf;
|
||||
|
||||
char rem_host[DATUM_MAX_IP_LEN+1];
|
||||
|
||||
bool kill_request;
|
||||
|
||||
void *app_client_data;
|
||||
|
||||
T_DATUM_THREAD_DATA *datum_thread;
|
||||
} T_DATUM_CLIENT_DATA;
|
||||
|
||||
typedef struct {
|
||||
char name[32];
|
||||
|
||||
// Called when a new threadpool thread is started
|
||||
DATUM_ThreadPool_Init_Func init_func;
|
||||
|
||||
// Called at the beginning of each loop of the threadpool thread
|
||||
DATUM_ThreadPool_Loop_Func loop_func;
|
||||
|
||||
// Called for each command the threadpool thread receives from a client
|
||||
DATUM_ThreadPool_ClientCmd_Func client_cmd_func;
|
||||
|
||||
// Called each time a client is disconnected (either on purpose or via an error)
|
||||
DATUM_ThreadPool_ClientClosed_Func closed_client_func;
|
||||
|
||||
// Called each time a new client connects and is assigned to a threadpool thread
|
||||
DATUM_ThreadPool_ClientNew_Func new_client_func;
|
||||
|
||||
// TCP port this server will listen on
|
||||
int listen_port;
|
||||
|
||||
// Maximum clients each thread can handle
|
||||
int max_clients_thread;
|
||||
|
||||
// Maximum threads in the thread pool for this server
|
||||
int max_threads;
|
||||
|
||||
// Maximum number of total clients for the server
|
||||
int max_clients;
|
||||
|
||||
// Memory allocated by the app before starting the listener for max_threads worth of thread data
|
||||
// TODO: Dynamically allocate client_data and events
|
||||
T_DATUM_THREAD_DATA *datum_threads;
|
||||
|
||||
int datum_active_threads;
|
||||
} T_DATUM_SOCKET_APP;
|
||||
|
||||
typedef struct {
|
||||
// app functions and global data for socket app
|
||||
T_DATUM_SOCKET_APP *config;
|
||||
|
||||
// application specific data for this thread
|
||||
void *data;
|
||||
} T_DATUM_SOCKET_APP_THREAD_DATA;
|
||||
|
||||
typedef struct T_DATUM_THREAD_DATA {
|
||||
pthread_t pthread;
|
||||
|
||||
bool is_active;
|
||||
|
||||
bool has_new_clients;
|
||||
|
||||
bool empty_request;
|
||||
bool has_client_kill_request;
|
||||
|
||||
int thread_id;
|
||||
//int newBlockCount;
|
||||
|
||||
// each client slot should have a pre-allocated chunk of memory
|
||||
// do not clear this entire structure!
|
||||
T_DATUM_CLIENT_DATA client_data[MAX_CLIENTS_THREAD];
|
||||
|
||||
pthread_mutex_t thread_data_lock;
|
||||
|
||||
int connected_clients;
|
||||
int next_open_client_index;
|
||||
|
||||
struct epoll_event ev, events[MAX_CLIENTS_THREAD*2];
|
||||
int epollfd;
|
||||
|
||||
// information for this socket application
|
||||
// this is global to the socket application
|
||||
T_DATUM_SOCKET_APP *app;
|
||||
|
||||
// Socket application data for threadpool
|
||||
// remember, this is per thread
|
||||
void *app_thread_data;
|
||||
} T_DATUM_THREAD_DATA;
|
||||
|
||||
void *datum_gateway_listener_thread(void *arg);
|
||||
void datum_socket_setoptions(int sock);
|
||||
|
||||
int datum_socket_send_string_to_client(T_DATUM_CLIENT_DATA *c, char *s);
|
||||
int datum_socket_send_chars_to_client(T_DATUM_CLIENT_DATA *c, char *s, int len);
|
||||
|
||||
int assign_to_thread(T_DATUM_SOCKET_APP *app, int fd);
|
||||
void *datum_threadpool_thread(void *arg);
|
||||
|
||||
static inline void datum_socket_thread_client_count_decrement(T_DATUM_THREAD_DATA *my, int cid_who_left, bool not_already_locked) {
|
||||
// compiler will optimize the if's away in most cases, since this is inline
|
||||
if (not_already_locked) pthread_mutex_lock(&my->thread_data_lock);
|
||||
|
||||
// decrement connected client count for the thread
|
||||
my->connected_clients--;
|
||||
|
||||
// if the ID we dropped is less than the expected next, drop it down to speed that up
|
||||
if (cid_who_left < my->next_open_client_index) {
|
||||
my->next_open_client_index = cid_who_left;
|
||||
}
|
||||
my->client_data[cid_who_left].fd = 0;
|
||||
if (not_already_locked) pthread_mutex_unlock(&my->thread_data_lock);
|
||||
}
|
||||
|
||||
#endif
|
2195
src/datum_stratum.c
Normal file
2195
src/datum_stratum.c
Normal file
File diff suppressed because it is too large
Load Diff
290
src/datum_stratum.h
Normal file
290
src/datum_stratum.h
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_STRATUM_H_
|
||||
#define _DATUM_STRATUM_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef T_DATUM_CLIENT_DATA
|
||||
#include "datum_sockets.h"
|
||||
#endif
|
||||
|
||||
#ifndef T_DATUM_TEMPLATE_DATA
|
||||
#include "datum_blocktemplates.h"
|
||||
#endif
|
||||
|
||||
#define MAX_STRATUM_JOBS 256
|
||||
|
||||
#define MAX_COINBASE_TYPES 6
|
||||
#define COINBASE_TYPE_TINY 0 // "empty", just pays pool
|
||||
#define COINBASE_TYPE_SMALL 1 // Nicehash needs a tiny coinb1, among other things. Max 500 bytes.
|
||||
#define COINBASE_TYPE_ANTMAIN 2 // Hack for antminer stock firmware to 750 bytes
|
||||
#define COINBASE_TYPE_RESPECTABLE 3 // 6500 byte max (whatsminers)
|
||||
#define COINBASE_TYPE_YUGE 4 // 16KB max (ePIC, bitaxe)
|
||||
#define COINBASE_TYPE_ANTMAIN2 5 // 2.25KB max (S21, +?)
|
||||
|
||||
// Submitblock json rpc command max size is max block size * 2 for ascii plus some breathing room
|
||||
#define MAX_SUBMITBLOCK_SIZE 8500000
|
||||
|
||||
/////////////////////////////////
|
||||
// Stratum job types
|
||||
/////////////////////////////////
|
||||
|
||||
// Potential job paths are:
|
||||
// 1 -> 3 -> 4 -> 5 -> 5 ...
|
||||
// 2E -> 2F -> 4 -> 5 -> 5 ...
|
||||
|
||||
// Unknown job state. don't use this job.
|
||||
#define JOB_STATE_UNKNOWN 0
|
||||
|
||||
// This job is empty only. No template data available to switch to.
|
||||
// No template is expected to end up on this job and we should IMMEDIATELY change to the next job when seen
|
||||
#define JOB_STATE_EMPTY_ONLY 1
|
||||
|
||||
// this job is the result of a GBT call that got us the latest full template, but we need to do an empty first
|
||||
// use for the empty. wait for other threads. immediately send the full work with the "blank" coinbase
|
||||
// template is max sized for a "blank" coinbase. other coinbases are not expected to be used
|
||||
// this is the fastest empty->full work setup
|
||||
#define JOB_STATE_EMPTY_PLUS 2
|
||||
|
||||
// this job is a full GBT wo/coinbaser which we're expected to immediately broadcast to miners after a JOB_STATE_EMPTY_ONLY
|
||||
#define JOB_STATE_FULL_PRIORITY 3
|
||||
|
||||
// this is a normal job that waits for a full coinbaser setup after either JOB_STATE_FULL_PRIORITY or JOB_STATE_EMPTY_PLUS's full template
|
||||
// it's broadcast immediately when ready
|
||||
#define JOB_STATE_FULL_PRIORITY_WAIT_COINBASER 4
|
||||
|
||||
//
|
||||
// all of the above jobs will use a GBT with the least common denominator for a block size/coinbaser combo. coinbaser gets truncated.
|
||||
//
|
||||
|
||||
// this is a normal job
|
||||
// a GBT call is made, and the coinbaser is queued
|
||||
// once the coinbaser returns (or fails) this job is gently broadcasted to all miners across the work change interval
|
||||
// TODO: Fit the multiple coinbasers to multiple templates sized specifically for them
|
||||
#define JOB_STATE_FULL_NORMAL_WAIT_COINBASER 5
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct {
|
||||
char coinb1[STRATUM_COINBASE1_MAX_LEN];
|
||||
char coinb2[STRATUM_COINBASE2_MAX_LEN];
|
||||
unsigned char coinb1_bin[STRATUM_COINBASE1_MAX_LEN>>1];
|
||||
unsigned char coinb2_bin[STRATUM_COINBASE2_MAX_LEN>>1];
|
||||
|
||||
int coinb1_len;
|
||||
int coinb2_len;
|
||||
} T_DATUM_STRATUM_COINBASE;
|
||||
|
||||
typedef struct {
|
||||
unsigned char output_script[64];
|
||||
int output_script_len;
|
||||
uint64_t value_sats;
|
||||
int sigops;
|
||||
} T_DATUM_TXN_OUTPUT;
|
||||
|
||||
typedef struct {
|
||||
int global_index;
|
||||
|
||||
char job_id[24];
|
||||
char prevhash[68];
|
||||
unsigned char prevhash_bin[32];
|
||||
char version[10];
|
||||
uint32_t version_uint;
|
||||
char nbits[10];
|
||||
unsigned char nbits_bin[4];
|
||||
uint32_t nbits_uint;
|
||||
char ntime[10];
|
||||
|
||||
unsigned char block_target[32];
|
||||
|
||||
T_DATUM_TEMPLATE_DATA *block_template;
|
||||
|
||||
unsigned char merklebranch_count;
|
||||
char merklebranches_hex[24][72];
|
||||
unsigned char merklebranches_bin[24][32];
|
||||
|
||||
char merklebranches_full[4096];
|
||||
|
||||
// when fetching the coinbaser, we'll just stash all of the possible and valid output scripts here
|
||||
T_DATUM_TXN_OUTPUT available_coinbase_outputs[512];
|
||||
int available_coinbase_outputs_count;
|
||||
unsigned char pool_addr_script[64];
|
||||
int pool_addr_script_len;
|
||||
|
||||
// multiple coinbase options
|
||||
// 0 = "empty" --- just pays pool addr, and possibly TIDES data. extranonce in coinbase if fits, or in first output if not.
|
||||
// 1 = "nicehash" --- roughly 500 bytes total... smaller than antminer... has nothing before the extranonce OP_RETURN (or no extranonce OP_RETURN if enough space in the coinbase)
|
||||
// 2 = "antminer" --- roughly 730 bytes max size, using a larger coinb1 and UART sync bits. This also works as a good default.
|
||||
// 3 = "whatsminer" --- max 6500 bytes tested. does not need the extranonce OP_RETURN unless there's no space in the coinbase itself after tags
|
||||
// 4 = "huge" --- max 16kB --- this is probably the most we should reasonably attempt to do in the coinbase... something like 380 to 530 outputs, depending on the type of output
|
||||
// 5 = "antminer2" --- max 2250 bytes --- latest S21s appear to support this
|
||||
T_DATUM_STRATUM_COINBASE coinbase[MAX_COINBASE_TYPES];
|
||||
T_DATUM_STRATUM_COINBASE subsidy_only_coinbase;
|
||||
int target_pot_index; // where in coinb1 do we put our per-user vardiff pot value?
|
||||
|
||||
uint64_t coinbase_value;
|
||||
uint64_t height;
|
||||
uint16_t enprefix;
|
||||
|
||||
uint64_t tsms; // local timestamp for when job was created. can differ from the bitcoin network timestamp.
|
||||
|
||||
bool is_new_block;
|
||||
bool is_stale_prevblock;
|
||||
|
||||
int job_state;
|
||||
|
||||
bool need_coinbaser;
|
||||
|
||||
bool is_datum_job;
|
||||
unsigned char datum_job_idx;
|
||||
unsigned char datum_coinbaser_id;
|
||||
} T_DATUM_STRATUM_JOB;
|
||||
|
||||
typedef struct T_DATUM_STRATUM_THREADPOOL_DATA {
|
||||
T_DATUM_STRATUM_JOB *cur_stratum_job;
|
||||
int latest_stratum_job_index;
|
||||
bool new_job;
|
||||
bool last_was_empty;
|
||||
int last_sent_job_state;
|
||||
uint64_t loop_tsms;
|
||||
bool full_coinbase_ready;
|
||||
|
||||
int notify_remaining_count;
|
||||
uint64_t notify_start_time;
|
||||
uint64_t notify_last_time;
|
||||
uint64_t notify_delay_per_slot_tsms;
|
||||
int notify_last_cid;
|
||||
uint64_t last_job_height;
|
||||
uint64_t next_kick_check_tsms;
|
||||
|
||||
char submitblock_req[MAX_SUBMITBLOCK_SIZE];
|
||||
|
||||
void *dupes;
|
||||
} T_DATUM_STRATUM_THREADPOOL_DATA;
|
||||
|
||||
typedef struct {
|
||||
unsigned char active_index; // the one we're adding to. use the other for stats
|
||||
|
||||
uint64_t last_swap_tsms; // timestamp of last swap
|
||||
uint64_t last_swap_ms; // length of time for the last
|
||||
|
||||
uint64_t diff_accepted[2];
|
||||
|
||||
uint64_t last_share_tsms;
|
||||
} T_DATUM_STRATUM_USER_STATS;
|
||||
|
||||
typedef struct {
|
||||
uint32_t sid, sid_inv;
|
||||
uint64_t connect_tsms;
|
||||
char useragent[128];
|
||||
char last_auth_username[192];
|
||||
|
||||
bool extension_version_rolling;
|
||||
uint32_t extension_version_rolling_mask;
|
||||
unsigned char extension_version_rolling_bits;
|
||||
|
||||
bool extension_minimum_difficulty;
|
||||
double extension_minimum_difficulty_value;
|
||||
|
||||
bool authorized;
|
||||
bool subscribed;
|
||||
uint64_t subscribe_tsms;
|
||||
|
||||
uint64_t last_sent_diff;
|
||||
uint64_t current_diff;
|
||||
|
||||
uint8_t stratum_job_targets[MAX_STRATUM_JOBS][32];
|
||||
uint64_t stratum_job_diffs[MAX_STRATUM_JOBS];
|
||||
|
||||
unsigned char coinbase_selection;
|
||||
|
||||
uint64_t share_diff_accepted;
|
||||
uint64_t share_count_accepted;
|
||||
|
||||
uint64_t share_diff_rejected;
|
||||
uint64_t share_count_rejected;
|
||||
|
||||
// for vardiff
|
||||
uint64_t share_count_since_snap;
|
||||
uint64_t share_diff_since_snap;
|
||||
uint64_t share_snap_tsms;
|
||||
|
||||
bool quickdiff_active;
|
||||
uint64_t quickdiff_value;
|
||||
uint8_t quickdiff_target[32];
|
||||
|
||||
uint64_t forced_high_min_diff;
|
||||
|
||||
int last_sent_stratum_job_index;
|
||||
|
||||
T_DATUM_STRATUM_USER_STATS stats;
|
||||
|
||||
T_DATUM_STRATUM_THREADPOOL_DATA *sdata;
|
||||
} T_DATUM_MINER_DATA;
|
||||
|
||||
extern int global_latest_stratum_job_index;
|
||||
extern pthread_rwlock_t stratum_global_job_ptr_lock;
|
||||
extern T_DATUM_STRATUM_JOB *global_cur_stratum_jobs[MAX_STRATUM_JOBS];
|
||||
|
||||
int send_mining_notify(T_DATUM_CLIENT_DATA *c, bool clean, bool quickdiff, bool new_block);
|
||||
void update_stratum_job(T_DATUM_TEMPLATE_DATA *block_template, bool new_block, int job_state);
|
||||
void stratum_job_merkle_root_calc(T_DATUM_STRATUM_JOB *s, unsigned char *coinbase_txn_hash, unsigned char *merkle_root_output);
|
||||
int assembleBlockAndSubmit(uint8_t *block_header, uint8_t *coinbase_txn, size_t coinbase_txn_size, T_DATUM_STRATUM_JOB *job, T_DATUM_STRATUM_THREADPOOL_DATA *sdata, const char *block_hash_hex, bool empty_work);
|
||||
void generate_coinbase_txns_for_stratum_job(T_DATUM_STRATUM_JOB *s, bool empty_only);
|
||||
int send_mining_set_difficulty(T_DATUM_CLIENT_DATA *c);
|
||||
bool stratum_latest_empty_check_ready_for_full(void);
|
||||
|
||||
// Server thread main loop
|
||||
void *datum_stratum_v1_socket_server(void *arg);
|
||||
// DATUM socket callbacks
|
||||
void datum_stratum_v1_socket_thread_init(T_DATUM_THREAD_DATA *my);
|
||||
void datum_stratum_v1_socket_thread_loop(T_DATUM_THREAD_DATA *my);
|
||||
int datum_stratum_v1_socket_thread_client_cmd(T_DATUM_CLIENT_DATA *c, char *line);
|
||||
void datum_stratum_v1_socket_thread_client_closed(T_DATUM_CLIENT_DATA *c, const char *msg);
|
||||
void datum_stratum_v1_socket_thread_client_new(T_DATUM_CLIENT_DATA *c);
|
||||
int datum_stratum_v1_global_subscriber_count(void);
|
||||
double datum_stratum_v1_est_total_th_sec(void);
|
||||
void datum_stratum_v1_shutdown_all(void);
|
||||
|
||||
extern T_DATUM_SOCKET_APP *global_stratum_app;
|
||||
|
||||
extern pthread_rwlock_t need_coinbaser_rwlocks[MAX_STRATUM_JOBS];
|
||||
extern bool need_coinbaser_rwlocks_init_done;
|
||||
|
||||
#endif
|
426
src/datum_stratum_dupes.c
Normal file
426
src/datum_stratum_dupes.c
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "datum_stratum_dupes.h"
|
||||
#include "datum_stratum.h"
|
||||
#include "datum_conf.h"
|
||||
#include "datum_utils.h"
|
||||
|
||||
// TODO: Refactor to just use the block header sanely.
|
||||
// This is more contrived than it needs to be, although it profiles quite well
|
||||
|
||||
void datum_stratum_dupes_init(void *sdata_v) {
|
||||
T_DATUM_STRATUM_THREADPOOL_DATA *sdata = sdata_v;
|
||||
T_DATUM_STRATUM_DUPES *dupes = NULL;
|
||||
sdata->dupes = calloc( sizeof(T_DATUM_STRATUM_DUPES) + 16, 1 );
|
||||
if (!sdata->dupes) {
|
||||
DLOG_FATAL("Could not allocate RAM for dupe struct (small one, %d bytes)",sizeof(T_DATUM_STRATUM_DUPES) + 16);
|
||||
panic_from_thread(__LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
dupes = sdata->dupes;
|
||||
|
||||
dupes->ptr = calloc((datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16), sizeof(T_DATUM_STRATUM_DUPE_ITEM) );
|
||||
if (!dupes->ptr) {
|
||||
DLOG_FATAL("Could not allocate RAM for dupe struct (big one, %d bytes)",(datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16) * sizeof(T_DATUM_STRATUM_DUPE_ITEM));
|
||||
panic_from_thread(__LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
dupes->max_items = (datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16);
|
||||
dupes->current_items = 0;
|
||||
|
||||
DLOG_DEBUG("Initialized dupe check thread data. %"PRIu64" bytes of RAM used for %d max entries @ %p for %p", (uint64_t)dupes->max_items * (uint64_t)sizeof(T_DATUM_STRATUM_DUPE_ITEM), dupes->max_items, dupes, sdata);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int datum_stratum_dupes_cleanup_sort_compare(const void *a, const void *b) {
|
||||
const T_DATUM_STRATUM_DUPE_ITEM *item1 = a;
|
||||
const T_DATUM_STRATUM_DUPE_ITEM *item2 = b;
|
||||
|
||||
if (item1 == NULL && item2 == NULL) return 0;
|
||||
if (item1 == NULL) return 1;
|
||||
if (item2 == NULL) return -1;
|
||||
|
||||
if (item1->job_index >= MAX_STRATUM_JOBS) return 1;
|
||||
if (item2->job_index >= MAX_STRATUM_JOBS) return -1;
|
||||
|
||||
if (global_cur_stratum_jobs[item1->job_index] == NULL && global_cur_stratum_jobs[item2->job_index] == NULL) return 0;
|
||||
if (global_cur_stratum_jobs[item1->job_index] == NULL) return 1;
|
||||
if (global_cur_stratum_jobs[item2->job_index] == NULL) return -1;
|
||||
|
||||
uint64_t tsms1 = global_cur_stratum_jobs[item1->job_index]->tsms;
|
||||
uint64_t tsms2 = global_cur_stratum_jobs[item2->job_index]->tsms;
|
||||
|
||||
if (tsms1 > tsms2) return -1;
|
||||
if (tsms1 < tsms2) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int find_first_less_than(T_DATUM_STRATUM_DUPE_ITEM *ptr, size_t max_items, uint64_t given_tsms) {
|
||||
int low = 0;
|
||||
int high = max_items - 1;
|
||||
int result = -1;
|
||||
uint64_t tsms;
|
||||
|
||||
while (low <= high) {
|
||||
int mid = low + (high - low) / 2;
|
||||
|
||||
// sanity
|
||||
if (mid < 0) mid = 0;
|
||||
if (mid > (max_items-1)) mid = max_items-1;
|
||||
|
||||
// more sanity
|
||||
if (ptr[mid].job_index < 0 || ptr[mid].job_index > MAX_STRATUM_JOBS) {
|
||||
tsms = 0;
|
||||
} else if (global_cur_stratum_jobs[ptr[mid].job_index] != NULL) {
|
||||
tsms = global_cur_stratum_jobs[ptr[mid].job_index]->tsms;
|
||||
} else {
|
||||
tsms = 0; // Treat NULL as the smallest possible value... we can trim NULLs
|
||||
}
|
||||
|
||||
// bsearch until we find the first entry < given
|
||||
if (tsms < given_tsms) {
|
||||
result = mid;
|
||||
high = mid - 1;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void datum_stratum_dupes_expand(T_DATUM_STRATUM_DUPES *dupes) {
|
||||
T_DATUM_STRATUM_DUPE_ITEM *new_ptr;
|
||||
int new_max = ((dupes->max_items * 125)/100);
|
||||
new_ptr = realloc(dupes->ptr, sizeof(T_DATUM_STRATUM_DUPE_ITEM) * new_max);
|
||||
if (!new_ptr) {
|
||||
DLOG_FATAL("Could not reallocate dupes ptr %p of %d items to %d items!", dupes->ptr, dupes->max_items, new_max);
|
||||
panic_from_thread(__LINE__);
|
||||
return;
|
||||
}
|
||||
memset(&new_ptr[dupes->max_items], 0, sizeof(T_DATUM_STRATUM_DUPE_ITEM) * (new_max - dupes->max_items));
|
||||
DLOG_DEBUG("INFO: Had to allocate more RAM to duplicate share checking for thread. %d to %d items (%"PRIu64" bytes)", dupes->max_items, new_max, (uint64_t)sizeof(T_DATUM_STRATUM_DUPE_ITEM) * (uint64_t)new_max);
|
||||
|
||||
dupes->max_items = new_max;
|
||||
dupes->ptr = new_ptr;
|
||||
|
||||
// always needs reoganizing after this... do externally.
|
||||
}
|
||||
|
||||
void datum_stratum_dupes_reorganize(T_DATUM_STRATUM_DUPES *dupes) {
|
||||
int i;
|
||||
T_DATUM_STRATUM_DUPE_ITEM *q,*p=NULL;
|
||||
|
||||
for(i=0;i<dupes->max_items;i++) {
|
||||
// we'll use ntime as an indicator, since obvious ntime cant be zero
|
||||
if (dupes->ptr[i].ntime == 0) break;
|
||||
|
||||
if (!dupes->index[dupes->ptr[i].nonce_high]) {
|
||||
// easy. this is the first
|
||||
dupes->index[dupes->ptr[i].nonce_high] = &dupes->ptr[i];
|
||||
dupes->ptr[i].next = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
q = dupes->index[dupes->ptr[i].nonce_high];
|
||||
p = NULL;
|
||||
do {
|
||||
if (q->nonce_low > dupes->ptr[i].nonce_low) {
|
||||
if (p) {
|
||||
// insert after p
|
||||
p->next = &dupes->ptr[i];
|
||||
} else {
|
||||
// insert as first entry, before this one
|
||||
dupes->index[dupes->ptr[i].nonce_high] = &dupes->ptr[i];
|
||||
}
|
||||
dupes->ptr[i].next = q;
|
||||
break;
|
||||
}
|
||||
|
||||
// this should be safe here, even though we haven't cleaned up all the old pointers
|
||||
// the reason is that nothing we having cleaned should have ended up in the index yet
|
||||
p = q;
|
||||
if (!q->next) {
|
||||
// ended up at the last entry without finding one greater than me... add to the end
|
||||
q->next = &dupes->ptr[i];
|
||||
dupes->ptr[i].next = NULL;
|
||||
q = NULL;
|
||||
break;
|
||||
} else {
|
||||
q = q->next;
|
||||
}
|
||||
} while(q);
|
||||
}
|
||||
|
||||
dupes->current_items = i;
|
||||
|
||||
// should be all straightened out now
|
||||
}
|
||||
|
||||
void datum_stratum_dupes_cleanup(T_DATUM_STRATUM_DUPES *dupes, bool full_wipe) {
|
||||
int i;
|
||||
|
||||
if (full_wipe) {
|
||||
// we're just cleaning up after a new block or whatever
|
||||
memset(dupes->ptr, 0, sizeof(T_DATUM_STRATUM_DUPE_ITEM) * dupes->max_items);
|
||||
dupes->current_items = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Somewhat expensive cleanup of dupes...
|
||||
// first, sort the full dupe list by the dupe's stratum job timestamp, decending... which is trickyish
|
||||
// then, bsearch find the index of the first item with a job timestamp that is from a stale job
|
||||
// if there are entries after it, we're good and we can prune those
|
||||
// fix the linked list
|
||||
|
||||
// this breaks all links. we'll need to reconstruct them!
|
||||
qsort(dupes->ptr, dupes->max_items, sizeof(T_DATUM_STRATUM_DUPE_ITEM), datum_stratum_dupes_cleanup_sort_compare);
|
||||
|
||||
// links all broken, so wipe out the starting table
|
||||
memset(dupes->index, 0, sizeof(T_DATUM_STRATUM_DUPE_ITEM *) * 65536);
|
||||
|
||||
// find the first stale index
|
||||
i = find_first_less_than(dupes->ptr, dupes->max_items, current_time_millis() - (datum_config.stratum_v1_share_stale_seconds*1000));
|
||||
|
||||
if ((i == -1) || (i == dupes->max_items-1)) {
|
||||
// none of the items are stale...
|
||||
datum_stratum_dupes_expand(dupes);
|
||||
} else {
|
||||
// ok, we want to free up at least 5% of the entries, otherwise we'll be right back here wasting CPU time
|
||||
if (i < ((dupes->max_items * 95)/100)) {
|
||||
// at least 5% are good
|
||||
// clear out the stales...
|
||||
dupes->current_items = i;
|
||||
memset(&dupes->ptr[i], 0, sizeof(T_DATUM_STRATUM_DUPE_ITEM) * (dupes->max_items - i));
|
||||
} else {
|
||||
// < 5% are freeable... we just need more RAM.
|
||||
datum_stratum_dupes_expand(dupes);
|
||||
}
|
||||
}
|
||||
|
||||
// fix all the links in our now either expanded or cleaned dupe list
|
||||
datum_stratum_dupes_reorganize(dupes);
|
||||
}
|
||||
|
||||
T_DATUM_STRATUM_DUPE_ITEM *datum_stratum_add_new_dupe(T_DATUM_STRATUM_DUPES *dupes, unsigned int nonce, unsigned short job_index, unsigned int ntime_val, unsigned int version_bits, unsigned char *extranonce_bin, T_DATUM_STRATUM_DUPE_ITEM *insert_after) {
|
||||
T_DATUM_STRATUM_DUPE_ITEM *i;
|
||||
|
||||
i = &dupes->ptr[dupes->current_items];
|
||||
if (!i) {
|
||||
DLOG_FATAL("Could not add entry to dupe table!");
|
||||
panic_from_thread(__LINE__);
|
||||
return NULL;
|
||||
}
|
||||
i->nonce_high = nonce&0xFFFF;
|
||||
i->nonce_low = (nonce>>16) & 0xFFFF;
|
||||
i->job_index = job_index;
|
||||
i->ntime = ntime_val;
|
||||
i->version_bits = version_bits;
|
||||
i->extra_nonce1 = *((uint32_t *)&extranonce_bin[0]);
|
||||
i->extra_nonce2 = *((uint64_t *)&extranonce_bin[4]);
|
||||
if (!insert_after) {
|
||||
// is a new entry
|
||||
i->next = NULL;
|
||||
} else {
|
||||
i->next = insert_after->next;
|
||||
insert_after->next = i;
|
||||
}
|
||||
dupes->current_items++;
|
||||
|
||||
if (dupes->current_items >= dupes->max_items) {
|
||||
datum_stratum_dupes_cleanup(dupes, false);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
bool datum_stratum_check_for_dupe(T_DATUM_STRATUM_THREADPOOL_DATA *t, unsigned int nonce, unsigned short job_index, unsigned int ntime_val, unsigned int version_bits, unsigned char *extranonce_bin) {
|
||||
// check if a share is a dupe
|
||||
// if so, say so
|
||||
// if not, add to the
|
||||
T_DATUM_STRATUM_DUPES *dupes;
|
||||
unsigned short nonce_high = nonce&0xFFFF;
|
||||
unsigned short nonce_low;
|
||||
|
||||
T_DATUM_STRATUM_DUPE_ITEM *i, *p = NULL;
|
||||
|
||||
if (!t) {
|
||||
DLOG_FATAL("Threadpool data not available?!");
|
||||
panic_from_thread(__LINE__);
|
||||
return true;
|
||||
}
|
||||
|
||||
dupes = t->dupes;
|
||||
|
||||
if (dupes->index[nonce_high] == NULL) {
|
||||
// first nonce of its kind!
|
||||
// not a duplicate
|
||||
// add the new first entry!
|
||||
dupes->index[nonce_high] = datum_stratum_add_new_dupe(dupes, nonce, job_index, ntime_val, version_bits, extranonce_bin, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ok, there's an entry. go through the list
|
||||
i = dupes->index[nonce_high];
|
||||
nonce_low = (nonce>>16) & 0xFFFF;
|
||||
|
||||
do {
|
||||
if (i->nonce_low > nonce_low) {
|
||||
// we've reached a nonce higher than ours, so we can't be a dupe
|
||||
// we need to keep the list in order, so we need to insert ourselves before this entry (so, the previous entry)
|
||||
if (p) {
|
||||
datum_stratum_add_new_dupe(dupes, nonce, job_index, ntime_val, version_bits, extranonce_bin, p);
|
||||
} else {
|
||||
// we need to replace the first item in a list, so... let's make a new entry
|
||||
p = datum_stratum_add_new_dupe(dupes, nonce, job_index, ntime_val, version_bits, extranonce_bin, NULL);
|
||||
dupes->index[nonce_high] = p;
|
||||
p->next = i;
|
||||
}
|
||||
//LOG_PRINTF("DEBUG: Not dupe, nonce_low higher --- %d > %d", i->nonce_low, nonce_low);
|
||||
return false;
|
||||
}
|
||||
|
||||
// there can be more than one nonce that's equal, so can't just assume until we pass it or
|
||||
if (i->nonce_low == nonce_low) {
|
||||
// same nonce as us, so need to do the slow checks
|
||||
if (job_index == i->job_index) {
|
||||
// same job index...
|
||||
if (ntime_val == i->ntime) {
|
||||
// same ntime....!
|
||||
if (version_bits == i->version_bits) {
|
||||
// same version bits?!?!?!?
|
||||
if (i->extra_nonce1 == *((uint32_t *)&extranonce_bin[0])) {
|
||||
// same extra nonce 1?!?!?!??!
|
||||
if (i->extra_nonce2 == *((uint64_t *)&extranonce_bin[4])) {
|
||||
// ok, this is a duplicate :(
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store the current ptr for the next loop
|
||||
p = i;
|
||||
|
||||
// setup i to be the next link
|
||||
// if it's the end of the chain, this will be NULL and the loop will break
|
||||
i = i->next;
|
||||
} while (i);
|
||||
|
||||
// we reached the end of the list, and haven't found a dupe
|
||||
// means that all of the nonces in the list are lower than us, or the last nonce is equal but doesn't match us
|
||||
// so we should be safe to insert ourselves on to the end of the list and return
|
||||
datum_stratum_add_new_dupe(dupes, nonce, job_index, ntime_val, version_bits, extranonce_bin, p);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
void datum_stratum_dupes_codetest(void) {
|
||||
int i;
|
||||
uint64_t t;
|
||||
bool r;
|
||||
unsigned char en[12] = { 0 };
|
||||
int stratum_job_next = 0;
|
||||
T_DATUM_STRATUM_JOB *stratum_job_list;
|
||||
unsigned int nonce;
|
||||
|
||||
// make a fake thread pool
|
||||
T_DATUM_STRATUM_THREADPOOL_DATA tp;
|
||||
|
||||
datum_stratum_dupes_init(&tp);
|
||||
T_DATUM_STRATUM_DUPES *dupes = tp.dupes;
|
||||
|
||||
stratum_job_list = calloc(MAX_STRATUM_JOBS,sizeof(T_DATUM_STRATUM_JOB));
|
||||
|
||||
t = current_time_millis() - (datum_config.stratum_v1_share_stale_seconds*2000);
|
||||
|
||||
// fill stratum jobs with garbage tsms
|
||||
for(i=0;i<MAX_STRATUM_JOBS;i++) {
|
||||
global_cur_stratum_jobs[i] = &stratum_job_list[stratum_job_next];
|
||||
stratum_job_next++;
|
||||
if (stratum_job_next == MAX_STRATUM_JOBS) stratum_job_next = 0;
|
||||
global_cur_stratum_jobs[i]->tsms = t;
|
||||
t+=1000000000;
|
||||
}
|
||||
|
||||
for(i=0;i<(datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16)*80;i++) {
|
||||
en[0]=i%256;
|
||||
en[7]=en[0]^0xAA;
|
||||
nonce = ((i&0xFFFF)<<16)|(((i>>2)&0xFFFF)^0xFFFF);
|
||||
r = datum_stratum_check_for_dupe(&tp, nonce, (i^0x69) % MAX_STRATUM_JOBS, t/1000, 0x20000000 | i, &en[0]);
|
||||
r = datum_stratum_check_for_dupe(&tp, nonce, (i^0x69) % MAX_STRATUM_JOBS, t/1000, 0x20000000 | i, &en[0]);
|
||||
if (!r) {
|
||||
DLOG_DEBUG("MISSED A DUPE 1 - %d - %8.8x - %d / %4.4x",i, nonce , nonce & 0xFFFF, nonce & 0xFFFF);
|
||||
}
|
||||
r = datum_stratum_check_for_dupe(&tp, nonce, (i^0x69) % MAX_STRATUM_JOBS, t/1000, 0x20000000 | i, &en[0]);
|
||||
if (!r) {
|
||||
DLOG_DEBUG("MISSED A DUPE 2 - %d - %8.8x - %d / %4.4x",i, nonce , nonce & 0xFFFF, nonce & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
r = datum_stratum_check_for_dupe(&tp, 0xdeadc0de, 12, t, 0x20000001, &en[0]);
|
||||
DLOG_DEBUG("B %d %d %d",r?1:0, dupes->current_items, dupes->max_items);
|
||||
|
||||
uint64_t starttsms, endtsms;
|
||||
starttsms = current_time_millis();
|
||||
for(i=0;i<(datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16)*8;i++) {
|
||||
en[0]=i%256;
|
||||
en[7]=en[0]^0xAA;
|
||||
nonce = ((i&0xFFFF)<<16)|(((i>>2)&0xFFFF)^0xFFFF);
|
||||
r = datum_stratum_check_for_dupe(&tp, nonce, (i^0x69) % MAX_STRATUM_JOBS, t/1000, 0x20000000 | i, &en[0]);
|
||||
if (!r) {
|
||||
DLOG_DEBUG("MISSED A DUPE 3 - %d - %8.8x - %d / %4.4x",i, nonce , nonce & 0xFFFF, nonce & 0xFFFF);
|
||||
}
|
||||
}
|
||||
endtsms = current_time_millis();
|
||||
DLOG_DEBUG("%d dupe checks took %"PRIu64" miliseconds", (datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16)*80, endtsms-starttsms);
|
||||
|
||||
free(stratum_job_list);
|
||||
}
|
||||
|
||||
#endif
|
79
src/datum_stratum_dupes.h
Normal file
79
src/datum_stratum_dupes.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_STRATUM_DUPES_H_
|
||||
#define _DATUM_STRATUM_DUPES_H_
|
||||
|
||||
#ifndef uint64_t
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#ifndef bool
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
typedef struct T_DATUM_STRATUM_DUPE_ITEM {
|
||||
// things to compare against, in order, to check our list
|
||||
// in most cases, we shouldn't get beyond nonce_low, but we need the rest for completeness
|
||||
unsigned short nonce_high;
|
||||
unsigned short nonce_low;
|
||||
unsigned short job_index;
|
||||
unsigned int ntime;
|
||||
unsigned int version_bits;
|
||||
unsigned int extra_nonce1;
|
||||
uint64_t extra_nonce2;
|
||||
|
||||
struct T_DATUM_STRATUM_DUPE_ITEM *next;
|
||||
} T_DATUM_STRATUM_DUPE_ITEM;
|
||||
|
||||
typedef struct T_DATUM_STRATUM_DUPES {
|
||||
// high bits of nonce = index
|
||||
T_DATUM_STRATUM_DUPE_ITEM *index[65536];
|
||||
|
||||
// memory - we target 8 shares per minute per connection.
|
||||
// suggested items: datum_config.stratum_v1_max_clients_per_thread * datum_config.stratum_v1_vardiff_target_shares_min * (datum_config.stratum_v1_share_stale_seconds/60) * 16
|
||||
T_DATUM_STRATUM_DUPE_ITEM *ptr;
|
||||
int max_items;
|
||||
int current_items;
|
||||
} T_DATUM_STRATUM_DUPES;
|
||||
|
||||
void datum_stratum_dupes_init(void *vsdata);
|
||||
|
||||
#include "datum_stratum.h"
|
||||
|
||||
bool datum_stratum_check_for_dupe(T_DATUM_STRATUM_THREADPOOL_DATA *t, unsigned int nonce, unsigned short job_index, unsigned int ntime_val, unsigned int bver, unsigned char *extranonce_bin);
|
||||
void datum_stratum_dupes_cleanup(T_DATUM_STRATUM_DUPES *dupes, bool full_wipe);
|
||||
|
||||
#endif
|
181
src/datum_submitblock.c
Normal file
181
src/datum_submitblock.c
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <curl/curl.h>
|
||||
#include <pthread.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "datum_utils.h"
|
||||
#include "datum_conf.h"
|
||||
#include "datum_jsonrpc.h"
|
||||
|
||||
pthread_mutex_t submitblock_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_cond_t submitblock_cond = PTHREAD_COND_INITIALIZER;
|
||||
int submit_block_triggered = 0;
|
||||
const char *submitblock_ptr = NULL;
|
||||
char submitblock_hash[256] = { 0 };
|
||||
|
||||
void preciousblock(CURL *curl, char *blockhash) {
|
||||
json_t *json;
|
||||
char rpc_data[384];
|
||||
char userpass[1024];
|
||||
|
||||
// TODO: Move these types of things to the conf file
|
||||
snprintf(userpass, 1023, "%s:%s", datum_config.bitcoind_rpcuser, datum_config.bitcoind_rpcpassword);
|
||||
userpass[1023] = 0;
|
||||
|
||||
sprintf(rpc_data,"{\"method\":\"preciousblock\",\"params\":[\"%s\"],\"id\":1}",blockhash);
|
||||
json = json_rpc_call(curl, datum_config.bitcoind_rpcurl, userpass, rpc_data);
|
||||
if (!json) return;
|
||||
|
||||
json_decref(json);
|
||||
return;
|
||||
}
|
||||
|
||||
void datum_submitblock_doit(CURL *tcurl, char *url, const char *submitblock_req, const char *block_hash_hex) {
|
||||
char userpass[1024];
|
||||
json_t *r;
|
||||
char *s = NULL;
|
||||
// TODO: Move these types of things to the conf file
|
||||
if (!url) {
|
||||
snprintf(userpass, 1023, "%s:%s", datum_config.bitcoind_rpcuser, datum_config.bitcoind_rpcpassword);
|
||||
userpass[1023] = 0;
|
||||
|
||||
r = json_rpc_call(tcurl, datum_config.bitcoind_rpcurl, userpass, submitblock_req);
|
||||
} else {
|
||||
r = json_rpc_call(tcurl, url, NULL, submitblock_req);
|
||||
}
|
||||
if (!r) {
|
||||
// oddly, this means success here.
|
||||
DLOG_INFO("Block %s submitted to upstream node successfully!",block_hash_hex);
|
||||
} else {
|
||||
s = json_dumps(r, JSON_ENCODE_ANY);
|
||||
if (!s) {
|
||||
DLOG_WARN("Upstream node rejected our block! (unknown)");
|
||||
} else {
|
||||
DLOG_WARN("Upstream node rejected our block! (%s)",s);
|
||||
free(s);
|
||||
}
|
||||
json_decref(r);
|
||||
}
|
||||
|
||||
// precious block!
|
||||
preciousblock(tcurl, submitblock_hash);
|
||||
}
|
||||
|
||||
void *datum_submitblock_thread(void *ptr) {
|
||||
CURL *tcurl = NULL;
|
||||
int i;
|
||||
|
||||
tcurl = curl_easy_init();
|
||||
if (!tcurl) {
|
||||
DLOG_FATAL("Could not initialize cURL for submitblock!!! This is REALLY REALLY BAD. Like accidentally calling your wife your ex-girlfriend's name bad.");
|
||||
panic_from_thread(__LINE__);
|
||||
}
|
||||
|
||||
DLOG_DEBUG("Submitblock thread active");
|
||||
|
||||
while (1) {
|
||||
// Lock the mutex before waiting on the condition variable
|
||||
pthread_mutex_lock(&submitblock_mutex);
|
||||
|
||||
// Wait for the event to be triggered
|
||||
while (!submit_block_triggered) {
|
||||
pthread_cond_wait(&submitblock_cond, &submitblock_mutex);
|
||||
}
|
||||
|
||||
if (submitblock_ptr != NULL) {
|
||||
DLOG_DEBUG("SUBMITTING BLOCK TO OUR NODE!");
|
||||
|
||||
datum_submitblock_doit(tcurl,NULL,submitblock_ptr,submitblock_hash);
|
||||
|
||||
if (datum_config.extra_block_submissions_count > 0) {
|
||||
for(i=0;i<datum_config.extra_block_submissions_count;i++) {
|
||||
DLOG_DEBUG("SUBMITTING BLOCK TO EXTRA NODE %d!",i+1);
|
||||
datum_submitblock_doit(tcurl,(char *)datum_config.extra_block_submissions_urls[i],submitblock_ptr,submitblock_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the event flag
|
||||
submit_block_triggered = 0;
|
||||
|
||||
// Unlock the mutex after processing
|
||||
pthread_mutex_unlock(&submitblock_mutex);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void datum_submitblock_waitfree(void) {
|
||||
pthread_mutex_lock(&submitblock_mutex);
|
||||
DLOG_DEBUG("DEBUG: Lock acquired.");
|
||||
pthread_mutex_unlock(&submitblock_mutex);
|
||||
}
|
||||
|
||||
void datum_submitblock_trigger(const char *ptr, const char *hash) {
|
||||
// Lock the mutex before updating and triggering the event
|
||||
|
||||
int i;
|
||||
for(i=0;i<100;i++) {
|
||||
if (pthread_mutex_trylock(&submitblock_mutex) == 0) {
|
||||
// Update the shared data
|
||||
submitblock_ptr = ptr;
|
||||
strcpy(submitblock_hash, hash);
|
||||
|
||||
// Set the event flag and signal the condition variable
|
||||
submit_block_triggered = 1;
|
||||
pthread_cond_signal(&submitblock_cond);
|
||||
|
||||
// Unlock the mutex
|
||||
pthread_mutex_unlock(&submitblock_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
DLOG_ERROR("Could not acquire a lock on the submitblock thread after 100ms! This might be bad!");
|
||||
return;
|
||||
}
|
||||
|
||||
void datum_submitblock_init(void) {
|
||||
// TODO: Handle rare issues.
|
||||
pthread_t pthread_datum_submitblock_thread;
|
||||
pthread_create(&pthread_datum_submitblock_thread, NULL, datum_submitblock_thread, NULL);
|
||||
return;
|
||||
}
|
43
src/datum_submitblock.h
Normal file
43
src/datum_submitblock.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_SUBMITBLOCK_H_
|
||||
#define _DATUM_SUBMITBLOCK_H_
|
||||
|
||||
void datum_submitblock_init(void);
|
||||
void datum_submitblock_trigger(const char *ptr, const char *hash);
|
||||
void datum_submitblock_waitfree(void);
|
||||
|
||||
#endif
|
712
src/datum_utils.c
Normal file
712
src/datum_utils.c
Normal file
@ -0,0 +1,712 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sodium.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "datum_utils.h"
|
||||
#include "thirdparty_base58.h"
|
||||
#include "thirdparty_segwit_addr.h"
|
||||
#include "datum_logger.h"
|
||||
|
||||
volatile int panic_mode = 0;
|
||||
|
||||
void get_target_from_diff(unsigned char *result, uint64_t diff) {
|
||||
uint64_t dividend_parts[4] = {0, 0, 0, 0x00000000FFFF0000};
|
||||
uint64_t remainder = 0;
|
||||
uint64_t quotient;
|
||||
|
||||
memset(result, 0, 32);
|
||||
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
__uint128_t temp = remainder;
|
||||
temp = (temp << 64) | dividend_parts[i];
|
||||
|
||||
quotient = temp / diff;
|
||||
remainder = temp % diff;
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
result[(i<<3) + j] = (quotient >> (j<<3)) & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void datum_utils_init(void) {
|
||||
build_hex_lookup();
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
// faster, less portable
|
||||
uint64_t roundDownToPowerOfTwo_64(uint64_t x) {
|
||||
return 1ULL << (63 - __builtin_clzll(x));
|
||||
}
|
||||
|
||||
unsigned char floorPoT(uint64_t x) {
|
||||
if (x == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (63 - __builtin_clzll(x));
|
||||
}
|
||||
|
||||
#else
|
||||
// More portable but slower
|
||||
uint64_t roundDownToPowerOfTwo_64(uint64_t x) {
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
x |= x >> 32;
|
||||
return x - (x >> 1);
|
||||
}
|
||||
|
||||
unsigned char floorPoT(uint64_t x) {
|
||||
if (x == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned char pos = 0;
|
||||
while (x >>= 1) {
|
||||
pos++;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint64_t current_time_millis(void) {
|
||||
struct timeval te;
|
||||
gettimeofday(&te, NULL); // get current time
|
||||
uint64_t milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
uint64_t current_time_micros(void) {
|
||||
struct timeval te;
|
||||
gettimeofday(&te, NULL); // get current time
|
||||
uint64_t microseconds = te.tv_sec * 1000000LL + te.tv_usec; // calculate nanoseconds
|
||||
return microseconds;
|
||||
}
|
||||
|
||||
uint64_t current_time_nanos(void) {
|
||||
struct timespec te;
|
||||
clock_gettime(CLOCK_REALTIME, &te);
|
||||
uint64_t microseconds = te.tv_sec * 1000000000LL + te.tv_nsec; // calculate nanoseconds
|
||||
return microseconds;
|
||||
}
|
||||
|
||||
unsigned char hex_lookup[65536];
|
||||
unsigned short uchar_hex_lookup[256];
|
||||
|
||||
// crappy function written by wizkid057! v2! :D
|
||||
unsigned char hex2bin_uchar(const char *in) {
|
||||
return hex_lookup[*(const unsigned short *)in];
|
||||
}
|
||||
|
||||
void uchar_to_hex(char *s, const unsigned char b) {
|
||||
// place the ASCII hexidecimal value for the unsigned char into the string at ptr s
|
||||
// this does NOT null terminate the string!
|
||||
*(unsigned short *)s = uchar_hex_lookup[b];
|
||||
}
|
||||
|
||||
void build_hex_lookup(void) {
|
||||
unsigned int i;
|
||||
char a[3];
|
||||
unsigned short b;
|
||||
for(i = 0; i < 65535; ++i) hex_lookup[i] = 0;
|
||||
hex_lookup[65535] = 0;
|
||||
for(i=0;i<256;i++) {
|
||||
sprintf(a,"%2.2X",i);
|
||||
b = *(unsigned short *)&a;
|
||||
hex_lookup[b] = i;
|
||||
sprintf(a,"%2.2x",i);
|
||||
b = *(unsigned short *)&a;
|
||||
hex_lookup[b] = i;
|
||||
uchar_hex_lookup[i] = b;
|
||||
}
|
||||
}
|
||||
|
||||
bool my_sha256(void *digest, const void *buffer, size_t length) {
|
||||
crypto_hash_sha256(digest, buffer, length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool double_sha256(void *out, const void *in, size_t length) {
|
||||
unsigned char dg1[32];
|
||||
my_sha256(dg1, in, length);
|
||||
my_sha256(out, dg1, 32);
|
||||
return 1;
|
||||
}
|
||||
|
||||
long double get_approx_achieved_diff(const unsigned char *bytes) {
|
||||
if (bytes == NULL) {
|
||||
// Handle null pointer
|
||||
return 0.0L;
|
||||
}
|
||||
|
||||
// Check if all bytes are zero
|
||||
int allZero = 1;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
if (bytes[i] != 0) {
|
||||
allZero = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allZero) {
|
||||
return 0.0L;
|
||||
}
|
||||
|
||||
// bdiff 1
|
||||
unsigned char dividendBytes[32] = {
|
||||
// Least significant byte at index 0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00
|
||||
// Most significant byte at index 31
|
||||
};
|
||||
|
||||
long double dividend = 0.0L;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
dividend *= 256.0L; // Shift left by 8 bits
|
||||
dividend += dividendBytes[31 - i]; // Add the byte
|
||||
}
|
||||
|
||||
long double divisor = 0.0L;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
divisor *= 256.0L; // Shift left by 8 bits
|
||||
divisor += bytes[31 - i]; // Add the byte
|
||||
}
|
||||
|
||||
return dividend / divisor;
|
||||
}
|
||||
|
||||
void nbits_to_target(uint32_t nbits, uint8_t *target) {
|
||||
uint32_t exponent = (nbits >> 24) & 0xff;
|
||||
uint32_t mantissa = nbits & 0xffffff;
|
||||
int i;
|
||||
|
||||
memset(target, 0, 32);
|
||||
if (exponent <= 3) {
|
||||
mantissa >>= 8 * (3 - exponent);
|
||||
for (i = 0; i < 3; i++) {
|
||||
target[i] = (mantissa >> (8 * i)) & 0xff;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < 3; i++) {
|
||||
target[i + exponent - 3] = (mantissa >> (8 * i)) & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int compare_hashes(const uint8_t *share_hash, const uint8_t *target) {
|
||||
for (int i = 31; i >= 0; i--) {
|
||||
if (share_hash[i] < target[i]) return -1; // share_hash is smaller, valid block/share
|
||||
if (share_hash[i] > target[i]) return 1; // share_hash is larger, not valid
|
||||
}
|
||||
return 0; // hashes are equal
|
||||
}
|
||||
|
||||
unsigned long long block_reward(unsigned int block_height) {
|
||||
unsigned long long reward = 5000000000;
|
||||
unsigned int halvings = block_height / 210000;
|
||||
if (halvings >= 64) return 0;
|
||||
reward >>= halvings;
|
||||
return reward;
|
||||
}
|
||||
|
||||
int get_bitcoin_varint_len_bytes(uint64_t n) {
|
||||
if (n < 0xFD) {
|
||||
return 1;
|
||||
} else if (n <= 0xFFFF) {
|
||||
return 3;
|
||||
} else if (n <= 0xFFFFFFFF) {
|
||||
return 5;
|
||||
} else {
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
|
||||
int append_bitcoin_varint_hex(uint64_t n, char *s) {
|
||||
if (n < 0xFD) {
|
||||
// Single byte is sufficient
|
||||
uchar_to_hex(s, n);
|
||||
s[2] = 0;
|
||||
return 2;
|
||||
} else if (n <= 0xFFFF) {
|
||||
// Use 0xFD followed by the number, little-endian order
|
||||
sprintf(s, "fd%04" PRIx16, __builtin_bswap16((uint16_t)n));
|
||||
return 6;
|
||||
} else if (n <= 0xFFFFFFFF) {
|
||||
// Use 0xFE followed by the number, little-endian order
|
||||
sprintf(s, "fe%08" PRIx32, __builtin_bswap32((uint32_t)n));
|
||||
return 10;
|
||||
} else {
|
||||
// Use 0xFF followed by the number, little-endian order
|
||||
sprintf(s, "ff%016" PRIx64, __builtin_bswap64(n));
|
||||
return 18;
|
||||
}
|
||||
}
|
||||
|
||||
int append_UNum_hex(uint64_t n, char *s) {
|
||||
int count = 0;
|
||||
uint64_t temp = n;
|
||||
bool last_msb = false;
|
||||
|
||||
do {
|
||||
count++;
|
||||
temp >>= 8;
|
||||
} while (temp != 0);
|
||||
|
||||
int len = 2;
|
||||
uchar_to_hex(s, count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
uchar_to_hex(s+len, (uint8_t)(n & 0xFF));
|
||||
|
||||
last_msb = (n >= 0x80);
|
||||
|
||||
n >>= 8;
|
||||
len += 2;
|
||||
}
|
||||
|
||||
// if the last byte is >= 0x80, then we need to inject a zero at the end
|
||||
if (last_msb) {
|
||||
count++;
|
||||
uchar_to_hex(s, count);
|
||||
uchar_to_hex(s+len, 0);
|
||||
len+=2;
|
||||
}
|
||||
|
||||
s[len] = '\0';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void hex_to_bin_le(const char *hex, unsigned char *bin) {
|
||||
size_t len = strlen(hex);
|
||||
for (size_t i = 0; i < len>>1; i++) {
|
||||
bin[i] = hex2bin_uchar(&hex[len - ((i+1)<<1)]);
|
||||
}
|
||||
}
|
||||
|
||||
void hex_to_bin(const char *hex, unsigned char *bin) {
|
||||
size_t len = strlen(hex);
|
||||
for (size_t i = 0; i < len>>1; i++) {
|
||||
bin[i] = hex2bin_uchar(&hex[(i<<1)]);
|
||||
}
|
||||
}
|
||||
|
||||
void panic_from_thread(int a) {
|
||||
// set panic flag
|
||||
panic_mode = 1;
|
||||
|
||||
printf("PANIC TRIGGERED - %d\n",a);
|
||||
fflush(stdout);
|
||||
|
||||
DLOG_FATAL("***********************");
|
||||
DLOG_FATAL("*** PANIC TRIGGERED ***");
|
||||
DLOG_FATAL("***********************");
|
||||
|
||||
// the main thread needs to pickup on this failure and exit as gracefully as possible.
|
||||
while(1) sleep(1);
|
||||
}
|
||||
|
||||
void hash2hex(unsigned char *bytes, char *hexString) {
|
||||
const char hexDigits[] = "0123456789abcdef";
|
||||
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
hexString[i * 2] = hexDigits[(bytes[i] >> 4) & 0x0F];
|
||||
hexString[i * 2 + 1] = hexDigits[bytes[i] & 0x0F];
|
||||
}
|
||||
|
||||
hexString[64] = '\0';
|
||||
}
|
||||
|
||||
int addr_2_output_script(const char *addr, unsigned char *script, int max_len) {
|
||||
// takes any valid bitcoin address, and converts it to an output script
|
||||
// returns length of script written, or 0 on failure
|
||||
// NOTE: This is agnostic to testnet vs mainnet addresses! be careful with your networks!
|
||||
|
||||
int i;
|
||||
size_t al;
|
||||
uint8_t witprog[80];
|
||||
size_t witprog_len;
|
||||
int witver;
|
||||
const char* hrp = "bc";
|
||||
|
||||
al = strlen(addr);
|
||||
|
||||
if (al < 16) return 0;
|
||||
|
||||
if (((addr[0] == 'b') && (addr[1] == 'c')) || ((addr[0] == 't') && (addr[1] == 'b'))) {
|
||||
// bitcoin mainnet and testnet BIP 0173
|
||||
if (addr[0] == 't') {
|
||||
hrp = "tb";
|
||||
}
|
||||
i = segwit_addr_decode(&witver, witprog, &witprog_len, hrp, addr);
|
||||
if (!i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(((witver == 0) && ((witprog_len == 20) || (witprog_len == 32))) || ((witver == 1) && (witprog_len == 32)))) {
|
||||
// enforcing length restrictions and known witness versions
|
||||
// TODO: Add any new witness version/len combos that are valid
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (max_len < witprog_len+2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
script[0] = (uint8_t)(witver ? (witver + 0x50) : 0);
|
||||
script[1] = (uint8_t)witprog_len;
|
||||
memcpy(script + 2, witprog, witprog_len);
|
||||
return witprog_len + 2;
|
||||
} else {
|
||||
// try P2PKH or P2SH
|
||||
const size_t sz = blkmk_address_to_script(script, max_len, addr);
|
||||
if (sz > INT_MAX) return 0;
|
||||
return sz;
|
||||
}
|
||||
|
||||
// nothing worked?
|
||||
return 0;
|
||||
}
|
||||
|
||||
int output_script_2_addr(const unsigned char *script, const int len, char *addr) {
|
||||
int i;
|
||||
int version = 0;
|
||||
int programLen;
|
||||
const unsigned char *ptr;
|
||||
addr[0] = 0;
|
||||
unsigned char temp[32];
|
||||
unsigned char digest[32];
|
||||
size_t sz;
|
||||
|
||||
if (!len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (script[0] == 0x6A) {
|
||||
i = sprintf(addr, "OP_RETURN");
|
||||
return i;
|
||||
}
|
||||
|
||||
if ((script[0] == 0xA9) || (script[0] == 0x76)) { // P2SH / P2PKH
|
||||
if (script[0] == 0xA9) {
|
||||
version = 5;
|
||||
ptr = &script[2];
|
||||
if (len != 23) return 0;
|
||||
} else {
|
||||
version = 0;
|
||||
ptr = &script[3];
|
||||
if (len != 25) return 0;
|
||||
}
|
||||
|
||||
temp[0] = version;
|
||||
memcpy(&temp[1], ptr, 20);
|
||||
|
||||
double_sha256(digest, temp, 21);
|
||||
memcpy(&temp[21], digest, 4);
|
||||
sz = 255;
|
||||
if (!b58enc(addr, &sz, temp, 25)) {
|
||||
i = sprintf(addr, "UNKNOWN");
|
||||
return i;
|
||||
}
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
if ((script[0] == 0) || (script[0] == 0x51)) { // segwit/taproot
|
||||
if (len < 4 || len > 42) {
|
||||
i = sprintf(addr, "UNKNOWN");
|
||||
return i;
|
||||
}
|
||||
if (script[0] != 0x00 && (script[0] < 0x51 || script[0] > 0x60)) {
|
||||
i = sprintf(addr, "UNKNOWN");
|
||||
return i;
|
||||
}
|
||||
if (script[1] < 0x02 || script[1] > 0x28) {
|
||||
i = sprintf(addr, "UNKNOWN");
|
||||
return i;
|
||||
}
|
||||
|
||||
version = (script[0] == 0x00) ? 0 : script[0] - 0x50;
|
||||
programLen = script[1];
|
||||
|
||||
if (segwit_addr_encode(addr, "bc", version, &script[2], programLen) != 1) {
|
||||
i = sprintf(addr, "UNKNOWN");
|
||||
return i;
|
||||
}
|
||||
return strlen(addr);
|
||||
}
|
||||
|
||||
i = sprintf(addr, "UNKNOWN");
|
||||
return i;
|
||||
}
|
||||
|
||||
static const unsigned char b64d[] = {
|
||||
66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,66,66,66,66,66,66,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
|
||||
66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
|
||||
66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66
|
||||
};
|
||||
|
||||
int base64_decode(const char *in_c, size_t inLen, unsigned char *out, size_t *outLen) {
|
||||
const unsigned char *in = (const unsigned char *)in_c;
|
||||
const unsigned char * const end = in + inLen;
|
||||
char iter = 0;
|
||||
uint32_t buf = 0;
|
||||
size_t len = 0;
|
||||
|
||||
while (in < end) {
|
||||
unsigned char c = b64d[*in++];
|
||||
switch (c) {
|
||||
case 64: continue;
|
||||
case 66: return 1;
|
||||
case 65:
|
||||
in = end;
|
||||
continue;
|
||||
default:
|
||||
buf = buf << 6 | c;
|
||||
iter++;
|
||||
if (iter == 4) {
|
||||
if ((len += 3) > *outLen) return 2;
|
||||
*(out++) = (buf >> 16) & 255;
|
||||
*(out++) = (buf >> 8) & 255;
|
||||
*(out++) = buf & 255;
|
||||
buf = 0;
|
||||
iter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iter == 3) {
|
||||
if ((len += 2) > *outLen) return 3;
|
||||
*(out++) = (buf >> 10) & 255;
|
||||
*(out++) = (buf >> 2) & 255;
|
||||
} else if (iter == 2) {
|
||||
if (++len > *outLen) return 4;
|
||||
*(out++) = (buf >> 4) & 255;
|
||||
}
|
||||
*outLen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool strncpy_workerchars(char *out, const char *in, size_t maxlen) {
|
||||
// copy a string from in to out, stripping out unwanted characters
|
||||
// copy a max of maxlen chars from in to out
|
||||
// in could technically be longer than maxlen if it has a bunch of unwanted chars
|
||||
|
||||
int i=0;
|
||||
int j=0;
|
||||
char c;
|
||||
|
||||
if (in == NULL || out == NULL || maxlen == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out[0] = 0;
|
||||
while((in[i] != 0) && (maxlen > 1)) {
|
||||
c = in[i];
|
||||
if ((isalnum(c)) || (c == '.') || (c == '-') || (c == '_') || (c == '=') || (c == '@') || (c == ',')) {
|
||||
out[j] = c;
|
||||
j++;
|
||||
maxlen--;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
out[j] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool strncpy_uachars(char *out, const char *in, size_t maxlen) {
|
||||
// copy a string from in to out, stripping out unwanted characters
|
||||
// copy a max of maxlen chars from in to out
|
||||
// in could technically be longer than maxlen if it has a bunch of unwanted chars
|
||||
|
||||
int i=0;
|
||||
int j=0;
|
||||
char c;
|
||||
|
||||
if (in == NULL || out == NULL || maxlen == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out[0] = 0;
|
||||
while((in[i] != 0) && (maxlen > 1)) {
|
||||
c = in[i];
|
||||
if ((isalnum(c)) || (c == '.') || (c == '-') || (c == '_') || (c == '=') || (c == '@') || (c == ',') || (c == ' ') || (c == '|') || (c == '/') || (c == '|') || (c == ':') || (c == '<') || (c == '>') || (c == '\'') || (c == ';')) {
|
||||
out[j] = c;
|
||||
j++;
|
||||
maxlen--;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
out[j] = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
#define SIPHASH_ROTATE(a, b) ((uint64_t)(((a)<<(b))|((a)>>(64-(b)))))
|
||||
#define SIPHASH_HALF_ROUND(a,b,c,d,e,f) do { \
|
||||
a += b; \
|
||||
c += d; \
|
||||
b = SIPHASH_ROTATE(b, e) ^ a; \
|
||||
d = SIPHASH_ROTATE(d, f) ^ c; \
|
||||
a = SIPHASH_ROTATE(a, 32); \
|
||||
} while(0)
|
||||
#define SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3) do { \
|
||||
SIPHASH_HALF_ROUND(v0,v1,v2,v3,13,16); \
|
||||
SIPHASH_HALF_ROUND(v2,v1,v0,v3,17,21); \
|
||||
SIPHASH_HALF_ROUND(v0,v1,v2,v3,13,16); \
|
||||
SIPHASH_HALF_ROUND(v2,v1,v0,v3,17,21); \
|
||||
} while(0)
|
||||
|
||||
uint64_t datum_siphash(const void *src, uint64_t sz, const unsigned char key[16]) {
|
||||
uint64_t k0 = (*(const uint64_t *)&key[0]);
|
||||
uint64_t k1 = (*(const uint64_t *)&key[8]);
|
||||
uint64_t b = sz<<56;
|
||||
const uint64_t *in = src;
|
||||
uint64_t v0 = k0 ^ 0x736f6d6570736575ULL;
|
||||
uint64_t v1 = k1 ^ 0x646f72616e646f6dULL;
|
||||
uint64_t v2 = k0 ^ 0x6c7967656e657261ULL;
|
||||
uint64_t v3 = k1 ^ 0x7465646279746573ULL;
|
||||
uint64_t mi,t;
|
||||
uint8_t *pt;
|
||||
const uint8_t *m;
|
||||
|
||||
while (sz >= 8) {
|
||||
mi = *in;
|
||||
in += 1;
|
||||
sz -= 8;
|
||||
v3 ^= mi;
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
v0 ^= mi;
|
||||
}
|
||||
|
||||
t = 0;
|
||||
pt = (uint8_t *)&t;
|
||||
m = (const uint8_t *)in;
|
||||
|
||||
switch (sz) {
|
||||
case 7: pt[6] = m[6]; [[fallthrough]];
|
||||
case 6: pt[5] = m[5]; [[fallthrough]];
|
||||
case 5: pt[4] = m[4]; [[fallthrough]];
|
||||
case 4: pt[3] = m[3]; [[fallthrough]];
|
||||
case 3: pt[2] = m[2]; [[fallthrough]];
|
||||
case 2: pt[1] = m[1]; [[fallthrough]];
|
||||
case 1: pt[0] = m[0]; [[fallthrough]];
|
||||
default: break;
|
||||
}
|
||||
b |= t;
|
||||
|
||||
v3 ^= b;
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
v0 ^= b; v2 ^= 0xff;
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
return (v0 ^ v1) ^ (v2 ^ v3);
|
||||
}
|
||||
|
||||
uint64_t datum_siphash_mod8(const void *src, uint64_t sz, const unsigned char key[16]) {
|
||||
// use if size is guaranteed to be mod 8 = 0
|
||||
// should save a few cycles
|
||||
uint64_t k0 = (*(const uint64_t *)&key[0]);
|
||||
uint64_t k1 = (*(const uint64_t *)&key[8]);
|
||||
uint64_t b = sz<<56;
|
||||
const uint64_t *in = src;
|
||||
uint64_t v0 = k0 ^ 0x736f6d6570736575ULL;
|
||||
uint64_t v1 = k1 ^ 0x646f72616e646f6dULL;
|
||||
uint64_t v2 = k0 ^ 0x6c7967656e657261ULL;
|
||||
uint64_t v3 = k1 ^ 0x7465646279746573ULL;
|
||||
uint64_t mi;
|
||||
|
||||
while (sz >= 8) {
|
||||
mi = *in;
|
||||
in += 1;
|
||||
sz -= 8;
|
||||
v3 ^= mi;
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
v0 ^= mi;
|
||||
}
|
||||
|
||||
v3 ^= b;
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
v0 ^= b; v2 ^= 0xff;
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
SIPHASH_DOUBLE_ROUND(v0,v1,v2,v3);
|
||||
return (v0 ^ v1) ^ (v2 ^ v3);
|
||||
}
|
@ -1,7 +1,76 @@
|
||||
/*
|
||||
*
|
||||
* DATUM Gateway
|
||||
* Decentralized Alternative Templates for Universal Mining
|
||||
*
|
||||
* This file is part of OCEAN's Bitcoin mining decentralization
|
||||
* project, DATUM.
|
||||
*
|
||||
* https://ocean.xyz
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2024 Bitcoin Ocean, LLC & Jason Hughes
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATUM_UTILS_H_
|
||||
#define _DATUM_UTILS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "datum_logger.h"
|
||||
|
||||
void datum_utils_init(void);
|
||||
uint64_t current_time_millis(void);
|
||||
uint64_t current_time_nanos(void);
|
||||
uint64_t current_time_micros(void);
|
||||
unsigned char hex2bin_uchar(const char *in);
|
||||
void build_hex_lookup(void);
|
||||
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);
|
||||
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);
|
||||
void panic_from_thread(int a);
|
||||
bool double_sha256(void *out, const void *in, size_t length);
|
||||
void hex_to_bin_le(const char *hex, unsigned char *bin);
|
||||
void hex_to_bin(const char *hex, unsigned char *bin);
|
||||
void hash2hex(unsigned char *bytes, char *hexString);
|
||||
void get_target_from_diff(unsigned char *result, uint64_t diff);
|
||||
uint64_t roundDownToPowerOfTwo_64(uint64_t x);
|
||||
int addr_2_output_script(const char *addr, unsigned char *script, int max_len);
|
||||
int output_script_2_addr(const unsigned char *script, const int len, char *addr);
|
||||
int base64_decode(const char *in, size_t inLen, unsigned char *out, size_t *outLen);
|
||||
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);
|
||||
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]);
|
||||
|
||||
|
||||
static inline
|
||||
uint8_t upk_u8(const void * const bufp, const int offset)
|
||||
@ -86,4 +155,7 @@ void pk_u64le(void * const bufp, const int offset, const uint64_t nv)
|
||||
buf[offset+7] = (nv >> 0x38) & 0xff;
|
||||
}
|
||||
|
||||
|
||||
extern volatile int panic_mode;
|
||||
|
||||
#endif
|
||||
|
@ -2,29 +2,48 @@
|
||||
* Copyright 2012-2014 Luke Dashjr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the standard MIT license. See COPYING for more details.
|
||||
* under the terms of the standard MIT license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WIN32
|
||||
#include <arpa/inet.h>
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
/*
|
||||
Combined and slightly modified from Luke's libblkmaker and libbase68
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libbase58.h"
|
||||
#include "thirdparty_base58.h"
|
||||
#include "datum_utils.h"
|
||||
|
||||
#define b58_sha256_impl my_sha256
|
||||
|
||||
bool _blkmk_b58tobin(void *bin, size_t binsz, const char *b58, size_t b58sz) {
|
||||
return b58tobin(bin, &binsz, b58, b58sz);
|
||||
}
|
||||
|
||||
int _blkmk_b58check(void *bin, size_t binsz, const char *base58str) {
|
||||
if (!b58_sha256_impl)
|
||||
b58_sha256_impl = blkmk_sha256_impl;
|
||||
return b58check(bin, binsz, base58str, 34);
|
||||
}
|
||||
|
||||
@ -36,8 +55,6 @@ size_t blkmk_address_to_script(void *out, size_t outsz, const char *addr) {
|
||||
size_t rv;
|
||||
|
||||
rv = sizeof(addrbin);
|
||||
if (!b58_sha256_impl)
|
||||
b58_sha256_impl = blkmk_sha256_impl;
|
||||
if (!b58tobin(addrbin, &rv, addr, b58sz))
|
||||
return 0;
|
||||
addrver = b58check(addrbin, sizeof(addrbin), addr, b58sz);
|
||||
@ -67,7 +84,6 @@ size_t blkmk_address_to_script(void *out, size_t outsz, const char *addr) {
|
||||
}
|
||||
}
|
||||
|
||||
bool (*b58_sha256_impl)(void *, const void *, size_t) = NULL;
|
||||
|
||||
static const int8_t b58digits_map[] = {
|
||||
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
|
||||
|
@ -1,3 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-2014 Luke Dashjr
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the standard MIT license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LIBBASE58_H
|
||||
#define LIBBASE58_H
|
||||
|
||||
|
@ -18,12 +18,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "segwit_addr.h"
|
||||
#include "thirdparty_segwit_addr.h"
|
||||
|
||||
static uint32_t bech32_polymod_step(uint32_t pre) {
|
||||
uint8_t b = pre >> 25;
|
||||
@ -38,7 +37,7 @@ static uint32_t bech32_polymod_step(uint32_t pre) {
|
||||
static uint32_t bech32_final_constant(bech32_encoding enc) {
|
||||
if (enc == BECH32_ENCODING_BECH32) return 1;
|
||||
if (enc == BECH32_ENCODING_BECH32M) return 0x2bc830a3;
|
||||
assert(0);
|
||||
abort();
|
||||
}
|
||||
|
||||
static const char* charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
|
140
www/assets/style.css
Normal file
140
www/assets/style.css
Normal file
@ -0,0 +1,140 @@
|
||||
body {
|
||||
background-color: black;
|
||||
color: white;
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: lightblue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #222;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
margin-bottom: 0;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
|
||||
.header-content img {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
background-color: #222;
|
||||
padding: 1px;
|
||||
text-align: center;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
.menu-container a {
|
||||
display: inline-block;
|
||||
background-color: #444;
|
||||
color: #3498db;
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
font-weight: bold;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.menu-container a:hover {
|
||||
background-color: #012740;
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tables-container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background-color: #333;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 10px;
|
||||
flex: 1 1 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.table-container h2 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
td.label {
|
||||
background-color: #2a2a3b;
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td:not(.label) {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.table-wrapper {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 1 1 800px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.table-wrapper + .table-container {
|
||||
flex: 1 1 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-width {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
25
www/clients_top.html
Normal file
25
www/clients_top.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DATUM Gateway Status</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||
</div>
|
||||
<div class="menu-container">
|
||||
<a href="/">Status</a>
|
||||
<a href="/clients" style="background-color: darkslategrey;">Clients</a>
|
||||
<a href="/threads">Threads</a>
|
||||
<a href="/coinbaser">Coinbaser</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tables-container">
|
||||
<div class="table-wrapper">
|
||||
<div class="table-container">
|
||||
<h2>Stratum Client List</h2>
|
42
www/coinbaser_top.html
Normal file
42
www/coinbaser_top.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DATUM Gateway Status</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||
<style type="text/css">
|
||||
.table-wrapper {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||
</div>
|
||||
<div class="menu-container">
|
||||
<a href="/">Status</a>
|
||||
<a href="/clients">Clients</a>
|
||||
<a href="/threads">Threads</a>
|
||||
<a href="/coinbaser" style="background-color: darkslategrey;">Coinbaser</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tables-container">
|
||||
<div class="table-wrapper">
|
||||
<div class="table-container">
|
||||
<h2>Current Coinbaser</h2>
|
7
www/foot.html
Normal file
7
www/foot.html
Normal file
@ -0,0 +1,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BR>
|
||||
<CENTER><SMALL>Note: This page does not automatically refresh</SMALL></CENTER>
|
||||
</body>
|
||||
</html>
|
176
www/home.html
Normal file
176
www/home.html
Normal file
@ -0,0 +1,176 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DATUM Gateway Status</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||
<style type="text/css">
|
||||
.table-wrapper {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.leading_zeros {
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||
</div>
|
||||
<div class="menu-container">
|
||||
<a href="/" style="background-color: darkslategrey;">Status</a>
|
||||
<a href="/clients">Clients</a>
|
||||
<a href="/threads">Threads</a>
|
||||
<a href="/coinbaser">Coinbaser</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tables-container">
|
||||
<div class="table-wrapper">
|
||||
<div class="table-container">
|
||||
<h2>Decentralized Client Stats</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="label">Shares Accepted:</td>
|
||||
<td>${DATUM_SHARES_ACCEPTED}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Shares Rejected:</td>
|
||||
<td>${DATUM_SHARES_REJECTED}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Status:</td>
|
||||
<td>${DATUM_CONNECTION_STATUS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Pool Host:</td>
|
||||
<td>${DATUM_POOL_HOST}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Pool Tag:</td>
|
||||
<td>${DATUM_POOL_TAG}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Secondary/Miner Tag:</td>
|
||||
<td>${DATUM_MINER_TAG}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Pool Current MinDiff:</td>
|
||||
<td>${DATUM_POOL_DIFF}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Pool Pubkey:</td>
|
||||
<td class="fixed-width">${DATUM_POOL_PUBKEY}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<h2>Stratum Server Info</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="label">Active Threads:</td>
|
||||
<td>${STRATUM_ACTIVE_THREADS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Total Connections:</td>
|
||||
<td>${STRATUM_TOTAL_CONNECTIONS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Total Work Subscriptions:</td>
|
||||
<td>${STRATUM_TOTAL_SUBSCRIPTIONS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Estimated Hashrate:</td>
|
||||
<td>${STRATUM_HASHRATE_ESTIMATE}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<div class="table-container" style="min-width: fit-content;">
|
||||
<h2>Current Stratum Job</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="label">Job ID:</td>
|
||||
<td>${STRATUM_JOB_INFO}</td> <!-- Job ID (Index) @ Timestamp -->
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Block Height:</td>
|
||||
<td>${STRATUM_JOB_BLOCK_HEIGHT}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Block Value:</td>
|
||||
<td>${STRATUM_JOB_BLOCK_VALUE}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Previous Block:</td>
|
||||
<td class="fixed-width">${STRATUM_JOB_PREVBLOCK}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Block Target:</td>
|
||||
<td class="fixed-width">${STRATUM_JOB_TARGET}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Witness Commitment:</td>
|
||||
<td class="fixed-width">${STRATUM_JOB_WITNESS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Block Difficulty:</td>
|
||||
<td>${STRATUM_JOB_DIFF}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Version:</td>
|
||||
<td>${STRATUM_JOB_VERSION}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Bits:</td>
|
||||
<td>${STRATUM_JOB_BITS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Time:</td>
|
||||
<td>${STRATUM_JOB_TIMEINFO}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Limits:</td>
|
||||
<td>${STRATUM_JOB_LIMITINFO}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Size:</td>
|
||||
<td>${STRATUM_JOB_SIZE}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Weight:</td>
|
||||
<td>${STRATUM_JOB_WEIGHT}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Sigops:</td>
|
||||
<td>${STRATUM_JOB_SIGOPS}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Txn Count:</td>
|
||||
<td>${STRATUM_JOB_TXNCOUNT}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BR>
|
||||
<CENTER><SMALL>Note: This page does not automatically refresh</SMALL></CENTER>
|
||||
</body>
|
||||
</html>
|
42
www/threads_top.html
Normal file
42
www/threads_top.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DATUM Gateway Status</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||
<style type="text/css">
|
||||
.table-wrapper {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||
</div>
|
||||
<div class="menu-container">
|
||||
<a href="/">Status</a>
|
||||
<a href="/clients">Clients</a>
|
||||
<a href="/threads" style="background-color: darkslategrey;">Threads</a>
|
||||
<a href="/coinbaser">Coinbaser</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tables-container">
|
||||
<div class="table-wrapper">
|
||||
<div class="table-container">
|
||||
<h2>Thread Stats</h2>
|
Loading…
Reference in New Issue
Block a user