From c65b3ba4ec32ffce8c4f51c9148af7c11cd8c675 Mon Sep 17 00:00:00 2001 From: ceedii Date: Mon, 14 Oct 2024 02:59:32 +0000 Subject: [PATCH] Add support for minerEffort Add support for PPLNSBF with customizable percentage KASPA family optimizations (more forks, security improvements, transaction fee control) Add support for ALPH "Mining Protocol" Add support for Flex algorithm Add support for FishHash algorithm Add support for KarlsenHashV2 algorithm Add support for AstroBWTv3 algorithm Use VRSC node diff1 Add support for Salvium (SAL) Add support for WARTHOG (WART) --- Dockerfile | 2 +- build-debian-11.sh | 2 +- build-debian-12.sh | 2 +- build-ubuntu-20.04.sh | 2 +- build-ubuntu-21.04.sh | 2 +- build-ubuntu-22.04.sh | 2 +- build.backup/config.json | 3657 ++++++++ examples/kaspa_pool.json | 6 +- examples/veruscoin_pool.json | 12 +- examples/warthog_pool.json | 119 + src/Miningcore.Tests/Miningcore.Tests.csproj | 2 +- .../Api/Controllers/PoolApiController.cs | 31 +- .../Api/Extensions/MiningPoolExtensions.cs | 4 + .../Api/Responses/GetBlocksResponse.cs | 1 + .../Api/Responses/GetMinerStatsResponse.cs | 3 + .../Api/Responses/GetPoolsResponse.cs | 3 + src/Miningcore/AutoMapperProfile.cs | 4 +- src/Miningcore/AutofacModule.cs | 12 + .../Blockchain/Alephium/AlephiumConstants.cs | 1 + .../Blockchain/Alephium/AlephiumJob.cs | 13 +- .../Blockchain/Alephium/AlephiumJobManager.cs | 53 +- .../Blockchain/Alephium/AlephiumPool.cs | 2 +- .../Alephium/AlephiumWorkerContext.cs | 4 +- src/Miningcore/Blockchain/Beam/BeamJob.cs | 7 +- .../Blockchain/Beam/BeamWorkerContext.cs | 4 +- .../Blockchain/Bitcoin/BitcoinJob.cs | 34 +- .../Bitcoin/BitcoinWorkerContext.cs | 4 +- .../DaemonResponses/CoinbaseDevReward.cs | 20 + .../Blockchain/Conceal/ConcealJobManager.cs | 18 +- .../Conceal/ConcealWorkerContext.cs | 4 +- .../Cryptonote/CryptonoteConstants.cs | 13 + .../Cryptonote/CryptonotePayoutHandler.cs | 119 +- .../Cryptonote/CryptonoteWorkerContext.cs | 4 +- .../DaemonRequests/TransferRequest.cs | 28 + .../Blockchain/Equihash/EquihashConstants.cs | 3 +- .../Blockchain/Ergo/ErgoWorkerContext.cs | 4 +- .../Configuration/EthereumPoolConfigExtra.cs | 4 +- .../Ethereum/EthereumPayoutHandler.cs | 44 +- .../Blockchain/Ethereum/EthereumPool.cs | 4 +- .../Ethereum/EthereumWorkerContext.cs | 4 +- .../Blockchain/Handshake/HandshakeJob.cs | 16 +- .../Blockchain/Handshake/HandshakePool.cs | 2 - .../KaspaPaymentProcessingConfigExtra.cs | 16 +- .../Configuration/KaspaPoolConfigExtra.cs | 6 + .../Kaspa/Custom/Astrix/AstrixJob.cs | 92 + .../Custom/Karlsencoin/KarlsencoinJob.cs | 95 +- .../Blockchain/Kaspa/Custom/Pyrin/PyrinJob.cs | 3 +- .../Kaspa/Custom/Spectre/SpectreJob.cs | 129 + .../Blockchain/Kaspa/KaspaConstants.cs | 90 +- src/Miningcore/Blockchain/Kaspa/KaspaJob.cs | 56 +- .../Blockchain/Kaspa/KaspaJobManager.cs | 75 +- .../Blockchain/Kaspa/KaspaPayoutHandler.cs | 186 +- src/Miningcore/Blockchain/Kaspa/KaspaPool.cs | 79 +- src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs | 139 +- .../Blockchain/Kaspa/KaspaWorkerContext.cs | 4 +- .../Kaspa/RPC/KaspaWalletd/KaspaWalletd.cs | 971 +- .../RPC/KaspaWalletd/KaspaWalletdGrpc.cs | 68 + .../Kaspa/RPC/KaspaWalletd/kaspawalletd.proto | 29 + .../Blockchain/Kaspa/RPC/Kaspad/P2P.cs | 30 +- .../Blockchain/Kaspa/RPC/Kaspad/Rpc.cs | 30 +- .../Blockchain/Kaspa/RPC/Kaspad/p2p.proto | 1 + .../Blockchain/Kaspa/RPC/Kaspad/rpc.proto | 1 + .../Progpow/ProgpowWorkerContext.cs | 4 +- .../WarthogDaemonEndpointConfigExtra.cs | 19 + .../WarthogPaymentProcessingConfigExtra.cs | 33 + .../Configuration/WarthogPoolConfigExtra.cs | 14 + .../DaemonRequests/SendTransactionRequest.cs | 24 + .../DaemonRequests/SubmitBlockRequest.cs | 8 + .../Warthog/DaemonResponses/GetBalance.cs | 19 + .../DaemonResponses/GetBlockResponse.cs | 85 + .../GetBlockTemplateResponse.cs | 28 + .../DaemonResponses/GetChainInfoResponse.cs | 30 + .../GetFeeE8EncodedResponse.cs | 14 + .../GetNetworkHashrateResponse.cs | 15 + .../DaemonResponses/GetPeersResponse.cs | 14 + .../DaemonResponses/GetWalletResponse.cs | 19 + .../SendTransactionResponse.cs | 14 + .../DaemonResponses/SubmitBlockResponse.cs | 5 + .../DaemonResponses/WarthogResponseBase.cs | 12 + .../Blockchain/Warthog/WarthogConstants.cs | 88 + .../Warthog/WarthogExtraNonceProvider.cs | 8 + .../Blockchain/Warthog/WarthogJob.cs | 337 + .../Blockchain/Warthog/WarthogJobManager.cs | 588 ++ .../Warthog/WarthogPayoutHandler.cs | 492 + .../Blockchain/Warthog/WarthogPool.cs | 411 + .../Blockchain/Warthog/WarthogUtils.cs | 1095 +++ .../Warthog/WarthogWorkerContext.cs | 21 + src/Miningcore/Configuration/ClusterConfig.cs | 47 + .../Configuration/ClusterConfigExtensions.cs | 45 +- .../Crypto/Hashing/Algorithms/FishHash.cs | 24 +- .../Hashing/Algorithms/FishHashKarlsen.cs | 33 +- .../Crypto/Hashing/Algorithms/Flex.cs | 22 + .../Crypto/Hashing/Algorithms/Kezzak.cs | 2 +- .../Crypto/Hashing/Algorithms/Sha256T.cs | 23 + src/Miningcore/Mining/PoolBase.cs | 26 + src/Miningcore/Mining/WorkerContextBase.cs | 10 + src/Miningcore/Miningcore.csproj | 21 +- src/Miningcore/Native/AstroBWTv3.cs | 36 + src/Miningcore/Native/Multihash.cs | 32 +- src/Miningcore/Native/Verushash.cs | 15 +- .../PaymentSchemes/PPLNSBFPaymentScheme.cs | 259 + .../PaymentSchemes/PPLNSPaymentScheme.cs | 2 +- src/Miningcore/Payments/PayoutManager.cs | 28 + src/Miningcore/Persistence/Model/Block.cs | 1 + .../Model/Projections/MinerStats.cs | 4 + .../Persistence/Postgres/Entities/Block.cs | 1 + .../Postgres/Repositories/BlockRepository.cs | 56 +- .../Postgres/Repositories/ShareRepository.cs | 18 +- .../Postgres/Repositories/StatsRepository.cs | 14 + .../Postgres/Scripts/add_minereffort.sql | 1 + .../Persistence/Postgres/Scripts/createdb.sql | 1 + .../Repositories/IBlockRepository.cs | 7 +- .../Repositories/IShareRepository.cs | 4 +- .../Repositories/IStatsRepository.cs | 2 + src/Miningcore/build-libs-linux.sh | 8 +- src/Miningcore/coins.json | 259 +- src/Miningcore/config.schema.json | 13 + src/Native/libbeamhash/libbeamhash.sln | 2 +- src/Native/libcryptonote/Makefile | 3 +- src/Native/libcryptonote/Makefile.MSys2 | 1 + src/Native/libcryptonote/cryptonote_config.h | 2 + .../cryptonote_core/cryptonote_basic.h | 169 +- .../cryptonote_format_utils.cpp | 87 +- src/Native/libcryptonote/ringct/rctTypes.h | 201 +- .../salvium_oracle/asset_types.h | 77 + .../salvium_oracle/pricing_record.cpp | 246 + .../salvium_oracle/pricing_record.h | 159 + .../serialization/serialization.h | 7 + .../serialization/zephyr_pricing_record.h | 75 +- .../zephyr_oracle/pricing_record.cpp | 51 +- .../zephyr_oracle/pricing_record.h | 59 +- src/Native/libdero/Makefile | 46 + src/Native/libdero/astro_aarch64.cpp | 4281 +++++++++ src/Native/libdero/astro_aarch64.hpp | 14 + src/Native/libdero/astrobwtv3.cpp | 7288 +++++++++++++++ src/Native/libdero/astrobwtv3.h | 402 + src/Native/libdero/dllmain.cpp | 19 + src/Native/libdero/exports.cpp | 29 + src/Native/libdero/include/Salsa20.h | 116 + src/Native/libdero/include/Salsa20.inl | 161 + src/Native/libdero/include/endian.hpp | 14 + src/Native/libdero/include/fnv1a.h | 54 + .../include/highwayhash/arch_specific.cc | 193 + .../include/highwayhash/arch_specific.h | 179 + .../libdero/include/highwayhash/benchmark.cc | 331 + .../libdero/include/highwayhash/c_bindings.cc | 35 + .../libdero/include/highwayhash/c_bindings.h | 57 + .../include/highwayhash/compiler_specific.h | 90 + .../include/highwayhash/data_parallel.h | 341 + .../highwayhash/data_parallel_benchmark.cc | 157 + .../include/highwayhash/data_parallel_test.cc | 175 + .../libdero/include/highwayhash/endianess.h | 108 + .../libdero/include/highwayhash/example.cc | 40 + .../libdero/include/highwayhash/hh_avx2.cc | 19 + .../libdero/include/highwayhash/hh_avx2.h | 381 + .../libdero/include/highwayhash/hh_buffer.h | 116 + .../libdero/include/highwayhash/hh_neon.cc | 22 + .../libdero/include/highwayhash/hh_neon.h | 335 + .../include/highwayhash/hh_portable.cc | 19 + .../libdero/include/highwayhash/hh_portable.h | 302 + .../libdero/include/highwayhash/hh_sse41.cc | 19 + .../libdero/include/highwayhash/hh_sse41.h | 327 + .../libdero/include/highwayhash/hh_types.h | 50 + .../libdero/include/highwayhash/hh_vsx.cc | 22 + .../libdero/include/highwayhash/hh_vsx.h | 335 + .../libdero/include/highwayhash/highwayhash.h | 216 + .../include/highwayhash/highwayhash_fuzzer.cc | 25 + .../include/highwayhash/highwayhash_target.cc | 104 + .../include/highwayhash/highwayhash_target.h | 91 + .../include/highwayhash/highwayhash_test.cc | 391 + .../highwayhash/highwayhash_test_avx2.cc | 19 + .../highwayhash/highwayhash_test_neon.cc | 22 + .../highwayhash/highwayhash_test_portable.cc | 19 + .../highwayhash/highwayhash_test_sse41.cc | 19 + .../highwayhash/highwayhash_test_target.cc | 220 + .../highwayhash/highwayhash_test_target.h | 90 + .../highwayhash/highwayhash_test_vsx.cc | 22 + src/Native/libdero/include/highwayhash/iaca.h | 63 + .../include/highwayhash/instruction_sets.cc | 141 + .../include/highwayhash/instruction_sets.h | 118 + .../libdero/include/highwayhash/load3.h | 144 + .../include/highwayhash/nanobenchmark.cc | 451 + .../include/highwayhash/nanobenchmark.h | 189 + .../highwayhash/nanobenchmark_example.cc | 58 + .../libdero/include/highwayhash/os_mac.cc | 44 + .../libdero/include/highwayhash/os_mac.h | 62 + .../include/highwayhash/os_specific.cc | 260 + .../libdero/include/highwayhash/os_specific.h | 54 + .../libdero/include/highwayhash/profiler.h | 762 ++ .../include/highwayhash/profiler_example.cc | 97 + .../include/highwayhash/robust_statistics.h | 135 + .../libdero/include/highwayhash/scalar.h | 352 + .../highwayhash/scalar_sip_tree_hash.cc | 183 + .../highwayhash/scalar_sip_tree_hash.h | 37 + .../libdero/include/highwayhash/sip_hash.cc | 33 + .../libdero/include/highwayhash/sip_hash.h | 171 + .../include/highwayhash/sip_hash_fuzzer.cc | 20 + .../include/highwayhash/sip_hash_test.cc | 148 + .../include/highwayhash/sip_tree_hash.cc | 227 + .../include/highwayhash/sip_tree_hash.h | 52 + .../include/highwayhash/state_helpers.h | 130 + .../libdero/include/highwayhash/tsc_timer.h | 208 + .../libdero/include/highwayhash/vector128.h | 796 ++ .../libdero/include/highwayhash/vector256.h | 758 ++ .../libdero/include/highwayhash/vector_neon.h | 1032 +++ .../include/highwayhash/vector_test.cc | 66 + .../include/highwayhash/vector_test_avx2.cc | 19 + .../include/highwayhash/vector_test_neon.cc | 19 + .../highwayhash/vector_test_portable.cc | 19 + .../include/highwayhash/vector_test_sse41.cc | 19 + .../include/highwayhash/vector_test_target.cc | 225 + .../include/highwayhash/vector_test_target.h | 37 + src/Native/libdero/include/hugepages.h | 160 + src/Native/libdero/include/int-util.h | 23 + .../libdero/include/libdivsufsort/config.h | 81 + .../include/libdivsufsort/divsufsort.c | 399 + .../include/libdivsufsort/divsufsort.h | 177 + .../libdivsufsort/divsufsort_private.h | 225 + .../libdero/include/libdivsufsort/lfs.h | 56 + .../libdero/include/libdivsufsort/sssort.c | 815 ++ .../libdero/include/libdivsufsort/trsort.c | 586 ++ .../libdero/include/libdivsufsort/utils.c | 381 + src/Native/libdero/include/libkeccak/common.h | 61 + src/Native/libdero/include/libkeccak/digest.c | 699 ++ .../include/libkeccak/libkeccak-legacy.h | 42 + .../libdero/include/libkeccak/libkeccak.h | 1214 +++ .../include/libkeccak/libkeccak_behex_lower.c | 22 + .../include/libkeccak/libkeccak_behex_upper.c | 22 + .../libkeccak/libkeccak_cshake_initialise.c | 275 + .../libkeccak/libkeccak_cshake_suffix.c | 5 + .../libkeccak/libkeccak_degeneralise_spec.c | 120 + .../libkeccak_generalised_spec_initialise.c | 5 + .../libkeccak/libkeccak_generalised_sum_fd.c | 112 + .../include/libkeccak/libkeccak_hmac_copy.c | 38 + .../include/libkeccak/libkeccak_hmac_create.c | 5 + .../libkeccak/libkeccak_hmac_destroy.c | 5 + .../include/libkeccak/libkeccak_hmac_digest.c | 81 + .../libkeccak/libkeccak_hmac_duplicate.c | 5 + .../libkeccak/libkeccak_hmac_fast_destroy.c | 5 + .../libkeccak/libkeccak_hmac_fast_digest.c | 78 + .../libkeccak/libkeccak_hmac_fast_free.c | 5 + .../libkeccak/libkeccak_hmac_fast_update.c | 53 + .../include/libkeccak/libkeccak_hmac_free.c | 5 + .../libkeccak/libkeccak_hmac_initialise.c | 6 + .../libkeccak/libkeccak_hmac_marshal.c | 5 + .../include/libkeccak/libkeccak_hmac_reset.c | 5 + .../libkeccak/libkeccak_hmac_set_key.c | 48 + .../libkeccak/libkeccak_hmac_unmarshal.c | 59 + .../include/libkeccak/libkeccak_hmac_update.c | 53 + .../include/libkeccak/libkeccak_hmac_wipe.c | 24 + .../libkeccak/libkeccak_keccaksum_fd.c | 5 + .../libkeccak/libkeccak_rawshakesum_fd.c | 5 + .../include/libkeccak/libkeccak_sha3sum_fd.c | 5 + .../include/libkeccak/libkeccak_shakesum_fd.c | 5 + .../include/libkeccak/libkeccak_spec_check.c | 5 + .../libkeccak/libkeccak_spec_rawshake.c | 6 + .../include/libkeccak/libkeccak_spec_sha3.c | 5 + .../include/libkeccak/libkeccak_state_copy.c | 25 + .../libkeccak/libkeccak_state_create.c | 20 + .../libkeccak/libkeccak_state_destroy.c | 5 + .../libkeccak/libkeccak_state_duplicate.c | 20 + .../libkeccak/libkeccak_state_fast_destroy.c | 5 + .../libkeccak/libkeccak_state_fast_free.c | 5 + .../include/libkeccak/libkeccak_state_free.c | 5 + .../libkeccak/libkeccak_state_initialise.c | 45 + .../libkeccak/libkeccak_state_marshal.c | 46 + .../include/libkeccak/libkeccak_state_reset.c | 5 + .../libkeccak/libkeccak_state_unmarshal.c | 57 + .../include/libkeccak/libkeccak_state_wipe.c | 15 + .../libkeccak/libkeccak_state_wipe_message.c | 18 + .../libkeccak/libkeccak_state_wipe_sponge.c | 18 + .../include/libkeccak/libkeccak_unhex.c | 30 + .../libkeccak/libkeccak_zerocopy_chunksize.c | 5 + src/Native/libdero/include/libsais/libsais.c | 7978 +++++++++++++++++ src/Native/libdero/include/libsais/libsais.h | 394 + src/Native/libdero/include/lookup.h | 14 + src/Native/libdero/include/xxhash64.h | 202 + src/Native/libdero/libdero.sln | 31 + src/Native/libdero/libdero.vcxproj | 278 + src/Native/libdero/stdafx.cpp | 8 + src/Native/libdero/stdafx.h | 16 + src/Native/libdero/stdint.h | 259 + src/Native/libdero/targetver.h | 8 + src/Native/libfiropow/Makefile | 2 +- src/Native/libfiropow/libfiropow.vcxproj | 4 +- src/Native/libfiropow/stdint.h | 259 + src/Native/libkawpow/Makefile | 2 +- src/Native/libkawpow/libkawpow.vcxproj | 4 +- src/Native/libkawpow/stdint.h | 259 + src/Native/libmeowpow/Makefile | 2 +- src/Native/libmeowpow/libmeowpow.vcxproj | 4 +- src/Native/libmeowpow/stdint.h | 259 + src/Native/libmultihash/Makefile | 7 +- src/Native/libmultihash/exports.cpp | 6 + .../3rdParty/{keccak.c => fishhash_keccak.c} | 10 +- .../fishhash/3rdParty/fishhash_keccak.h | 17 + .../libmultihash/fishhash/3rdParty/keccak.h | 17 - src/Native/libmultihash/fishhash/fishhash.cpp | 202 +- src/Native/libmultihash/fishhash/fishhash.h | 5 +- .../flex/cryptonote/crypto/aesb.c | 177 + .../flex/cryptonote/crypto/c_blake256.c | 326 + .../flex/cryptonote/crypto/c_blake256.h | 43 + .../flex/cryptonote/crypto/c_groestl.c | 360 + .../flex/cryptonote/crypto/c_groestl.h | 56 + .../flex/cryptonote/crypto/c_jh.c | 367 + .../flex/cryptonote/crypto/c_jh.h | 20 + .../flex/cryptonote/crypto/c_keccak.c | 112 + .../flex/cryptonote/crypto/c_keccak.h | 26 + .../flex/cryptonote/crypto/c_skein.c | 2036 +++++ .../flex/cryptonote/crypto/c_skein.h | 45 + .../flex/cryptonote/crypto/crypto.h | 186 + .../flex/cryptonote/crypto/groestl_tables.h | 38 + .../flex/cryptonote/crypto/hash-ops.h | 57 + .../flex/cryptonote/crypto/hash.c | 24 + .../flex/cryptonote/crypto/hash.h | 22 + .../flex/cryptonote/crypto/int-util.h | 230 + .../flex/cryptonote/crypto/oaes_config.h | 50 + .../flex/cryptonote/crypto/oaes_lib.c | 1468 +++ .../flex/cryptonote/crypto/oaes_lib.h | 215 + .../flex/cryptonote/crypto/skein_port.h | 190 + .../cryptonote/crypto/variant2_int_sqrt.h | 168 + .../flex/cryptonote/crypto/wild_keccak.cpp | 119 + .../flex/cryptonote/crypto/wild_keccak.h | 168 + .../flex/cryptonote/cryptonight.c | 300 + .../flex/cryptonote/cryptonight.h | 17 + .../flex/cryptonote/cryptonight_dark.c | 300 + .../flex/cryptonote/cryptonight_dark.h | 17 + .../flex/cryptonote/cryptonight_dark_lite.c | 300 + .../flex/cryptonote/cryptonight_dark_lite.h | 17 + .../flex/cryptonote/cryptonight_fast.c | 297 + .../flex/cryptonote/cryptonight_fast.h | 17 + .../flex/cryptonote/cryptonight_lite.c | 300 + .../flex/cryptonote/cryptonight_lite.h | 17 + .../flex/cryptonote/cryptonight_soft_shell.c | 298 + .../flex/cryptonote/cryptonight_soft_shell.h | 17 + .../flex/cryptonote/cryptonight_turtle.c | 300 + .../flex/cryptonote/cryptonight_turtle.h | 17 + .../flex/cryptonote/cryptonight_turtle_lite.c | 300 + .../flex/cryptonote/cryptonight_turtle_lite.h | 17 + src/Native/libmultihash/flex/flex.c | 299 + src/Native/libmultihash/flex/flex.h | 16 + src/Native/libmultihash/libmultihash.vcxproj | 43 +- .../libmultihash/libmultihash.vcxproj.filters | 127 +- src/Native/libmultihash/sha3/sph_keccak.c | 119 + src/Native/libmultihash/sha3/sph_keccak.h | 54 + src/Native/libverushash/dllmain.cpp | 19 + src/Native/libverushash/exports.cpp | 5 + src/Native/libverushash/libverushash.sln | 2 +- src/Native/libverushash/libverushash.vcxproj | 5 + src/Native/libverushash/stdafx.cpp | 8 + src/Native/libverushash/stdafx.h | 16 + src/Native/libverushash/stdint.h | 259 + src/Native/libverushash/targetver.h | 8 + src/Native/libverushash/verushashverify.cpp | 11 + src/Native/libverushash/verushashverify.h | 1 + 355 files changed, 62302 insertions(+), 660 deletions(-) create mode 100644 build.backup/config.json create mode 100644 examples/warthog_pool.json create mode 100644 src/Miningcore/Blockchain/Bitcoin/DaemonResponses/CoinbaseDevReward.cs create mode 100644 src/Miningcore/Blockchain/Kaspa/Custom/Astrix/AstrixJob.cs create mode 100644 src/Miningcore/Blockchain/Kaspa/Custom/Spectre/SpectreJob.cs create mode 100644 src/Miningcore/Blockchain/Warthog/Configuration/WarthogDaemonEndpointConfigExtra.cs create mode 100644 src/Miningcore/Blockchain/Warthog/Configuration/WarthogPaymentProcessingConfigExtra.cs create mode 100644 src/Miningcore/Blockchain/Warthog/Configuration/WarthogPoolConfigExtra.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonRequests/SendTransactionRequest.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonRequests/SubmitBlockRequest.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBalance.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockTemplateResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetChainInfoResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetFeeE8EncodedResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetNetworkHashrateResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetPeersResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/GetWalletResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/SendTransactionResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/SubmitBlockResponse.cs create mode 100644 src/Miningcore/Blockchain/Warthog/DaemonResponses/WarthogResponseBase.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogConstants.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogExtraNonceProvider.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogJob.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogJobManager.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogPayoutHandler.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogPool.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogUtils.cs create mode 100644 src/Miningcore/Blockchain/Warthog/WarthogWorkerContext.cs create mode 100644 src/Miningcore/Crypto/Hashing/Algorithms/Flex.cs create mode 100644 src/Miningcore/Crypto/Hashing/Algorithms/Sha256T.cs create mode 100644 src/Miningcore/Native/AstroBWTv3.cs create mode 100644 src/Miningcore/Payments/PaymentSchemes/PPLNSBFPaymentScheme.cs create mode 100644 src/Miningcore/Persistence/Postgres/Scripts/add_minereffort.sql create mode 100644 src/Native/libcryptonote/salvium_oracle/asset_types.h create mode 100644 src/Native/libcryptonote/salvium_oracle/pricing_record.cpp create mode 100644 src/Native/libcryptonote/salvium_oracle/pricing_record.h create mode 100644 src/Native/libdero/Makefile create mode 100644 src/Native/libdero/astro_aarch64.cpp create mode 100644 src/Native/libdero/astro_aarch64.hpp create mode 100644 src/Native/libdero/astrobwtv3.cpp create mode 100644 src/Native/libdero/astrobwtv3.h create mode 100644 src/Native/libdero/dllmain.cpp create mode 100644 src/Native/libdero/exports.cpp create mode 100755 src/Native/libdero/include/Salsa20.h create mode 100755 src/Native/libdero/include/Salsa20.inl create mode 100644 src/Native/libdero/include/endian.hpp create mode 100755 src/Native/libdero/include/fnv1a.h create mode 100755 src/Native/libdero/include/highwayhash/arch_specific.cc create mode 100755 src/Native/libdero/include/highwayhash/arch_specific.h create mode 100755 src/Native/libdero/include/highwayhash/benchmark.cc create mode 100755 src/Native/libdero/include/highwayhash/c_bindings.cc create mode 100755 src/Native/libdero/include/highwayhash/c_bindings.h create mode 100755 src/Native/libdero/include/highwayhash/compiler_specific.h create mode 100755 src/Native/libdero/include/highwayhash/data_parallel.h create mode 100755 src/Native/libdero/include/highwayhash/data_parallel_benchmark.cc create mode 100755 src/Native/libdero/include/highwayhash/data_parallel_test.cc create mode 100755 src/Native/libdero/include/highwayhash/endianess.h create mode 100755 src/Native/libdero/include/highwayhash/example.cc create mode 100755 src/Native/libdero/include/highwayhash/hh_avx2.cc create mode 100755 src/Native/libdero/include/highwayhash/hh_avx2.h create mode 100755 src/Native/libdero/include/highwayhash/hh_buffer.h create mode 100755 src/Native/libdero/include/highwayhash/hh_neon.cc create mode 100755 src/Native/libdero/include/highwayhash/hh_neon.h create mode 100755 src/Native/libdero/include/highwayhash/hh_portable.cc create mode 100755 src/Native/libdero/include/highwayhash/hh_portable.h create mode 100755 src/Native/libdero/include/highwayhash/hh_sse41.cc create mode 100755 src/Native/libdero/include/highwayhash/hh_sse41.h create mode 100755 src/Native/libdero/include/highwayhash/hh_types.h create mode 100755 src/Native/libdero/include/highwayhash/hh_vsx.cc create mode 100755 src/Native/libdero/include/highwayhash/hh_vsx.h create mode 100755 src/Native/libdero/include/highwayhash/highwayhash.h create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_fuzzer.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_target.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_target.h create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_avx2.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_neon.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_portable.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_sse41.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_target.cc create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_target.h create mode 100755 src/Native/libdero/include/highwayhash/highwayhash_test_vsx.cc create mode 100755 src/Native/libdero/include/highwayhash/iaca.h create mode 100755 src/Native/libdero/include/highwayhash/instruction_sets.cc create mode 100755 src/Native/libdero/include/highwayhash/instruction_sets.h create mode 100755 src/Native/libdero/include/highwayhash/load3.h create mode 100755 src/Native/libdero/include/highwayhash/nanobenchmark.cc create mode 100755 src/Native/libdero/include/highwayhash/nanobenchmark.h create mode 100755 src/Native/libdero/include/highwayhash/nanobenchmark_example.cc create mode 100755 src/Native/libdero/include/highwayhash/os_mac.cc create mode 100755 src/Native/libdero/include/highwayhash/os_mac.h create mode 100755 src/Native/libdero/include/highwayhash/os_specific.cc create mode 100755 src/Native/libdero/include/highwayhash/os_specific.h create mode 100755 src/Native/libdero/include/highwayhash/profiler.h create mode 100755 src/Native/libdero/include/highwayhash/profiler_example.cc create mode 100755 src/Native/libdero/include/highwayhash/robust_statistics.h create mode 100755 src/Native/libdero/include/highwayhash/scalar.h create mode 100755 src/Native/libdero/include/highwayhash/scalar_sip_tree_hash.cc create mode 100755 src/Native/libdero/include/highwayhash/scalar_sip_tree_hash.h create mode 100755 src/Native/libdero/include/highwayhash/sip_hash.cc create mode 100755 src/Native/libdero/include/highwayhash/sip_hash.h create mode 100755 src/Native/libdero/include/highwayhash/sip_hash_fuzzer.cc create mode 100755 src/Native/libdero/include/highwayhash/sip_hash_test.cc create mode 100755 src/Native/libdero/include/highwayhash/sip_tree_hash.cc create mode 100755 src/Native/libdero/include/highwayhash/sip_tree_hash.h create mode 100755 src/Native/libdero/include/highwayhash/state_helpers.h create mode 100755 src/Native/libdero/include/highwayhash/tsc_timer.h create mode 100755 src/Native/libdero/include/highwayhash/vector128.h create mode 100755 src/Native/libdero/include/highwayhash/vector256.h create mode 100755 src/Native/libdero/include/highwayhash/vector_neon.h create mode 100755 src/Native/libdero/include/highwayhash/vector_test.cc create mode 100755 src/Native/libdero/include/highwayhash/vector_test_avx2.cc create mode 100755 src/Native/libdero/include/highwayhash/vector_test_neon.cc create mode 100755 src/Native/libdero/include/highwayhash/vector_test_portable.cc create mode 100755 src/Native/libdero/include/highwayhash/vector_test_sse41.cc create mode 100755 src/Native/libdero/include/highwayhash/vector_test_target.cc create mode 100755 src/Native/libdero/include/highwayhash/vector_test_target.h create mode 100644 src/Native/libdero/include/hugepages.h create mode 100644 src/Native/libdero/include/int-util.h create mode 100644 src/Native/libdero/include/libdivsufsort/config.h create mode 100644 src/Native/libdero/include/libdivsufsort/divsufsort.c create mode 100644 src/Native/libdero/include/libdivsufsort/divsufsort.h create mode 100644 src/Native/libdero/include/libdivsufsort/divsufsort_private.h create mode 100644 src/Native/libdero/include/libdivsufsort/lfs.h create mode 100644 src/Native/libdero/include/libdivsufsort/sssort.c create mode 100644 src/Native/libdero/include/libdivsufsort/trsort.c create mode 100644 src/Native/libdero/include/libdivsufsort/utils.c create mode 100755 src/Native/libdero/include/libkeccak/common.h create mode 100755 src/Native/libdero/include/libkeccak/digest.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak-legacy.h create mode 100755 src/Native/libdero/include/libkeccak/libkeccak.h create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_behex_lower.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_behex_upper.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_cshake_initialise.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_cshake_suffix.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_degeneralise_spec.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_generalised_spec_initialise.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_generalised_sum_fd.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_copy.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_create.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_destroy.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_digest.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_duplicate.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_fast_destroy.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_fast_digest.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_fast_free.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_fast_update.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_free.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_initialise.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_marshal.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_reset.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_set_key.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_unmarshal.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_update.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_hmac_wipe.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_keccaksum_fd.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_rawshakesum_fd.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_sha3sum_fd.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_shakesum_fd.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_spec_check.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_spec_rawshake.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_spec_sha3.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_copy.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_create.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_destroy.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_duplicate.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_fast_destroy.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_fast_free.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_free.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_initialise.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_marshal.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_reset.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_unmarshal.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_wipe.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_wipe_message.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_state_wipe_sponge.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_unhex.c create mode 100755 src/Native/libdero/include/libkeccak/libkeccak_zerocopy_chunksize.c create mode 100644 src/Native/libdero/include/libsais/libsais.c create mode 100644 src/Native/libdero/include/libsais/libsais.h create mode 100644 src/Native/libdero/include/lookup.h create mode 100755 src/Native/libdero/include/xxhash64.h create mode 100644 src/Native/libdero/libdero.sln create mode 100644 src/Native/libdero/libdero.vcxproj create mode 100644 src/Native/libdero/stdafx.cpp create mode 100644 src/Native/libdero/stdafx.h create mode 100644 src/Native/libdero/stdint.h create mode 100644 src/Native/libdero/targetver.h create mode 100644 src/Native/libfiropow/stdint.h create mode 100644 src/Native/libkawpow/stdint.h create mode 100644 src/Native/libmeowpow/stdint.h rename src/Native/libmultihash/fishhash/3rdParty/{keccak.c => fishhash_keccak.c} (97%) create mode 100644 src/Native/libmultihash/fishhash/3rdParty/fishhash_keccak.h delete mode 100644 src/Native/libmultihash/fishhash/3rdParty/keccak.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/aesb.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_blake256.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_blake256.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_groestl.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_groestl.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_jh.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_jh.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_keccak.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_keccak.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_skein.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/c_skein.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/crypto.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/groestl_tables.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/hash-ops.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/hash.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/hash.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/int-util.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/oaes_config.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/oaes_lib.c create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/oaes_lib.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/skein_port.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/variant2_int_sqrt.h create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/wild_keccak.cpp create mode 100644 src/Native/libmultihash/flex/cryptonote/crypto/wild_keccak.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_dark.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_dark.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_dark_lite.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_dark_lite.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_fast.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_fast.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_lite.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_lite.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_soft_shell.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_soft_shell.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_turtle.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_turtle.h create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_turtle_lite.c create mode 100644 src/Native/libmultihash/flex/cryptonote/cryptonight_turtle_lite.h create mode 100644 src/Native/libmultihash/flex/flex.c create mode 100644 src/Native/libmultihash/flex/flex.h create mode 100644 src/Native/libverushash/dllmain.cpp create mode 100644 src/Native/libverushash/stdafx.cpp create mode 100644 src/Native/libverushash/stdafx.h create mode 100644 src/Native/libverushash/stdint.h create mode 100644 src/Native/libverushash/targetver.h diff --git a/Dockerfile b/Dockerfile index 0075e3412..7333e48d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0-jammy as BUILDER WORKDIR /app RUN apt-get update && \ - apt-get -y install cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libzmq3-dev golang-go libgmp-dev + apt-get -y install cmake clang ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libzmq3-dev golang-go libgmp-dev libc++-dev COPY . . WORKDIR /app/src/Miningcore RUN dotnet publish -c Release --framework net6.0 -o ../../build diff --git a/build-debian-11.sh b/build-debian-11.sh index 87b3f46d5..f67c4ed9c 100755 --- a/build-debian-11.sh +++ b/build-debian-11.sh @@ -11,7 +11,7 @@ rm packages-microsoft-prod.deb # install dev-dependencies sudo apt-get update; \ - sudo apt-get -y install dotnet-sdk-6.0 git cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5-dev libgmp-dev + sudo apt-get -y install dotnet-sdk-6.0 git cmake clang ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5-dev libgmp-dev libc++-dev (cd src/Miningcore && \ BUILDIR=${1:-../../build} && \ diff --git a/build-debian-12.sh b/build-debian-12.sh index 4ed7c3e29..10c1358ca 100644 --- a/build-debian-12.sh +++ b/build-debian-12.sh @@ -11,7 +11,7 @@ rm packages-microsoft-prod.deb # install dev-dependencies sudo apt-get update; \ - sudo apt-get -y install dotnet-sdk-6.0 git cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5-dev libgmp-dev + sudo apt-get -y install dotnet-sdk-6.0 git cmake clang ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5-dev libgmp-dev libc++-dev (cd src/Miningcore && \ BUILDIR=${1:-../../build} && \ diff --git a/build-ubuntu-20.04.sh b/build-ubuntu-20.04.sh index e0d514592..77a0d4d4b 100755 --- a/build-ubuntu-20.04.sh +++ b/build-ubuntu-20.04.sh @@ -11,7 +11,7 @@ rm packages-microsoft-prod.deb # install dev-dependencies sudo apt-get update; \ - sudo apt-get -y install dotnet-sdk-6.0 git cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libgmp-dev + sudo apt-get -y install dotnet-sdk-6.0 git cmake clang ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libgmp-dev libc++-dev (cd src/Miningcore && \ BUILDIR=${1:-../../build} && \ diff --git a/build-ubuntu-21.04.sh b/build-ubuntu-21.04.sh index c377bfff5..85385f626 100755 --- a/build-ubuntu-21.04.sh +++ b/build-ubuntu-21.04.sh @@ -11,7 +11,7 @@ rm packages-microsoft-prod.deb # install dev-dependencies sudo apt-get update; \ - sudo apt-get -y install dotnet-sdk-6.0 git cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libgmp-dev + sudo apt-get -y install dotnet-sdk-6.0 git cmake clang ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libgmp-dev libc++-dev (cd src/Miningcore && \ BUILDIR=${1:-../../build} && \ diff --git a/build-ubuntu-22.04.sh b/build-ubuntu-22.04.sh index 26d8f0f76..63f3733aa 100644 --- a/build-ubuntu-22.04.sh +++ b/build-ubuntu-22.04.sh @@ -4,7 +4,7 @@ # install dev-dependencies sudo apt-get update; \ - sudo apt-get -y install dotnet-sdk-6.0 git cmake ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libgmp-dev + sudo apt-get -y install dotnet-sdk-6.0 git cmake clang ninja-build build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 libgmp-dev libc++-dev (cd src/Miningcore && \ BUILDIR=${1:-../../build} && \ diff --git a/build.backup/config.json b/build.backup/config.json new file mode 100644 index 000000000..389c7f583 --- /dev/null +++ b/build.backup/config.json @@ -0,0 +1,3657 @@ +{ + "logging": { + "level": "debug", + "enableConsoleLog": true, + "enableConsoleColors": true, + "logFile": "miningcore.log", + "apiLogFile": "api.log", + "logBaseDirectory": "/home/ceedii/miningcore/build/", + "perPoolLogFile": true + }, + "banning": { + "manager": "Integrated", + "banOnJunkReceive": true, + "banOnInvalidShares": true + }, + "notifications": { + "enabled": false, + "email": { + "host": "localhost", + "port": 25, + "user": "", + "password": "", + "fromAddress": "noreply@mining-pool.cedric-crispin.local", + "fromName": "[Mining Pool - Cedric CRISPIN - Developpement]" + }, + "admin": { + "enabled": false, + "emailAddress": "cedric.crispin@gmail.com", + "notifyBlockFound": true + } + }, + "persistence": { + "postgres": { + "host": "127.0.0.1", + "port": 5432, + "user": "mining_pool_cedric_crispin_com", + "password": "kpnFVVPkRXzqt7RdgKMJkHCMWTK9PFhtp4T", + "database": "mining_pool_cedric_crispin_com", + "commandTimeout": 600 + } + }, + "equihashMaxThreads": 4, + "paymentProcessing": { + "enabled": true, + "interval": 600, + "shareRecoveryFile": "recovered-shares.txt" + }, + "api": { + "enabled": true, + "listenAddress": "0.0.0.0", + "port": 4000, + "adminIpWhitelist": ["192.168.1.7"], + "metricsIpWhitelist": ["192.168.1.7"], + "rateLimiting": { + "disabled": false, + "rules": [ + { + "Endpoint": "*", + "Period": "1s", + "Limit": 30 + } + ], + "ipWhitelist": ["192.168.1.7"] + } + }, + "pools": [ + { + "id": "vtc1", + "enabled": false, + "coin": "vertcoin", + "vertHashDataFile": "/home/ceedii/.vertcoin/testnet3/verthash.dat", + "address": "X1n3Gf4QHwM8XLbE63iRJo3HcWzE34bjmk", + "rewardRecipients": [ + { + "type": "op", + "address": "X1n3Gf4QHwM8XLbE63iRJo3HcWzE34bjmk", + "percentage": 0.1 + } + ], + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3334": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "3335": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 15888, + "user": "vtc1qsjurynfa3uzsw2wctuhn4kzzh27adv3c5zg0wu", + "password": "98bct78hKopZYxWZVWRcbtb85IKB6yxN9H6qAH_09oU=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:15890", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "xmr1", + "enabled": false, + "coin": "monero", + "randomXRealm": "xmr1", + "randomXVmCount": 1, + "randomXFlagsAdd": "RANDOMX_FLAG_FULL_MEM", + "address": "9w2B8t5aGdKCZTaGsUFre32FA7Z3hz2X2KzUvA3mVmoZFCK3HzaFQjEK9wMm1LAtR2378jij9uAPjM4UChux3GuZFd3aGqu", + "rewardRecipients": [ + { + "type": "op", + "address": "9w2B8t5aGdKCZTaGsUFre32FA7Z3hz2X2KzUvA3mVmoZFCK3HzaFQjEK9wMm1LAtR2378jij9uAPjM4UChux3GuZFd3aGqu", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3344": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 7500, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 30000 + } + }, + "3345": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 7500, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 30000 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 28081, + "user": null, + "password": null, + "zmqBlockNotifySocket": "tcp://127.0.0.1:28084", + "zmqBlockNotifyTopic": "json-minimal-chain_main" + }, + { + "host": "127.0.0.1", + "port": 28083, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNSBF", + "payoutSchemeConfig": { + "factor": 0.5, + "blockFinderPercentage": 40.0 + } + } + }, + { + "id": "erg1", + "enabled": false, + "coin": "ergo", + "address": "3WzFWwWi6E7TvnXvZEeNUTaieTx4PUckVPhnL6PED5wjr8vWSdo9", + "rewardRecipients": [ + { + "type": "op", + "address": "3WzFWwWi6E7TvnXvZEeNUTaieTx4PUckVPhnL6PED5wjr8vWSdo9", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 250, + "jobRebroadcastTimeout": 3, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3354": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "3355": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 9052, + "user": null, + "password": null, + "apiKey": "TXfjTjjH7L9bthrHdtx3xmpNnxNCNgNJ9Hv" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "z=f?!3!-wx^tWrF!fHjX", + "minimumPayment": 0.01, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "ccx1", + "enabled": false, + "coin": "conceal", + "networkTypeOverride": "testnet", + "cryptonightMaxThreads": 2, + "address": "ccx7BRXk4eubahkPNe4mmZ1PYwigPmqGT5ciBWib39aQQjWctY6vzA7RuBBkcoSKxEVBQArdvJi5YhEHRY29sSpS9EiD5c2nW5", + "rewardRecipients": [ + { + "type": "op", + "address": "ccx7BRXk4eubahkPNe4mmZ1PYwigPmqGT5ciBWib39aQQjWctY6vzA7RuBBkcoSKxEVBQArdvJi5YhEHRY29sSpS9EiD5c2nW5", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 250, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3364": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 5000, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2500, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 20000 + } + }, + "3365": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 5000, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2500, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 20000 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 16600, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8770, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "rxd1", + "enabled": false, + "coin": "radiant", + "address": "ms4rYFqGfot5gz9hZ6FYNaGnYiFh3sXVCT", + "rewardRecipients": [ + { + "type": "op", + "address": "ms4rYFqGfot5gz9hZ6FYNaGnYiFh3sXVCT", + "percentage": 0.1 + } + ], + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3374": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 0.512, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.512, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "3375": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 0.512, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.512, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 17332, + "user": "ms4rYFqGfot5gz9hZ6FYNaGnYiFh3sXVCT", + "password": "qT-CvLnXDmOSw5sckZghZG0ezX7-Oz3jKUW33hVvP6A=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:17334", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "ubq1", + "enabled": false, + "coin": "ubiq", + "address": "0x6101Ee22AB2cd3907E5CA0b70EF12A2afE842A56", + "rewardRecipients": [ + { + "type": "op", + "address": "0x6101Ee22AB2cd3907E5CA0b70EF12A2afE842A56", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3384": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "3385": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "chainTypeOverride": "Ubiq", + "dagDir": "/home/ceedii/.ubqhash/", + "daemons": [ + { + "host": "127.0.0.1", + "port": 8588, + "portWs": 8589, + "user": "", + "password": "" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + }, + "gas": 21000, + "maxFeePerGas": 80000000000, + "BlockSearchOffset": 100, + "keepUncles": false, + "keepTransactionFees": false + } + }, + { + "id": "etc1", + "enabled": false, + "coin": "ethereumclassic", + "address": "0x421Afb2ce225D3A2d3DD6e63Fe57E124B40e20Af", + "rewardRecipients": [ + { + "type": "op", + "address": "0x421Afb2ce225D3A2d3DD6e63Fe57E124B40e20Af", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "3394": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "3395": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "chainTypeOverride": "Mordor", + "daemons": [ + { + "host": "127.0.0.1", + "port": 8545, + "portWs": 8546, + "user": "", + "password": "" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + }, + "gas": 21000, + "maxFeePerGas": 50000000000, + "BlockSearchOffset": 100, + "keepUncles": false, + "keepTransactionFees": false + } + }, + { + "id": "plsr1", + "enabled": false, + "coin": "pulsar", + "address": "PKeqLKLnPv7ZWmuBxnjHp9G9b4S3wZQZoN", + "rewardRecipients": [ + { + "type": "op", + "address": "PKeqLKLnPv7ZWmuBxnjHp9G9b4S3wZQZoN", + "percentage": 0.1 + } + ], + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4004": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.00001, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.00001, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4005": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.00001, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.00001, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 5996, + "user": "PKeqLKLnPv7ZWmuBxnjHp9G9b4S3wZQZoN", + "password": "kP1-W4R8dt7xU6UWr4ns1w45CSoFQUfjhQj0IIuEJ60=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:5997", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "beam1", + "enabled": false, + "coin": "beam", + "address": "2b53f3e23bd471de6b502575f6ef6f65fee55ac85baed63f307324f9cf78d0c4ebe", + "rewardRecipients": [ + { + "type": "op", + "address": "2b53f3e23bd471de6b502575f6ef6f65fee55ac85baed63f307324f9cf78d0c4ebe", + "percentage": 0.1 + } + ], + "clientConnectionTimeout": 600, + "maxActiveJobs": 4, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4014": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 512, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4015": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 512, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 11001, + "user": null, + "password": null, + "apiKey": "EX7Q10qiKRcNaieNy+V2rTDvkUjyIYMTXNxtPZer8e+b/4OG4Fgp9vhY1XPuyCj0351ZzCJxfDKhvZHX/cIrR4ZSjgmjyVGfFl4=" + }, + { + "host": "127.0.0.1", + "port": 10000, + "user": null, + "password": null, + "category": "wallet" + }, + { + "host": "127.0.0.1", + "port": 10002, + "user": null, + "password": null, + "category": "explorer" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "vrsc1", + "enabled": false, + "coin": "veruscoin", + "address": "RGk5aoCpv9Yqajnf7HDwdDUgPhRVvVGBVe", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "RGk5aoCpv9Yqajnf7HDwdDUgPhRVvVGBVe", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 900, + "maxActiveJobs": 4, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4024": { + "name": "CPU/SMARTPHONE", + "listenAddress": "0.0.0.0", + "difficulty": 1280000, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1280000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 5120000 + } + }, + "4025": { + "name": "CPU/SMARTPHONE", + "listenAddress": "0.0.0.0", + "difficulty": 1280000, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1280000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 5120000 + } + }, + "4026": { + "name": "NICEHASH", + "listenAddress": "0.0.0.0", + "difficulty": 25600000, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 20760000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 102400000 + } + }, + "4027": { + "name": "NICEHASH", + "listenAddress": "0.0.0.0", + "difficulty": 25600000, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 20760000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 102400000 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 17771, + "user": "RGk5aoCpv9Yqajnf7HDwdDUgPhRVvVGBVe", + "password": "qsnrdbq7KdWwNctKq7jkbjmvftPnXbTRjqn", + "zmqBlockNotifySocket": "tcp://127.0.0.1:17772", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "rvn1", + "enabled": false, + "coin": "ravencoin", + "address": "mxJpJbGnT6ewaTvZ5jghPGN76BM4v9DTw1", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "mxJpJbGnT6ewaTvZ5jghPGN76BM4v9DTw1", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4054": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4055": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 18766, + "user": "mxJpJbGnT6ewaTvZ5jghPGN76BM4v9DTw1", + "password": "gB6OjbV8v7keqjEY86jJvdgB3YIO5ZdZppTZh96mMOw=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:18767", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "firo1", + "enabled": false, + "coin": "firo", + "address": "TDGKtn7TXjjAh8Gi9JAZdkm5UXQeBVRfu9", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "TDGKtn7TXjjAh8Gi9JAZdkm5UXQeBVRfu9", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4064": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4065": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 18888, + "user": "TDGKtn7TXjjAh8Gi9JAZdkm5UXQeBVRfu9", + "password": "tn8TXba9UNMBQAdsJADfAy9CHXcRzeYHyppE9G3ZFjE=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:18889", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "octa1", + "enabled": false, + "coin": "octaspace", + "address": "0x6a68307e8b4A6FD65eCf6943CaE404e7dA6cc0Ef", + "rewardRecipients": [ + { + "type": "op", + "address": "0x6a68307e8b4A6FD65eCf6943CaE404e7dA6cc0Ef", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4074": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4075": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "chainTypeOverride": "OctaSpace", + "dagDir": "/home/ceedii/.octaspace_ethash/", + "daemons": [ + { + "host": "127.0.0.1", + "port": 8547, + "portWs": 8548, + "user": "", + "password": "" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + }, + "gas": 21000, + "maxFeePerGas": 50000000000, + "BlockSearchOffset": 100, + "keepUncles": false, + "keepTransactionFees": false + } + }, + { + "id": "alph1", + "enabled": false, + "coin": "alephium", + "address": "1F7eEzddhQx8Q2XQVDguYP3vJ3RvFP63hNDNMP43yp2Qa", + "rewardRecipients": [ + { + "type": "op", + "address": "1F7eEzddhQx8Q2XQVDguYP3vJ3RvFP63hNDNMP43yp2Qa", + "percentage": 0.1 + } + ], + "clientConnectionTimeout": 600, + "socketJobMessageBufferSize": 16384, + "maxActiveJobs": 8, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4084": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.256, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4085": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.256, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4086": { + "name": "FPGA [FK33/TH53/TH55/E300]", + "listenAddress": "0.0.0.0", + "difficulty": 8, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 4, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4087": { + "name": "FPGA [FK33/TH53/TH55/E300]", + "listenAddress": "0.0.0.0", + "difficulty": 8, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 4, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4088": { + "name": "Goldshell AL-BOX", + "listenAddress": "0.0.0.0", + "difficulty": 512, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 256, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 2048 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 12973, + "user": null, + "password": null, + "apiKey": "voYFfD8vexc13PQa2RCFGsY8vHZbrb8NFIwhJQcbc2oOpqZt", + "minerApiPort": 10973 + } + ], + "paymentProcessing": { + "enabled": true, + "walletName": "alephium-pool-local-testnet", + "walletPassword": "bdwC7rwMqCMjvkgXfCR9Pb77bk9KtHjLLgj", + "blockRewardsLockTime": 500, + "keepTransactionFees": true, + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "rth1", + "enabled": false, + "coin": "hypra", + "address": "0xCa11302398EF64F3197050AcA2AA5C27Df33Eaa1", + "rewardRecipients": [ + { + "type": "op", + "address": "0xCa11302398EF64F3197050AcA2AA5C27Df33Eaa1", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4094": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4095": { + "name": "GPU/FPGA/ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "chainTypeOverride": "Hypra", + "dagDir": "/home/ceedii/.Ethash-B3/", + "daemons": [ + { + "host": "127.0.0.1", + "port": 8549, + "portWs": 8550, + "user": "", + "password": "" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + }, + "gas": 21000, + "maxFeePerGas": 50000000000, + "BlockSearchOffset": 100, + "keepUncles": false, + "keepTransactionFees": false + } + }, + { + "id": "kas1", + "enabled": false, + "coin": "kaspa", + "address": "kaspatest:qzh6z35q8033pdkp2yq38vvhxaj4rv5x0csmq8y9cpqy77w4cffqjsjfvf9fy", + "rewardRecipients": [ + { + "type": "op", + "address": "kaspatest:qzh6z35q8033pdkp2yq38vvhxaj4rv5x0csmq8y9cpqy77w4cffqjsjfvf9fy", + "percentage": 0.1 + } + ], + "clientConnectionTimeout": 600, + "maxActiveJobs": 16, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50, + "minerEffortPercent": 0.00000000256113708, + "minerEffortTime": 259200 + }, + "ports": { + "4114": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4115": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4117": { + "name": "FPGA/ASIC (IceRiver KS0~KS0 Pro)", + "listenAddress": "0.0.0.0", + "difficulty": 128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 64, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4118": { + "name": "FPGA/ASIC (IceRiver KS0~KS0 Pro)", + "listenAddress": "0.0.0.0", + "difficulty": 128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 64, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4119": { + "name": "FPGA/ASIC (IceRiver KS0 Ultra)", + "listenAddress": "0.0.0.0", + "difficulty": 512, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 256, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 2048 + } + }, + "4120": { + "name": "FPGA/ASIC (IceRiver KS0 Ultra)", + "listenAddress": "0.0.0.0", + "difficulty": 512, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 256, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 2048 + } + }, + "4121": { + "name": "ASIC (IceRiver KS1~KS2~KS2 LITE - Goldshell KA-BOX~KA-BOX PRO)", + "listenAddress": "0.0.0.0", + "difficulty": 1024, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 512, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 4096 + } + }, + "4122": { + "name": "ASIC (IceRiver KS1~KS2~KS2 LITE - Goldshell KA-BOX~KA-BOX PRO)", + "listenAddress": "0.0.0.0", + "difficulty": 1024, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 512, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 4096 + } + }, + "4127": { + "name": "ASIC (IceRiver KS3~KS3L - Bitmain Antminer KS3)", + "listenAddress": "0.0.0.0", + "difficulty": 4096, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2048, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 16384 + } + }, + "4128": { + "name": "ASIC (IceRiver KS3~KS3L - Bitmain Antminer KS3)", + "listenAddress": "0.0.0.0", + "difficulty": 4096, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2048, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 16384 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 16210, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8082, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "9TW4dm4HvrKhT3RLpJXFxVbpcnpcMtCtbhT", + "minimumPayment": 0.1, + "minimumConfirmations": 240, + "versionEnablingMaxFee": "v0.12.18-rc5", + "maxFee": 200000, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "nexa1", + "enabled": false, + "coin": "nexa", + "address": "nexatest:nqtsq5g5ajg8egsgcxf88g896ur63hex90zfdlsqurd9etnc", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "nexatest:nqtsq5g5ajg8egsgcxf88g896ur63hex90zfdlsqurd9etnc", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4134": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.0128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4135": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.0128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 7229, + "user": "nqtsq5g5ajg8egsgcxf88g896ur63hex90zfdlsqurd9etnc", + "password": "s9C0Wmo_nBgpguF4yrnCF0zF5_HxwwgjTduxvXGuFOc=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:7226", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.01, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "zeph1", + "enabled": false, + "coin": "zephyr", + "randomXRealm": "zeph1", + "randomXVmCount": 1, + "randomXFlagsAdd": "RANDOMX_FLAG_FULL_MEM", + "address": "ZPHTjdd8G4n1mo1wJgTp6J4xZY48VH2KtKyDbN1L8H2nCYp65t5ectdfkX5wBaWxPxH8K1drC96kxDDq6MMHwFEABNYK25tG56f", + "rewardRecipients": [ + { + "type": "op", + "address": "ZPHTjdd8G4n1mo1wJgTp6J4xZY48VH2KtKyDbN1L8H2nCYp65t5ectdfkX5wBaWxPxH8K1drC96kxDDq6MMHwFEABNYK25tG56f", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4144": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 7500, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 30000 + } + }, + "4145": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 7500, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 30000 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 27767, + "user": null, + "password": null, + "zmqBlockNotifySocket": "tcp://127.0.0.1:27770", + "zmqBlockNotifyTopic": "json-minimal-chain_main" + }, + { + "host": "127.0.0.1", + "port": 27769, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "kls1", + "enabled": false, + "coin": "karlsencoin", + "address": "karlsentest:qpkk3xs6n9tnwfasrssxrpr9lswawy7gel75try2hm0f4slvm3ckjqv0mwx8x", + "rewardRecipients": [ + { + "type": "op", + "address": "karlsentest:qpkk3xs6n9tnwfasrssxrpr9lswawy7gel75try2hm0f4slvm3ckjqv0mwx8x", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "karlsenwalletd.karlsenwalletd", + "clientConnectionTimeout": 600, + "maxActiveJobs": 16, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50, + "minerEffortPercent": 0.00000000256113708, + "minerEffortTime": 259200 + }, + "ports": { + "4154": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 0.00512, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.00256, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4155": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 0.00512, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.00256, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 42210, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8083, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "jXccRbPJcvfCHdqRLdCfwfR9kC4WCfgLw4HgsvjH", + "minimumPayment": 0.1, + "payoutScheme": "PPLNSBF", + "payoutSchemeConfig": { + "factor": 0.5, + "blockFinderPercentage": 40.0 + } + } + }, + { + "id": "xna1", + "enabled": false, + "coin": "neurai", + "address": "t83D4zYjvymYZRHAkpmpqLrnS5oWTbTn8t", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "t83D4zYjvymYZRHAkpmpqLrnS5oWTbTn8t", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4184": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4185": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 19101, + "user": "t83D4zYjvymYZRHAkpmpqLrnS5oWTbTn8t", + "password": "K_tZYYodR_SWvr4oPUzBpVjIu4wCePHuAeGLL2GKoLU=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:19002", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "aipg1", + "enabled": false, + "coin": "aipg", + "address": "AZDz4y4gSUrGcweA3H6QLq6hgA8croGdoz", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "AZDz4y4gSUrGcweA3H6QLq6hgA8croGdoz", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4204": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4205": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 9788, + "user": "AZDz4y4gSUrGcweA3H6QLq6hgA8croGdoz", + "password": "iULW7qNyFDYHv9qaKR9ngjz2xUtqlSq3feCkewWtibg=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:9789", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "vish1", + "enabled": false, + "coin": "vishai", + "address": "uqYgCKkSA7WAAC1aeCJdZoDbxkSUpmrHfp", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "uqYgCKkSA7WAAC1aeCJdZoDbxkSUpmrHfp", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4214": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.0128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4215": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.0128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 26920, + "user": "uqYgCKkSA7WAAC1aeCJdZoDbxkSUpmrHfp", + "password": "Dcr74kwjxpy86bNIncf7OWQyPGu18Itd2d2wusZfXK4=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:26921", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "clore1", + "enabled": false, + "coin": "clore", + "address": "ALX2ByrG2oKHed6B7RcUczamujsXBKEe7q", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "ALX2ByrG2oKHed6B7RcUczamujsXBKEe7q", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4224": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4225": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 9766, + "user": "ALX2ByrG2oKHed6B7RcUczamujsXBKEe7q", + "password": "HMjW4B6kBncygRbJ9D0eIOJD7XwVp_xDSUNbNYQHbm8=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:9767", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "satox1", + "enabled": false, + "coin": "satoxcoin", + "address": "SWwYpZQM9wFpJXfocHt93LLqhRdqAByift", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "SWwYpZQM9wFpJXfocHt93LLqhRdqAByift", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4234": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4235": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 7777, + "user": "SWwYpZQM9wFpJXfocHt93LLqhRdqAByift", + "password": "cAFHvRvDeAtphAvnYabrArbh021c5BT3ZnA_kLXIpnw=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:7778", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "zec1", + "enabled": false, + "coin": "zcash", + "address": "tmYpXX8XDknwzcV62CsZ5C1urQgBWhZq6E4", + "z-address": "utest1w7jmknd4htdsmvcgle07wru6z4kvs8j594mwxls99slna9pykk44ykvuw30wtslm74d27cmsd8ru9rews32egykmf3eukq7rv3277crnlk0tkc2g5y2yrwjdjr6r8ep8lkxdu4s4xlf605wrmkgv75qp5m3pleafrq858jwxc0xwdm2hm7gzp33x9p9xr5q2qsh60tss340m6f566p9", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "tmYpXX8XDknwzcV62CsZ5C1urQgBWhZq6E4", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 900, + "maxActiveJobs": 4, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4254": { + "name": "CPU/GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4255": { + "name": "CPU/GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4256": { + "name": "ASIC/FGPA", + "listenAddress": "0.0.0.0", + "difficulty": 128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4257": { + "name": "ASIC/FGPA", + "listenAddress": "0.0.0.0", + "difficulty": 128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4258": { + "name": "ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 1024, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1024, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 4096 + } + }, + "4259": { + "name": "ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 1024, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1024, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 4096 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 18232, + "user": "tmYpXX8XDknwzcV62CsZ5C1urQgBWhZq6E4", + "password": "71n4LAMbKA5Xp2saUVa5HLchNdZ1H5XUjzq8Giex6lQ=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:8234", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "arrr1", + "enabled": false, + "coin": "piratechain", + "address": "RYa6MuQsYpMDp4pa975c1m9rJgRkvrZQbu", + "z-address": "zs15rerrsmh8k5yeq45ven5pl9k89fm5d42rth5ff5qwul07pcsv8e8mandkrc6058l0wtsw942tfs", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "RYa6MuQsYpMDp4pa975c1m9rJgRkvrZQbu", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 900, + "maxActiveJobs": 4, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4264": { + "name": "CPU/GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4265": { + "name": "CPU/GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4266": { + "name": "ASIC/FGPA", + "listenAddress": "0.0.0.0", + "difficulty": 128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4267": { + "name": "ASIC/FGPA", + "listenAddress": "0.0.0.0", + "difficulty": 128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 512 + } + }, + "4268": { + "name": "ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 1024, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1024, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 4096 + } + }, + "4269": { + "name": "ASIC", + "listenAddress": "0.0.0.0", + "difficulty": 1024, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1024, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 4096 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 8881, + "user": "RYa6MuQsYpMDp4pa975c1m9rJgRkvrZQbu", + "password": "4LFTzk4zRzXjLvjXwKLFfrjb93NfzLKzmc7", + "zmqBlockNotifySocket": "tcp://127.0.0.1:8882", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "btg1", + "enabled": false, + "coin": "bitcoin-gold", + "address": "msCGMtHSBwTTcoM72pAU8cQ1wTC2KSNqFk", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "msCGMtHSBwTTcoM72pAU8cQ1wTC2KSNqFk", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 900, + "maxActiveJobs": 4, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4274": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4275": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.0128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 18332, + "user": "msCGMtHSBwTTcoM72pAU8cQ1wTC2KSNqFk", + "password": "2qrV6EPvCgv9B9p7gM1unjF0zy-iR6sHoA5bGJbzlro=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:8339", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "mewc1", + "enabled": false, + "coin": "meowcoin", + "address": "mCiw9d5RcNstxfsLD4p8PhByMbKsGeCvRZ", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "mCiw9d5RcNstxfsLD4p8PhByMbKsGeCvRZ", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4284": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4285": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 19760, + "user": "mCiw9d5RcNstxfsLD4p8PhByMbKsGeCvRZ", + "password": "xm3e3QnbOpI9NSymkrP1Sl8EC3KlYnrE5G0UJtOJXx4=", + "zmqBlockNotifySocket": "tcp://127.0.0.1:9781", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "hns1", + "enabled": false, + "coin": "handshake", + "address": "rs1q4h5fv4g7d53glunvswacljf0ktrz95d9tpc6nm", + "pubKey": "0370ce96be2fc2288d308a9f9ca233efa830a1b8a5cee9a26bcc4de6a4c0892687", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "rs1q4h5fv4g7d53glunvswacljf0ktrz95d9tpc6nm", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 250, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "maxActiveJobs": 1, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4289": { + "name": "GPU/ASIC (varDiff)", + "listenAddress": "0.0.0.0", + "difficulty": 8, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 4, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 38048 + } + }, + "4290": { + "name": "ASIC (Blackminer F1)", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false + }, + "4291": { + "name": "ASIC (Goldshell HS1)", + "listenAddress": "0.0.0.0", + "difficulty": 64, + "tls": false + }, + "4292": { + "name": "ASIC (Goldshell HS Box)", + "listenAddress": "0.0.0.0", + "difficulty": 256, + "tls": false + }, + "4293": { + "name": "ASIC (Goldshell HS Box II)", + "listenAddress": "0.0.0.0", + "difficulty": 512, + "tls": false + }, + "4294": { + "name": "ASIC (Goldshell HS Lite)", + "listenAddress": "0.0.0.0", + "difficulty": 1512, + "tls": false + }, + "4295": { + "name": "ASIC (Goldshell HS5)", + "listenAddress": "0.0.0.0", + "difficulty": 3032, + "tls": false + }, + "4296": { + "name": "ASIC (Goldshell HS6 SE)", + "listenAddress": "0.0.0.0", + "difficulty": 4032, + "tls": false + }, + "4297": { + "name": "ASIC (Goldshell HS6)", + "listenAddress": "0.0.0.0", + "difficulty": 4512, + "tls": false + }, + "4298": { + "name": "ASIC (Bitmain Antminer HS3)", + "listenAddress": "0.0.0.0", + "difficulty": 9512, + "tls": false + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 14037, + "user": "x", + "password": "KdbPzjNdsXvhfdHmspCRvwzn7997gkqztMg" + }, + { + "host": "127.0.0.1", + "port": 14039, + "user": "x", + "password": "KdbPzjNdsXvhfdHmspCRvwzn7997gkqztMg", + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "nxl1", + "enabled": false, + "coin": "nexellia", + "address": "nexellia:qp5hpz8ugfxzgwg66anqgyjlaugrselfkv032acgr99jdu4nm873qas6cfxhf", + "rewardRecipients": [ + { + "type": "op", + "address": "nexellia:qp5hpz8ugfxzgwg66anqgyjlaugrselfkv032acgr99jdu4nm873qas6cfxhf", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "nexelliawalletd.nexelliawalletd", + "clientConnectionTimeout": 600, + "maxActiveJobs": 16, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50, + "minerEffortPercent": 0.00000000256113708, + "minerEffortTime": 259200 + }, + "ports": { + "4304": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4305": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 33455, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8085, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "qhVJghKHPgPtzX7N4LpJJkqscLkkW4gChwH", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "htn1", + "enabled": false, + "coin": "hoosat", + "address": "hoosat:qrazge2kzuhercdm950fm2svshpevf9vke7n4hncy54ktgzumwve7vhglgqgq", + "rewardRecipients": [ + { + "type": "op", + "address": "hoosat:qrazge2kzuhercdm950fm2svshpevf9vke7n4hncy54ktgzumwve7vhglgqgq", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "htnwalletd.htnwalletd", + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4314": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4315": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 42420, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8086, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "9FXdRWzdbrc7CtC4hWcqvjRrdgjKqpVgtrp", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "ntl1", + "enabled": false, + "coin": "nautilus-network", + "address": "hoosat:qrazge2kzuhercdm950fm2svshpevf9vke7n4hncy54ktgzumwve7vhglgqgq", + "rewardRecipients": [ + { + "type": "op", + "address": "hoosat:qrazge2kzuhercdm950fm2svshpevf9vke7n4hncy54ktgzumwve7vhglgqgq", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "nautiluswalletd.nautiluswalletd", + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4324": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4325": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 34455, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8087, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "9FXdRWzdbrc7CtC4hWcqvjRrdgjKqpVgtrp", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "cas1", + "enabled": false, + "coin": "kaspaclassic", + "address": "cas:qr05g9x5xyxwx8mafgcamt8ers7eht2y7vp4rwhuw7qx25fzn5tzwpkx8p25t", + "rewardRecipients": [ + { + "type": "op", + "address": "cas:qr05g9x5xyxwx8mafgcamt8ers7eht2y7vp4rwhuw7qx25fzn5tzwpkx8p25t", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "caswalletd.caswalletd", + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4334": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4335": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 17111, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8088, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "4hWrTcbmNmHRf7gjzJcKWtttxTdgvCf7qLm", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "bga1", + "enabled": false, + "coin": "bugna", + "address": "bugna:qrdwu8dmdjmqq5566g7c3q5rmq9pm9hh2zn5r7frfngwpzdmarz8w37pe5gpr", + "rewardRecipients": [ + { + "type": "op", + "address": "bugna:qrdwu8dmdjmqq5566g7c3q5rmq9pm9hh2zn5r7frfngwpzdmarz8w37pe5gpr", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "bugnawalletd.bugnawalletd", + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4344": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4345": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 4, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 2, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 38138, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8089, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "xHHRnmpFjpRRTWxgRkmhFq37CX9JCvHwMjr", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "wart1", + "enabled": false, + "coin": "warthog", + "address": "5d896758ee0ade5f09fa93d012783beddd82a6ff322f2073", + "rewardRecipients": [ + { + "type": "op", + "address": "5d896758ee0ade5f09fa93d012783beddd82a6ff322f2073", + "percentage": 0.1 + } + ], + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4354": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 204800000, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 102400000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 15, + "variancePercent": 25, + "maxDelta": 819200000 + } + }, + "4355": { + "name": "GPU", + "listenAddress": "0.0.0.0", + "difficulty": 204800000, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 102400000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 15, + "variancePercent": 25, + "maxDelta": 819200000 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 3100, + "portWs": 3100, + "httpPathWs": "/ws/chain_delta", + "user": null, + "password": null + } + ], + "paymentProcessing": { + "walletPrivateKey": "7239f41c3969b07214549ccb1f028f7f609852afe59e4f2cd32d5edd8e039778", + "enabled": true, + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "spr1", + "enabled": false, + "coin": "spectre-network", + "address": "spectre:qrzvwz0fjphr75cpf55kzwk6u77x33gjrj5w6zdmkgd5mpjwznm3uqv83x9n0", + "rewardRecipients": [ + { + "type": "op", + "address": "spectre:qrzvwz0fjphr75cpf55kzwk6u77x33gjrj5w6zdmkgd5mpjwznm3uqv83x9n0", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "spectrewalletd.spectrewalletd", + "clientConnectionTimeout": 600, + "maxActiveJobs": 16, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50, + "minerEffortPercent": 0.078125, + "minerEffortTime": 259200 + }, + "ports": { + "4364": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 32, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 16, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 16384 + } + }, + "4365": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 32, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 16, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 16384 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 18110, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8090, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "CnJsJfM7VPLhCsvmxjH4tfCmhRp7kxgcwhpVjghm", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "kcn1", + "enabled": false, + "coin": "kylacoin", + "address": "Gc69rC65ZvTamNg4U97kP97g8ywnKWHN12", + "GBTArgs": [{ + "capabilities": [ + "coinbasetxn", + "workid", + "coinbase/append" + ], + "rules": [ + "segwit" + ] + }], + "rewardRecipients": [ + { + "type": "op", + "address": "Gc69rC65ZvTamNg4U97kP97g8ywnKWHN12", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4374": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.000000128, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.000000128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 0.000008192 + } + }, + "4375": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 0.000000128, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.000000128, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 0.000008192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 5221, + "user": "Gc69rC65ZvTamNg4U97kP97g8ywnKWHN12", + "password": "kYzp7iqb39AKsM5_55bSZpoKxQ37d3RbR2mrbBgiI8Y", + "zmqBlockNotifySocket": "tcp://127.0.0.1:5223", + "zmqBlockNotifyTopic": "hashblock" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.0001, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "aix1", + "enabled": false, + "coin": "astrix-network", + "address": "astrix:qzc9yhdszyy2putw4cjxdq60hmz6mv7jvjv40kzc7lfkp5vk00jgweqhwgqn6", + "rewardRecipients": [ + { + "type": "op", + "address": "astrix:qzc9yhdszyy2putw4cjxdq60hmz6mv7jvjv40kzc7lfkp5vk00jgweqhwgqn6", + "percentage": 0.1 + } + ], + "protobufWalletRpcServiceName": "astrixwalletd.astrixwalletd", + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50, + "minerEffortPercent": 0.00000000256113708, + "minerEffortTime": 259200 + }, + "ports": { + "4384": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 0.256, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + }, + "4385": { + "name": "GPU/FPGA", + "listenAddress": "0.0.0.0", + "difficulty": 0.256, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 0.128, + "maxDiff": null, + "targetTime": 3, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 8192 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 34150, + "user": null, + "password": null + }, + { + "host": "127.0.0.1", + "port": 8093, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "walletPassword": "CNfPvLTsshXHWPXX9qhJtpMsftVbwbw7NHjwjbxs", + "minimumPayment": 0.1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }, + { + "id": "sal1", + "enabled": false, + "coin": "salvium", + "randomXRealm": "sal1", + "randomXVmCount": 1, + "randomXFlagsAdd": "RANDOMX_FLAG_FULL_MEM", + "address": "SaLvTyLeC9n8BqCx5P7ZgEAE8oR64a5fXLjXEnarwTV6H13DyfZfRSTA4AgBv9AK989WMz6UxXKswa95iEc5srHHS1LnCyvu9p43h", + "rewardRecipients": [ + { + "type": "op", + "address": "SaLvTyLeC9n8BqCx5P7ZgEAE8oR64a5fXLjXEnarwTV6H13DyfZfRSTA4AgBv9AK989WMz6UxXKswa95iEc5srHHS1LnCyvu9p43h", + "percentage": 0.1 + } + ], + "blockRefreshInterval": 0, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4394": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 7500, + "tls": false, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 30000 + } + }, + "4395": { + "name": "CPU", + "listenAddress": "0.0.0.0", + "difficulty": 7500, + "tls": true, + "tlsPfxFile": "/etc/apache2/ssl/www.cedric-crispin.local.pfx", + "tlsPfxPassword": "f`k+4b4{Fbr?++?{`@M[", + "varDiff": { + "minDiff": 1000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 5, + "variancePercent": 25, + "maxDelta": 30000 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 29081, + "user": null, + "password": null, + "zmqBlockNotifySocket": "tcp://127.0.0.1:29084", + "zmqBlockNotifyTopic": "json-minimal-chain_main" + }, + { + "host": "127.0.0.1", + "port": 29083, + "user": null, + "password": null, + "category": "wallet" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 0.001, + "payoutScheme": "PPLNSBF", + "payoutSchemeConfig": { + "factor": 0.5, + "blockFinderPercentage": 40.0 + } + } + } + ] +} \ No newline at end of file diff --git a/examples/kaspa_pool.json b/examples/kaspa_pool.json index aa9ff8925..29ab32e3d 100644 --- a/examples/kaspa_pool.json +++ b/examples/kaspa_pool.json @@ -80,7 +80,9 @@ "enabled": true, "time": 600, "invalidPercent": 50, - "checkThreshold": 50 + "checkThreshold": 50, + "minerEffortPercent": 0.00000000465661287, + "minerEffortTime": 259200 }, "ports": { "3094": { @@ -130,6 +132,8 @@ "enabled": true, "walletPassword": "", "minimumPayment": 1, + "versionEnablingMaxFee": "v0.12.18-rc5", + "maxFee": 200000, "payoutScheme": "PPLNS", "payoutSchemeConfig": { "factor": 0.5 diff --git a/examples/veruscoin_pool.json b/examples/veruscoin_pool.json index 295a2e142..5ca7534e1 100644 --- a/examples/veruscoin_pool.json +++ b/examples/veruscoin_pool.json @@ -96,10 +96,10 @@ "ports": { "3092": { "listenAddress": "0.0.0.0", - "difficulty": 256, + "difficulty": 25600000, "varDiff": { - "minDiff": 256, - "maxDiff": 1048576000, + "minDiff": 25600000, + "maxDiff": null, "targetTime": 15, "retargetTime": 90, "variancePercent": 30, @@ -108,13 +108,13 @@ }, "3093": { "listenAddress": "0.0.0.0", - "difficulty": 256, + "difficulty": 25600000, "tls": true, "tlsPfxFile": "", "tlsPfxPassword": "password", "varDiff": { - "minDiff": 256, - "maxDiff": 1048576000, + "minDiff": 25600000, + "maxDiff": null, "targetTime": 15, "retargetTime": 90, "variancePercent": 30, diff --git a/examples/warthog_pool.json b/examples/warthog_pool.json new file mode 100644 index 000000000..ebe22690f --- /dev/null +++ b/examples/warthog_pool.json @@ -0,0 +1,119 @@ +{ + "logging": { + "level": "info", + "enableConsoleLog": true, + "enableConsoleColors": true, + "logFile": "", + "apiLogFile": "", + "logBaseDirectory": "", + "perPoolLogFile": false + }, + "banning": { + "manager": "Integrated", + "banOnJunkReceive": false, + "banOnInvalidShares": false + }, + "notifications": { + "enabled": false, + "email": { + "host": "smtp.example.com", + "port": 587, + "user": "user", + "password": "password", + "fromAddress": "info@yourpool.org", + "fromName": "support" + }, + "admin": { + "enabled": false, + "emailAddress": "user@example.com", + "notifyBlockFound": true + } + }, + "persistence": { + "postgres": { + "host": "127.0.0.1", + "port": 5432, + "user": "miningcore", + "password": "password", + "database": "miningcore" + } + }, + "paymentProcessing": { + "enabled": true, + "interval": 600, + "shareRecoveryFile": "recovered-shares.txt" + }, + "api": { + "enabled": true, + "listenAddress": "*", + "port": 4000, + "metricsIpWhitelist": [], + "rateLimiting": { + "disabled": true, + "rules": [ + { + "Endpoint": "*", + "Period": "1s", + "Limit": 5 + } + ], + "ipWhitelist": [ + "" + ] + } + }, + "pools": [{ + "id": "wart1", + "enabled": true, + "coin": "warthog", + "address": "5d896758ee0ade5f09fa93d012783beddd82a6ff322f2073", + "rewardRecipients": [ + { + "type": "op", + "address": "5d896758ee0ade5f09fa93d012783beddd82a6ff322f2073", + "percentage": 1.0 + } + ], + "blockRefreshInterval": 250, + "jobRebroadcastTimeout": 0, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4073": { + "name": "GPU-SMALL", + "listenAddress": "*", + "difficulty": 512000000, + "varDiff": { + "minDiff": 256000000, + "maxDiff": null, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 100, + "maxDelta": 512 + } + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 3000, + "user": "", + "password": "" + } + ], + "paymentProcessing": { + "walletPrivateKey": "", + "enabled": true, + "minimumPayment": 1, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + }] +} \ No newline at end of file diff --git a/src/Miningcore.Tests/Miningcore.Tests.csproj b/src/Miningcore.Tests/Miningcore.Tests.csproj index a41b1c9aa..a5f89b92c 100644 --- a/src/Miningcore.Tests/Miningcore.Tests.csproj +++ b/src/Miningcore.Tests/Miningcore.Tests.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index 1fc031dd8..7ce1d1ecf 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -68,13 +68,17 @@ public async Task Get(CancellationToken ct, [FromQuery] uint t // enrich result.TotalPaid = await cf.Run(con => statsRepo.GetTotalPoolPaymentsAsync(con, config.Id, ct)); result.TotalBlocks = await cf.Run(con => blocksRepo.GetPoolBlockCountAsync(con, config.Id, ct)); - var lastBlockTime = await cf.Run(con => blocksRepo.GetLastPoolBlockTimeAsync(con, config.Id)); + result.TotalConfirmedBlocks = await cf.Run(con => blocksRepo.GetTotalConfirmedBlocksAsync(con, config.Id, ct)); + result.TotalPendingBlocks = await cf.Run(con => blocksRepo.GetTotalPendingBlocksAsync(con, config.Id, ct)); + // get reward of the last confirmed block and set BlockReward + result.BlockReward = await cf.Run(con => blocksRepo.GetLastConfirmedBlockRewardAsync(con, config.Id, ct)); + var lastBlockTime = await cf.Run(con => blocksRepo.GetLastPoolBlockTimeAsync(con, config.Id, ct)); result.LastPoolBlockTime = lastBlockTime; if(lastBlockTime.HasValue) { var startTime = lastBlockTime.Value; - var poolEffort = await cf.Run(con => shareRepo.GetEffortBetweenCreatedAsync(con, config.Id, pool.ShareMultiplier, startTime, clock.Now)); + var poolEffort = await cf.Run(con => shareRepo.GetEffortBetweenCreatedAsync(con, config.Id, pool.ShareMultiplier, startTime, clock.Now, ct)); if(poolEffort.HasValue) result.PoolEffort = poolEffort.Value; } @@ -137,13 +141,17 @@ public async Task GetPoolInfoAsync(string poolId, CancellationT // enrich response.Pool.TotalPaid = await cf.Run(con => statsRepo.GetTotalPoolPaymentsAsync(con, pool.Id, ct)); response.Pool.TotalBlocks = await cf.Run(con => blocksRepo.GetPoolBlockCountAsync(con, pool.Id, ct)); - var lastBlockTime = await cf.Run(con => blocksRepo.GetLastPoolBlockTimeAsync(con, pool.Id)); + response.Pool.TotalConfirmedBlocks = await cf.Run(con => blocksRepo.GetTotalConfirmedBlocksAsync(con, pool.Id, ct)); + response.Pool.TotalPendingBlocks = await cf.Run(con => blocksRepo.GetTotalPendingBlocksAsync(con, pool.Id, ct)); + // get reward of the last confirmed block and set BlockReward + response.Pool.BlockReward = await cf.Run(con => blocksRepo.GetLastConfirmedBlockRewardAsync(con, pool.Id, ct)); + var lastBlockTime = await cf.Run(con => blocksRepo.GetLastPoolBlockTimeAsync(con, pool.Id, ct)); response.Pool.LastPoolBlockTime = lastBlockTime; if(lastBlockTime.HasValue) { var startTime = lastBlockTime.Value; - var poolEffort = await cf.Run(con => shareRepo.GetEffortBetweenCreatedAsync(con, pool.Id, poolInstance.ShareMultiplier, startTime, clock.Now)); + var poolEffort = await cf.Run(con => shareRepo.GetEffortBetweenCreatedAsync(con, pool.Id, poolInstance.ShareMultiplier, startTime, clock.Now, ct)); if(poolEffort.HasValue) response.Pool.PoolEffort = poolEffort.Value; } @@ -393,7 +401,22 @@ public async Task PagePoolMinersAsync( stats.LastPaymentLink = string.Format(baseUrl, statsResult.LastPayment.TransactionConfirmationData); } + var lastBlockTime = await cf.Run(con => blocksRepo.GetLastPoolBlockTimeAsync(con, pool.Id, ct)); + if(lastBlockTime.HasValue) + { + var startTime = lastBlockTime.Value; + var minerEffort = await cf.Run(con => shareRepo.GetMinerEffortBetweenCreatedAsync(con, pool.Id, address, startTime, clock.Now, ct)); + if(minerEffort.HasValue) + stats.MinerEffort = minerEffort.Value; + } + stats.PerformanceSamples = await GetMinerPerformanceInternal(perfMode, pool, address, ct); + + // add total confirmed and pending blocks + var totalConfirmedBlocks = await cf.Run(con => statsRepo.GetMinerTotalConfirmedBlocksAsync(con, pool.Id, address, ct)); + var totalPendingBlocks = await cf.Run(con => statsRepo.GetMinerTotalPendingBlocksAsync(con, pool.Id, address, ct)); + stats.TotalConfirmedBlocks = totalConfirmedBlocks; + stats.TotalPendingBlocks = totalPendingBlocks; } return stats; diff --git a/src/Miningcore/Api/Extensions/MiningPoolExtensions.cs b/src/Miningcore/Api/Extensions/MiningPoolExtensions.cs index 301922bda..c0ac2a86d 100644 --- a/src/Miningcore/Api/Extensions/MiningPoolExtensions.cs +++ b/src/Miningcore/Api/Extensions/MiningPoolExtensions.cs @@ -6,6 +6,7 @@ using Miningcore.Blockchain.Ergo.Configuration; using Miningcore.Blockchain.Handshake.Configuration; using Miningcore.Blockchain.Kaspa.Configuration; +using Miningcore.Blockchain.Warthog.Configuration; using Miningcore.Configuration; using Miningcore.Extensions; using Miningcore.Mining; @@ -51,6 +52,9 @@ public static PoolInfo ToPoolInfo(this PoolConfig poolConfig, IMapper mapper, Pe case "kaspa": extra.StripValue(nameof(KaspaPaymentProcessingConfigExtra.WalletPassword)); break; + case "warthog": + extra.StripValue(nameof(WarthogPaymentProcessingConfigExtra.WalletPrivateKey)); + break; } } diff --git a/src/Miningcore/Api/Responses/GetBlocksResponse.cs b/src/Miningcore/Api/Responses/GetBlocksResponse.cs index dd1f9d62b..558ae6f79 100644 --- a/src/Miningcore/Api/Responses/GetBlocksResponse.cs +++ b/src/Miningcore/Api/Responses/GetBlocksResponse.cs @@ -9,6 +9,7 @@ public class Block public string Type { get; set; } public double ConfirmationProgress { get; set; } public double? Effort { get; set; } + public double? MinerEffort { get; set; } public string TransactionConfirmationData { get; set; } public decimal Reward { get; set; } public string InfoLink { get; set; } diff --git a/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs b/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs index 25e9be285..8c1672665 100644 --- a/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs +++ b/src/Miningcore/Api/Responses/GetMinerStatsResponse.cs @@ -25,8 +25,11 @@ public class MinerStats public decimal PendingBalance { get; set; } public decimal TotalPaid { get; set; } public decimal TodayPaid { get; set; } + public double MinerEffort { get; set; } public DateTime? LastPayment { get; set; } public string LastPaymentLink { get; set; } public WorkerPerformanceStatsContainer Performance { get; set; } public WorkerPerformanceStatsContainer[] PerformanceSamples { get; set; } + public long TotalConfirmedBlocks { get; set; } + public long TotalPendingBlocks { get; set; } } diff --git a/src/Miningcore/Api/Responses/GetPoolsResponse.cs b/src/Miningcore/Api/Responses/GetPoolsResponse.cs index 5d531ef0d..c82858779 100644 --- a/src/Miningcore/Api/Responses/GetPoolsResponse.cs +++ b/src/Miningcore/Api/Responses/GetPoolsResponse.cs @@ -71,6 +71,9 @@ public partial class PoolInfo public MinerPerformanceStats[] TopMiners { get; set; } public decimal TotalPaid { get; set; } public uint TotalBlocks { get; set; } + public uint TotalConfirmedBlocks { get; set; } + public uint TotalPendingBlocks { get; set; } + public decimal BlockReward { get; set; } public DateTime? LastPoolBlockTime { get; set; } public double PoolEffort { get; set; } } diff --git a/src/Miningcore/AutoMapperProfile.cs b/src/Miningcore/AutoMapperProfile.cs index d0d0efeb9..38f88b2b2 100644 --- a/src/Miningcore/AutoMapperProfile.cs +++ b/src/Miningcore/AutoMapperProfile.cs @@ -63,7 +63,9 @@ public AutoMapperProfile() CreateMap() .ForMember(dest => dest.LastPayment, opt => opt.Ignore()) - .ForMember(dest => dest.LastPaymentLink, opt => opt.Ignore()); + .ForMember(dest => dest.LastPaymentLink, opt => opt.Ignore()) + .ForMember(dest => dest.TotalConfirmedBlocks, opt => opt.MapFrom(src => src.TotalConfirmedBlocks)) + .ForMember(dest => dest.TotalPendingBlocks, opt => opt.MapFrom(src => src.TotalPendingBlocks)); CreateMap(); CreateMap(); diff --git a/src/Miningcore/AutofacModule.cs b/src/Miningcore/AutofacModule.cs index 281fd80c9..dba403eba 100644 --- a/src/Miningcore/AutofacModule.cs +++ b/src/Miningcore/AutofacModule.cs @@ -14,6 +14,7 @@ using Miningcore.Blockchain.Kaspa; using Miningcore.Blockchain.Nexa; using Miningcore.Blockchain.Progpow; +using Miningcore.Blockchain.Warthog; using Miningcore.Configuration; using Miningcore.Crypto; using Miningcore.Crypto.Hashing.Equihash; @@ -159,6 +160,10 @@ protected override void Load(ContainerBuilder builder) .Keyed(PayoutScheme.PPLNS) .SingleInstance(); + builder.RegisterType() + .Keyed(PayoutScheme.PPLNSBF) + .SingleInstance(); + builder.RegisterType() .Keyed(PayoutScheme.SOLO) .SingleInstance(); @@ -209,6 +214,7 @@ protected override void Load(ContainerBuilder builder) ////////////////////// // Handshake + builder.RegisterType(); ////////////////////// @@ -218,6 +224,7 @@ protected override void Load(ContainerBuilder builder) ////////////////////// // Nexa + builder.RegisterType(); ////////////////////// @@ -225,6 +232,11 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType(); + ////////////////////// + // Warthog + + builder.RegisterType(); + base.Load(builder); } } diff --git a/src/Miningcore/Blockchain/Alephium/AlephiumConstants.cs b/src/Miningcore/Blockchain/Alephium/AlephiumConstants.cs index ce778a428..51a206880 100644 --- a/src/Miningcore/Blockchain/Alephium/AlephiumConstants.cs +++ b/src/Miningcore/Blockchain/Alephium/AlephiumConstants.cs @@ -28,6 +28,7 @@ public static class AlephiumConstants // Socket miner API public const int MessageHeaderSize = 4; // 4 bytes body length + public const byte MiningProtocolVersion = 0x01; public const byte JobsMessageType = 0x00; public const byte SubmitResultMessageType = 0x01; public const byte SubmitBlockMessageType = 0x00; diff --git a/src/Miningcore/Blockchain/Alephium/AlephiumJob.cs b/src/Miningcore/Blockchain/Alephium/AlephiumJob.cs index b5ec12bc1..810d05b28 100644 --- a/src/Miningcore/Blockchain/Alephium/AlephiumJob.cs +++ b/src/Miningcore/Blockchain/Alephium/AlephiumJob.cs @@ -56,18 +56,23 @@ protected bool RegisterSubmit(string nonce) return submissions.TryAdd(key, true); } - public virtual byte[] SerializeCoinbase(string nonce) + public virtual byte[] SerializeCoinbase(string nonce, int socketMiningProtocol = 0) { var nonceBytes = (Span) nonce.HexToByteArray(); var headerBlobBytes = (Span) BlockTemplate.HeaderBlob.HexToByteArray(); var txsBlobBytes = (Span) BlockTemplate.TxsBlob.HexToByteArray(); - + uint blockSize = (uint)nonceBytes.Length + (uint)headerBlobBytes.Length + (uint)txsBlobBytes.Length; - uint messageSize = 4 + 1 + blockSize; // encodedBlockSize(4 bytes) + messageType(1 byte) - + int messagePrefixSize = (socketMiningProtocol > 0) ? 1 + 1 + 4: 4 + 1; // socketMiningProtocol: 0 => encodedBlockSize(4 bytes) + messageType(1 byte) || socketMiningProtocol: 1 => version(1 byte) + messageType(1 byte) + encodedBlockSize(4 bytes) + uint messageSize = (uint)messagePrefixSize + blockSize; + using(var stream = new MemoryStream()) { stream.Write(GetBigEndianUInt32(messageSize)); + + if(socketMiningProtocol > 0) + stream.WriteByte(AlephiumConstants.MiningProtocolVersion); + stream.WriteByte(AlephiumConstants.SubmitBlockMessageType); stream.Write(GetBigEndianUInt32(blockSize)); stream.Write(nonceBytes); diff --git a/src/Miningcore/Blockchain/Alephium/AlephiumJobManager.cs b/src/Miningcore/Blockchain/Alephium/AlephiumJobManager.cs index 5e8975aaa..a8aea9ab4 100644 --- a/src/Miningcore/Blockchain/Alephium/AlephiumJobManager.cs +++ b/src/Miningcore/Blockchain/Alephium/AlephiumJobManager.cs @@ -54,6 +54,7 @@ public AlephiumJobManager( private AlephiumPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; protected int maxActiveJobs; private int socketJobMessageBufferSize; + private int socketMiningProtocol = 0; protected IObservable AlephiumSubscribeStratumApiSocketClient(CancellationToken ct, DaemonEndpointConfig endPoint, AlephiumDaemonEndpointConfigExtra extraDaemonEndpoint, object payload = null, @@ -92,6 +93,7 @@ protected IObservable AlephiumSubscribeStratumApiSocket int receivedBytes; // Message + byte messageVersion = AlephiumConstants.MiningProtocolVersion; // only available since node release >= "3.6.0" byte messageType; uint messageLength; @@ -106,6 +108,7 @@ protected IObservable AlephiumSubscribeStratumApiSocket uint jobSize; int fromGroup; int toGroup; + ulong height = 0; uint headerBlobLength; byte[] headerBlob; uint txsBlobLength; @@ -141,8 +144,14 @@ protected IObservable AlephiumSubscribeStratumApiSocket if (bufferArray.Length >= messageLength + AlephiumConstants.MessageHeaderSize) { + if(socketMiningProtocol > 0) + { + messageVersion = reader.ReadByte(); + logger.Debug(() => $"Message version: {messageVersion}"); + } + messageType = reader.ReadByte(); - if (messageType == AlephiumConstants.JobsMessageType) + if (messageVersion == AlephiumConstants.MiningProtocolVersion && messageType == AlephiumConstants.JobsMessageType) { jobSize = ReadBigEndianUInt32(reader); logger.Debug(() => $"Parsing {jobSize} job(s) :D"); @@ -156,8 +165,12 @@ protected IObservable AlephiumSubscribeStratumApiSocket toGroup = (int)ReadBigEndianUInt32(reader); logger.Debug(() => $"fromGroup: {fromGroup} - toGroup: {toGroup}"); - chainInfo = await rpc.GetBlockflowChainInfoAsync(fromGroup, toGroup, cts.Token); - logger.Debug(() => $"Height: {chainInfo?.CurrentHeight + 1}"); + if(socketMiningProtocol == 0) + { + chainInfo = await rpc.GetBlockflowChainInfoAsync(fromGroup, toGroup, cts.Token); + height = (ulong) chainInfo?.CurrentHeight + 1; + logger.Debug(() => $"height: {height}"); + } headerBlobLength = ReadBigEndianUInt32(reader); logger.Debug(() => $"headerBlobLength: {headerBlobLength}"); @@ -174,10 +187,16 @@ protected IObservable AlephiumSubscribeStratumApiSocket targetBlob = reader.ReadBytes((int)targetLength); logger.Debug(() => $"targetBlob: {targetBlob.ToHexString()}"); + if(socketMiningProtocol > 0) + { + height = (ulong)ReadBigEndianUInt32(reader); + logger.Debug(() => $"height: {height}"); + } + alephiumBlockTemplate[index] = new AlephiumBlockTemplate { JobId = NextJobId("X"), - Height = (ulong) chainInfo?.CurrentHeight + 1, + Height = height, Timestamp = clock.Now, FromGroup = fromGroup, ToGroup = toGroup, @@ -196,7 +215,7 @@ protected IObservable AlephiumSubscribeStratumApiSocket } else { - logger.Debug(() => $"Unknown message type, wait for more data..."); + logger.Warn(() => $"Message version: {messageVersion}, expects {AlephiumConstants.MiningProtocolVersion} - Message type: {messageType}, expects {AlephiumConstants.JobsMessageType}, wait for more data..."); break; } } @@ -431,8 +450,10 @@ private async Task SubmitBlockAsync(CancellationToken ct, DaemonEndpointCo int receivedBytes; // Message + int messageTypeOffset; byte messageType; int startOffset; + int succeedIndex; byte[] message; logger.Debug(() => $"[Submit Block] - Submitting coinbase"); @@ -444,15 +465,17 @@ private async Task SubmitBlockAsync(CancellationToken ct, DaemonEndpointCo { logger.Debug(() => $"{receivedBytes} byte(s) of data have been received"); - messageType = receiveBuffer[AlephiumConstants.MessageHeaderSize]; + messageTypeOffset = (socketMiningProtocol > 0) ? 1: 0; // socketMiningProtocol: 0 => messageType(1 byte) || socketMiningProtocol: 1 => version(1 byte) + messageType(1 byte) + messageType = receiveBuffer[AlephiumConstants.MessageHeaderSize + messageTypeOffset]; if (messageType == AlephiumConstants.SubmitResultMessageType) { logger.Debug(() => $"[Submit Block] - Response received :D"); - startOffset = AlephiumConstants.MessageHeaderSize + 1; // 1 byte message type + startOffset = AlephiumConstants.MessageHeaderSize + messageTypeOffset + 1; // 1 byte message = new byte[receivedBytes - startOffset]; Buffer.BlockCopy(receiveBuffer, startOffset, message, 0, receivedBytes - startOffset); - succeed = (message[8] == 1); + succeedIndex = (socketMiningProtocol > 0) ? 40: 8; // socketMiningProtocol: 0 => succeed: index[8] || socketMiningProtocol: 1 => succeed: index[40] + succeed = (message[succeedIndex] == 1); break; } } @@ -535,7 +558,7 @@ public async ValueTask SubmitShareAsync(StratumConnection worker, Alephiu // Prepare data for the stratum API socket var daemonEndpoint = daemonEndpoints.First(); var extraDaemonEndpoint = daemonEndpoint.Extra.SafeExtensionDataAs(); - var jobCoinbase = job.SerializeCoinbase(nonce); + var jobCoinbase = job.SerializeCoinbase(nonce, socketMiningProtocol); var acceptResponse = await SubmitBlockAsync(ct, daemonEndpoint, extraDaemonEndpoint, jobCoinbase); @@ -724,7 +747,17 @@ protected override async Task AreDaemonsConnectedAsync(CancellationToken c // update stats if(!string.IsNullOrEmpty(nodeInfo?.BuildInfo.ReleaseVersion)) - BlockchainStats.NodeVersion = nodeInfo?.BuildInfo.ReleaseVersion; + { + BlockchainStats.NodeVersion = nodeInfo.BuildInfo.ReleaseVersion; + + // update socket mining protocol + string numbersOnly = Regex.Replace(nodeInfo.BuildInfo.ReleaseVersion, "[^0-9.]", ""); + string[] numbers = numbersOnly.Split("."); + + // update socket mining protocol + if(numbers.Length >= 2) + socketMiningProtocol = ((Convert.ToUInt32(numbers[0]) > 3) || (Convert.ToUInt32(numbers[0]) == 3 && Convert.ToUInt32(numbers[1]) >= 6)) ? 1: 0; + } if(infosChainParams?.NetworkId != 7) return info?.Count > 0; diff --git a/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs b/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs index 9914d7d54..3ccd1d302 100644 --- a/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs +++ b/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs @@ -169,7 +169,7 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time await connection.NotifyAsync(AlephiumStratumMethods.SetExtraNonce, manager.GetSubscriberData(connection)); // log association - logger.Info(() => $"[{connection.ConnectionId}] Authorized worker {workerValue}"); + logger.Info(() => $"[{connection.ConnectionId}]{(!string.IsNullOrEmpty(context.UserAgent) ? $"[{context.UserAgent}]" : string.Empty)} Authorized worker {workerValue}"); // extract control vars from password var staticDiff = GetStaticDiffFromPassparts(passParts); diff --git a/src/Miningcore/Blockchain/Alephium/AlephiumWorkerContext.cs b/src/Miningcore/Blockchain/Alephium/AlephiumWorkerContext.cs index a6a91d0d9..ccd0acc0d 100644 --- a/src/Miningcore/Blockchain/Alephium/AlephiumWorkerContext.cs +++ b/src/Miningcore/Blockchain/Alephium/AlephiumWorkerContext.cs @@ -16,12 +16,12 @@ public class AlephiumWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Unique value assigned per worker diff --git a/src/Miningcore/Blockchain/Beam/BeamJob.cs b/src/Miningcore/Blockchain/Beam/BeamJob.cs index 0bb17ec91..85a46f71c 100644 --- a/src/Miningcore/Blockchain/Beam/BeamJob.cs +++ b/src/Miningcore/Blockchain/Beam/BeamJob.cs @@ -3,12 +3,13 @@ using System.Globalization; using System.Numerics; using System.Reflection; -using System.Security.Cryptography; using System.Text; using Miningcore.Blockchain.Bitcoin; using Miningcore.Blockchain.Beam.DaemonResponses; using Miningcore.Configuration; using Miningcore.Contracts; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Algorithms; using Miningcore.Extensions; using Miningcore.Native; using Miningcore.Stratum; @@ -28,7 +29,7 @@ public class BeamJob protected readonly ConcurrentDictionary submissions = new(StringComparer.OrdinalIgnoreCase); protected BeamHash solver; - protected readonly HashAlgorithm sha256 = SHA256.Create(); + protected readonly IHashAlgorithm sha256S = new Sha256S(); private (Share Share, string BlockHex, short stratumError) ProcessShareInternal(StratumConnection worker, string nonce, string solution) @@ -45,7 +46,7 @@ public class BeamJob // hash block-solution Span solutionHash = stackalloc byte[32]; - SHA256.HashData(solutionBytes, solutionHash); + sha256S.Digest(solutionBytes, solutionHash); BigInteger solutionHashValue = new BigInteger(solutionHash, true, true); // calc share-diff diff --git a/src/Miningcore/Blockchain/Beam/BeamWorkerContext.cs b/src/Miningcore/Blockchain/Beam/BeamWorkerContext.cs index 27216fa63..6232f9b67 100644 --- a/src/Miningcore/Blockchain/Beam/BeamWorkerContext.cs +++ b/src/Miningcore/Blockchain/Beam/BeamWorkerContext.cs @@ -7,12 +7,12 @@ public class BeamWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Unique value assigned per worker diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs index 582005fce..2f21a338c 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs @@ -252,6 +252,9 @@ protected virtual Transaction CreateOutputTransaction() if(coin.HasCommunityAddress) rewardToPool = CreateCommunityAddressOutputs(tx, rewardToPool); + if(coin.HasCoinbaseDevReward) + rewardToPool = CreateCoinbaseDevRewardOutputs(tx, rewardToPool); + // Remaining amount goes to pool tx.Outputs.Add(rewardToPool, poolAddressDestination); @@ -307,7 +310,7 @@ protected byte[] SerializeHeader(Span coinbaseHash, uint nTime, uint nonce Nonce = nonce }; - return blockHeader.ToBytes(); + return blockHeader.ToBytes(); } protected virtual (Share Share, string BlockHex) ProcessShareInternal( @@ -571,6 +574,32 @@ protected virtual Money CreateCommunityAddressOutputs(Transaction tx, Money rewa } #endregion // CommunityAddres + #region CoinbaseDevReward + + protected CoinbaseDevRewardTemplateExtra CoinbaseDevRewardParams; + + protected virtual Money CreateCoinbaseDevRewardOutputs(Transaction tx, Money reward) + { + if(CoinbaseDevRewardParams.CoinbaseDevReward != null) + { + CoinbaseDevReward[] CBRewards; + CBRewards = new[] { CoinbaseDevRewardParams.CoinbaseDevReward.ToObject() }; + + foreach(var CBReward in CBRewards) + { + if(!string.IsNullOrEmpty(CBReward.ScriptPubkey)) + { + Script payeeAddress = new Script(CBReward.ScriptPubkey.HexToByteArray()); + var payeeReward = CBReward.Value; + tx.Outputs.Add(payeeReward, payeeAddress); + } + } + } + return reward; + } + + #endregion // CoinbaseDevReward + #region API-Surface public BlockTemplate BlockTemplate { get; protected set; } @@ -647,6 +676,9 @@ public void Init(BlockTemplate blockTemplate, string jobId, if (coin.HasMinerFund) minerFundParameters = BlockTemplate.Extra.SafeExtensionDataAs("coinbasetxn", "minerfund"); + if(coin.HasCoinbaseDevReward) + CoinbaseDevRewardParams = BlockTemplate.Extra.SafeExtensionDataAs(); + this.coinbaseHasher = coinbaseHasher; this.headerHasher = headerHasher; this.blockHasher = blockHasher; diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs index 6304081b8..45d2af005 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinWorkerContext.cs @@ -7,12 +7,12 @@ public class BitcoinWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Unique value assigned per worker diff --git a/src/Miningcore/Blockchain/Bitcoin/DaemonResponses/CoinbaseDevReward.cs b/src/Miningcore/Blockchain/Bitcoin/DaemonResponses/CoinbaseDevReward.cs new file mode 100644 index 000000000..b38a17a67 --- /dev/null +++ b/src/Miningcore/Blockchain/Bitcoin/DaemonResponses/CoinbaseDevReward.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Miningcore.Blockchain.Bitcoin.DaemonResponses +{ + public class CoinbaseDevReward + { + public string ScriptPubkey { get; set; } + public long Value { get; set; } + } + + public class CoinbaseDevRewardTemplateExtra + { + [JsonProperty("coinbasedevreward")] + public JToken CoinbaseDevReward { get; set; } + } +} diff --git a/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs b/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs index 0693fa538..2361ad739 100644 --- a/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs +++ b/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs @@ -391,14 +391,20 @@ protected override async Task AreDaemonsHealthyAsync(CancellationToken ct) // test daemons try { - var response = await restClient.Get(ConcealConstants.DaemonRpcGetInfoLocation, ct); - if(response?.Status != "OK") + var request = new GetBlockTemplateRequest { - logger.Debug(() => $"conceald daemon did not responded..."); - return false; - } + WalletAddress = poolConfig.Address, + ReserveSize = ConcealConstants.ReserveSize + }; - logger.Debug(() => $"{response?.Status} - Incoming: {response?.IncomingConnectionsCount} - Outgoing: {response?.OutgoingConnectionsCount})"); + var response = await rpc.ExecuteAsync(logger, + ConcealCommands.GetBlockTemplate, ct, request); + + if(response.Error != null) + logger.Debug(() => $"conceald daemon response: {response.Error.Message} (Code {response.Error.Code})"); + + if(response.Error is {Code: -9}) + return false; } catch(Exception) diff --git a/src/Miningcore/Blockchain/Conceal/ConcealWorkerContext.cs b/src/Miningcore/Blockchain/Conceal/ConcealWorkerContext.cs index 9539d0bfd..3725f4b13 100644 --- a/src/Miningcore/Blockchain/Conceal/ConcealWorkerContext.cs +++ b/src/Miningcore/Blockchain/Conceal/ConcealWorkerContext.cs @@ -8,12 +8,12 @@ public class ConcealWorkerContext : WorkerContextBase /// Usually a wallet address /// NOTE: May include paymentid (seperated by a dot .) /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } private List validJobs { get; } = new(); diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteConstants.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteConstants.cs index 7a611c8d7..284fbdf40 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteConstants.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteConstants.cs @@ -78,3 +78,16 @@ public static class CryptonoteWalletCommands public const string SplitIntegratedAddress = "split_integrated_address"; public const string Store = "store"; } + +public enum SalviumTransactionType +{ + Unset = 0, + Miner = 1, + Protocol = 2, + Transfer = 3, + Convert = 4, + Burn = 5, + Stake = 6, + Return = 7, + Max = 7 +} diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index ea2963aae..56a0c27f6 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -144,9 +144,10 @@ private async Task EnsureBalance(decimal requiredAmount, CryptonoteCoinTem decimal unlockedBalance = 0.0m; decimal balance = 0.0m; - // Not all Cryptonote coins are equal + // not all Cryptonote coins are equal switch(coin.Symbol) { + case "SAL": case "ZEPH": var responseBalances = await rpcClientWallet.ExecuteAsync(logger, CryptonoteWalletCommands.GetBalance, ct); @@ -198,24 +199,56 @@ private async Task PayoutBatch(Balance[] balances, CancellationToken ct) if(!await EnsureBalance(balances.Sum(x => x.Amount), coin, ct)) return false; - // build request - var request = new TransferRequest + TransferRequest request; + + // not all Cryptonote coins are equal + switch(coin.Symbol) { - Destinations = balances - .Where(x => x.Amount > 0) - .Select(x => + case "SAL": + // build request + request = new TransferRequest { - ExtractAddressAndPaymentId(x.Address, out var address, out _); + Destinations = balances + .Where(x => x.Amount > 0) + .Select(x => + { + ExtractAddressAndPaymentId(x.Address, out var address, out _); - return new TransferDestination - { - Address = address, - Amount = (ulong) Math.Floor(x.Amount * coin.SmallestUnit) - }; - }).ToArray(), + return new TransferDestination + { + Address = address, + Amount = (ulong) Math.Floor(x.Amount * coin.SmallestUnit), + AssetType = coin.Symbol + }; + }).ToArray(), + + TransactionType = (uint) SalviumTransactionType.Transfer, + SourceAsset = coin.Symbol, + DestinationAsset = coin.Symbol, + GetTxKey = true + }; + break; + default: + // build request + request = new TransferRequest + { + Destinations = balances + .Where(x => x.Amount > 0) + .Select(x => + { + ExtractAddressAndPaymentId(x.Address, out var address, out _); - GetTxKey = true - }; + return new TransferDestination + { + Address = address, + Amount = (ulong) Math.Floor(x.Amount * coin.SmallestUnit) + }; + }).ToArray(), + + GetTxKey = true + }; + break; + } if(request.Destinations.Length == 0) return true; @@ -276,20 +309,50 @@ private async Task PayoutToPaymentId(Balance balance, CancellationToken ct if(!await EnsureBalance(balance.Amount, coin, ct)) return false; - // build request - var request = new TransferRequest + TransferRequest request; + + // not all Cryptonote coins are equal + switch(coin.Symbol) { - Destinations = new[] - { - new TransferDestination + case "SAL": + // build request + request = new TransferRequest { - Address = address, - Amount = (ulong) Math.Floor(balance.Amount * coin.SmallestUnit) - } - }, - PaymentId = paymentId, - GetTxKey = true - }; + Destinations = new[] + { + new TransferDestination + { + Address = address, + Amount = (ulong) Math.Floor(balance.Amount * coin.SmallestUnit), + AssetType = coin.Symbol + } + }, + + TransactionType = (uint) SalviumTransactionType.Transfer, + PaymentId = paymentId, + SourceAsset = coin.Symbol, + DestinationAsset = coin.Symbol, + GetTxKey = true + }; + break; + default: + // build request + request = new TransferRequest + { + Destinations = new[] + { + new TransferDestination + { + Address = address, + Amount = (ulong) Math.Floor(balance.Amount * coin.SmallestUnit) + } + }, + + PaymentId = paymentId, + GetTxKey = true + }; + break; + } if(!isIntegratedAddress) request.PaymentId = paymentId; @@ -432,7 +495,7 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, block.Status = BlockStatus.Confirmed; block.ConfirmationProgress = 1; - // Not all Cryptonote coins are equal + // not all Cryptonote coins are equal switch(coin.Symbol) { case "ZEPH": diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteWorkerContext.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteWorkerContext.cs index 87684841e..bdde46fd6 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteWorkerContext.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteWorkerContext.cs @@ -8,12 +8,12 @@ public class CryptonoteWorkerContext : WorkerContextBase /// Usually a wallet address /// NOTE: May include paymentid (seperated by a dot .) /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } private List validJobs { get; } = new(); diff --git a/src/Miningcore/Blockchain/Cryptonote/DaemonRequests/TransferRequest.cs b/src/Miningcore/Blockchain/Cryptonote/DaemonRequests/TransferRequest.cs index 981ab0f34..66071a748 100644 --- a/src/Miningcore/Blockchain/Cryptonote/DaemonRequests/TransferRequest.cs +++ b/src/Miningcore/Blockchain/Cryptonote/DaemonRequests/TransferRequest.cs @@ -6,6 +6,13 @@ public class TransferDestination { public string Address { get; set; } public ulong Amount { get; set; } + + // Salvium + /// + /// Define the type of coin to be received + /// + [JsonProperty("asset_type", NullValueHandling = NullValueHandling.Ignore)] + public string AssetType { get; set; } } public class TransferRequest @@ -51,4 +58,25 @@ public class TransferRequest /// [JsonProperty("unlock_time")] public uint UnlockTime { get; set; } + + // Salvium + /// + /// Define the type of transaction + /// + [JsonProperty("tx_type", NullValueHandling = NullValueHandling.Ignore)] + public uint? TransactionType { get; set; } + + // Salvium + /// + /// Define the type of coin to be sent + /// + [JsonProperty("source_asset", NullValueHandling = NullValueHandling.Ignore)] + public string SourceAsset { get; set; } + + // Salvium + /// + /// Define the type of coin to be received + /// + [JsonProperty("dest_asset", NullValueHandling = NullValueHandling.Ignore)] + public string DestinationAsset { get; set; } } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashConstants.cs b/src/Miningcore/Blockchain/Equihash/EquihashConstants.cs index eb7493b7d..6ebc0a5ff 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashConstants.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashConstants.cs @@ -13,7 +13,8 @@ public class EquihashConstants public class VeruscoinConstants { public const int SolutionSlice = 6; - public const string HashVersion2b2 = "2b2"; + public const string HashVersion2b2 = "2b2"; // PBaaS detection + public const string HashVersion2b2o = "2b2o"; public const string HashVersion2b1 = "2b1"; public const string HashVersion2b = "2b"; public const string HashVersion2 = "2"; diff --git a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs index 6d19d8f5a..73cc15c5d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoWorkerContext.cs @@ -7,12 +7,12 @@ public class ErgoWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Unique value assigned per worker diff --git a/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolConfigExtra.cs b/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolConfigExtra.cs index 167015175..39b2fd815 100644 --- a/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolConfigExtra.cs +++ b/src/Miningcore/Blockchain/Ethereum/Configuration/EthereumPoolConfigExtra.cs @@ -19,10 +19,10 @@ public class EthereumPoolConfigExtra /// https://braiins.com/blog/hashrate-robbery-stratum-v2-fixes-this-and-more /// https://eips.ethereum.org/EIPS/eip-1571 /// https://github.com/AndreaLanfranchi/EthereumStratum-2.0.0/issues/10#issuecomment-595053258 - /// Based on that critical fact, mining pool should be cautious of the risks of using a such deprecated and broken stratum protocol. Used it at your own risks. + /// Based on that critical fact, mining pool should be cautious of the risks of using a such deprecated and broken stratum protocol. Use it at your own risks. /// "Ethash Stratum V1" protocol is disabled by default /// - public bool enableEthashStratumV1 { get; set; } = false; + public bool EnableEthashStratumV1 { get; set; } = false; /// /// getWork stream published via ZMQ diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 8156ca0fa..bd56b4fef 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -115,6 +115,8 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, result.Add(block); messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); + + uint totalDuplicateBlockBefore = 0; // is it block mined by us? if(string.Equals(blockInfo.Miner, poolConfig.Address, StringComparison.OrdinalIgnoreCase)) @@ -147,8 +149,28 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, { logger.Info(() => $"[{LogCategory}] Got {totalDuplicateBlock} `{block.Status}` blocks with the same blockHeight: {block.BlockHeight}"); - block.Reward = GetUncleReward(chainType, block.BlockHeight, block.BlockHeight); - block.Status = BlockStatus.Confirmed; + totalDuplicateBlockBefore = await cf.Run(con => blockRepo.GetPoolDuplicateBlockBeforeCountByPoolHeightNoTypeAndStatusAsync(con, poolConfig.Id, Convert.ToInt64(block.BlockHeight), new[] + { + BlockStatus.Confirmed, + BlockStatus.Orphaned, + BlockStatus.Pending + }, block.Created)); + + block.Reward = GetUncleReward(chainType, block.BlockHeight, block.BlockHeight, totalDuplicateBlockBefore); + + // There is a rare case-scenario where an Uncle has a block reward of zero + // We must handle it carefully otherwise payout will be stuck forever + if(block.Reward > 0) + { + block.Status = BlockStatus.Confirmed; + block.ConfirmationProgress = 1; + } + else + { + block.Status = BlockStatus.Orphaned; + block.Reward = 0; + } + block.ConfirmationProgress = 1; block.BlockHeight = (ulong) blockInfo.Height; block.Type = EthereumConstants.BlockTypeUncle; @@ -233,9 +255,16 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, if(uncle != null) { + totalDuplicateBlockBefore = await cf.Run(con => blockRepo.GetPoolDuplicateBlockBeforeCountByPoolHeightNoTypeAndStatusAsync(con, poolConfig.Id, Convert.ToInt64(uncle.Height.Value), new[] + { + BlockStatus.Confirmed, + BlockStatus.Orphaned, + BlockStatus.Pending + }, block.Created)); + // mature? if(block.Reward == 0) - block.Reward = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value); + block.Reward = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value, totalDuplicateBlockBefore); if(latestBlockHeight - uncle.Height.Value >= EthereumConstants.MinConfimations) { @@ -254,7 +283,7 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, } block.Hash = uncle.Hash; - block.Reward = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value); + block.Reward = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value, totalDuplicateBlockBefore); block.BlockHeight = uncle.Height.Value; // There is a rare case-scenario where an Uncle has a block reward of zero @@ -496,7 +525,7 @@ private async Task GetTxRewardAsync(DaemonResponses.Block blockInfo, Ca return result; } - internal static decimal GetUncleReward(GethChainType chainType, ulong uheight, ulong height) + internal static decimal GetUncleReward(GethChainType chainType, ulong uheight, ulong height, uint totalDuplicateBlockBefore) { var reward = GetBaseBlockReward(chainType, height); @@ -539,7 +568,10 @@ internal static decimal GetUncleReward(GethChainType chainType, ulong uheight, u break; } - + + if(totalDuplicateBlockBefore > 0) + reward = 0m; + return reward; } diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index 6d7420522..bb438c81f 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -499,7 +499,7 @@ protected override async Task OnRequestAsync(StratumConnection connection, break; case EthereumStratumMethods.GetWork: - if(!extraPoolConfig.enableEthashStratumV1) + if(!extraPoolConfig.EnableEthashStratumV1) { logger.Info(() => $"[{connection.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); @@ -515,7 +515,7 @@ protected override async Task OnRequestAsync(StratumConnection connection, break; case EthereumStratumMethods.SubmitWork: - if(!extraPoolConfig.enableEthashStratumV1) + if(!extraPoolConfig.EnableEthashStratumV1) { logger.Info(() => $"[{connection.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs b/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs index 66c9bf751..d10cd20bd 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumWorkerContext.cs @@ -7,12 +7,12 @@ public class EthereumWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Stratum protocol version diff --git a/src/Miningcore/Blockchain/Handshake/HandshakeJob.cs b/src/Miningcore/Blockchain/Handshake/HandshakeJob.cs index 462be3b93..475674aa9 100644 --- a/src/Miningcore/Blockchain/Handshake/HandshakeJob.cs +++ b/src/Miningcore/Blockchain/Handshake/HandshakeJob.cs @@ -82,7 +82,7 @@ protected virtual void BuildMerkleBranches() transactionHashes.Add(transaction.TxId.HexToByteArray()); // build merkle-root - merkleRoot = merkleTree.CreateRoot(headerHasher, transactionHashes); + merkleRoot = merkleTree.CreateRoot(coin.MerkleTreeHasherValue, transactionHashes); } protected virtual void BuildWitnessBranches() @@ -95,7 +95,7 @@ protected virtual void BuildWitnessBranches() transactionHashes.Add(transaction.Hash.HexToByteArray()); // build witness-root - witnessRoot = merkleTree.CreateRoot(headerHasher, transactionHashes); + witnessRoot = merkleTree.CreateRoot(coin.MerkleTreeHasherValue, transactionHashes); } protected virtual void BuildCoinbase() @@ -131,7 +131,7 @@ protected virtual void BuildCoinbase() coinbaseInitialHex = coinbaseInitial.ToHexString(); Span coinbaseInitialBytes = stackalloc byte[32]; - headerHasher.Digest((Span) coinbaseInitial, coinbaseInitialBytes); + coinbaseHasher.Digest(coinbaseInitial, coinbaseInitialBytes); coinbaseMerkle = coinbaseInitialBytes.ToArray(); } @@ -150,14 +150,14 @@ protected virtual void BuildCoinbase() coinbaseFinalHex = coinbaseFinal.ToHexString(); Span coinbaseFinalBytes = stackalloc byte[32]; - headerHasher.Digest((Span) coinbaseFinal, coinbaseFinalBytes); + coinbaseHasher.Digest(coinbaseFinal, coinbaseFinalBytes); Span coinbaseMerklCoinbaseFinalBytes = stackalloc byte[coinbaseMerkle.Length + coinbaseFinalBytes.Length]; coinbaseMerkle.CopyTo(coinbaseMerklCoinbaseFinalBytes); coinbaseFinalBytes.CopyTo(coinbaseMerklCoinbaseFinalBytes[coinbaseMerkle.Length..]); Span coinbaseBytes = stackalloc byte[32]; - headerHasher.Digest(coinbaseMerklCoinbaseFinalBytes, coinbaseBytes); + coinbaseHasher.Digest(coinbaseMerklCoinbaseFinalBytes, coinbaseBytes); coinbaseWitness = coinbaseBytes.ToArray(); } @@ -546,7 +546,7 @@ protected virtual (Share Share, string BlockHex) ProcessShareInternal( var shareBytes = SerializeShare(nonce, nTime, commitHashBytes); Span shareLeftBytes = stackalloc byte[64]; - headerHasher.Digest(shareBytes, shareLeftBytes); + blockHasher.Digest(shareBytes, shareLeftBytes); var rightPaddingBytes = (Span) PaddingPreviousBlockWithTreeRoot(8); Span shareRightPaddingBytes = stackalloc byte[shareBytes.Length + rightPaddingBytes.Length]; @@ -554,7 +554,7 @@ protected virtual (Share Share, string BlockHex) ProcessShareInternal( rightPaddingBytes.CopyTo(shareRightPaddingBytes[shareBytes.Length..]); Span shareRightBytes = stackalloc byte[32]; - coinbaseHasher.Digest(shareRightPaddingBytes, shareRightBytes); + coin.ShareHasherValue.Digest(shareRightPaddingBytes, shareRightBytes); var centerPaddingBytes = (Span) PaddingPreviousBlockWithTreeRoot(32); Span shareLeftCenterPaddingHeaderRighthBytes = stackalloc byte[shareLeftBytes.Length + centerPaddingBytes.Length + shareRightBytes.Length]; @@ -563,7 +563,7 @@ protected virtual (Share Share, string BlockHex) ProcessShareInternal( shareRightBytes.CopyTo(shareLeftCenterPaddingHeaderRighthBytes[(shareLeftBytes.Length + centerPaddingBytes.Length)..]); Span shareHashBytes = stackalloc byte[32]; - headerHasher.Digest(shareLeftCenterPaddingHeaderRighthBytes, shareHashBytes); + blockHasher.Digest(shareLeftCenterPaddingHeaderRighthBytes, shareHashBytes); for (int i = 0; i < maskBytes.Length; i++) shareHashBytes[i] ^= maskBytes[i]; diff --git a/src/Miningcore/Blockchain/Handshake/HandshakePool.cs b/src/Miningcore/Blockchain/Handshake/HandshakePool.cs index 2f5c7556a..fe9cdfd6d 100644 --- a/src/Miningcore/Blockchain/Handshake/HandshakePool.cs +++ b/src/Miningcore/Blockchain/Handshake/HandshakePool.cs @@ -89,8 +89,6 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time context.SetDifficulty(staticDiff.Value); logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); - - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); } } diff --git a/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPaymentProcessingConfigExtra.cs b/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPaymentProcessingConfigExtra.cs index 280e4b254..9037a1173 100644 --- a/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPaymentProcessingConfigExtra.cs +++ b/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPaymentProcessingConfigExtra.cs @@ -9,13 +9,19 @@ public class KaspaPaymentProcessingConfigExtra /// /// Minimum block confirmations - /// Default: 10 + /// KAS minimum confirmation can change over time so please always study all those different changes very wisely: https://github.com/kaspanet/rusty-kaspa/blob/master/wallet/core/src/utxo/settings.rs + /// Default: (mainnet: 120, testnet: 110) /// public int? MinimumConfirmations { get; set; } - + + /// + /// kaspawallet daemon version which enables MaxFee (KASPA: "v0.12.18-rc5") + /// + public string VersionEnablingMaxFee { get; set; } + /// - /// Maximum number of payouts which can be done in parallel - /// Default: 2 + /// Maximum amount you're willing to pay (in SOMPI) + /// Default: 20000 (0.0002 KAS) /// - public int? MaxDegreeOfParallelPayouts { get; set; } + public ulong? MaxFee { get; set; } } \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPoolConfigExtra.cs b/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPoolConfigExtra.cs index 93372cb73..508ba72eb 100644 --- a/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPoolConfigExtra.cs +++ b/src/Miningcore/Blockchain/Kaspa/Configuration/KaspaPoolConfigExtra.cs @@ -4,6 +4,12 @@ namespace Miningcore.Blockchain.Kaspa.Configuration; public class KaspaPoolConfigExtra { + /// + /// There are several reports of IDIOTS mining with ridiculous amount of hashrate and maliciously using a very low staticDiff in order to attack mining pools. + /// StaticDiff is now disabled by default for the KASPA family. Use it at your own risks. + /// + public bool EnableStaticDifficulty { get; set; } = false; + /// /// Maximum number of tracked jobs. /// Default: 8 diff --git a/src/Miningcore/Blockchain/Kaspa/Custom/Astrix/AstrixJob.cs b/src/Miningcore/Blockchain/Kaspa/Custom/Astrix/AstrixJob.cs new file mode 100644 index 000000000..b2cc051a9 --- /dev/null +++ b/src/Miningcore/Blockchain/Kaspa/Custom/Astrix/AstrixJob.cs @@ -0,0 +1,92 @@ +using System; +using System.Numerics; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Algorithms; +using Miningcore.Extensions; +using Miningcore.Stratum; +using Miningcore.Util; +using NBitcoin; + +namespace Miningcore.Blockchain.Kaspa.Custom.Astrix; + +public class AstrixJob : KaspaJob +{ + protected Blake3 blake3Hasher; + protected Sha3_256 sha3_256Hasher; + + public AstrixJob(IHashAlgorithm customBlockHeaderHasher, IHashAlgorithm customCoinbaseHasher, IHashAlgorithm customShareHasher) : base(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher) + { + this.blake3Hasher = new Blake3(); + this.sha3_256Hasher = new Sha3_256(); + } + + protected override Share ProcessShareInternal(StratumConnection worker, string nonce) + { + var context = worker.ContextAs(); + + BlockTemplate.Header.Nonce = Convert.ToUInt64(nonce, 16); + + var prePowHashBytes = SerializeHeader(BlockTemplate.Header, true); + var coinbaseBytes = SerializeCoinbase(prePowHashBytes, BlockTemplate.Header.Timestamp, BlockTemplate.Header.Nonce); + + Span blake3Bytes = stackalloc byte[32]; + blake3Hasher.Digest(coinbaseBytes, blake3Bytes); + + Span sha3_256Bytes = stackalloc byte[32]; + sha3_256Hasher.Digest(blake3Bytes, sha3_256Bytes); + + Span hashCoinbaseBytes = stackalloc byte[32]; + shareHasher.Digest(ComputeCoinbase(prePowHashBytes, sha3_256Bytes), hashCoinbaseBytes); + + var targetHashCoinbaseBytes = new Target(new BigInteger(hashCoinbaseBytes.ToNewReverseArray(), true, true)); + var hashCoinbaseBytesValue = targetHashCoinbaseBytes.ToUInt256(); + //throw new StratumException(StratumError.LowDifficultyShare, $"nonce: {nonce} ||| hashCoinbaseBytes: {hashCoinbaseBytes.ToHexString()} ||| BigInteger: {targetHashCoinbaseBytes.ToBigInteger()} ||| Target: {hashCoinbaseBytesValue} - [stratum: {KaspaUtils.DifficultyToTarget(context.Difficulty)} - blockTemplate: {blockTargetValue}] ||| BigToCompact: {KaspaUtils.BigToCompact(targetHashCoinbaseBytes.ToBigInteger())} - [stratum: {KaspaUtils.BigToCompact(KaspaUtils.DifficultyToTarget(context.Difficulty))} - blockTemplate: {BlockTemplate.Header.Bits}] ||| shareDiff: {(double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier} - [stratum: {context.Difficulty} - blockTemplate: {KaspaUtils.TargetToDifficulty(KaspaUtils.CompactToBig(BlockTemplate.Header.Bits)) * (double) KaspaConstants.MinHash}]"); + + // calc share-diff + var shareDiff = (double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier; + + // diff check + var stratumDifficulty = context.Difficulty; + var ratio = shareDiff / stratumDifficulty; + + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = hashCoinbaseBytesValue <= blockTargetValue; + //var isBlockCandidate = true; + + // test if share meets at least workers current difficulty + if(!isBlockCandidate && ratio < 0.99) + { + // check if share matched the previous difficulty from before a vardiff retarget + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + { + ratio = shareDiff / context.PreviousDifficulty.Value; + + if(ratio < 0.99) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + + // use previous difficulty + stratumDifficulty = context.PreviousDifficulty.Value; + } + + else + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + } + + var result = new Share + { + BlockHeight = (long) BlockTemplate.Header.DaaScore, + NetworkDifficulty = Difficulty, + Difficulty = context.Difficulty / shareMultiplier + }; + + if(isBlockCandidate) + { + var hashBytes = SerializeHeader(BlockTemplate.Header, false); + + result.IsBlockCandidate = true; + result.BlockHash = hashBytes.ToHexString(); + } + + return result; + } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Kaspa/Custom/Karlsencoin/KarlsencoinJob.cs b/src/Miningcore/Blockchain/Kaspa/Custom/Karlsencoin/KarlsencoinJob.cs index 8ec5432e5..21c059d40 100644 --- a/src/Miningcore/Blockchain/Kaspa/Custom/Karlsencoin/KarlsencoinJob.cs +++ b/src/Miningcore/Blockchain/Kaspa/Custom/Karlsencoin/KarlsencoinJob.cs @@ -1,6 +1,11 @@ -using Miningcore.Contracts; +using System; +using System.Numerics; using Miningcore.Crypto; using Miningcore.Crypto.Hashing.Algorithms; +using Miningcore.Extensions; +using Miningcore.Stratum; +using Miningcore.Util; +using NBitcoin; namespace Miningcore.Blockchain.Kaspa.Custom.Karlsencoin; @@ -9,4 +14,92 @@ public class KarlsencoinJob : KaspaJob public KarlsencoinJob(IHashAlgorithm customBlockHeaderHasher, IHashAlgorithm customCoinbaseHasher, IHashAlgorithm customShareHasher) : base(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher) { } + + protected override Span SerializeCoinbase(Span prePowHash, long timestamp, ulong nonce) + { + using(var stream = new MemoryStream()) + { + stream.Write(prePowHash); + stream.Write(BitConverter.GetBytes((ulong) timestamp)); + stream.Write(new byte[32]); // 32 zero bytes padding + stream.Write(BitConverter.GetBytes(nonce)); + + if(shareHasher is not FishHashKarlsen) + { + Span hashBytes = stackalloc byte[32]; + coinbaseHasher.Digest(stream.ToArray(), hashBytes); + + return (Span) hashBytes.ToArray(); + } + else + return (Span) stream.ToArray(); + } + } + + protected override Share ProcessShareInternal(StratumConnection worker, string nonce) + { + var context = worker.ContextAs(); + + BlockTemplate.Header.Nonce = Convert.ToUInt64(nonce, 16); + + var prePowHashBytes = SerializeHeader(BlockTemplate.Header, true); + var coinbaseBytes = SerializeCoinbase(prePowHashBytes, BlockTemplate.Header.Timestamp, BlockTemplate.Header.Nonce); + Span hashCoinbaseBytes = stackalloc byte[32]; + + if(shareHasher is not FishHashKarlsen) + shareHasher.Digest(ComputeCoinbase(prePowHashBytes, coinbaseBytes), hashCoinbaseBytes); + else + shareHasher.Digest(coinbaseBytes, hashCoinbaseBytes); + + var targetHashCoinbaseBytes = new Target(new BigInteger(hashCoinbaseBytes.ToNewReverseArray(), true, true)); + var hashCoinbaseBytesValue = targetHashCoinbaseBytes.ToUInt256(); + //throw new StratumException(StratumError.LowDifficultyShare, $"nonce: {nonce} ||| hashCoinbaseBytes: {hashCoinbaseBytes.ToHexString()} ||| BigInteger: {targetHashCoinbaseBytes.ToBigInteger()} ||| Target: {hashCoinbaseBytesValue} - [stratum: {KaspaUtils.DifficultyToTarget(context.Difficulty)} - blockTemplate: {blockTargetValue}] ||| BigToCompact: {KaspaUtils.BigToCompact(targetHashCoinbaseBytes.ToBigInteger())} - [stratum: {KaspaUtils.BigToCompact(KaspaUtils.DifficultyToTarget(context.Difficulty))} - blockTemplate: {BlockTemplate.Header.Bits}] ||| shareDiff: {(double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier} - [stratum: {context.Difficulty} - blockTemplate: {KaspaUtils.TargetToDifficulty(KaspaUtils.CompactToBig(BlockTemplate.Header.Bits)) * (double) KaspaConstants.MinHash}]"); + + // calc share-diff + var shareDiff = (double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier; + + // diff check + var stratumDifficulty = context.Difficulty; + var ratio = shareDiff / stratumDifficulty; + + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = hashCoinbaseBytesValue <= blockTargetValue; + //var isBlockCandidate = true; + + // test if share meets at least workers current difficulty + if(!isBlockCandidate && ratio < 0.99) + { + // check if share matched the previous difficulty from before a vardiff retarget + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + { + ratio = shareDiff / context.PreviousDifficulty.Value; + + if(ratio < 0.99) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + + // use previous difficulty + stratumDifficulty = context.PreviousDifficulty.Value; + } + + else + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + } + + var result = new Share + { + BlockHeight = (long) BlockTemplate.Header.DaaScore, + NetworkDifficulty = Difficulty, + Difficulty = context.Difficulty / shareMultiplier + }; + + if(isBlockCandidate) + { + var hashBytes = SerializeHeader(BlockTemplate.Header, false); + + result.IsBlockCandidate = true; + result.BlockHash = hashBytes.ToHexString(); + } + + return result; + } } \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Kaspa/Custom/Pyrin/PyrinJob.cs b/src/Miningcore/Blockchain/Kaspa/Custom/Pyrin/PyrinJob.cs index 7896b3fdb..ed136c97a 100644 --- a/src/Miningcore/Blockchain/Kaspa/Custom/Pyrin/PyrinJob.cs +++ b/src/Miningcore/Blockchain/Kaspa/Custom/Pyrin/PyrinJob.cs @@ -1,5 +1,4 @@ using System.Text; -using Miningcore.Contracts; using Miningcore.Crypto; using Miningcore.Crypto.Hashing.Algorithms; using Miningcore.Extensions; @@ -10,5 +9,5 @@ public class PyrinJob : KaspaJob { public PyrinJob(IHashAlgorithm customBlockHeaderHasher, IHashAlgorithm customCoinbaseHasher, IHashAlgorithm customShareHasher) : base(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher) { - } + } } \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Kaspa/Custom/Spectre/SpectreJob.cs b/src/Miningcore/Blockchain/Kaspa/Custom/Spectre/SpectreJob.cs new file mode 100644 index 000000000..bad5bcf02 --- /dev/null +++ b/src/Miningcore/Blockchain/Kaspa/Custom/Spectre/SpectreJob.cs @@ -0,0 +1,129 @@ +using System; +using System.Numerics; +using Miningcore.Contracts; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Algorithms; +using Miningcore.Extensions; +using Miningcore.Native; +using Miningcore.Stratum; +using Miningcore.Util; +using NBitcoin; +using kaspad = Miningcore.Blockchain.Kaspa.Kaspad; + +namespace Miningcore.Blockchain.Kaspa.Custom.Spectre; + +public class SpectreJob : KaspaJob +{ + protected AstroBWTv3 astroBWTv3Hasher; + + public SpectreJob(IHashAlgorithm customBlockHeaderHasher, IHashAlgorithm customCoinbaseHasher, IHashAlgorithm customShareHasher) : base(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher) + { + this.astroBWTv3Hasher = new AstroBWTv3(); + } + + protected override Span SerializeCoinbase(Span prePowHash, long timestamp, ulong nonce) + { + using(var stream = new MemoryStream()) + { + stream.Write(prePowHash); + stream.Write(BitConverter.GetBytes((ulong) timestamp)); + stream.Write(new byte[32]); // 32 zero bytes padding + stream.Write(BitConverter.GetBytes(nonce)); + + return (Span) stream.ToArray(); + } + } + + protected override Share ProcessShareInternal(StratumConnection worker, string nonce) + { + var context = worker.ContextAs(); + + BlockTemplate.Header.Nonce = Convert.ToUInt64(nonce, 16); + + var prePowHashBytes = SerializeHeader(BlockTemplate.Header, true); + var coinbaseRawBytes = SerializeCoinbase(prePowHashBytes, BlockTemplate.Header.Timestamp, BlockTemplate.Header.Nonce); + + Span coinbaseBytes = stackalloc byte[32]; + coinbaseHasher.Digest(coinbaseRawBytes, coinbaseBytes); + + Span astroBWTv3Bytes = stackalloc byte[32]; + astroBWTv3Hasher.Digest(coinbaseBytes, astroBWTv3Bytes); + + Span hashCoinbaseBytes = stackalloc byte[32]; + shareHasher.Digest(ComputeCoinbase(coinbaseRawBytes, astroBWTv3Bytes), hashCoinbaseBytes); + + var targetHashCoinbaseBytes = new Target(new BigInteger(hashCoinbaseBytes.ToNewReverseArray(), true, true)); + var hashCoinbaseBytesValue = targetHashCoinbaseBytes.ToUInt256(); + //throw new StratumException(StratumError.LowDifficultyShare, $"nonce: {nonce} ||| hashCoinbaseBytes: {hashCoinbaseBytes.ToHexString()} ||| BigInteger: {targetHashCoinbaseBytes.ToBigInteger()} ||| Target: {hashCoinbaseBytesValue} - [stratum: {KaspaUtils.DifficultyToTarget(context.Difficulty)} - blockTemplate: {blockTargetValue}] ||| BigToCompact: {KaspaUtils.BigToCompact(targetHashCoinbaseBytes.ToBigInteger())} - [stratum: {KaspaUtils.BigToCompact(KaspaUtils.DifficultyToTarget(context.Difficulty))} - blockTemplate: {BlockTemplate.Header.Bits}] ||| shareDiff: {(double) new BigRational(SpectreConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier} - [stratum: {context.Difficulty} - blockTemplate: {KaspaUtils.TargetToDifficulty(KaspaUtils.CompactToBig(BlockTemplate.Header.Bits)) * (double) SpectreConstants.MinHash}]"); + + // calc share-diff + var shareDiff = (double) new BigRational(SpectreConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier; + + // diff check + var stratumDifficulty = context.Difficulty; + var ratio = shareDiff / stratumDifficulty; + + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = hashCoinbaseBytesValue <= blockTargetValue; + //var isBlockCandidate = true; + + // test if share meets at least workers current difficulty + if(!isBlockCandidate && ratio < 0.99) + { + // check if share matched the previous difficulty from before a vardiff retarget + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + { + ratio = shareDiff / context.PreviousDifficulty.Value; + + if(ratio < 0.99) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + + // use previous difficulty + stratumDifficulty = context.PreviousDifficulty.Value; + } + + else + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + } + + var result = new Share + { + BlockHeight = (long) BlockTemplate.Header.DaaScore, + NetworkDifficulty = Difficulty, + Difficulty = context.Difficulty / shareMultiplier + }; + + if(isBlockCandidate) + { + var hashBytes = SerializeHeader(BlockTemplate.Header, false); + + result.IsBlockCandidate = true; + result.BlockHash = hashBytes.ToHexString(); + } + + return result; + } + + public override void Init(kaspad.RpcBlock blockTemplate, string jobId, double shareMultiplier) + { + Contract.RequiresNonNull(blockTemplate); + Contract.RequiresNonNull(jobId); + + JobId = jobId; + this.shareMultiplier = shareMultiplier; + + var target = new Target(KaspaUtils.CompactToBig(blockTemplate.Header.Bits)); + Difficulty = KaspaUtils.TargetToDifficulty(target.ToBigInteger()) * (double) SpectreConstants.MinHash; + blockTargetValue = target.ToUInt256(); + BlockTemplate = blockTemplate; + + var (largeJob, regularJob) = SerializeJobParamsData(SerializeHeader(blockTemplate.Header)); + jobParams = new object[] + { + JobId, + largeJob + BitConverter.GetBytes(blockTemplate.Header.Timestamp).ToHexString().PadLeft(16, '0'), + regularJob, + blockTemplate.Header.Timestamp, + }; + } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs b/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs index 13d7b8ebc..28a991b63 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs @@ -6,53 +6,27 @@ namespace Miningcore.Blockchain.Kaspa; -public static class BugnaConstants -{ - // List of BGA prefixes: https://github.com/bugnanetwork/bugnad/blob/master/util/address.go - public const string ChainPrefixDevnet = "bugnadev"; - public const string ChainPrefixSimnet = "bugnasim"; - public const string ChainPrefixTestnet = "bugnatest"; - public const string ChainPrefixMainnet = "bugna"; -} - -public static class HoosatConstants -{ - // List of HTN prefixes: https://github.com/Hoosat-Oy/HTND/blob/master/util/address.go - public const string ChainPrefixDevnet = "htndev"; - public const string ChainPrefixSimnet = "hoosatsim"; - public const string ChainPrefixTestnet = "hoosattest"; - public const string ChainPrefixMainnet = "hoosat"; -} - public static class KaspaConstants { public const string WalletDaemonCategory = "wallet"; public const int Diff1TargetNumZero = 31; - public static readonly BigInteger Diff1b = BigInteger.Parse("00000000ffff0000000000000000000000000000000000000000000000000000", NumberStyles.HexNumber); + public static readonly BigInteger Diff1b = BigInteger.Parse("00ffff0000000000000000000000000000000000000000000000000000000000", NumberStyles.HexNumber); public static BigInteger Diff1 = BigInteger.Pow(2, 256); public static BigInteger Diff1Target = BigInteger.Pow(2, 255) - 1; public static readonly double Pow2xDiff1TargetNumZero = Math.Pow(2, Diff1TargetNumZero); - public static BigInteger BigOne = BigInteger.One; - public static BigInteger OneLsh256 = BigInteger.One << 256; public static BigInteger MinHash = BigInteger.Divide(Diff1, Diff1Target); - public static BigInteger BigGig = BigInteger.Pow(10, 9); public const int ExtranoncePlaceHolderLength = 8; public static int NonceLength = 16; - public const uint ShareMultiplier = 1; // KAS smallest unit is called SOMPI: https://github.com/kaspanet/kaspad/blob/master/util/amount.go public const decimal SmallestUnit = 100000000; - // List of KAS prefixes: https://github.com/kaspanet/kaspad/blob/master/util/address.go - public const string ChainPrefixDevnet = "kaspadev"; - public const string ChainPrefixSimnet = "kaspasim"; - public const string ChainPrefixTestnet = "kaspatest"; - public const string ChainPrefixMainnet = "kaspa"; - public static readonly Regex RegexUserAgentBzMiner = new("bzminer", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static readonly Regex RegexUserAgentIceRiverMiner = new("iceriverminer", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static readonly Regex RegexUserAgentGodMiner = new("godminer", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static readonly Regex RegexUserAgentGoldShell = new("(goldshell|bzminer)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static readonly Regex RegexUserAgentTNNMiner = new("tnn-miner", RegexOptions.Compiled | RegexOptions.IgnoreCase); public const string CoinbaseBlockHash = "BlockHash"; public const string CoinbaseProofOfWorkHash = "ProofOfWorkHash"; @@ -75,63 +49,25 @@ public static class KaspaConstants public const int Blake2bSize256 = 32; } -public static class KaspaClassicConstants -{ - // List of CAS prefixes: https://github.com/kaspaclassic/caspad/blob/main/util/address.go - public const string ChainPrefixDevnet = "caspadev"; - public const string ChainPrefixSimnet = "pyrinsim"; - public const string ChainPrefixTestnet = "pyrintest"; - public const string ChainPrefixMainnet = "cas"; -} - public static class KarlsencoinConstants -{ - // List of KLS prefixes: https://github.com/karlsen-network/karlsend/blob/master/util/address.go - public const string ChainPrefixDevnet = "karlsendev"; - public const string ChainPrefixSimnet = "karlsensim"; - public const string ChainPrefixTestnet = "karlsentest"; - public const string ChainPrefixMainnet = "karlsen"; - +{ public const long FishHashForkHeightTestnet = 0; - public const long FishHashPlusForkHeightTestnet = 6000000; -} - -public static class NautilusConstants -{ - // List of NTL prefixes: https://github.com/Nautilus-Network/nautiliad/blob/master/util/address.go - public const string ChainPrefixDevnet = "nautiliadev"; - public const string ChainPrefixSimnet = "nautilussim"; - public const string ChainPrefixTestnet = "nautilustest"; - public const string ChainPrefixMainnet = "nautilus"; -} - -public static class NexelliaConstants -{ - // List of NXL prefixes: https://github.com/Nexellia-Network/nexelliad/blob/master/util/address.go - public const string ChainPrefixDevnet = "nexelliadev"; - public const string ChainPrefixSimnet = "nexelliasim"; - public const string ChainPrefixTestnet = "nexelliatest"; - public const string ChainPrefixMainnet = "nexellia"; + public const long FishHashPlusForkHeightTestnet = 43200; + public const long FishHashPlusForkHeightMainnet = 26962009; } +// Pyrin is definitely a scam, use at your own risk public static class PyrinConstants -{ - // List of KLS prefixes: https://github.com/Pyrinpyi/pyipad/blob/master/util/address.go - public const string ChainPrefixDevnet = "pyipadev"; - public const string ChainPrefixSimnet = "pyrinsim"; - public const string ChainPrefixTestnet = "pyrintest"; - public const string ChainPrefixMainnet = "pyrin"; - +{ public const long Blake3ForkHeight = 1484741; } -public static class SedraCoinConstants +public static class SpectreConstants { - // List of HTN prefixes: https://github.com/sedracoin/sedrad/blob/main/util/address.go - public const string ChainPrefixDevnet = "sedradev"; - public const string ChainPrefixSimnet = "sedrasim"; - public const string ChainPrefixTestnet = "sedratest"; - public const string ChainPrefixMainnet = "sedra"; + public const int Diff1TargetNumZero = 7; + public static readonly BigInteger Diff1b = BigInteger.Parse("00ffff0000000000000000000000000000000000000000000000000000000000", NumberStyles.HexNumber); + public static readonly double Pow2xDiff1TargetNumZero = Math.Pow(2, Diff1TargetNumZero); + public static BigInteger MinHash = BigInteger.One; } public enum KaspaBech32Prefix diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaJob.cs b/src/Miningcore/Blockchain/Kaspa/KaspaJob.cs index e1b41e7c6..180f2a133 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaJob.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaJob.cs @@ -56,12 +56,13 @@ private static ulong RotateRight64(ulong value, int offset) public class KaspaJob { protected IMasterClock clock; - public kaspad.RpcBlock BlockTemplate { get; private set; } - public double Difficulty { get; private set; } + protected double shareMultiplier; + public kaspad.RpcBlock BlockTemplate { get; protected set; } + public double Difficulty { get; protected set; } public string JobId { get; protected set; } public uint256 blockTargetValue { get; protected set; } - private object[] jobParams; + protected object[] jobParams; private readonly ConcurrentDictionary submissions = new(StringComparer.OrdinalIgnoreCase); protected IHashAlgorithm blockHeaderHasher; @@ -200,7 +201,7 @@ protected virtual Span SerializeCoinbase(Span prePowHash, long times } } - protected virtual Span SerializeHeader(kaspad.RpcBlockHeader header, bool isPrePow = true, bool isLittleEndian = false) + protected virtual Span SerializeHeader(kaspad.RpcBlockHeader header, bool isPrePow = true) { ulong nonce = isPrePow ? 0 : header.Nonce; long timestamp = isPrePow ? 0 : header.Timestamp; @@ -209,14 +210,14 @@ protected virtual Span SerializeHeader(kaspad.RpcBlockHeader header, bool using(var stream = new MemoryStream()) { - var versionBytes = (isLittleEndian) ? BitConverter.GetBytes((ushort) header.Version).ReverseInPlace() : BitConverter.GetBytes((ushort) header.Version); + var versionBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes((ushort) header.Version).ReverseInPlace() : BitConverter.GetBytes((ushort) header.Version); stream.Write(versionBytes); - var parentsBytes = (isLittleEndian) ? BitConverter.GetBytes((ulong) header.Parents.Count).ReverseInPlace() : BitConverter.GetBytes((ulong) header.Parents.Count); + var parentsBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes((ulong) header.Parents.Count).ReverseInPlace() : BitConverter.GetBytes((ulong) header.Parents.Count); stream.Write(parentsBytes); foreach (var parent in header.Parents) { - var parentHashesBytes = (isLittleEndian) ? BitConverter.GetBytes((ulong) parent.ParentHashes.Count).ReverseInPlace() : BitConverter.GetBytes((ulong) parent.ParentHashes.Count); + var parentHashesBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes((ulong) parent.ParentHashes.Count).ReverseInPlace() : BitConverter.GetBytes((ulong) parent.ParentHashes.Count); stream.Write(parentHashesBytes); foreach (var parentHash in parent.ParentHashes) @@ -229,21 +230,21 @@ protected virtual Span SerializeHeader(kaspad.RpcBlockHeader header, bool stream.Write(header.AcceptedIdMerkleRoot.HexToByteArray()); stream.Write(header.UtxoCommitment.HexToByteArray()); - var timestampBytes = (isLittleEndian) ? BitConverter.GetBytes((ulong) timestamp).ReverseInPlace() : BitConverter.GetBytes((ulong) timestamp); + var timestampBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes((ulong) timestamp).ReverseInPlace() : BitConverter.GetBytes((ulong) timestamp); stream.Write(timestampBytes); - var bitsBytes = (isLittleEndian) ? BitConverter.GetBytes(header.Bits).ReverseInPlace() : BitConverter.GetBytes(header.Bits); + var bitsBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes(header.Bits).ReverseInPlace() : BitConverter.GetBytes(header.Bits); stream.Write(bitsBytes); - var nonceBytes = (isLittleEndian) ? BitConverter.GetBytes(nonce).ReverseInPlace() : BitConverter.GetBytes(nonce); + var nonceBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes(nonce).ReverseInPlace() : BitConverter.GetBytes(nonce); stream.Write(nonceBytes); - var daaScoreBytes = (isLittleEndian) ? BitConverter.GetBytes(header.DaaScore).ReverseInPlace() : BitConverter.GetBytes(header.DaaScore); + var daaScoreBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes(header.DaaScore).ReverseInPlace() : BitConverter.GetBytes(header.DaaScore); stream.Write(daaScoreBytes); - var blueScoreBytes = (isLittleEndian) ? BitConverter.GetBytes(header.BlueScore).ReverseInPlace() : BitConverter.GetBytes(header.BlueScore); + var blueScoreBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes(header.BlueScore).ReverseInPlace() : BitConverter.GetBytes(header.BlueScore); stream.Write(blueScoreBytes); var blueWork = header.BlueWork.PadLeft(header.BlueWork.Length + (header.BlueWork.Length % 2), '0'); var blueWorkBytes = blueWork.HexToByteArray(); - var blueWorkLengthBytes = (isLittleEndian) ? BitConverter.GetBytes((ulong) blueWorkBytes.Length).ReverseInPlace() : BitConverter.GetBytes((ulong) blueWorkBytes.Length); + var blueWorkLengthBytes = (!BitConverter.IsLittleEndian) ? BitConverter.GetBytes((ulong) blueWorkBytes.Length).ReverseInPlace() : BitConverter.GetBytes((ulong) blueWorkBytes.Length); stream.Write(blueWorkLengthBytes); stream.Write(blueWorkBytes); @@ -255,14 +256,14 @@ protected virtual Span SerializeHeader(kaspad.RpcBlockHeader header, bool } } - protected virtual (string, ulong[]) SerializeJobParamsData(Span prePowHash, bool isLittleEndian = false) + protected virtual (string, ulong[]) SerializeJobParamsData(Span prePowHash) { ulong[] preHashU64s = new ulong[4]; string preHashStrings = ""; for (int i = 0; i < 4; i++) { - var slice = (isLittleEndian) ? prePowHash.Slice(i * 8, 8).ToNewReverseArray() : prePowHash.Slice(i * 8, 8); + var slice = prePowHash.Slice(i * 8, 8); preHashStrings += slice.ToHexString().PadLeft(16, '0'); preHashU64s[i] = BitConverter.ToUInt64(slice); @@ -274,24 +275,21 @@ protected virtual (string, ulong[]) SerializeJobParamsData(Span prePowHash protected virtual Share ProcessShareInternal(StratumConnection worker, string nonce) { var context = worker.ContextAs(); - + BlockTemplate.Header.Nonce = Convert.ToUInt64(nonce, 16); - + var prePowHashBytes = SerializeHeader(BlockTemplate.Header, true); var coinbaseBytes = SerializeCoinbase(prePowHashBytes, BlockTemplate.Header.Timestamp, BlockTemplate.Header.Nonce); + Span hashCoinbaseBytes = stackalloc byte[32]; + shareHasher.Digest(ComputeCoinbase(prePowHashBytes, coinbaseBytes), hashCoinbaseBytes); - if(shareHasher is not FishHashKarlsen) - shareHasher.Digest(ComputeCoinbase(prePowHashBytes, coinbaseBytes), hashCoinbaseBytes); - else - shareHasher.Digest(coinbaseBytes, hashCoinbaseBytes); - var targetHashCoinbaseBytes = new Target(new BigInteger(hashCoinbaseBytes.ToNewReverseArray(), true, true)); var hashCoinbaseBytesValue = targetHashCoinbaseBytes.ToUInt256(); - //throw new StratumException(StratumError.LowDifficultyShare, $"nonce: {nonce} ||| BigInteger: {targetHashCoinbaseBytes.ToBigInteger()} ||| Target: {hashCoinbaseBytesValue} - [stratum: {KaspaUtils.DifficultyToTarget(context.Difficulty)} - blockTemplate: {blockTargetValue}] ||| BigToCompact: {KaspaUtils.BigToCompact(targetHashCoinbaseBytes.ToBigInteger())} - [stratum: {KaspaUtils.BigToCompact(KaspaUtils.DifficultyToTarget(context.Difficulty))} - blockTemplate: {BlockTemplate.Header.Bits}] ||| shareDiff: {(double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * (double) KaspaConstants.MinHash / KaspaConstants.ShareMultiplier} - [stratum: {context.Difficulty} - blockTemplate: {KaspaUtils.TargetToDifficulty(KaspaUtils.CompactToBig(BlockTemplate.Header.Bits)) * (double) KaspaConstants.MinHash / KaspaConstants.ShareMultiplier}] ||| AdjustShareDifficulty: {(double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash / KaspaConstants.ShareMultiplier} - [stratum: {context.Difficulty * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash / KaspaConstants.ShareMultiplier}]"); - + //throw new StratumException(StratumError.LowDifficultyShare, $"nonce: {nonce} ||| hashCoinbaseBytes: {hashCoinbaseBytes.ToHexString()} ||| BigInteger: {targetHashCoinbaseBytes.ToBigInteger()} ||| Target: {hashCoinbaseBytesValue} - [stratum: {KaspaUtils.DifficultyToTarget(context.Difficulty)} - blockTemplate: {blockTargetValue}] ||| BigToCompact: {KaspaUtils.BigToCompact(targetHashCoinbaseBytes.ToBigInteger())} - [stratum: {KaspaUtils.BigToCompact(KaspaUtils.DifficultyToTarget(context.Difficulty))} - blockTemplate: {BlockTemplate.Header.Bits}] ||| shareDiff: {(double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier} - [stratum: {context.Difficulty} - blockTemplate: {KaspaUtils.TargetToDifficulty(KaspaUtils.CompactToBig(BlockTemplate.Header.Bits)) * (double) KaspaConstants.MinHash}]"); + // calc share-diff - var shareDiff = (double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash / KaspaConstants.ShareMultiplier; + var shareDiff = (double) new BigRational(KaspaConstants.Diff1b, targetHashCoinbaseBytes.ToBigInteger()) * shareMultiplier; // diff check var stratumDifficulty = context.Difficulty; @@ -324,7 +322,7 @@ protected virtual Share ProcessShareInternal(StratumConnection worker, string no { BlockHeight = (long) BlockTemplate.Header.DaaScore, NetworkDifficulty = Difficulty, - Difficulty = context.Difficulty / KaspaConstants.ShareMultiplier + Difficulty = context.Difficulty / shareMultiplier }; if(isBlockCandidate) @@ -366,14 +364,16 @@ public virtual Share ProcessShare(StratumConnection worker, string nonce) return ProcessShareInternal(worker, nonce); } - public void Init(kaspad.RpcBlock blockTemplate, string jobId) + public virtual void Init(kaspad.RpcBlock blockTemplate, string jobId, double shareMultiplier) { Contract.RequiresNonNull(blockTemplate); Contract.RequiresNonNull(jobId); JobId = jobId; + this.shareMultiplier = shareMultiplier; + var target = new Target(KaspaUtils.CompactToBig(blockTemplate.Header.Bits)); - Difficulty = KaspaUtils.TargetToDifficulty(target.ToBigInteger()) * (double) KaspaConstants.MinHash / KaspaConstants.ShareMultiplier; + Difficulty = KaspaUtils.TargetToDifficulty(target.ToBigInteger()) * (double) KaspaConstants.MinHash; blockTargetValue = target.ToUInt256(); BlockTemplate = blockTemplate; diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs b/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs index 65602ccc8..3dffa995d 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs @@ -13,8 +13,10 @@ using Grpc.Core; using Grpc.Net.Client; using Miningcore.Blockchain.Kaspa.Configuration; +using Miningcore.Blockchain.Kaspa.Custom.Astrix; using Miningcore.Blockchain.Kaspa.Custom.Karlsencoin; using Miningcore.Blockchain.Kaspa.Custom.Pyrin; +using Miningcore.Blockchain.Kaspa.Custom.Spectre; using NLog; using Miningcore.Configuration; using Miningcore.Crypto; @@ -215,6 +217,17 @@ private KaspaJob CreateJob(long blockHeight) { switch(coin.Symbol) { + case "AIX": + if(customBlockHeaderHasher is not Blake2b) + customBlockHeaderHasher = new Blake2b(Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseBlockHash)); + + if(customCoinbaseHasher is not CShake256) + customCoinbaseHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseProofOfWorkHash)); + + if(customShareHasher is not CShake256) + customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash)); + + return new AstrixJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); case "CAS": case "HTN": if(customBlockHeaderHasher is not Blake3) @@ -240,41 +253,35 @@ private KaspaJob CreateJob(long blockHeight) if(customCoinbaseHasher is not Blake3) customCoinbaseHasher = new Blake3(); - if(karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashPlusForkHeightTestnet) + if((karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashPlusForkHeightTestnet) || (karlsenNetwork == "mainnet" && blockHeight >= KarlsencoinConstants.FishHashPlusForkHeightMainnet)) { logger.Debug(() => $"fishHashPlusHardFork activated"); if(customShareHasher is not FishHashKarlsen) + customShareHasher = new FishHashKarlsen(FishHash.FishHashKernelPlus); + else if(customShareHasher is FishHashKarlsen fishHashKarlsenAlgo) { - var started = DateTime.Now; - logger.Debug(() => $"Generating light cache"); - - customShareHasher = new FishHashKarlsen(true); - - logger.Debug(() => $"Done generating light cache after {DateTime.Now - started}"); + if(fishHashKarlsenAlgo.fishHashKernel != FishHash.FishHashKernelPlus) + customShareHasher = new FishHashKarlsen(FishHash.FishHashKernelPlus); } + } else if(karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashForkHeightTestnet) { logger.Debug(() => $"fishHashHardFork activated"); if(customShareHasher is not FishHashKarlsen) - { - var started = DateTime.Now; - logger.Debug(() => $"Generating light cache"); - customShareHasher = new FishHashKarlsen(); - - logger.Debug(() => $"Done generating light cache after {DateTime.Now - started}"); - } } else if(customShareHasher is not CShake256) customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash)); return new KarlsencoinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); + case "CSS": case "NTL": case "NXL": + case "PUG": if(customBlockHeaderHasher is not Blake2b) customBlockHeaderHasher = new Blake2b(Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseBlockHash)); @@ -316,6 +323,17 @@ private KaspaJob CreateJob(long blockHeight) } return new PyrinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); + case "SPR": + if(customBlockHeaderHasher is not Blake2b) + customBlockHeaderHasher = new Blake2b(Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseBlockHash)); + + if(customCoinbaseHasher is not CShake256) + customCoinbaseHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseProofOfWorkHash)); + + if(customShareHasher is not CShake256) + customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash)); + + return new SpectreJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); } if(customBlockHeaderHasher is not Blake2b) @@ -354,7 +372,7 @@ private async Task UpdateJob(CancellationToken ct, string via = null, kasp { job = CreateJob((long) blockTemplate.Header.DaaScore); - job.Init(blockTemplate, NextJobId()); + job.Init(blockTemplate, NextJobId(), ShareMultiplier); lock(jobLock) { @@ -626,6 +644,9 @@ public bool ValidateIsLargeJob(string userAgent) if(ValidateIsIceRiverMiner(userAgent)) return true; + + if(ValidateIsGoldShell(userAgent)) + return true; return false; } @@ -660,6 +681,28 @@ public bool ValidateIsIceRiverMiner(string userAgent) return (matchesUserAgentIceRiverMiner.Count > 0); } + public bool ValidateIsGoldShell(string userAgent) + { + if(string.IsNullOrEmpty(userAgent)) + return false; + + // Find matches + MatchCollection matchesUserAgentGoldShell = KaspaConstants.RegexUserAgentGoldShell.Matches(userAgent); + return (matchesUserAgentGoldShell.Count > 0); + } + + public bool ValidateIsTNNMiner(string userAgent) + { + if(string.IsNullOrEmpty(userAgent)) + return false; + + // Find matches + MatchCollection matchesUserAgentTNNMiner = KaspaConstants.RegexUserAgentTNNMiner.Matches(userAgent); + return (matchesUserAgentTNNMiner.Count > 0); + } + + public double ShareMultiplier => coin.ShareMultiplier; + #endregion // API-Surface #region Overrides @@ -686,7 +729,7 @@ await Guard(() => stream.RequestStream.WriteAsync(request), break; } - var (kaspaAddressUtility, errorKaspaAddressUtility) = KaspaUtils.ValidateAddress(poolConfig.Address, network, coin.Symbol); + var (kaspaAddressUtility, errorKaspaAddressUtility) = KaspaUtils.ValidateAddress(poolConfig.Address, network, coin); if(errorKaspaAddressUtility != null) throw new PoolStartupException($"Pool address: {poolConfig.Address} is invalid for network [{network}]: {errorKaspaAddressUtility}", poolConfig.Id); else diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaPayoutHandler.cs b/src/Miningcore/Blockchain/Kaspa/KaspaPayoutHandler.cs index 5ad881a51..a14cd860f 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaPayoutHandler.cs @@ -1,5 +1,6 @@ using System; using System.Net.Http; +using System.Text.RegularExpressions; using Autofac; using AutoMapper; using Grpc.Core; @@ -52,6 +53,7 @@ public KaspaPayoutHandler( private string network; private KaspaPoolConfigExtra extraPoolConfig; private KaspaPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; + private bool supportsMaxFee = false; protected override string LogCategory => "Kaspa Payout Handler"; @@ -100,6 +102,31 @@ await Guard(() => stream.RequestStream.WriteAsync(request), break; } await stream.RequestStream.CompleteAsync(); + + var callGetVersion = walletRpc.GetVersionAsync(new kaspaWalletd.GetVersionRequest()); + var walletVersion = await Guard(() => callGetVersion.ResponseAsync, + ex=> logger.Debug(ex)); + callGetVersion.Dispose(); + + if(!string.IsNullOrEmpty(walletVersion?.Version)) + { + logger.Info(() => $"[{LogCategory}] Wallet version: {walletVersion.Version}"); + + if(!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.VersionEnablingMaxFee)) + { + logger.Info(() => $"[{LogCategory}] Wallet daemon version which enables MaxFee: {extraPoolPaymentProcessingConfig.VersionEnablingMaxFee}"); + + string walletVersionNumbersOnly = Regex.Replace(walletVersion.Version, "[^0-9.]", ""); + string[] walletVersionNumbers = walletVersionNumbersOnly.Split("."); + + string versionEnablingMaxFeeNumbersOnly = Regex.Replace(extraPoolPaymentProcessingConfig.VersionEnablingMaxFee, "[^0-9.]", ""); + string[] versionEnablingMaxFeeNumbers = versionEnablingMaxFeeNumbersOnly.Split("."); + + // update supports max fee + if(walletVersionNumbers.Length >= 3 && versionEnablingMaxFeeNumbers.Length >= 3) + supportsMaxFee = ((Convert.ToUInt32(walletVersionNumbers[0]) > Convert.ToUInt32(versionEnablingMaxFeeNumbers[0])) || (Convert.ToUInt32(walletVersionNumbers[0]) == Convert.ToUInt32(versionEnablingMaxFeeNumbers[0]) && Convert.ToUInt32(walletVersionNumbers[1]) > Convert.ToUInt32(versionEnablingMaxFeeNumbers[1])) || (Convert.ToUInt32(walletVersionNumbers[0]) == Convert.ToUInt32(versionEnablingMaxFeeNumbers[0]) && Convert.ToUInt32(walletVersionNumbers[1]) == Convert.ToUInt32(versionEnablingMaxFeeNumbers[1]) && Convert.ToUInt32(walletVersionNumbers[2]) >= Convert.ToUInt32(versionEnablingMaxFeeNumbers[2]))); + } + } } public virtual async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, CancellationToken ct) @@ -114,6 +141,7 @@ public virtual async Task ClassifyBlocksAsync(IMiningPool pool, Block[] var pageSize = 100; var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); var result = new List(); + // KAS minimum confirmation can change over time so please always aknowledge all those different changes very wisely: https://github.com/kaspanet/rusty-kaspa/blob/master/wallet/core/src/utxo/settings.rs int minConfirmations = extraPoolPaymentProcessingConfig?.MinimumConfirmations ?? (network == "mainnet" ? 120 : 110); // we need a stream to communicate with Kaspad @@ -168,6 +196,8 @@ await Guard(() => stream.RequestStream.WriteAsync(requestConfirmations), ex=> logger.Debug(ex)); await foreach (var responseConfirmations in stream.ResponseStream.ReadAllAsync(ct)) { + logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} [{responseConfirmations.GetBlocksResponse.BlockHashes.Count}]"); + block.ConfirmationProgress = Math.Min(1.0d, (double) responseConfirmations.GetBlocksResponse.BlockHashes.Count / minConfirmations); break; } @@ -317,6 +347,8 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc // build args var amounts = balances .Where(x => x.Amount > 0) + .OrderBy(x => x.Updated) + .ThenByDescending(x => x.Amount) .ToDictionary(x => x.Address, x => x.Amount); if(amounts.Count == 0) @@ -324,14 +356,14 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc var balancesTotal = amounts.Sum(x => x.Value); - logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses"); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balancesTotal)} to {balances.Length} addresses"); logger.Info(() => $"[{LogCategory}] Validating addresses..."); var coin = poolConfig.Template.As(); foreach(var pair in amounts) { logger.Debug(() => $"[{LogCategory}] Address {pair.Key} with amount [{FormatAmount(pair.Value)}]"); - var (kaspaAddressUtility, errorKaspaAddressUtility) = KaspaUtils.ValidateAddress(pair.Key, network, coin.Symbol); + var (kaspaAddressUtility, errorKaspaAddressUtility) = KaspaUtils.ValidateAddress(pair.Key, network, coin); if(errorKaspaAddressUtility != null) logger.Warn(()=> $"[{LogCategory}] Address {pair.Key} is not valid : {errorKaspaAddressUtility}"); @@ -353,57 +385,105 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc logger.Warn(() => $"[{LogCategory}] Wallet balance currently short of {FormatAmount(balancesTotal - walletBalanceAvailable)}. Will try again"); return; } - + var txFailures = new List, Exception>>(); var successBalances = new Dictionary(); - var parallelOptions = new ParallelOptions + // Payments on KASPA are a bit tricky, it does not have a strong multi-recipient method, the only way is to create unsigned transactions, signed them and then broadcast them, let's do this! + foreach (var amount in amounts) { - MaxDegreeOfParallelism = extraPoolPaymentProcessingConfig?.MaxDegreeOfParallelPayouts ?? 2, - CancellationToken = ct - }; + kaspaWalletd.CreateUnsignedTransactionsResponse unsignedTransaction; + kaspaWalletd.SignResponse signedTransaction; - await Parallel.ForEachAsync(amounts, parallelOptions, async (x, _ct) => - { - var (address, amount) = x; + // use a common id for all log entries related to this transfer + var transferId = CorrelationIdGenerator.GetNextId(); - await Guard(async () => - { - // use a common id for all log entries related to this transfer - var transferId = CorrelationIdGenerator.GetNextId(); + logger.Info(()=> $"[{LogCategory}] [{transferId}] Sending {FormatAmount(amount.Value)} to {amount.Key}"); - logger.Info(()=> $"[{LogCategory}] [{transferId}] Sending {FormatAmount(amount)} to {address}"); - - var callSend = walletRpc.SendAsync(new kaspaWalletd.SendRequest { - ToAddress = address.ToLower(), - Amount = (ulong) (amount * KaspaConstants.SmallestUnit), - Password = extraPoolPaymentProcessingConfig?.WalletPassword ?? null, - UseExistingChangeAddress = true, - IsSendAll = false, - }); - var sendTransaction = await Guard(() => callSend.ResponseAsync, - ex=> throw new PaymentException($"[{transferId}] kaspawalletd returned error: {ex}")); - callSend.Dispose(); + logger.Info(()=> $"[{LogCategory}] [{transferId}] 1/3 Create an unsigned transaction"); + + var createUnsignedTransactionsRequest = new kaspaWalletd.CreateUnsignedTransactionsRequest + { + Address = amount.Key.ToLower(), + Amount = (ulong) (amount.Value * KaspaConstants.SmallestUnit), + UseExistingChangeAddress = false, + IsSendAll = false + }; - // check result - var txId = sendTransaction.TxIDs.First(); + if(supportsMaxFee) + { + ulong maxFee = extraPoolPaymentProcessingConfig?.MaxFee ?? 20000; - if(string.IsNullOrEmpty(txId)) - throw new Exception($"[{transferId}] kaspawalletd did not return a transaction id!"); - else - logger.Info(() => $"[{LogCategory}] [{transferId}] Payment transaction id: {txId}"); + logger.Info(()=> $"[{LogCategory}] Max fee: {maxFee} SOMPI"); - successBalances.Add(new Balance + createUnsignedTransactionsRequest.FeePolicy = new kaspaWalletd.FeePolicy { - PoolId = poolConfig.Id, - Address = address, - Amount = amount, - }, txId); - }, ex => + MaxFee = maxFee + }; + } + + var callUnsignedTransaction = walletRpc.CreateUnsignedTransactionsAsync(createUnsignedTransactionsRequest); + + unsignedTransaction = await Guard(() => callUnsignedTransaction.ResponseAsync, ex => { - txFailures.Add(Tuple.Create(x, ex)); + txFailures.Add(Tuple.Create(amount, ex)); }); - }); + callUnsignedTransaction.Dispose(); + + logger.Debug(()=> $"[{LogCategory}] [{transferId}] {(unsignedTransaction?.UnsignedTransactions == null ? 0 : unsignedTransaction?.UnsignedTransactions.Count)} unsigned transaction(s) created"); + + // we have transactions to sign + if(unsignedTransaction?.UnsignedTransactions.Count > 0) + { + logger.Info(()=> $"[{LogCategory}] [{transferId}] 2/3 Sign {unsignedTransaction.UnsignedTransactions.Count} unsigned transaction(s)"); + + var signRequest = new kaspaWalletd.SignRequest + { + Password = extraPoolPaymentProcessingConfig?.WalletPassword ?? null + }; + signRequest.UnsignedTransactions.Add(unsignedTransaction.UnsignedTransactions); + + var callSignedTransaction = walletRpc.SignAsync(signRequest); + signedTransaction = await Guard(() => callSignedTransaction.ResponseAsync, ex => + { + txFailures.Add(Tuple.Create(amount, ex)); + }); + callSignedTransaction.Dispose(); + + logger.Debug(()=> $"[{LogCategory}] [{transferId}] {(signedTransaction?.SignedTransactions == null ? 0 : signedTransaction?.SignedTransactions.Count)} signed transaction(s) created"); + + // we have transactions to broadcast + if(signedTransaction?.SignedTransactions.Count > 0) + { + var broadcastRequest = new kaspaWalletd.BroadcastRequest(); + kaspaWalletd.BroadcastResponse broadcastTransaction; + + logger.Info(()=> $"[{LogCategory}] [{transferId}] 3/3 Broadcast {signedTransaction.SignedTransactions.Count} signed transaction(s)"); + + broadcastRequest.Transactions.Add(signedTransaction.SignedTransactions); + var callBroadcast = walletRpc.BroadcastAsync(broadcastRequest); + broadcastTransaction = await Guard(() => callBroadcast.ResponseAsync, + ex=> logger.Warn(ex)); + callBroadcast.Dispose(); + + logger.Debug(()=> $"[{LogCategory}] {(broadcastTransaction?.TxIDs == null ? 0 : broadcastTransaction?.TxIDs.Count)} transaction ID(s) returned"); + + if(broadcastTransaction?.TxIDs.Count > 0) + { + var txId = broadcastTransaction?.TxIDs.First(); + + logger.Info(() => $"[{LogCategory}] [{amount.Key} - {FormatAmount(amount.Value)}] Payment transaction id: {txId}"); + + successBalances.Add(new Balance + { + PoolId = poolConfig.Id, + Address = amount.Key, + Amount = amount.Value, + }, txId); + } + } + } + } if(successBalances.Any()) { @@ -422,15 +502,35 @@ await Guard(async () => NotifyPayoutFailure(poolConfig.Id, failureBalances, error, null); } } - + public override double AdjustShareDifficulty(double difficulty) { - return difficulty * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash; + var coin = poolConfig.Template.As(); + + switch(coin.Symbol) + { + case "SPR": + + return difficulty * SpectreConstants.Pow2xDiff1TargetNumZero * (double) SpectreConstants.MinHash; + default: + + return difficulty * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash; + } } public double AdjustBlockEffort(double effort) { - return effort * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash; + var coin = poolConfig.Template.As(); + + switch(coin.Symbol) + { + case "SPR": + + return effort * SpectreConstants.Pow2xDiff1TargetNumZero * (double) SpectreConstants.MinHash; + default: + + return effort * KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash; + } } #endregion // IPayoutHandler diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaPool.cs b/src/Miningcore/Blockchain/Kaspa/KaspaPool.cs index 3bcfa866d..1c52d013c 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaPool.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaPool.cs @@ -78,7 +78,18 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time "KaspaStratum/1.0.0", }; - await connection.RespondAsync(data, request.Id); + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(data, request.Id); + + if(context.IsNicehash || manager.ValidateIsGoldShell(context.UserAgent)) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + await connection.RespondAsync(response); await connection.NotifyAsync(KaspaStratumMethods.SetExtraNonce, manager.GetSubscriberData(connection)); } @@ -112,13 +123,13 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; // assumes that minerName is an address - var (kaspaAddressUtility, errorKaspaAddressUtility) = KaspaUtils.ValidateAddress(minerName, manager.Network, coin.Symbol); + var (kaspaAddressUtility, errorKaspaAddressUtility) = KaspaUtils.ValidateAddress(minerName, manager.Network, coin); if (errorKaspaAddressUtility != null) - logger.Warn(() => $"[{connection.ConnectionId}] Unauthorized worker: {errorKaspaAddressUtility}"); + logger.Warn(() => $"[{connection.ConnectionId}]{(!string.IsNullOrEmpty(context.UserAgent) ? $"[{context.UserAgent}]" : string.Empty)} Unauthorized worker: {errorKaspaAddressUtility}"); else { context.IsAuthorized = true; - logger.Info(() => $"[{connection.ConnectionId}] worker: {minerName} => {KaspaConstants.KaspaAddressType[kaspaAddressUtility.KaspaAddress.Version()]}"); + logger.Info(() => $"[{connection.ConnectionId}]{(!string.IsNullOrEmpty(context.UserAgent) ? $"[{context.UserAgent}]" : string.Empty)} worker: {minerName} => {KaspaConstants.KaspaAddressType[kaspaAddressUtility.KaspaAddress.Version()]}"); } context.Miner = minerName; @@ -126,8 +137,19 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time if(context.IsAuthorized) { + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(context.IsAuthorized, request.Id); + + if(context.IsNicehash || manager.ValidateIsGoldShell(context.UserAgent)) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + // respond - await connection.RespondAsync(context.IsAuthorized, request.Id); + await connection.RespondAsync(response); // log association logger.Info(() => $"[{connection.ConnectionId}] Authorized worker {workerValue}"); @@ -156,12 +178,22 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time (context.VarDiff != null && staticDiff.Value >= context.VarDiff.Config.MinDiff || context.VarDiff == null && staticDiff.Value > context.Difficulty)) { - context.VarDiff = null; // disable vardiff + // There are several reports of IDIOTS mining with ridiculous amount of hashrate and maliciously using a very low staticDiff in order to attack mining pools. + // StaticDiff is now disabled by default for the KASPA family. Use it at your own risks. + if(extraPoolConfig.EnableStaticDifficulty) + context.VarDiff = null; // disable vardiff + context.SetDifficulty(staticDiff.Value); - logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); + if(extraPoolConfig.EnableStaticDifficulty) + logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); + else + logger.Warn(() => $"[{connection.ConnectionId}] Requesting static difficulty of {staticDiff.Value} (Request has been ignored and instead used as 'initial difficulty' for varDiff)"); } - + + // send initial difficulty + await connection.NotifyAsync(KaspaStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + // send intial job await SendJob(connection, context, currentJobParams); } @@ -214,7 +246,18 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // submit var share = await manager.SubmitShareAsync(connection, requestParams, ct); - await connection.RespondAsync(true, request.Id); + + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(true, request.Id); + + if(context.IsNicehash || manager.ValidateIsGoldShell(context.UserAgent)) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + await connection.RespondAsync(response); // publish messageBus.SendMessage(share); @@ -222,7 +265,7 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); - logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * KaspaConstants.ShareMultiplier, 3)}"); + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * coin.ShareMultiplier, 3)}"); // update pool stats if(share.IsBlockCandidate) @@ -259,7 +302,11 @@ protected virtual async Task OnNewJobAsync(object[] jobParams) await Guard(() => ForEachMinerAsync(async (connection, ct) => { var context = connection.ContextAs(); - + + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await connection.NotifyAsync(KaspaStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await SendJob(connection, context, currentJobParams); })); } @@ -282,9 +329,6 @@ private async Task SendJob(StratumConnection connection, KaspaWorkerContext cont jobParams[3], }; } - - // send difficulty - await connection.NotifyAsync(KaspaStratumMethods.SetDifficulty, new object[] { context.Difficulty }); // send job await connection.NotifyAsync(KaspaStratumMethods.MiningNotify, jobParamsActual); @@ -292,13 +336,13 @@ private async Task SendJob(StratumConnection connection, KaspaWorkerContext cont public override double HashrateFromShares(double shares, double interval) { - var multiplier = KaspaConstants.Pow2xDiff1TargetNumZero * (double) KaspaConstants.MinHash; + var multiplier = coin.HashrateMultiplier; var result = shares * multiplier / interval; return result; } - public override double ShareMultiplier => KaspaConstants.ShareMultiplier; + public override double ShareMultiplier => coin.ShareMultiplier; #region Overrides @@ -422,6 +466,9 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, if(context.ApplyPendingDifficulty()) { + // send difficulty + await connection.NotifyAsync(KaspaStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + // send job await SendJob(connection, context, currentJobParams); } diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs b/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs index b32b92a8f..b08fb382e 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs @@ -6,6 +6,8 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Miningcore.Configuration; +using Miningcore.Contracts; using Miningcore.Crypto; using Miningcore.Crypto.Hashing.Algorithms; using Miningcore.Util; @@ -15,8 +17,10 @@ namespace Miningcore.Blockchain.Kaspa; public static class KaspaUtils { - public static (KaspaAddressUtility, Exception) ValidateAddress(string address, string network, string coinSymbol = "KAS") + public static (KaspaAddressUtility, Exception) ValidateAddress(string address, string network, KaspaCoinTemplate coin) { + Contract.RequiresNonNull(coin); + if(string.IsNullOrEmpty(address)) return (null, new ArgumentException($"Empty address...")); @@ -44,7 +48,7 @@ public static (KaspaAddressUtility, Exception) ValidateAddress(string address, s try { - var kaspaAddressUtility = new KaspaAddressUtility(coinSymbol); + var kaspaAddressUtility = new KaspaAddressUtility(coin); kaspaAddressUtility.DecodeAddress(address, networkBech32Prefix); return (kaspaAddressUtility, null); @@ -57,7 +61,7 @@ public static (KaspaAddressUtility, Exception) ValidateAddress(string address, s public static BigInteger DifficultyToTarget(double difficulty) { - return BigInteger.Divide(KaspaConstants.Diff1Target, new BigInteger(difficulty)); + return (BigInteger) BigRational.Divide(new BigRational(KaspaConstants.Diff1Target), new BigRational(difficulty)); } public static BigInteger CalculateTarget(uint bits) @@ -89,12 +93,7 @@ public static BigInteger CalculateTarget(uint bits) public static double TargetToDifficulty(BigInteger target) { - return (double) new BigRational(KaspaConstants.Diff1Target, target); - } - - public static double DifficultyToHashrate(double diff) - { - return (double) new BigRational(BigInteger.Multiply(BigInteger.Multiply(KaspaConstants.MinHash, KaspaConstants.BigGig), new BigInteger(diff)), KaspaConstants.Diff1); + return (double) BigRational.Divide(new BigRational(KaspaConstants.Diff1Target), new BigRational(target)); } public static double BigDiffToLittle(BigInteger diff) @@ -174,16 +173,6 @@ public static uint BigToCompact(BigInteger n) return compact; } - public static double CalcWork(uint bits) - { - BigInteger difficultyNum = CompactToBig(bits); - - if (difficultyNum.Sign <= 0) - return (double) BigInteger.Zero; - - return (double) new BigRational(KaspaConstants.OneLsh256, BigInteger.Add(difficultyNum, KaspaConstants.BigOne)); - } - public static byte[] HashBlake2b(byte[] serializedScript) { IHashAlgorithm scriptHasher = new Blake2b(); @@ -345,106 +334,24 @@ public override string ToString() public class KaspaAddressUtility { public KaspaIAddress KaspaAddress { get; private set; } - public string CoinSymbol { get; private set; } + public KaspaCoinTemplate coin { get; private set; } + + private Dictionary stringsToBech32Prefixes; - public KaspaAddressUtility(string coinSymbol = "KAS") + public KaspaAddressUtility(KaspaCoinTemplate coin) { - this.CoinSymbol = coinSymbol; + Contract.RequiresNonNull(coin); - // Build address pattern based on network type and coin symbol - switch(this.CoinSymbol) - { - case "BGA": - this.stringsToBech32Prefixes = new Dictionary - { - { BugnaConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { BugnaConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { BugnaConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { BugnaConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - case "CAS": - this.stringsToBech32Prefixes = new Dictionary - { - { KaspaClassicConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { KaspaClassicConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { KaspaClassicConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { KaspaClassicConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - case "HTN": - this.stringsToBech32Prefixes = new Dictionary - { - { HoosatConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { HoosatConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { HoosatConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { HoosatConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - case "KLS": - this.stringsToBech32Prefixes = new Dictionary - { - { KarlsencoinConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { KarlsencoinConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { KarlsencoinConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { KarlsencoinConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; + this.coin = coin; - break; - case "NTL": - this.stringsToBech32Prefixes = new Dictionary - { - { NautilusConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { NautilusConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { NautilusConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { NautilusConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - case "NXL": - this.stringsToBech32Prefixes = new Dictionary - { - { NexelliaConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { NexelliaConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { NexelliaConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { NexelliaConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - case "PYI": - this.stringsToBech32Prefixes = new Dictionary - { - { PyrinConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { PyrinConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { PyrinConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { PyrinConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - case "SDR": - this.stringsToBech32Prefixes = new Dictionary - { - { SedraCoinConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { SedraCoinConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { SedraCoinConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { SedraCoinConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - default: - this.stringsToBech32Prefixes = new Dictionary - { - { KaspaConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, - { KaspaConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, - { KaspaConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, - { KaspaConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, - }; - - break; - } + // Build address pattern based on network type and KaspaCoinTemplate + this.stringsToBech32Prefixes = new Dictionary + { + { this.coin.AddressBech32Prefix, KaspaBech32Prefix.KaspaMain }, + { this.coin.AddressBech32PrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { this.coin.AddressBech32PrefixSimnet, KaspaBech32Prefix.KaspaSim }, + { this.coin.AddressBech32PrefixTestnet, KaspaBech32Prefix.KaspaTest } + }; } public string EncodeAddress(KaspaBech32Prefix prefix, byte[] payload, byte version) @@ -499,8 +406,6 @@ public string PrefixToString(KaspaBech32Prefix prefix) return string.Empty; } - - private Dictionary stringsToBech32Prefixes; } public static class KaspaBech32 diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaWorkerContext.cs b/src/Miningcore/Blockchain/Kaspa/KaspaWorkerContext.cs index 4dbeee595..2ea64056e 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaWorkerContext.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaWorkerContext.cs @@ -7,12 +7,12 @@ public class KaspaWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Unique value assigned per worker diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletd.cs b/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletd.cs index db044a7bc..13aa68389 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletd.cs +++ b/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletd.cs @@ -78,14 +78,31 @@ static KaspawalletdReflection() { "Lkthc3BhV2FsbGV0ZC5TZW5kUmVzcG9uc2UiABI/CgRTaWduEhkuS2FzcGFX", "YWxsZXRkLlNpZ25SZXF1ZXN0GhouS2FzcGFXYWxsZXRkLlNpZ25SZXNwb25z", "ZSIAQiuqAihNaW5pbmdjb3JlLkJsb2NrY2hhaW4uS2FzcGEuS2FzcGFXYWxs", - "ZXRkYgZwcm90bzM=")); + "ZXN0GiMua2FzcGF3YWxsZXRkLlNob3dBZGRyZXNzZXNSZXNwb25zZSIAElEK", + "Ck5ld0FkZHJlc3MSHy5rYXNwYXdhbGxldGQuTmV3QWRkcmVzc1JlcXVlc3Qa", + "IC5rYXNwYXdhbGxldGQuTmV3QWRkcmVzc1Jlc3BvbnNlIgASSwoIU2h1dGRv", + "d24SHS5rYXNwYXdhbGxldGQuU2h1dGRvd25SZXF1ZXN0Gh4ua2FzcGF3YWxs", + "ZXRkLlNodXRkb3duUmVzcG9uc2UiABJOCglCcm9hZGNhc3QSHi5rYXNwYXdh", + "bGxldGQuQnJvYWRjYXN0UmVxdWVzdBofLmthc3Bhd2FsbGV0ZC5Ccm9hZGNh", + "c3RSZXNwb25zZSIAElkKFEJyb2FkY2FzdFJlcGxhY2VtZW50Eh4ua2FzcGF3", + "YWxsZXRkLkJyb2FkY2FzdFJlcXVlc3QaHy5rYXNwYXdhbGxldGQuQnJvYWRj", + "YXN0UmVzcG9uc2UiABI/CgRTZW5kEhkua2FzcGF3YWxsZXRkLlNlbmRSZXF1", + "ZXN0Ghoua2FzcGF3YWxsZXRkLlNlbmRSZXNwb25zZSIAEj8KBFNpZ24SGS5r", + "YXNwYXdhbGxldGQuU2lnblJlcXVlc3QaGi5rYXNwYXdhbGxldGQuU2lnblJl", + "c3BvbnNlIgASUQoKR2V0VmVyc2lvbhIfLmthc3Bhd2FsbGV0ZC5HZXRWZXJz", + "aW9uUmVxdWVzdBogLmthc3Bhd2FsbGV0ZC5HZXRWZXJzaW9uUmVzcG9uc2Ui", + "ABJICgdCdW1wRmVlEhwua2FzcGF3YWxsZXRkLkJ1bXBGZWVSZXF1ZXN0Gh0u", + "a2FzcGF3YWxsZXRkLkJ1bXBGZWVSZXNwb25zZSIAQltaNGdpdGh1Yi5jb20v", + "a2FzcGFuZXQva2FzcGFkL2NtZC9rYXNwYXdhbGxldC9kYWVtb24vcGKqAiJH", + "cnBjR3JlZXRlckNsaWVudC5SUEMuS2FzcGFXYWxsZXRkYgZwcm90bzM=")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetBalanceRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetBalanceRequest.Parser, null, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetBalanceResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetBalanceResponse.Parser, new[]{ "Available", "Pending", "AddressBalances" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.AddressBalances), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.AddressBalances.Parser, new[]{ "Address", "Available", "Pending" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.CreateUnsignedTransactionsRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.CreateUnsignedTransactionsRequest.Parser, new[]{ "Address", "Amount", "From", "UseExistingChangeAddress", "IsSendAll" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy.Parser, new[]{ "MaxFeeRate", "ExactFeeRate", "MaxFee" }, new[]{ "FeePolicy" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.CreateUnsignedTransactionsRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.CreateUnsignedTransactionsRequest.Parser, new[]{ "Address", "Amount", "From", "UseExistingChangeAddress", "IsSendAll", "FeePolicy" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.CreateUnsignedTransactionsResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.CreateUnsignedTransactionsResponse.Parser, new[]{ "UnsignedTransactions" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.ShowAddressesRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.ShowAddressesRequest.Parser, null, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.ShowAddressesResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.ShowAddressesResponse.Parser, new[]{ "Address" }, null, null, null, null), @@ -101,10 +118,14 @@ static KaspawalletdReflection() { new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.UtxoEntry), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.UtxoEntry.Parser, new[]{ "Amount", "ScriptPublicKey", "BlockDaaScore", "IsCoinbase" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetExternalSpendableUTXOsRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetExternalSpendableUTXOsRequest.Parser, new[]{ "Address" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetExternalSpendableUTXOsResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetExternalSpendableUTXOsResponse.Parser, new[]{ "Entries" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SendRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SendRequest.Parser, new[]{ "ToAddress", "Amount", "Password", "From", "UseExistingChangeAddress", "IsSendAll" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SendRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SendRequest.Parser, new[]{ "ToAddress", "Amount", "Password", "From", "UseExistingChangeAddress", "IsSendAll", "FeePolicy" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SendResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SendResponse.Parser, new[]{ "TxIDs", "SignedTransactions" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignRequest.Parser, new[]{ "UnsignedTransactions", "Password" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignResponse.Parser, new[]{ "SignedTransactions" }, null, null, null, null) + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignResponse.Parser, new[]{ "SignedTransactions" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest.Parser, null, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionResponse.Parser, new[]{ "Version" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest.Parser, new[]{ "Password", "From", "UseExistingChangeAddress", "FeePolicy", "TxID" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeResponse), global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeResponse.Parser, new[]{ "Transactions", "TxIDs" }, null, null, null, null) })); } #endregion @@ -574,6 +595,267 @@ public void MergeFrom(pb::CodedInputStream input) { } + public sealed partial class FeePolicy : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new FeePolicy()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Miningcore.Blockchain.Kaspa.KaspaWalletd.KaspawalletdReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FeePolicy() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FeePolicy(FeePolicy other) : this() { + switch (other.FeePolicyCase) { + case FeePolicyOneofCase.MaxFeeRate: + MaxFeeRate = other.MaxFeeRate; + break; + case FeePolicyOneofCase.ExactFeeRate: + ExactFeeRate = other.ExactFeeRate; + break; + case FeePolicyOneofCase.MaxFee: + MaxFee = other.MaxFee; + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FeePolicy Clone() { + return new FeePolicy(this); + } + + /// Field number for the "maxFeeRate" field. + public const int MaxFeeRateFieldNumber = 6; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public double MaxFeeRate { + get { return HasMaxFeeRate ? (double) feePolicy_ : 0D; } + set { + feePolicy_ = value; + feePolicyCase_ = FeePolicyOneofCase.MaxFeeRate; + } + } + + /// Gets whether the "maxFeeRate" field is set + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool HasMaxFeeRate { + get { return feePolicyCase_ == FeePolicyOneofCase.MaxFeeRate; } + } + + /// Clears the value of the oneof if it's currently set to "maxFeeRate" + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearMaxFeeRate() { + if (HasMaxFeeRate) { + ClearFeePolicy(); + } + } + + /// Field number for the "exactFeeRate" field. + public const int ExactFeeRateFieldNumber = 7; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public double ExactFeeRate { + get { return HasExactFeeRate ? (double) feePolicy_ : 0D; } + set { + feePolicy_ = value; + feePolicyCase_ = FeePolicyOneofCase.ExactFeeRate; + } + } + + /// Gets whether the "exactFeeRate" field is set + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool HasExactFeeRate { + get { return feePolicyCase_ == FeePolicyOneofCase.ExactFeeRate; } + } + + /// Clears the value of the oneof if it's currently set to "exactFeeRate" + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearExactFeeRate() { + if (HasExactFeeRate) { + ClearFeePolicy(); + } + } + + /// Field number for the "maxFee" field. + public const int MaxFeeFieldNumber = 8; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong MaxFee { + get { return HasMaxFee ? (ulong) feePolicy_ : 0UL; } + set { + feePolicy_ = value; + feePolicyCase_ = FeePolicyOneofCase.MaxFee; + } + } + + /// Gets whether the "maxFee" field is set + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool HasMaxFee { + get { return feePolicyCase_ == FeePolicyOneofCase.MaxFee; } + } + + /// Clears the value of the oneof if it's currently set to "maxFee" + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearMaxFee() { + if (HasMaxFee) { + ClearFeePolicy(); + } + } + + private object feePolicy_; + /// Enum of possible cases for the "feePolicy" oneof. + public enum FeePolicyOneofCase { + None = 0, + MaxFeeRate = 6, + ExactFeeRate = 7, + MaxFee = 8, + } + private FeePolicyOneofCase feePolicyCase_ = FeePolicyOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public FeePolicyOneofCase FeePolicyCase { + get { return feePolicyCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearFeePolicy() { + feePolicyCase_ = FeePolicyOneofCase.None; + feePolicy_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as FeePolicy); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(FeePolicy other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(MaxFeeRate, other.MaxFeeRate)) return false; + if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ExactFeeRate, other.ExactFeeRate)) return false; + if (MaxFee != other.MaxFee) return false; + if (FeePolicyCase != other.FeePolicyCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (HasMaxFeeRate) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(MaxFeeRate); + if (HasExactFeeRate) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ExactFeeRate); + if (HasMaxFee) hash ^= MaxFee.GetHashCode(); + hash ^= (int) feePolicyCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (HasMaxFeeRate) { + output.WriteRawTag(49); + output.WriteDouble(MaxFeeRate); + } + if (HasExactFeeRate) { + output.WriteRawTag(57); + output.WriteDouble(ExactFeeRate); + } + if (HasMaxFee) { + output.WriteRawTag(64); + output.WriteUInt64(MaxFee); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (HasMaxFeeRate) { + size += 1 + 8; + } + if (HasExactFeeRate) { + size += 1 + 8; + } + if (HasMaxFee) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(MaxFee); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(FeePolicy other) { + if (other == null) { + return; + } + switch (other.FeePolicyCase) { + case FeePolicyOneofCase.MaxFeeRate: + MaxFeeRate = other.MaxFeeRate; + break; + case FeePolicyOneofCase.ExactFeeRate: + ExactFeeRate = other.ExactFeeRate; + break; + case FeePolicyOneofCase.MaxFee: + MaxFee = other.MaxFee; + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 49: { + MaxFeeRate = input.ReadDouble(); + break; + } + case 57: { + ExactFeeRate = input.ReadDouble(); + break; + } + case 64: { + MaxFee = input.ReadUInt64(); + break; + } + } + } + } + + } + public sealed partial class CreateUnsignedTransactionsRequest : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CreateUnsignedTransactionsRequest()); private pb::UnknownFieldSet _unknownFields; @@ -604,6 +886,7 @@ public CreateUnsignedTransactionsRequest(CreateUnsignedTransactionsRequest other from_ = other.from_.Clone(); useExistingChangeAddress_ = other.useExistingChangeAddress_; isSendAll_ = other.isSendAll_; + feePolicy_ = other.feePolicy_ != null ? other.feePolicy_.Clone() : null; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -666,6 +949,17 @@ public bool IsSendAll { } } + /// Field number for the "feePolicy" field. + public const int FeePolicyFieldNumber = 6; + private global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy feePolicy_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy FeePolicy { + get { return feePolicy_; } + set { + feePolicy_ = value; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override bool Equals(object other) { return Equals(other as CreateUnsignedTransactionsRequest); @@ -684,6 +978,7 @@ public bool Equals(CreateUnsignedTransactionsRequest other) { if(!from_.Equals(other.from_)) return false; if (UseExistingChangeAddress != other.UseExistingChangeAddress) return false; if (IsSendAll != other.IsSendAll) return false; + if (!object.Equals(FeePolicy, other.FeePolicy)) return false; return Equals(_unknownFields, other._unknownFields); } @@ -695,6 +990,7 @@ public override int GetHashCode() { hash ^= from_.GetHashCode(); if (UseExistingChangeAddress != false) hash ^= UseExistingChangeAddress.GetHashCode(); if (IsSendAll != false) hash ^= IsSendAll.GetHashCode(); + if (feePolicy_ != null) hash ^= FeePolicy.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -725,6 +1021,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(40); output.WriteBool(IsSendAll); } + if (feePolicy_ != null) { + output.WriteRawTag(50); + output.WriteMessage(FeePolicy); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -746,6 +1046,9 @@ public int CalculateSize() { if (IsSendAll != false) { size += 1 + 1; } + if (feePolicy_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(FeePolicy); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -770,6 +1073,12 @@ public void MergeFrom(CreateUnsignedTransactionsRequest other) { if (other.IsSendAll != false) { IsSendAll = other.IsSendAll; } + if (other.feePolicy_ != null) { + if (feePolicy_ == null) { + FeePolicy = new global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy(); + } + FeePolicy.MergeFrom(other.FeePolicy); + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -801,6 +1110,13 @@ public void MergeFrom(pb::CodedInputStream input) { IsSendAll = input.ReadBool(); break; } + case 50: { + if (feePolicy_ == null) { + FeePolicy = new global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy(); + } + input.ReadMessage(FeePolicy); + break; + } } } } @@ -2866,6 +3182,7 @@ public SendRequest(SendRequest other) : this() { from_ = other.from_.Clone(); useExistingChangeAddress_ = other.useExistingChangeAddress_; isSendAll_ = other.isSendAll_; + feePolicy_ = other.feePolicy_ != null ? other.feePolicy_.Clone() : null; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -2939,6 +3256,17 @@ public bool IsSendAll { } } + /// Field number for the "feePolicy" field. + public const int FeePolicyFieldNumber = 7; + private global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy feePolicy_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy FeePolicy { + get { return feePolicy_; } + set { + feePolicy_ = value; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override bool Equals(object other) { return Equals(other as SendRequest); @@ -2958,6 +3286,7 @@ public bool Equals(SendRequest other) { if(!from_.Equals(other.from_)) return false; if (UseExistingChangeAddress != other.UseExistingChangeAddress) return false; if (IsSendAll != other.IsSendAll) return false; + if (!object.Equals(FeePolicy, other.FeePolicy)) return false; return Equals(_unknownFields, other._unknownFields); } @@ -2970,6 +3299,7 @@ public override int GetHashCode() { hash ^= from_.GetHashCode(); if (UseExistingChangeAddress != false) hash ^= UseExistingChangeAddress.GetHashCode(); if (IsSendAll != false) hash ^= IsSendAll.GetHashCode(); + if (feePolicy_ != null) hash ^= FeePolicy.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -3004,6 +3334,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(48); output.WriteBool(IsSendAll); } + if (feePolicy_ != null) { + output.WriteRawTag(58); + output.WriteMessage(FeePolicy); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -3028,6 +3362,9 @@ public int CalculateSize() { if (IsSendAll != false) { size += 1 + 1; } + if (feePolicy_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(FeePolicy); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -3055,6 +3392,12 @@ public void MergeFrom(SendRequest other) { if (other.IsSendAll != false) { IsSendAll = other.IsSendAll; } + if (other.feePolicy_ != null) { + if (feePolicy_ == null) { + FeePolicy = new global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy(); + } + FeePolicy.MergeFrom(other.FeePolicy); + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -3090,6 +3433,13 @@ public void MergeFrom(pb::CodedInputStream input) { IsSendAll = input.ReadBool(); break; } + case 58: { + if (feePolicy_ == null) { + FeePolicy = new global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy(); + } + input.ReadMessage(FeePolicy); + break; + } } } } @@ -3510,6 +3860,619 @@ public void MergeFrom(pb::CodedInputStream input) { } + public sealed partial class GetVersionRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetVersionRequest()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Miningcore.Blockchain.Kaspa.KaspaWalletd.KaspawalletdReflection.Descriptor.MessageTypes[24]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public GetVersionRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public GetVersionRequest(GetVersionRequest other) : this() { + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public GetVersionRequest Clone() { + return new GetVersionRequest(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as GetVersionRequest); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(GetVersionRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(GetVersionRequest other) { + if (other == null) { + return; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + } + } + } + + } + + public sealed partial class GetVersionResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GetVersionResponse()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Miningcore.Blockchain.Kaspa.KaspaWalletd.KaspawalletdReflection.Descriptor.MessageTypes[25]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public GetVersionResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public GetVersionResponse(GetVersionResponse other) : this() { + version_ = other.version_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public GetVersionResponse Clone() { + return new GetVersionResponse(this); + } + + /// Field number for the "version" field. + public const int VersionFieldNumber = 1; + private string version_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + + public string Version { + get { return version_; } + set { + version_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as GetVersionResponse); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(GetVersionResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Version != other.Version) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Version.Length != 0) hash ^= Version.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Version.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Version); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Version.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Version); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(GetVersionResponse other) { + if (other == null) { + return; + } + if (other.Version.Length != 0) { + Version = other.Version; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Version = input.ReadString(); + break; + } + } + } + } + + } + + public sealed partial class BumpFeeRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BumpFeeRequest()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Miningcore.Blockchain.Kaspa.KaspaWalletd.KaspawalletdReflection.Descriptor.MessageTypes[26]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BumpFeeRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BumpFeeRequest(BumpFeeRequest other) : this() { + password_ = other.password_; + from_ = other.from_.Clone(); + useExistingChangeAddress_ = other.useExistingChangeAddress_; + feePolicy_ = other.feePolicy_ != null ? other.feePolicy_.Clone() : null; + txID_ = other.txID_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BumpFeeRequest Clone() { + return new BumpFeeRequest(this); + } + + /// Field number for the "password" field. + public const int PasswordFieldNumber = 1; + private string password_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Password { + get { return password_; } + set { + password_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "from" field. + public const int FromFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_from_codec + = pb::FieldCodec.ForString(18); + private readonly pbc::RepeatedField from_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField From { + get { return from_; } + } + + /// Field number for the "useExistingChangeAddress" field. + public const int UseExistingChangeAddressFieldNumber = 3; + private bool useExistingChangeAddress_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool UseExistingChangeAddress { + get { return useExistingChangeAddress_; } + set { + useExistingChangeAddress_ = value; + } + } + + /// Field number for the "feePolicy" field. + public const int FeePolicyFieldNumber = 4; + private global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy feePolicy_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy FeePolicy { + get { return feePolicy_; } + set { + feePolicy_ = value; + } + } + + /// Field number for the "txID" field. + public const int TxIDFieldNumber = 5; + private string txID_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string TxID { + get { return txID_; } + set { + txID_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as BumpFeeRequest); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + + public bool Equals(BumpFeeRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Password != other.Password) return false; + if(!from_.Equals(other.from_)) return false; + if (UseExistingChangeAddress != other.UseExistingChangeAddress) return false; + if (!object.Equals(FeePolicy, other.FeePolicy)) return false; + if (TxID != other.TxID) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Password.Length != 0) hash ^= Password.GetHashCode(); + hash ^= from_.GetHashCode(); + if (UseExistingChangeAddress != false) hash ^= UseExistingChangeAddress.GetHashCode(); + if (feePolicy_ != null) hash ^= FeePolicy.GetHashCode(); + if (TxID.Length != 0) hash ^= TxID.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Password.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Password); + } + from_.WriteTo(output, _repeated_from_codec); + if (UseExistingChangeAddress != false) { + output.WriteRawTag(24); + output.WriteBool(UseExistingChangeAddress); + } + if (feePolicy_ != null) { + output.WriteRawTag(34); + output.WriteMessage(FeePolicy); + } + if (TxID.Length != 0) { + output.WriteRawTag(42); + output.WriteString(TxID); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Password.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Password); + } + size += from_.CalculateSize(_repeated_from_codec); + if (UseExistingChangeAddress != false) { + size += 1 + 1; + } + if (feePolicy_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(FeePolicy); + } + if (TxID.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TxID); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(BumpFeeRequest other) { + if (other == null) { + return; + } + if (other.Password.Length != 0) { + Password = other.Password; + } + from_.Add(other.from_); + if (other.UseExistingChangeAddress != false) { + UseExistingChangeAddress = other.UseExistingChangeAddress; + } + if (other.feePolicy_ != null) { + if (feePolicy_ == null) { + FeePolicy = new global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy(); + } + FeePolicy.MergeFrom(other.FeePolicy); + } + if (other.TxID.Length != 0) { + TxID = other.TxID; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Password = input.ReadString(); + break; + } + case 18: { + from_.AddEntriesFrom(input, _repeated_from_codec); + break; + } + case 24: { + UseExistingChangeAddress = input.ReadBool(); + break; + } + case 34: { + if (feePolicy_ == null) { + FeePolicy = new global::Miningcore.Blockchain.Kaspa.KaspaWalletd.FeePolicy(); + } + input.ReadMessage(FeePolicy); + break; + } + case 42: { + TxID = input.ReadString(); + break; + } + } + } + } + + } + + public sealed partial class BumpFeeResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new BumpFeeResponse()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Miningcore.Blockchain.Kaspa.KaspaWalletd.KaspawalletdReflection.Descriptor.MessageTypes[27]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BumpFeeResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BumpFeeResponse(BumpFeeResponse other) : this() { + transactions_ = other.transactions_.Clone(); + txIDs_ = other.txIDs_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public BumpFeeResponse Clone() { + return new BumpFeeResponse(this); + } + + /// Field number for the "transactions" field. + public const int TransactionsFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_transactions_codec + = pb::FieldCodec.ForBytes(10); + private readonly pbc::RepeatedField transactions_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Transactions { + get { return transactions_; } + } + + /// Field number for the "txIDs" field. + public const int TxIDsFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_txIDs_codec + = pb::FieldCodec.ForString(18); + private readonly pbc::RepeatedField txIDs_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + + public pbc::RepeatedField TxIDs { + get { return txIDs_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as BumpFeeResponse); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(BumpFeeResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!transactions_.Equals(other.transactions_)) return false; + if(!txIDs_.Equals(other.txIDs_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= transactions_.GetHashCode(); + hash ^= txIDs_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + transactions_.WriteTo(output, _repeated_transactions_codec); + txIDs_.WriteTo(output, _repeated_txIDs_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += transactions_.CalculateSize(_repeated_transactions_codec); + size += txIDs_.CalculateSize(_repeated_txIDs_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(BumpFeeResponse other) { + if (other == null) { + return; + } + transactions_.Add(other.transactions_); + txIDs_.Add(other.txIDs_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + transactions_.AddEntriesFrom(input, _repeated_transactions_codec); + break; + } + case 18: { + txIDs_.AddEntriesFrom(input, _repeated_txIDs_codec); + break; + } + } + } + } + + } + #endregion } diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletdGrpc.cs b/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletdGrpc.cs index a477c24b8..ae893e331 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletdGrpc.cs +++ b/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/KaspaWalletdGrpc.cs @@ -77,6 +77,20 @@ public KaspaWalletdRPC(string __ServiceName) "Sign", __Marshaller_KaspaWalletdRPC_SignRequest, __Marshaller_KaspaWalletdRPC_SignResponse); + + this.__Method_GetVersion = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "GetVersion", + __Marshaller_kaspawalletd_GetVersionRequest, + __Marshaller_kaspawalletd_GetVersionResponse); + + this.__Method_BumpFee = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "BumpFee", + __Marshaller_kaspawalletd_BumpFeeRequest, + __Marshaller_kaspawalletd_BumpFeeResponse); } [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] @@ -148,6 +162,14 @@ static T __Helper_DeserializeMessage(grpc::DeserializationContext context, gl static readonly grpc::Marshaller __Marshaller_KaspaWalletdRPC_SignRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignRequest.Parser)); [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] static readonly grpc::Marshaller __Marshaller_KaspaWalletdRPC_SignResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Miningcore.Blockchain.Kaspa.KaspaWalletd.SignResponse.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_kaspawalletd_GetVersionRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_kaspawalletd_GetVersionResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionResponse.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_kaspawalletd_BumpFeeRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_kaspawalletd_BumpFeeResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeResponse.Parser)); [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] public grpc::Method __Method_GetBalance { get; private set; } @@ -176,6 +198,12 @@ static T __Helper_DeserializeMessage(grpc::DeserializationContext context, gl [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] public grpc::Method __Method_Sign { get; private set; } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public grpc::Method __Method_GetVersion { get; private set; } + + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public grpc::Method __Method_BumpFee { get; private set; } + /// Service descriptor public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor { @@ -448,6 +476,46 @@ protected KaspaWalletdRPCClient(ClientBaseConfiguration configuration) : base(co { return CallInvoker.AsyncUnaryCall(__KaspaWalletdRPC.__Method_Sign, null, options, request); } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionResponse GetVersion(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return GetVersion(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionResponse GetVersion(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__KaspaWalletdRPC.__Method_GetVersion, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall GetVersionAsync(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return GetVersionAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall GetVersionAsync(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.GetVersionRequest request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__KaspaWalletdRPC.__Method_GetVersion, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeResponse BumpFee(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return BumpFee(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeResponse BumpFee(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__KaspaWalletdRPC.__Method_BumpFee, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall BumpFeeAsync(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return BumpFeeAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall BumpFeeAsync(global::Miningcore.Blockchain.Kaspa.KaspaWalletd.BumpFeeRequest request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__KaspaWalletdRPC.__Method_BumpFee, null, options, request); + } /// Creates a new instance of client from given ClientBaseConfiguration. [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] protected override KaspaWalletdRPCClient NewInstance(ClientBaseConfiguration configuration) diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/kaspawalletd.proto b/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/kaspawalletd.proto index ca665aa6a..5668871a5 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/kaspawalletd.proto +++ b/src/Miningcore/Blockchain/Kaspa/RPC/KaspaWalletd/kaspawalletd.proto @@ -15,6 +15,8 @@ service kaspawalletd { rpc Send(SendRequest) returns (SendResponse) {} // Since SignRequest contains a password - this command should only be used on a trusted or secure connection rpc Sign(SignRequest) returns (SignResponse) {} + rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {} + rpc BumpFee(BumpFeeRequest) returns (BumpFeeResponse) {} } message GetBalanceRequest { @@ -32,12 +34,21 @@ message AddressBalances { uint64 pending = 3; } +message FeePolicy { + oneof feePolicy { + double maxFeeRate = 6; + double exactFeeRate = 7; + uint64 maxFee = 8; + } +} + message CreateUnsignedTransactionsRequest { string address = 1; uint64 amount = 2; repeated string from = 3; bool useExistingChangeAddress = 4; bool isSendAll = 5; + FeePolicy feePolicy = 6; } message CreateUnsignedTransactionsResponse { @@ -111,6 +122,7 @@ message SendRequest{ repeated string from = 4; bool useExistingChangeAddress = 5; bool isSendAll = 6; + FeePolicy feePolicy = 7; } message SendResponse{ @@ -127,3 +139,20 @@ message SignRequest{ message SignResponse{ repeated bytes signedTransactions = 1; } + +message GetVersionRequest {} + +message GetVersionResponse { string version = 1; } + +message BumpFeeRequest { + string password = 1; + repeated string from = 2; + bool useExistingChangeAddress = 3; + FeePolicy feePolicy = 4; + string txID = 5; +} + +message BumpFeeResponse { + repeated bytes transactions = 1; + repeated string txIDs = 2; +} diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/P2P.cs b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/P2P.cs index c5adf4cdc..c2ea71f6a 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/P2P.cs +++ b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/P2P.cs @@ -145,7 +145,7 @@ static P2PReflection() { new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.AddressesMessage), global::Miningcore.Blockchain.Kaspa.Kaspad.AddressesMessage.Parser, new[]{ "AddressList" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.NetAddress), global::Miningcore.Blockchain.Kaspa.Kaspad.NetAddress.Parser, new[]{ "Timestamp", "Ip", "Port" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.SubnetworkId), global::Miningcore.Blockchain.Kaspa.Kaspad.SubnetworkId.Parser, new[]{ "Bytes" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionMessage), global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionMessage.Parser, new[]{ "Version", "Inputs", "Outputs", "LockTime", "SubnetworkId", "Gas", "Payload" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionMessage), global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionMessage.Parser, new[]{ "Version", "Inputs", "Outputs", "LockTime", "SubnetworkId", "Gas", "Payload", "Mass" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionInput), global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionInput.Parser, new[]{ "PreviousOutpoint", "SignatureScript", "Sequence", "SigOpCount" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.Outpoint), global::Miningcore.Blockchain.Kaspa.Kaspad.Outpoint.Parser, new[]{ "TransactionId", "Index" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionId), global::Miningcore.Blockchain.Kaspa.Kaspad.TransactionId.Parser, new[]{ "Bytes" }, null, null, null, null), @@ -837,6 +837,7 @@ public TransactionMessage(TransactionMessage other) : this() { subnetworkId_ = other.subnetworkId_ != null ? other.subnetworkId_.Clone() : null; gas_ = other.gas_; payload_ = other.payload_; + mass_ = other.mass_; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -920,6 +921,17 @@ public ulong Gas { } } + /// Field number for the "mass" field. + public const int MassFieldNumber = 9; + private ulong mass_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong Mass { + get { return mass_; } + set { + mass_ = value; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override bool Equals(object other) { return Equals(other as TransactionMessage); @@ -940,6 +952,7 @@ public bool Equals(TransactionMessage other) { if (!object.Equals(SubnetworkId, other.SubnetworkId)) return false; if (Gas != other.Gas) return false; if (Payload != other.Payload) return false; + if (Mass != other.Mass) return false; return Equals(_unknownFields, other._unknownFields); } @@ -953,6 +966,7 @@ public override int GetHashCode() { if (subnetworkId_ != null) hash ^= SubnetworkId.GetHashCode(); if (Gas != 0UL) hash ^= Gas.GetHashCode(); if (Payload.Length != 0) hash ^= Payload.GetHashCode(); + if (Mass != 0UL) hash ^= Mass.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -988,6 +1002,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(66); output.WriteBytes(Payload); } + if (Mass != 0UL) { + output.WriteRawTag(72); + output.WriteUInt64(Mass); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -1013,6 +1031,9 @@ public int CalculateSize() { if (Payload.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeBytesSize(Payload); } + if (Mass != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Mass); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -1044,6 +1065,9 @@ public void MergeFrom(TransactionMessage other) { if (other.Payload.Length != 0) { Payload = other.Payload; } + if (other.Mass != 0UL) { + Mass = other.Mass; + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -1086,6 +1110,10 @@ public void MergeFrom(pb::CodedInputStream input) { Payload = input.ReadBytes(); break; } + case 72: { + Mass = input.ReadUInt64(); + break; + } } } } diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/Rpc.cs b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/Rpc.cs index 219093eed..7f73fd763 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/Rpc.cs +++ b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/Rpc.cs @@ -262,7 +262,7 @@ static RpcReflection() { new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcBlockHeader), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcBlockHeader.Parser, new[]{ "Version", "Parents", "HashMerkleRoot", "AcceptedIdMerkleRoot", "UtxoCommitment", "Timestamp", "Bits", "Nonce", "DaaScore", "BlueWork", "PruningPoint", "BlueScore" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcBlockLevelParents), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcBlockLevelParents.Parser, new[]{ "ParentHashes" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcBlockVerboseData), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcBlockVerboseData.Parser, new[]{ "Hash", "Difficulty", "SelectedParentHash", "TransactionIds", "IsHeaderOnly", "BlueScore", "ChildrenHashes", "MergeSetBluesHashes", "MergeSetRedsHashes", "IsChainBlock" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransaction), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransaction.Parser, new[]{ "Version", "Inputs", "Outputs", "LockTime", "SubnetworkId", "Gas", "Payload", "VerboseData" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransaction), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransaction.Parser, new[]{ "Version", "Inputs", "Outputs", "LockTime", "SubnetworkId", "Gas", "Payload", "VerboseData", "Mass" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransactionInput), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransactionInput.Parser, new[]{ "PreviousOutpoint", "SignatureScript", "Sequence", "SigOpCount", "VerboseData" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcScriptPublicKey), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcScriptPublicKey.Parser, new[]{ "Version", "ScriptPublicKey" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransactionOutput), global::Miningcore.Blockchain.Kaspa.Kaspad.RpcTransactionOutput.Parser, new[]{ "Amount", "ScriptPublicKey", "VerboseData" }, null, null, null, null), @@ -1626,6 +1626,7 @@ public RpcTransaction(RpcTransaction other) : this() { gas_ = other.gas_; payload_ = other.payload_; verboseData_ = other.verboseData_ != null ? other.verboseData_.Clone() : null; + mass_ = other.mass_; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -1720,6 +1721,17 @@ public string Payload { } } + /// Field number for the "mass" field. + public const int MassFieldNumber = 10; + private ulong mass_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong Mass { + get { return mass_; } + set { + mass_ = value; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override bool Equals(object other) { return Equals(other as RpcTransaction); @@ -1741,6 +1753,7 @@ public bool Equals(RpcTransaction other) { if (Gas != other.Gas) return false; if (Payload != other.Payload) return false; if (!object.Equals(VerboseData, other.VerboseData)) return false; + if (Mass != other.Mass) return false; return Equals(_unknownFields, other._unknownFields); } @@ -1755,6 +1768,7 @@ public override int GetHashCode() { if (Gas != 0UL) hash ^= Gas.GetHashCode(); if (Payload.Length != 0) hash ^= Payload.GetHashCode(); if (verboseData_ != null) hash ^= VerboseData.GetHashCode(); + if (Mass != 0UL) hash ^= Mass.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -1794,6 +1808,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(74); output.WriteMessage(VerboseData); } + if (Mass != 0UL) { + output.WriteRawTag(80); + output.WriteUInt64(Mass); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -1822,6 +1840,9 @@ public int CalculateSize() { if (verboseData_ != null) { size += 1 + pb::CodedOutputStream.ComputeMessageSize(VerboseData); } + if (Mass != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Mass); + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -1856,6 +1877,9 @@ public void MergeFrom(RpcTransaction other) { } VerboseData.MergeFrom(other.VerboseData); } + if (other.Mass != 0UL) { + Mass = other.Mass; + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -1902,6 +1926,10 @@ public void MergeFrom(pb::CodedInputStream input) { input.ReadMessage(VerboseData); break; } + case 80: { + Mass = input.ReadUInt64(); + break; + } } } } diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/p2p.proto b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/p2p.proto index 6527082d9..5f8bec520 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/p2p.proto +++ b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/p2p.proto @@ -31,6 +31,7 @@ message TransactionMessage{ SubnetworkId subnetworkId = 5; uint64 gas = 6; bytes payload = 8; + uint64 mass = 9; } message TransactionInput{ diff --git a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/rpc.proto b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/rpc.proto index 91eeb6f5a..67ca4a402 100644 --- a/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/rpc.proto +++ b/src/Miningcore/Blockchain/Kaspa/RPC/Kaspad/rpc.proto @@ -67,6 +67,7 @@ message RpcTransaction { uint64 gas = 6; string payload = 8; RpcTransactionVerboseData verboseData = 9; + uint64 mass = 10; } message RpcTransactionInput { diff --git a/src/Miningcore/Blockchain/Progpow/ProgpowWorkerContext.cs b/src/Miningcore/Blockchain/Progpow/ProgpowWorkerContext.cs index fbf439aee..478224d8e 100644 --- a/src/Miningcore/Blockchain/Progpow/ProgpowWorkerContext.cs +++ b/src/Miningcore/Blockchain/Progpow/ProgpowWorkerContext.cs @@ -7,12 +7,12 @@ public class ProgpowWorkerContext : WorkerContextBase /// /// Usually a wallet address /// - public string Miner { get; set; } + public override string Miner { get; set; } /// /// Arbitrary worker identififer for miners using multiple rigs /// - public string Worker { get; set; } + public override string Worker { get; set; } /// /// Unique value assigned per worker diff --git a/src/Miningcore/Blockchain/Warthog/Configuration/WarthogDaemonEndpointConfigExtra.cs b/src/Miningcore/Blockchain/Warthog/Configuration/WarthogDaemonEndpointConfigExtra.cs new file mode 100644 index 000000000..5dca5a3be --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/Configuration/WarthogDaemonEndpointConfigExtra.cs @@ -0,0 +1,19 @@ +namespace Miningcore.Blockchain.Warthog.Configuration; + +public class WarthogDaemonEndpointConfigExtra +{ + /// + /// Optional port for streaming WebSocket data + /// + public int? PortWs { get; set; } + + /// + /// Optional http-path for streaming WebSocket data + /// + public string HttpPathWs { get; set; } + + /// + /// Optional: Use SSL to for daemon websocket streaming + /// + public bool SslWs { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/Configuration/WarthogPaymentProcessingConfigExtra.cs b/src/Miningcore/Blockchain/Warthog/Configuration/WarthogPaymentProcessingConfigExtra.cs new file mode 100644 index 000000000..a184825cd --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/Configuration/WarthogPaymentProcessingConfigExtra.cs @@ -0,0 +1,33 @@ +namespace Miningcore.Blockchain.Warthog.Configuration; + +public class WarthogPaymentProcessingConfigExtra +{ + /// + /// DANGER: The privateKey of your wallet is a very sensitive data, since it can be used for restoring the wallet, please keep it very private at all cost + /// MANDATORY for sending payments + /// + public string WalletPrivateKey { get; set; } + + /// + /// Maximum amount you're willing to pay for transaction fees (in UNIT) + /// Default: 1 + /// + public decimal? MaximumTransactionFees { get; set; } + + /// + /// True to exempt transaction fees from miner rewards + /// + public bool KeepTransactionFees { get; set; } + + /// + /// Minimum block confirmations + /// Default: "Mainnet" (120) - "Testnet" (110) + /// + public int? MinimumConfirmations { get; set; } + + /// + /// Maximum number of payouts which can be done in parallel + /// Default: 2 + /// + public int? MaxDegreeOfParallelPayouts { get; set; } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/Configuration/WarthogPoolConfigExtra.cs b/src/Miningcore/Blockchain/Warthog/Configuration/WarthogPoolConfigExtra.cs new file mode 100644 index 000000000..4b1bcc168 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/Configuration/WarthogPoolConfigExtra.cs @@ -0,0 +1,14 @@ +using Miningcore.Configuration; + +namespace Miningcore.Blockchain.Warthog.Configuration; + +public class WarthogPoolConfigExtra +{ + /// + /// Maximum number of tracked jobs. + /// Default: 4 + /// + public int? MaxActiveJobs { get; set; } + + public int? ExtraNonce1Size { get; set; } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/DaemonRequests/SendTransactionRequest.cs b/src/Miningcore/Blockchain/Warthog/DaemonRequests/SendTransactionRequest.cs new file mode 100644 index 000000000..9bc660d56 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonRequests/SendTransactionRequest.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonRequests; + +public class WarthogSendTransactionRequest +{ + [JsonPropertyName("pinHeight")] + public uint PinHeight { get; set; } + + [JsonPropertyName("nonceId")] + public uint NonceId { get; set; } + + [JsonPropertyName("toAddr")] + public string ToAddress { get; set; } + + [JsonPropertyName("amountE8")] + public ulong Amount { get; set; } + + [JsonPropertyName("feeE8")] + public ulong Fee { get; set; } + + [JsonPropertyName("signature65")] + public string Signature { get; set; } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/DaemonRequests/SubmitBlockRequest.cs b/src/Miningcore/Blockchain/Warthog/DaemonRequests/SubmitBlockRequest.cs new file mode 100644 index 000000000..9ef9a3e77 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonRequests/SubmitBlockRequest.cs @@ -0,0 +1,8 @@ +namespace Miningcore.Blockchain.Warthog.DaemonRequests; + +public class WarthogSubmitBlockRequest +{ + public uint Height { get; set; } + public string Header { get; set; } + public string Body { get; set; } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBalance.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBalance.cs new file mode 100644 index 000000000..054bcabf5 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBalance.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogBalanceData +{ + [JsonPropertyName("accountId")] + public ulong AccountId { get; set; } + + public string Address { get; set; } + + [JsonPropertyName("balanceE8")] + public ulong Balance { get; set; } +} + +public class WarthogBalance : WarthogResponseBase +{ + public WarthogBalanceData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockResponse.cs new file mode 100644 index 000000000..366ae4f23 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockResponse.cs @@ -0,0 +1,85 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogBlockDataBodyBlockReward +{ + [JsonPropertyName("amountE8")] + public ulong Amount { get; set; } + + [JsonPropertyName("toAddress")] + public string ToAddress { get; set; } + + /// + /// Publically searchable transaction hash + /// + [JsonPropertyName("txHash")] + public string TxHash { get; set; } +} + +public class WarthogBlockDataBody +{ + [JsonPropertyName("rewards")] + public WarthogBlockDataBodyBlockReward[] BlockReward { get; set; } +} + +public class WarthogBlockDataTransaction +{ + [JsonPropertyName("amountE8")] + public ulong Amount { get; set; } + + [JsonPropertyName("feeE8")] + public ulong Fee { get; set; } + + [JsonPropertyName("fromAddress")] + public string FromAddress { get; set; } + + [JsonPropertyName("nonceId")] + public uint NonceId { get; set; } + + [JsonPropertyName("pinHeight")] + public uint PinHeight { get; set; } + + [JsonPropertyName("toAddress")] + public string ToAddress { get; set; } + + /// + /// Publically searchable transaction hash + /// + [JsonPropertyName("txHash")] + public string TxHash { get; set; } +} + +public class WarthogBlockDataHeader +{ + public double Difficulty { get; set; } + public string Hash { get; set; } + + [JsonPropertyName("merkleroot")] + public string MerkleRoot { get; set; } + + public string Nonce { get; set; } + + [JsonPropertyName("prevHash")] + public string PrevHash { get; set; } + + public string Raw { get; set; } + public string Target { get; set; } + public ulong Timestamp { get; set; } + public string Version { get; set; } +} + +public class WarthogBlockData +{ + public WarthogBlockDataBody Body { get; set; } + public WarthogBlockDataTransaction[] Transaction { get; set; } + public ulong confirmations { get; set; } + public WarthogBlockDataHeader Header { get; set; } + public uint Height { get; set; } + public ulong timestamp { get; set; } +} + +public class WarthogBlock : WarthogResponseBase +{ + public WarthogBlockData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockTemplateResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockTemplateResponse.cs new file mode 100644 index 000000000..bec3b419e --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetBlockTemplateResponse.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogBlockTemplateData +{ + [JsonPropertyName("blockRewardE8")] + public ulong BlockReward { get; set; } + + public string Body { get; set; } + public double Difficulty { get; set; } + public string Header { get; set; } + public uint Height { get; set; } + + [JsonPropertyName("merklePrefix")] + public string MerklePrefix { get; set; } + + public bool Synced { get; set; } + public bool Testnet { get; set; } + + [JsonPropertyName("totalTxFeeE8")] + public ulong TotalTxFee { get; set; } +} + +public class WarthogBlockTemplate : WarthogResponseBase +{ + public WarthogBlockTemplateData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetChainInfoResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetChainInfoResponse.cs new file mode 100644 index 000000000..7035b3d32 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetChainInfoResponse.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class GetChainInfoData +{ + public double Difficulty { get; set; } + public string Hash { get; set; } + public uint Height { get; set; } + + [JsonPropertyName("is_janushash")] + public bool IsJanusHash { get; set; } + + [JsonPropertyName("pinHash")] + public string PinHash { get; set; } + + [JsonPropertyName("pinHeight")] + public uint PinHeight { get; set; } + + public bool Synced { get; set; } + public double Worksum { get; set; } + + [JsonPropertyName("worksumHex")] + public string WorksumHex { get; set; } +} + +public class GetChainInfoResponse : WarthogResponseBase +{ + public GetChainInfoData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetFeeE8EncodedResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetFeeE8EncodedResponse.cs new file mode 100644 index 000000000..a6d53f108 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetFeeE8EncodedResponse.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogFeeE8EncodedData +{ + [JsonPropertyName("roundedE8")] + public ulong Rounded { get; set; } +} + +public class WarthogFeeE8EncodedResponse : WarthogResponseBase +{ + public WarthogFeeE8EncodedData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetNetworkHashrateResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetNetworkHashrateResponse.cs new file mode 100644 index 000000000..8bbd52cdc --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetNetworkHashrateResponse.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class GetNetworkHashrateData +{ + [JsonPropertyName("lastNBlocksEstimate")] + public double Hashrate { get; set; } +} + +public class GetNetworkHashrateResponse : WarthogResponseBase +{ + public GetNetworkHashrateData Data { get; set; } +} + diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetPeersResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetPeersResponse.cs new file mode 100644 index 000000000..cc967da1b --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetPeersResponse.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class GetPeersChain +{ + [JsonPropertyName("length")] + public ulong Height { get; set; } +} + +public class GetPeersResponse +{ + public GetPeersChain Chain { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetWalletResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetWalletResponse.cs new file mode 100644 index 000000000..6ee793a2d --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/GetWalletResponse.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogWalletData +{ + public string Address { get; set; } + + [JsonPropertyName("privKey")] + public string PrivateKey { get; set; } + + [JsonPropertyName("pubKey")] + public string PublicKey { get; set; } +} + +public class WarthogWalletResponse : WarthogResponseBase +{ + public WarthogWalletData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/SendTransactionResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/SendTransactionResponse.cs new file mode 100644 index 000000000..152708f69 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/SendTransactionResponse.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogSendTransactionData +{ + [JsonPropertyName("txHash")] + public string TxHash { get; set; } +} + +public class WarthogSendTransactionResponse : WarthogResponseBase +{ + public WarthogSendTransactionData Data { get; set; } +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/SubmitBlockResponse.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/SubmitBlockResponse.cs new file mode 100644 index 000000000..60c77582f --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/SubmitBlockResponse.cs @@ -0,0 +1,5 @@ +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogSubmitBlockResponse : WarthogResponseBase +{ +} diff --git a/src/Miningcore/Blockchain/Warthog/DaemonResponses/WarthogResponseBase.cs b/src/Miningcore/Blockchain/Warthog/DaemonResponses/WarthogResponseBase.cs new file mode 100644 index 000000000..15afa3042 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/DaemonResponses/WarthogResponseBase.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Miningcore.Blockchain.Warthog.DaemonResponses; + +public class WarthogResponseBase +{ + [JsonProperty("code", NullValueHandling = NullValueHandling.Ignore)] + public short? Code { get; set; } + + [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)] + public string Error { get; set; } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogConstants.cs b/src/Miningcore/Blockchain/Warthog/WarthogConstants.cs new file mode 100644 index 000000000..a4158cb28 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogConstants.cs @@ -0,0 +1,88 @@ +// ReSharper disable InconsistentNaming + +namespace Miningcore.Blockchain.Warthog; + +public static class WarthogConstants +{ + public const string JanusMiner = "janusminer"; + + public const int ExtranoncePlaceHolderLength = 10; + public static int NonceLength = 8; + public const int TimeTolerance = 600; // in seconds + + // https://www.warthog.network/docs/developers/integrations/pools/stratum/#miningset_difficulty + // In contrast to Bitcoin's stratum protocol the target is just the inverse of the difficulty. In Bitcoin there is an additional factor of 2^32 involved for historical reasons. + // Since Warthog was written from scratch, it does not carry this historical burden. This means the miner must meet the target 1/difficulty to mine a share. + public static readonly double Diff1 = 1; + + public const byte GenesisDifficultyExponent = 32; + public const uint HardestTargetHost = 0xFF800000u; + public const uint GenesisTargetHost = (uint)(GenesisDifficultyExponent << 24) | 0x00FFFFFFu; + public const uint JanusHashMaxTargetHost = 0xe00fffffu; + public const byte JanusHashMinDiffExponent = 22; + public static readonly double Pow2x1 = Math.Pow(2, 1); + public const uint JanusHashMinTargetHost = (uint)(JanusHashMinDiffExponent << 24) | 0x003FFFFFu; + + public const uint NewMerkleRootBlockHeight = 900000; + public const uint JanusHashRetargetBlockHeight = 745200; + public const uint JanusHashV2RetargetBlockHeight = 769680; + public const uint JanusHashV3RetargetBlockHeight = 776880; + public const uint JanusHashV4RetargetBlockHeight = 809280; + public const uint JanusHashV5RetargetBlockHeight = 855000; + public const uint JanusHashV6RetargetBlockHeight = 879500; + public const uint JanusHashV7RetargetBlockHeight = 987000; + + public static readonly WarthogCustomFloat ProofOfBalancedWorkC = new WarthogCustomFloat(-7, 2748779069, true); // 0.005 + public static readonly WarthogCustomFloat ProofOfBalancedWorkExponent = new WarthogCustomFloat(0, 3006477107, true); // = 0.7 <-- this can be decreased if necessary + + public const byte HeaderOffsetPrevHash = 0; + public const byte HeaderOffsetTarget = 32; + public const byte HeaderOffsetMerkleRoot = 36; + public const byte HeaderOffsetVersion = 68; + public const byte HeaderOffsetTimestamp = 72; + public const byte HeaderOffsetNonce = 76; + public const byte HeaderByteSize = 80; + + // WART smallest unit is called UNIT: https://github.com/warthog-network/Warthog/blob/master/src/shared/src/general/params.hpp#L5 + public const decimal SmallestUnit = 100000000; + + // Amount in UNIT + public const decimal MinimumTransactionFees = 1; + + public const byte PinHeightNonceIdFeeByteSize = 19; + public const byte Zero = 0; + public const byte ToAddressOffset = 20; + public const byte AmountByteSize = 8; + public const byte FullSignatureByteSize = 65; +} + +public static class WarthogCommands +{ + public const string DaemonName = "wart-node"; + public const string DataLabel = ":data:"; + + public const string Websocket = "/ws/chain_delta"; + public const string WebsocketEventBlockAppend = "blockAppend"; + public const string WebsocketEventRollback = "rollback"; + + public const string GetChainInfo = "/chain/head"; + public const string GetPeers = "/peers/connected"; + public const string GetNetworkHashrate = "/chain/hashrate/" + DataLabel; + public const string GetBlockTemplate = "/chain/mine/" + DataLabel; + + public const string SubmitBlock = "/chain/append"; + + public const string GetBlockByHeight = "/chain/block/" + DataLabel; + + public const string GetWallet = "/tools/wallet/from_privkey/" + DataLabel; + public const string GetBalance = "/account/" + DataLabel + "/balance"; + public const string GetTransaction = "/transaction/lookup/" + DataLabel; + public const string SendTransaction = "/transaction/add"; + public const string GetFeeE8Encoded = "/tools/encode16bit/from_e8/" + DataLabel; +} + +public enum WarthogNetworkType +{ + Testnet, + Mainnet, +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogExtraNonceProvider.cs b/src/Miningcore/Blockchain/Warthog/WarthogExtraNonceProvider.cs new file mode 100644 index 000000000..ffc736f8e --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogExtraNonceProvider.cs @@ -0,0 +1,8 @@ +namespace Miningcore.Blockchain.Warthog; + +public class WarthogExtraNonceProvider : ExtraNonceProviderBase +{ + public WarthogExtraNonceProvider(string poolId, int size, byte? clusterInstanceId) : base(poolId, size, clusterInstanceId) + { + } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogJob.cs b/src/Miningcore/Blockchain/Warthog/WarthogJob.cs new file mode 100644 index 000000000..8136af906 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogJob.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Linq; +using System.Text; +using VeruscoinConstants = Miningcore.Blockchain.Equihash.VeruscoinConstants; +using Miningcore.Blockchain.Warthog.DaemonResponses; +using Miningcore.Contracts; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Algorithms; +using Miningcore.Extensions; +using Miningcore.Native; +using Miningcore.Stratum; +using Miningcore.Time; + +namespace Miningcore.Blockchain.Warthog; + +public class WarthogJob +{ + protected IMasterClock clock; + protected readonly IHashAlgorithm sha256S = new Sha256S(); + protected readonly IHashAlgorithm sha256D = new Sha256D(); + protected readonly Verushash verusHash = new Verushash(); + + protected readonly ConcurrentDictionary submissions = new(StringComparer.OrdinalIgnoreCase); + protected WarthogTarget blockTargetValue; + protected byte[] prevHashBytes; + protected byte[] merklePrefixBytes; + //protected byte[] merkleRootBytes; + protected byte[] versionBytes; + protected byte[] nBitsBytes; + protected byte[] nTimeBytes; + + protected object[] jobParams; + + #region API-Surface + + public WarthogBlockTemplate BlockTemplate { get; protected set; } + public double Difficulty { get; protected set; } + public WarthogNetworkType network { get; protected set; } + public bool IsJanusHash { get; protected set; } + + public string JobId { get; protected set; } + + protected virtual byte[] SerializeHeader(byte[] extraNonce, uint nTime, uint nonce) + { + var merkleRootBytes = SerializeMerkleRoot(extraNonce); + + using(var stream = new MemoryStream(WarthogConstants.HeaderByteSize)) + { + var bw = new BinaryWriter(stream); + + bw.Write(prevHashBytes); + bw.Write(nBitsBytes); + bw.Write(merkleRootBytes); + bw.Write(versionBytes); + bw.Write((BitConverter.IsLittleEndian ? BitConverter.GetBytes(nTime).ReverseInPlace() : BitConverter.GetBytes(nTime))); // wart-node expects a big endian format. + bw.Write((BitConverter.IsLittleEndian ? BitConverter.GetBytes(nonce).ReverseInPlace() : BitConverter.GetBytes(nonce))); // wart-node expects a big endian format. + + return stream.ToArray(); + } + } + + protected virtual byte[] SerializeMerkleRoot(byte[] extraNonce) + { + // Merkle root computation + Span merklePrefixExtraNonceBytes = stackalloc byte[merklePrefixBytes.Length + extraNonce.Length]; + merklePrefixBytes.CopyTo(merklePrefixExtraNonceBytes); + extraNonce.CopyTo(merklePrefixExtraNonceBytes[merklePrefixBytes.Length..]); + + Span merkleRootBytes = stackalloc byte[32]; + sha256S.Digest(merklePrefixExtraNonceBytes, merkleRootBytes); + + return merkleRootBytes.ToArray(); + } + + protected virtual byte[] SerializeExtranonce(string extraNonce1, string extraNonce2) + { + var extraNonce1Bytes = extraNonce1.HexToByteArray(); + var extraNonce2Bytes = extraNonce2.HexToByteArray(); + + Span extraNonceBytes = stackalloc byte[extraNonce1Bytes.Length + extraNonce2Bytes.Length]; + extraNonce1Bytes.CopyTo(extraNonceBytes); + extraNonce2Bytes.CopyTo(extraNonceBytes[extraNonce1Bytes.Length..]); + + return extraNonceBytes.ToArray(); + } + + protected virtual byte[] SerializeBody(byte[] extraNonce) + { + var bodyBytes = BlockTemplate.Data.Body.HexToByteArray(); + var bodyBytesLength = bodyBytes.Length; + bodyBytes = bodyBytes.Skip(extraNonce.Length).Take(bodyBytesLength - extraNonce.Length).ToArray(); + + Span extraNonceBodyBytes = stackalloc byte[extraNonce.Length + bodyBytes.Length]; + extraNonce.CopyTo(extraNonceBodyBytes); + bodyBytes.CopyTo(extraNonceBodyBytes[extraNonce.Length..]); + + return extraNonceBodyBytes.ToArray(); + } + + protected virtual (Share Share, string HeaderHex) ProcessShareInternal( + StratumConnection worker, string extraNonce2, uint nTime, uint nonce) + { + var context = worker.ContextAs(); + var extraNonceBytes = SerializeExtranonce(context.ExtraNonce1, extraNonce2); + var headerSolutionBytes = SerializeHeader(extraNonceBytes, nTime, nonce); + + WarthogCustomFloat headerSolutionValue; + bool isBlockCandidate; + uint version = uint.Parse(versionBytes.ToHexString(), NumberStyles.HexNumber); + string verusHashVersion = version > 2 ? VeruscoinConstants.HashVersion2b2o : VeruscoinConstants.HashVersion2b1; + + // hash block-header with sha256D + Span headerSolutionSha256D = stackalloc byte[32]; + sha256D.Digest(headerSolutionBytes, headerSolutionSha256D); + + // hash block-header with sha256S + Span headerSolutionSha256T = stackalloc byte[32]; + sha256S.Digest(headerSolutionSha256D, headerSolutionSha256T); + + WarthogCustomFloat sha256TFloat = new WarthogCustomFloat(headerSolutionSha256T); + + // hash block-header with Verushash + Span headerSolutionVerusHash = stackalloc byte[32]; + verusHash.Digest(headerSolutionBytes, headerSolutionVerusHash, verusHashVersion); + + WarthogCustomFloat verusFloat = new WarthogCustomFloat(headerSolutionVerusHash); + + // I know the following looks incredibly overwhelming but it's the harsh reality about WARTHOG. And CODE is LAW, so we must follow it. + // https://github.com/warthog-network/Warthog/blob/master/src/shared/src/block/header/view.cpp + // Testnet + if(network == WarthogNetworkType.Testnet) + { + // The Sha256t hash must not be too small. We will adjust that if better miner(s) are available + if(sha256TFloat < WarthogConstants.ProofOfBalancedWorkC) + sha256TFloat = WarthogConstants.ProofOfBalancedWorkC; + + headerSolutionValue = verusFloat * WarthogCustomFloat.Pow(sha256TFloat, WarthogConstants.ProofOfBalancedWorkExponent); + + // check if the share meets the much harder block difficulty (block candidate) + isBlockCandidate = headerSolutionValue < blockTargetValue; + } + // Mainnet + else + { + // JanusHash activated + if(IsJanusHash && BlockTemplate.Data.Height > WarthogConstants.JanusHashRetargetBlockHeight) + { + // new JanusHash + if(BlockTemplate.Data.Height > WarthogConstants.JanusHashV2RetargetBlockHeight) + { + if(BlockTemplate.Data.Height > WarthogConstants.JanusHashV6RetargetBlockHeight) + { + // The Sha256t hash must not be too small. We will adjust that if better miner(s) are available + if(sha256TFloat < WarthogConstants.ProofOfBalancedWorkC) + sha256TFloat = WarthogConstants.ProofOfBalancedWorkC; + } + + headerSolutionValue = verusFloat * WarthogCustomFloat.Pow(sha256TFloat, WarthogConstants.ProofOfBalancedWorkExponent); + } + // old JanusHash + else + headerSolutionValue = verusFloat * sha256TFloat; + + // check if the share meets the much harder block difficulty (block candidate) + isBlockCandidate = headerSolutionValue < blockTargetValue; + } + // JanusHash not activated + else + { + headerSolutionValue = verusFloat * WarthogCustomFloat.Pow(sha256TFloat, WarthogConstants.ProofOfBalancedWorkExponent); + + // check if the share meets the much harder block difficulty (block candidate) + isBlockCandidate = headerSolutionSha256D < blockTargetValue; + } + } + + // Miner must meet the target "1/difficulty" or "1/janush_number" to mine a share - https://www.warthog.network/docs/developers/integrations/pools/stratum/#notable-differences-from-bitcoins-stratum-protocol-1 + // calc share-diff + var shareDiff = WarthogConstants.Diff1 / (double)headerSolutionValue; + + //throw new StratumException(StratumError.LowDifficultyShare, $"nonce: {nonce} - headerSolutionBytes: {headerSolutionBytes.ToHexString()} - headerSolutionVerusHash: {headerSolutionVerusHash.ToHexString()} - proofOfBalancedWorkC: {(double)WarthogConstants.ProofOfBalancedWorkC} - ProofOfBalancedWorkExponent: {(double)WarthogConstants.ProofOfBalancedWorkExponent} - sha256TFloat: {(double)sha256TFloat} - verusFloat: {(double)verusFloat} - CalculateHashrate: {WarthogUtils.CalculateHashrate(sha256TFloat, verusFloat)} ||| headerSolutionValue: {(double)headerSolutionValue} - exponent => (wcf: [{headerSolutionValue._exponent}, {(uint)(headerSolutionValue._exponent < 0 ? -headerSolutionValue._exponent : headerSolutionValue._exponent)}]), mantissa => (wcf: {headerSolutionValue._mantissa}) [stratum: {new WarthogTarget(context.Difficulty, IsJanusHash).data} - exponent => (wt: {new WarthogTarget(context.Difficulty, IsJanusHash).Zeros10()}), mantissa => (wt: {(new WarthogTarget(context.Difficulty, IsJanusHash).Bits22() << 10)}) - validShare: {(headerSolutionValue < new WarthogTarget(context.Difficulty, IsJanusHash))} - blockTemplate: {blockTargetValue.data} - exponent => (wt: {blockTargetValue.Zeros10()}), mantissa => (wt: {(blockTargetValue.Bits22() << 10)}) - blockCandidate: {isBlockCandidate}] ||| shareDiff: {shareDiff} [stratum: {context.Difficulty} - blockTemplate: {Difficulty}]"); + + var stratumDifficulty = context.Difficulty; + var ratio = shareDiff / stratumDifficulty; + + // test if share meets at least workers current difficulty + if(!isBlockCandidate && ratio < 0.99) + { + // check if share matched the previous difficulty from before a vardiff retarget + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + { + ratio = shareDiff / context.PreviousDifficulty.Value; + + if(ratio < 0.99) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + + // use previous difficulty + stratumDifficulty = context.PreviousDifficulty.Value; + } + + else + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + } + + var result = new Share + { + BlockHeight = BlockTemplate.Data.Height, + NetworkDifficulty = Difficulty, + Difficulty = stratumDifficulty, + }; + + if(isBlockCandidate) + { + result.IsBlockCandidate = true; + + result.BlockHash = headerSolutionSha256D.ToHexString(); + + var headerHex = headerSolutionBytes.ToHexString(); + + var extranonceBodyBytes = SerializeBody(extraNonceBytes); + BlockTemplate.Data.Body = extranonceBodyBytes.ToHexString(); + + return (result, headerHex); + } + + return (result, null); + } + + protected virtual byte[] SerializeBlock(byte[] header) + { + var height = BlockTemplate.Data.Height; + var bodyBytes = BlockTemplate.Data.Body.HexToByteArray(); + + using(var stream = new MemoryStream()) + { + var bw = new BinaryWriter(stream); + + bw.Write(height); + bw.Write(header); + bw.Write(bodyBytes); + + return stream.ToArray(); + } + } + + public virtual void Init(WarthogBlockTemplate blockTemplate, string jobId, IMasterClock clock, WarthogNetworkType network, bool isJanusHash) + { + Contract.RequiresNonNull(blockTemplate); + Contract.RequiresNonNull(clock); + Contract.RequiresNonNull(network); + Contract.Requires(!string.IsNullOrEmpty(jobId)); + + this.clock = clock; + BlockTemplate = blockTemplate; + JobId = jobId; + + this.network = network; + IsJanusHash = isJanusHash; + + Difficulty = BlockTemplate.Data.Difficulty; + + var headerBytes = BlockTemplate.Data.Header.HexToByteArray(); + prevHashBytes = headerBytes.Take(WarthogConstants.HeaderOffsetTarget - WarthogConstants.HeaderOffsetPrevHash).ToArray(); + nBitsBytes = headerBytes.Skip(WarthogConstants.HeaderOffsetTarget).Take(WarthogConstants.HeaderOffsetMerkleRoot - WarthogConstants.HeaderOffsetTarget).ToArray(); + //merkleRootBytes = headerBytes.Skip(WarthogConstants.HeaderOffsetMerkleRoot).Take(WarthogConstants.HeaderOffsetVersion - WarthogConstants.HeaderOffsetMerkleRoot).ToArray(); + versionBytes = headerBytes.Skip(WarthogConstants.HeaderOffsetVersion).Take(WarthogConstants.HeaderOffsetTimestamp - WarthogConstants.HeaderOffsetVersion).ToArray(); + nTimeBytes = headerBytes.Skip(WarthogConstants.HeaderOffsetTimestamp).Take(WarthogConstants.HeaderOffsetNonce - WarthogConstants.HeaderOffsetTimestamp).ToArray(); + + merklePrefixBytes = BlockTemplate.Data.MerklePrefix.HexToByteArray(); + + blockTargetValue = new WarthogTarget(Difficulty, IsJanusHash); + + jobParams = new object[] + { + JobId, + prevHashBytes.ToHexString(), + BlockTemplate.Data.MerklePrefix, + //uint.Parse(versionBytes.ToHexString(), NumberStyles.HexNumber), + versionBytes.ToHexString(), + nBitsBytes.ToHexString(), + nTimeBytes.ToHexString(), + false + }; + } + + public virtual object GetJobParams(bool isNew) + { + jobParams[^1] = isNew; + return jobParams; + } + + protected virtual bool RegisterSubmit(string extraNonce1, string extraNonce2, string nTime, string nonce) + { + var key = new StringBuilder() + .Append(extraNonce1) + .Append(extraNonce2) // lowercase as we don't want to accept case-sensitive values as valid. + .Append(nTime) + .Append(nonce) // lowercase as we don't want to accept case-sensitive values as valid. + .ToString(); + + return submissions.TryAdd(key, true); + } + + public virtual (Share Share, string HeaderHex) ProcessShare(StratumConnection worker, + string extraNonce2, string nTime, string nonce) + { + Contract.Requires(!string.IsNullOrEmpty(extraNonce2)); + Contract.Requires(!string.IsNullOrEmpty(nTime)); + Contract.Requires(!string.IsNullOrEmpty(nonce)); + + var context = worker.ContextAs(); + + // validate nTime + if(nTime.Length != 8) + throw new StratumException(StratumError.Other, "incorrect size of ntime"); + + var nTimeInt = uint.Parse(nTime, NumberStyles.HexNumber); + if(nTimeInt < uint.Parse(nTimeBytes.ToHexString(), NumberStyles.HexNumber) || nTimeInt > ((DateTimeOffset) clock.Now).ToUnixTimeSeconds() + WarthogConstants.TimeTolerance) + throw new StratumException(StratumError.Other, "ntime out of range"); + + // validate nonce + if(nonce.Length != WarthogConstants.NonceLength) + throw new StratumException(StratumError.Other, "incorrect size of nonce"); + + var nonceInt = uint.Parse(nonce, NumberStyles.HexNumber); + + // dupe check + if(!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce)) + throw new StratumException(StratumError.DuplicateShare, "duplicate"); + + return ProcessShareInternal(worker, extraNonce2, nTimeInt, nonceInt); + } + + #endregion // API-Surface +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogJobManager.cs b/src/Miningcore/Blockchain/Warthog/WarthogJobManager.cs new file mode 100644 index 000000000..644f6d5a9 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogJobManager.cs @@ -0,0 +1,588 @@ +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Text; +using Autofac; +using Miningcore.Blockchain.Warthog.Configuration; +using Miningcore.Blockchain.Warthog.DaemonRequests; +using Miningcore.Blockchain.Warthog.DaemonResponses; +using Miningcore.Configuration; +using Miningcore.Extensions; +using Miningcore.JsonRpc; +using Miningcore.Messaging; +using Miningcore.Mining; +using Miningcore.Native; +using Miningcore.Notifications.Messages; +using Miningcore.Rest; +using Miningcore.Rpc; +using Miningcore.Stratum; +using Miningcore.Time; +using Miningcore.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; +using Contract = Miningcore.Contracts.Contract; +using static Miningcore.Util.ActionUtils; + +namespace Miningcore.Blockchain.Warthog; + +public class WarthogJobManager : JobManagerBase +{ + public WarthogJobManager( + IComponentContext ctx, + IMasterClock clock, + IHttpClientFactory httpClientFactory, + IMessageBus messageBus, + IExtraNonceProvider extraNonceProvider) : + base(ctx, messageBus) + { + Contract.RequiresNonNull(ctx); + Contract.RequiresNonNull(clock); + Contract.RequiresNonNull(messageBus); + Contract.RequiresNonNull(extraNonceProvider); + + this.clock = clock; + this.extraNonceProvider = extraNonceProvider; + this.httpClientFactory = httpClientFactory; + } + + private WarthogCoinTemplate coin; + private DaemonEndpointConfig[] daemonEndpoints; + private IHttpClientFactory httpClientFactory; + private SimpleRestClient restClient; + private RpcClient rpc; + private WarthogNetworkType network; + private readonly List validJobs = new(); + private readonly IExtraNonceProvider extraNonceProvider; + private readonly IMasterClock clock; + private WarthogPoolConfigExtra extraPoolConfig; + private WarthogPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; + protected int maxActiveJobs; + protected bool isJanusHash; + + protected async Task UpdateJob(CancellationToken ct, string via = null) + { + try + { + var blockTemplate = await restClient.Get(WarthogCommands.GetBlockTemplate.Replace(WarthogCommands.DataLabel, poolConfig.Address), ct); + if(blockTemplate?.Error != null) + return false; + + var job = currentJob; + var isNew = currentJob == null || + job.BlockTemplate.Data.Height < blockTemplate.Data.Height || + job.BlockTemplate.Data.Header != blockTemplate.Data.Header; + + if(isNew) + { + messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Data.Height, poolConfig.Template); + + // update job + job = new WarthogJob(); + job.Init(blockTemplate, NextJobId(), clock, network, isJanusHash); + + lock(jobLock) + { + validJobs.Insert(0, job); + + // trim active jobs + while(validJobs.Count > maxActiveJobs) + validJobs.RemoveAt(validJobs.Count - 1); + } + + if(via != null) + logger.Info(() => $"Detected new block {blockTemplate.Data.Height} [{via}]"); + else + logger.Info(() => $"Detected new block {blockTemplate.Data.Height}"); + + // update stats + if (job.BlockTemplate.Data.Height > BlockchainStats.BlockHeight) + { + BlockchainStats.LastNetworkBlockTime = clock.Now; + BlockchainStats.BlockHeight = (ulong) job.BlockTemplate.Data.Height; + BlockchainStats.NetworkDifficulty = job.Difficulty; + } + + currentJob = job; + } + + else + { + if(via != null) + logger.Debug(() => $"Template update {blockTemplate.Data.Height} [{via}]"); + else + logger.Debug(() => $"Template update {blockTemplate.Data.Height}"); + } + + return isNew; + } + + catch(OperationCanceledException) + { + // ignored + } + + catch(Exception ex) + { + logger.Error(ex, () => $"Error during {nameof(UpdateJob)}"); + } + + return false; + } + + protected object GetJobParamsForStratum(bool isNew) + { + var job = currentJob; + return job?.GetJobParams(isNew); + } + + private async Task UpdateNetworkStatsAsync(CancellationToken ct) + { + try + { + var responseHashrate = await restClient.Get(WarthogCommands.GetNetworkHashrate.Replace(WarthogCommands.DataLabel, "300"), ct); + if(responseHashrate?.Code == 0) + BlockchainStats.NetworkHashrate = responseHashrate.Data.Hashrate; + + var responsePeers = await restClient.Get(WarthogCommands.GetPeers, ct); + BlockchainStats.ConnectedPeers = responsePeers.Length; + } + + catch(Exception e) + { + logger.Error(e); + } + } + + protected async Task SubmitBlockAsync(Share share, string headerHex, WarthogBlockTemplate blockTemplate, CancellationToken ct) + { + Contract.RequiresNonNull(blockTemplate); + + try + { + var block = new WarthogSubmitBlockRequest + { + Height = blockTemplate.Data.Height, + Header = headerHex, + Body = blockTemplate.Data.Body + }; + + var response = await restClient.Post(WarthogCommands.SubmitBlock, block, ct); + if(response?.Error != null) + { + logger.Warn(() => $"Block {share.BlockHeight} submission failed with: {response.Error}"); + messageBus.SendMessage(new AdminNotification("Block submission failed", $"Pool {poolConfig.Id} {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}failed to submit block {share.BlockHeight}: {response.Error}")); + return false; + } + + return response?.Code == 0; + } + + catch(Exception e) + { + logger.Error(e); + return false; + } + } + + #region API-Surface + + public IObservable Jobs { get; private set; } + public BlockchainStats BlockchainStats { get; } = new(); + + public WarthogCoinTemplate Coin => coin; + + public override void Configure(PoolConfig pc, ClusterConfig cc) + { + coin = pc.Template.As(); + + extraPoolConfig = pc.Extra.SafeExtensionDataAs(); + extraPoolPaymentProcessingConfig = pc.PaymentProcessing.Extra.SafeExtensionDataAs(); + + maxActiveJobs = extraPoolConfig?.MaxActiveJobs ?? 4; + + // extract standard daemon endpoints + daemonEndpoints = pc.Daemons + .Where(x => string.IsNullOrEmpty(x.Category)) + .ToArray(); + + base.Configure(pc, cc); + } + + public object[] GetSubscriberData(StratumConnection worker) + { + Contract.RequiresNonNull(worker); + + var context = worker.ContextAs(); + var extraNonce1Size = GetExtraNonce1Size(); + + // assign unique ExtraNonce1 to worker (miner) + context.ExtraNonce1 = extraNonceProvider.Next(); + + // setup response data + var responseData = new object[] + { + context.ExtraNonce1, + WarthogConstants.ExtranoncePlaceHolderLength - extraNonce1Size, + }; + + return responseData; + } + + public int GetExtraNonce1Size() + { + return extraPoolConfig?.ExtraNonce1Size ?? 4; + } + + public virtual async ValueTask SubmitShareAsync(StratumConnection worker, object submission, + CancellationToken ct) + { + Contract.RequiresNonNull(worker); + Contract.RequiresNonNull(submission); + + if(submission is not object[] submitParams) + throw new StratumException(StratumError.Other, "invalid params"); + + var context = worker.ContextAs(); + + // extract params + var jobId = submitParams[0] as string; + var extraNonce2 = submitParams[1] as string; + var nTime = submitParams[2] as string; + var nonce = submitParams[3] as string; + + WarthogJob job; + + lock(jobLock) + { + job = validJobs.FirstOrDefault(x => x.JobId == jobId); + } + + if(job == null) + throw new StratumException(StratumError.JobNotFound, "job not found"); + + // validate & process + var (share, headerHex) = job.ProcessShare(worker, extraNonce2, nTime, nonce); + + // enrich share with common data + share.PoolId = poolConfig.Id; + share.IpAddress = worker.RemoteEndpoint.Address.ToString(); + share.Miner = context.Miner; + share.Worker = context.Worker; + share.UserAgent = context.UserAgent; + share.Source = clusterConfig.ClusterName; + share.Created = clock.Now; + + // if block candidate, submit & check if accepted by network + if(share.IsBlockCandidate) + { + logger.Info(() => $"Submitting block {share.BlockHeight} [{share.BlockHash}][{headerHex}]"); + + var acceptResponse = await SubmitBlockAsync(share, headerHex, job.BlockTemplate, ct); + + // is it still a block candidate? + share.IsBlockCandidate = acceptResponse; + + if(share.IsBlockCandidate) + { + logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {context.Miner}"); + + OnBlockFound(); + + // persist the nonce to make block unlocking a bit more reliable + share.TransactionConfirmationData = nonce; + } + + else + { + // clear fields that no longer apply + share.TransactionConfirmationData = null; + } + } + + return share; + } + + public async Task ValidateAddressAsync(string address, CancellationToken ct) + { + if(string.IsNullOrEmpty(address)) + return false; + + try + { + var response = await restClient.Get(WarthogCommands.GetBlockTemplate.Replace(WarthogCommands.DataLabel, address), ct); + if(response?.Error != null) + { + logger.Warn(() => $"'{address}': {response.Error} (Code {response?.Code})"); + return false; + } + + return response?.Code == 0; + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName}' daemon does not seem to be running..."); + return false; + } + } + + #endregion // API-Surface + + #region Overrides + + protected override void ConfigureDaemons() + { + var jsonSerializerSettings = ctx.Resolve(); + + restClient = new SimpleRestClient(httpClientFactory, "http://" + daemonEndpoints.First().Host.ToString() + ":" + daemonEndpoints.First().Port.ToString()); + rpc = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); + } + + protected override async Task AreDaemonsHealthyAsync(CancellationToken ct) + { + logger.Debug(() => $"Checking if '{WarthogCommands.DaemonName}' daemon is healthy..."); + + // test daemon + try + { + var response = await restClient.Get(WarthogCommands.GetChainInfo, ct); + if(response?.Error != null) + { + logger.Warn(() => $"'{WarthogCommands.GetChainInfo}': {response.Error} (Code {response?.Code})"); + return false; + } + + return response?.Code == 0; + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName}' daemon does not seem to be running..."); + return false; + } + } + + protected override async Task AreDaemonsConnectedAsync(CancellationToken ct) + { + logger.Debug(() => $"Checking if '{WarthogCommands.DaemonName}' daemon is connected..."); + + try + { + var response = await restClient.Get(WarthogCommands.GetPeers, ct); + + if(network == WarthogNetworkType.Testnet) + return response?.Length >= 0; + else + return response?.Length > 0; + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName}' daemon does not seem to be running..."); + return false; + } + } + + protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) + { + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5)); + + logger.Debug(() => $"Checking if '{WarthogCommands.DaemonName}' daemon is synched..."); + + do + { + try + { + var response = await restClient.Get(WarthogCommands.GetChainInfo, ct); + if(response?.Code == null) + logger.Debug(() => $"'{WarthogCommands.DaemonName}' daemon did not responded..."); + + if(response?.Error != null) + logger.Debug(() => $"'{WarthogCommands.GetChainInfo}': {response.Error} (Code {response?.Code})"); + + if(response.Data.Synced) + { + logger.Info(() => $"'{WarthogCommands.DaemonName}' daemon synched with blockchain"); + break; + } + + logger.Info(() => $"Daemon is still syncing with network. Current height: {response?.Data.Height}. Manager will be started once synced."); + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName}' daemon does not seem to be running..."); + } + } while(await timer.WaitForNextTickAsync(ct)); + } + + protected override async Task PostStartInitAsync(CancellationToken ct) + { + // validate pool address + if(string.IsNullOrEmpty(poolConfig.Address)) + throw new PoolStartupException("Pool address is not configured", poolConfig.Id); + + // test daemon + try + { + var responseChain = await restClient.Get(WarthogCommands.GetChainInfo, ct); + if(responseChain?.Code == null) + throw new PoolStartupException("Init RPC failed...", poolConfig.Id); + + isJanusHash = responseChain.Data.IsJanusHash; + if(isJanusHash) + logger.Info(() => "JanusHash activated"); + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName} - {WarthogCommands.GetChainInfo}' daemon does not seem to be running..."); + throw new PoolStartupException("Init RPC failed...", poolConfig.Id); + } + + try + { + var responsePoolAddress = await restClient.Get(WarthogCommands.GetBlockTemplate.Replace(WarthogCommands.DataLabel, poolConfig.Address), ct); + if(responsePoolAddress?.Error != null) + throw new PoolStartupException($"Pool address '{poolConfig.Address}': {responsePoolAddress.Error} (Code {responsePoolAddress?.Code})", poolConfig.Id); + + network = responsePoolAddress.Data.Testnet ? WarthogNetworkType.Testnet : WarthogNetworkType.Mainnet; + + // update stats + BlockchainStats.RewardType = "POW"; + BlockchainStats.NetworkType = $"{network}"; + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName} - {WarthogCommands.GetBlockTemplate}' daemon does not seem to be running..."); + throw new PoolStartupException($"Pool address check failed...", poolConfig.Id); + } + + if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + { + // validate pool address privateKey + if(string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPrivateKey)) + throw new PoolStartupException("Pool address private key is not configured", poolConfig.Id); + + try + { + var responsePoolAddressWalletPrivateKey = await restClient.Get(WarthogCommands.GetWallet.Replace(WarthogCommands.DataLabel, extraPoolPaymentProcessingConfig?.WalletPrivateKey), ct); + if(responsePoolAddressWalletPrivateKey?.Error != null) + throw new PoolStartupException($"Pool address private key '{extraPoolPaymentProcessingConfig?.WalletPrivateKey}': {responsePoolAddressWalletPrivateKey.Error} (Code {responsePoolAddressWalletPrivateKey?.Code})", poolConfig.Id); + + if(responsePoolAddressWalletPrivateKey.Data.Address != poolConfig.Address) + throw new PoolStartupException($"Pool address private key '{extraPoolPaymentProcessingConfig?.WalletPrivateKey}' [{responsePoolAddressWalletPrivateKey.Data.Address}] does not match pool address: {poolConfig.Address}", poolConfig.Id); + } + + catch(Exception) + { + logger.Warn(() => $"'{WarthogCommands.DaemonName} - {WarthogCommands.GetWallet}' daemon does not seem to be running..."); + throw new PoolStartupException($"Pool address private key check failed...", poolConfig.Id); + } + } + + await UpdateNetworkStatsAsync(ct); + + // Periodically update network stats + Observable.Interval(TimeSpan.FromMinutes(1)) + .Select(via => Observable.FromAsync(() => + Guard(()=> UpdateNetworkStatsAsync(ct), + ex=> logger.Error(ex)))) + .Concat() + .Subscribe(); + + SetupJobUpdates(ct); + } + + protected virtual void SetupJobUpdates(CancellationToken ct) + { + var pollingInterval = poolConfig?.BlockRefreshInterval ?? 0; + + var blockSubmission = blockFoundSubject.Synchronize(); + var pollTimerRestart = blockFoundSubject.Synchronize(); + + var triggers = new List> + { + blockSubmission.Select(_ => (JobRefreshBy.BlockFound, (string) null)) + }; + + var endpointExtra = daemonEndpoints + .Where(x => x.Extra.SafeExtensionDataAs() != null) + .Select(x=> Tuple.Create(x, x.Extra.SafeExtensionDataAs())) + .FirstOrDefault(); + + if(endpointExtra?.Item2?.PortWs.HasValue == true) + { + var (endpointConfig, extra) = endpointExtra; + + var wsEndpointConfig = new DaemonEndpointConfig + { + Host = endpointConfig.Host, + Port = extra.PortWs!.Value, + HttpPath = extra.HttpPathWs, + Ssl = extra.SslWs + }; + + logger.Info(() => $"Subscribing to WebSocket {(wsEndpointConfig.Ssl ? "wss" : "ws")}://{wsEndpointConfig.Host}:{wsEndpointConfig.Port}"); + + // stream work updates + var getWorkObs = rpc.WebsocketSubscribe(logger, ct, wsEndpointConfig, WarthogCommands.Websocket, new[] { WarthogCommands.WebsocketEventBlockAppend }) + .Publish() + .RefCount(); + + var websocketNotify = getWorkObs.Where(x => x != null) + .Publish() + .RefCount(); + + pollTimerRestart = blockSubmission.Merge(websocketNotify.Select(_ => Unit.Default)) + .Publish() + .RefCount(); + + triggers.Add(websocketNotify.Select(_ => (JobRefreshBy.WebSocket, (string) null))); + + if(pollingInterval > 0) + { + triggers.Add(Observable.Timer(TimeSpan.FromMilliseconds(pollingInterval)) + .TakeUntil(pollTimerRestart) + .Select(_ => (JobRefreshBy.Poll, (string) null)) + .Repeat()); + } + + else + { + // get initial blocktemplate + triggers.Add(Observable.Interval(TimeSpan.FromMilliseconds(1000)) + .Select(_ => (JobRefreshBy.Initial, (string) null)) + .TakeWhile(_ => !hasInitialBlockTemplate)); + } + } + + else + { + pollingInterval = pollingInterval > 0 ? pollingInterval : 1000; + + // ordinary polling (avoid this at all cost) + triggers.Add(Observable.Timer(TimeSpan.FromMilliseconds(pollingInterval)) + .TakeUntil(pollTimerRestart) + .Select(_ => (JobRefreshBy.Poll, (string) null)) + .Repeat()); + } + + Jobs = triggers.Merge() + .Select(x => Observable.FromAsync(() => UpdateJob(ct, x.Via))) + .Concat() + .Where(x => x) + .Do(x => + { + if(x) + hasInitialBlockTemplate = true; + }) + .Select(x => GetJobParamsForStratum(x)) + .Publish() + .RefCount(); + } + + #endregion // Overrides +} diff --git a/src/Miningcore/Blockchain/Warthog/WarthogPayoutHandler.cs b/src/Miningcore/Blockchain/Warthog/WarthogPayoutHandler.cs new file mode 100644 index 000000000..cef2bc5fe --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogPayoutHandler.cs @@ -0,0 +1,492 @@ +using System; +using System.Linq; +using Autofac; +using AutoMapper; +using Miningcore.Blockchain.Warthog.Configuration; +using Miningcore.Blockchain.Warthog.DaemonRequests; +using Miningcore.Blockchain.Warthog.DaemonResponses; +using Miningcore.Configuration; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Algorithms; +using Miningcore.Extensions; +using Miningcore.Messaging; +using Miningcore.Mining; +using Miningcore.Payments; +using Miningcore.Persistence; +using Miningcore.Persistence.Model; +using Miningcore.Persistence.Repositories; +using Miningcore.Rest; +using Miningcore.Time; +using Miningcore.Util; +using NBitcoin.Secp256k1; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Block = Miningcore.Persistence.Model.Block; +using Contract = Miningcore.Contracts.Contract; +using static Miningcore.Util.ActionUtils; + +namespace Miningcore.Blockchain.Warthog; + +[CoinFamily(CoinFamily.Warthog)] +public class WarthogPayoutHandler : PayoutHandlerBase, + IPayoutHandler +{ + public WarthogPayoutHandler( + IComponentContext ctx, + IConnectionFactory cf, + IMapper mapper, + IShareRepository shareRepo, + IBlockRepository blockRepo, + IBalanceRepository balanceRepo, + IPaymentRepository paymentRepo, + IMasterClock clock, + IHttpClientFactory httpClientFactory, + IMessageBus messageBus) : + base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, messageBus) + { + Contract.RequiresNonNull(ctx); + Contract.RequiresNonNull(balanceRepo); + Contract.RequiresNonNull(paymentRepo); + + this.ctx = ctx; + this.httpClientFactory = httpClientFactory; + } + + protected readonly IComponentContext ctx; + private IHttpClientFactory httpClientFactory; + private SimpleRestClient restClient; + private string network; + private WarthogPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; + private int minConfirmations; + private decimal maximumTransactionFees; + private ECPrivKey ellipticPrivateKey; + private readonly IHashAlgorithm sha256S = new Sha256S(); + private object nonceGenLock = new(); + + protected override string LogCategory => "Warthog Payout Handler"; + + #region IPayoutHandler + + public virtual async Task ConfigureAsync(ClusterConfig cc, PoolConfig pc, CancellationToken ct) + { + Contract.RequiresNonNull(pc); + + poolConfig = pc; + clusterConfig = cc; + extraPoolPaymentProcessingConfig = pc.PaymentProcessing.Extra.SafeExtensionDataAs(); + + maximumTransactionFees = extraPoolPaymentProcessingConfig?.MaximumTransactionFees ?? WarthogConstants.MinimumTransactionFees; + + logger = LogUtil.GetPoolScopedLogger(typeof(WarthogPayoutHandler), pc); + + // configure standard daemon + var jsonSerializerSettings = ctx.Resolve(); + + var daemonEndpoints = pc.Daemons + .Where(x => string.IsNullOrEmpty(x.Category)) + .ToArray(); + + restClient = new SimpleRestClient(httpClientFactory, "http://" + daemonEndpoints.First().Host.ToString() + ":" + daemonEndpoints.First().Port.ToString()); + + try + { + var response = await restClient.Get(WarthogCommands.GetBlockTemplate.Replace(WarthogCommands.DataLabel, poolConfig.Address), ct); + if(response?.Error != null) + throw new Exception($"Pool address '{poolConfig.Address}': {response.Error} (Code {response?.Code})"); + + network = response.Data.Testnet ? "testnet" : "mainnet"; + } + + catch(Exception e) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.DaemonName} - {WarthogCommands.GetBlockTemplate}' daemon does not seem to be running..."); + throw new Exception($"'{WarthogCommands.DaemonName}' returned error: {e}"); + } + + if(string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPrivateKey)) + throw new Exception("WalletPrivateKey is mandatory for signing and sending transactions"); + else + { + try + { + var responsePoolAddressWalletPrivateKey = await restClient.Get(WarthogCommands.GetWallet.Replace(WarthogCommands.DataLabel, extraPoolPaymentProcessingConfig?.WalletPrivateKey), ct); + if(responsePoolAddressWalletPrivateKey?.Error != null) + throw new Exception($"Pool address private key '{extraPoolPaymentProcessingConfig?.WalletPrivateKey}': {responsePoolAddressWalletPrivateKey.Error} (Code {responsePoolAddressWalletPrivateKey?.Code})"); + + if(responsePoolAddressWalletPrivateKey.Data.Address != poolConfig.Address) + throw new Exception($"Pool address private key '{extraPoolPaymentProcessingConfig?.WalletPrivateKey}' [{responsePoolAddressWalletPrivateKey.Data.Address}] does not match pool address: {poolConfig.Address}"); + } + + catch(Exception e) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.DaemonName} - {WarthogCommands.GetWallet}' daemon does not seem to be running..."); + throw new Exception($"'{WarthogCommands.DaemonName}' returned error: {e}"); + } + + ellipticPrivateKey = Context.Instance.CreateECPrivKey(extraPoolPaymentProcessingConfig.WalletPrivateKey.HexToByteArray()); + } + + minConfirmations = extraPoolPaymentProcessingConfig?.MinimumConfirmations ?? (network == "mainnet" ? 120 : 110); + } + + public virtual async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, CancellationToken ct) + { + Contract.RequiresNonNull(poolConfig); + Contract.RequiresNonNull(blocks); + + if(blocks.Length == 0) + return blocks; + + GetChainInfoResponse chainInfo; + try + { + chainInfo = await restClient.Get(WarthogCommands.GetChainInfo, ct); + if(chainInfo?.Error != null) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.GetChainInfo}': {chainInfo.Error} (Code {chainInfo?.Code})"); + return blocks; + } + } + + catch(Exception) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.DaemonName} - {WarthogCommands.GetChainInfo}' daemon does not seem to be running..."); + return blocks; + } + + var coin = poolConfig.Template.As(); + var pageSize = 100; + var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); + var result = new List(); + + for(var i = 0; i < pageCount; i++) + { + // get a page full of blocks + var page = blocks + .Skip(i * pageSize) + .Take(pageSize) + .ToArray(); + + for(var j = 0; j < page.Length; j++) + { + var block = page[j]; + + WarthogBlock response; + try + { + response = await restClient.Get(WarthogCommands.GetBlockByHeight.Replace(WarthogCommands.DataLabel, block.BlockHeight.ToString()), ct); + if(response?.Error != null) + logger.Warn(() => $"[{LogCategory}] Block {block.BlockHeight}: {response.Error} (Code {response?.Code})"); + } + + catch(Exception e) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.DaemonName} - {WarthogCommands.GetBlockByHeight}' daemon does not seem to be running..."); + throw new Exception($"'{WarthogCommands.DaemonName}' returned error: {e}"); + } + + // We lost that battle + if(response?.Data.Header.Hash != block.Hash) + { + result.Add(block); + + block.Status = BlockStatus.Orphaned; + block.Reward = 0; + + logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned because {(response?.Error != null ? "it's not on chain" : $"it has a different hash on chain: {response.Data.Header.Hash}")}"); + + messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + } + else + { + logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} uses a custom minimum confirmations calculation [{minConfirmations}]"); + + block.ConfirmationProgress = Math.Min(1.0d, (double) (chainInfo.Data.Height - block.BlockHeight) / minConfirmations); + + result.Add(block); + + messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin); + + // matured and spendable? + if(block.ConfirmationProgress >= 1) + { + block.ConfirmationProgress = 1; + + logger.Debug(() => $"[{LogCategory}] Block {block.BlockHeight} contains {response.Data.Body.BlockReward.Length} block reward(s)"); + + // reset block reward + block.Reward = 0; + + // we only need the block reward(s) related to our pool address + var blockRewards = response.Data.Body.BlockReward + .Where(x => x.ToAddress.Contains(poolConfig.Address)) + .ToList(); + + foreach (var blockReward in blockRewards) + { + block.Reward += (decimal) blockReward.Amount / WarthogConstants.SmallestUnit; + } + + // security + if (block.Reward > 0) + { + block.Status = BlockStatus.Confirmed; + + logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}"); + } + else + { + block.Status = BlockStatus.Orphaned; + block.Reward = 0; + } + + messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin); + } + } + } + } + + return result.ToArray(); + } + + public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct) + { + Contract.RequiresNonNull(balances); + + // build args + var amounts = balances + .Where(x => x.Amount > 0) + .ToDictionary(x => x.Address, x => x.Amount); + + if(amounts.Count == 0) + return; + + var balancesTotal = amounts.Sum(x => x.Value); + logger.Info(() => $"[{LogCategory}] Paying {FormatAmount(balancesTotal)} to {balances.Length} addresses"); + + logger.Info(() => $"[{LogCategory}] Validating addresses..."); + foreach(var pair in amounts) + { + logger.Debug(() => $"[{LogCategory}] Address {pair.Key} with amount [{FormatAmount(pair.Value)}]"); + try + { + var responseAddress = await restClient.Get(WarthogCommands.GetBlockTemplate.Replace(WarthogCommands.DataLabel, pair.Key), ct); + if(responseAddress?.Error != null) + logger.Warn(()=> $"[{LogCategory}] Address {pair.Key} is not valid: {responseAddress.Error} (Code {responseAddress?.Code})"); + } + + catch(Exception e) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.DaemonName} - {WarthogCommands.GetBlockTemplate}' daemon does not seem to be running..."); + throw new Exception($"'{WarthogCommands.DaemonName}' returned error: {e}"); + } + } + + WarthogBalance responseBalance; + try + { + responseBalance = await restClient.Get(WarthogCommands.GetBalance.Replace(WarthogCommands.DataLabel, poolConfig.Address), ct); + if(responseBalance?.Error != null) + logger.Warn(()=> $"[{LogCategory}] '{WarthogCommands.GetBalance}': {responseBalance.Error} (Code {responseBalance?.Code})"); + } + + catch(Exception e) + { + logger.Warn(() => $"[{LogCategory}] '{WarthogCommands.DaemonName} - {WarthogCommands.GetBalance}' daemon does not seem to be running..."); + throw new Exception($"'{WarthogCommands.DaemonName}' returned error: {e}"); + } + + var walletBalance = (decimal) (responseBalance?.Data.Balance == null ? 0 : responseBalance?.Data.Balance) / WarthogConstants.SmallestUnit; + + logger.Info(() => $"[{LogCategory}] Current wallet balance - Total: [{FormatAmount(walletBalance)}]"); + + // bail if balance does not satisfy payments + if(walletBalance < balancesTotal) + { + logger.Warn(() => $"[{LogCategory}] Wallet balance currently short of {FormatAmount(balancesTotal - walletBalance)}. Will try again"); + return; + } + + var txFailures = new List, Exception>>(); + var successBalances = new Dictionary(); + + Random randomNonceId = new Random(); + List usedNonceId = new List(); + + var parallelOptions = new ParallelOptions + { + MaxDegreeOfParallelism = extraPoolPaymentProcessingConfig?.MaxDegreeOfParallelPayouts ?? 2, + CancellationToken = ct + }; + + await Parallel.ForEachAsync(amounts, parallelOptions, async (x, _ct) => + { + var (address, amount) = x; + + await Guard(async () => + { + uint nonceId = (uint) randomNonceId.NextInt64((long)uint.MinValue, (long)uint.MaxValue); + + lock(nonceGenLock) + { + bool IsSafeToContinue = false; + while(!IsSafeToContinue) + { + if(!(usedNonceId.Contains(nonceId))) + { + logger.Debug(()=> $"[{LogCategory}] Transaction nonceId: [{nonceId}]"); + + usedNonceId.Add(nonceId); + IsSafeToContinue = true; + } + else + nonceId = (uint) randomNonceId.NextInt64((long)uint.MinValue, (long)uint.MaxValue); + } + } + + logger.Info(()=> $"[{LogCategory}] [{nonceId}] Sending {FormatAmount(amount)} to {address}"); + + // WART payment is quite complex: https://www.warthog.network/docs/developers/integrations/wallet-integration/ - https://www.warthog.network/docs/developers/api/#post-transactionadd + var chainInfo = await restClient.Get(WarthogCommands.GetChainInfo, ct); + if(chainInfo?.Error != null) + throw new Exception($"'{WarthogCommands.GetChainInfo}': {chainInfo.Error} (Code {chainInfo?.Code})"); + + var feeE8Encoded = await restClient.Get(WarthogCommands.GetFeeE8Encoded.Replace(WarthogCommands.DataLabel, maximumTransactionFees.ToString()), ct); + if(feeE8Encoded?.Error != null) + throw new Exception($"'{WarthogCommands.GetFeeE8Encoded}': {feeE8Encoded.Error} (Code {feeE8Encoded?.Code})"); + + var amountE8 = (ulong) Math.Floor(((extraPoolPaymentProcessingConfig?.KeepTransactionFees == false) ? amount : amount - maximumTransactionFees) * WarthogConstants.SmallestUnit); + + // generate bytes to sign + var pinHashBytes = chainInfo.Data.PinHash.HexToByteArray(); + var pinHeightNonceIdFeeBytes = SerializePinHeightNonceIdFee(chainInfo.Data.PinHeight, nonceId, feeE8Encoded.Data.Rounded); + var toAddressBytes = address.HexToByteArray().Take(WarthogConstants.ToAddressOffset).ToArray(); + var amountBytes = SerializeAmount(amountE8); + var signatureBytes = SerializeSignature(pinHashBytes, pinHeightNonceIdFeeBytes, toAddressBytes, amountBytes); + + // sign bytes + byte[] signatureHashBytes = new byte[32]; + sha256S.Digest(signatureBytes, (Span) signatureHashBytes); + + SecpECDSASignature signatureECDSA; + int recid; + + // this beautiful NBitcoin class automatically normalizes the signature and recid + var signedECDSA = ellipticPrivateKey.TrySignECDSA(signatureHashBytes, null, out recid, out signatureECDSA); + if(!signedECDSA || signatureECDSA == null) + throw new Exception("SignECDSA failed (bug in C# secp256k1)"); + + var fullSignatureBytes = SerializeFullSignature(signatureECDSA.r.ToBytes(), signatureECDSA.s.ToBytes(), (byte) recid); + + var sendTransaction = new WarthogSendTransactionRequest + { + PinHeight = chainInfo.Data.PinHeight, + NonceId = nonceId, + ToAddress = address, + Amount = amountE8, + Fee = feeE8Encoded.Data.Rounded, + Signature = fullSignatureBytes.ToHexString() + }; + + var response = await restClient.Post(WarthogCommands.SendTransaction, sendTransaction, ct); + if(response?.Error != null) + throw new Exception($"[{nonceId}] {WarthogCommands.SendTransaction} returned error: {response.Error} (Code {response?.Code})"); + + if(string.IsNullOrEmpty(response.Data.TxHash)) + throw new Exception($"[{nonceId}] {WarthogCommands.SendTransaction} did not return a transaction id!"); + else + logger.Info(() => $"[{LogCategory}] [{nonceId}] Payment transaction id: {response.Data.TxHash}"); + + successBalances.Add(new Balance + { + PoolId = poolConfig.Id, + Address = address, + Amount = amount, + }, response.Data.TxHash); + }, ex => + { + txFailures.Add(Tuple.Create(x, ex)); + }); + }); + + if(successBalances.Any()) + { + await PersistPaymentsAsync(successBalances); + + NotifyPayoutSuccess(poolConfig.Id, successBalances.Keys.ToArray(), successBalances.Values.ToArray(), null); + } + + if(txFailures.Any()) + { + var failureBalances = txFailures.Select(x=> new Balance { Amount = x.Item1.Value }).ToArray(); + var error = string.Join(", ", txFailures.Select(x => $"{x.Item1.Key} {FormatAmount(x.Item1.Value)}: {x.Item2.Message}")); + + logger.Error(()=> $"[{LogCategory}] Failed to transfer the following balances: {error}"); + + NotifyPayoutFailure(poolConfig.Id, failureBalances, error, null); + } + } + + public double AdjustBlockEffort(double effort) + { + return effort; + } + + #endregion // IPayoutHandler + + private byte[] SerializePinHeightNonceIdFee(uint pinHeight, uint nonceId, ulong feeE8Encoded) + { + using(var stream = new MemoryStream(WarthogConstants.PinHeightNonceIdFeeByteSize)) + { + var bw = new BinaryWriter(stream); + + bw.Write((BitConverter.IsLittleEndian ? BitConverter.GetBytes(pinHeight).ReverseInPlace() : BitConverter.GetBytes(pinHeight))); // wart-node expects a big endian format. + bw.Write((BitConverter.IsLittleEndian ? BitConverter.GetBytes(nonceId).ReverseInPlace() : BitConverter.GetBytes(nonceId))); // wart-node expects a big endian format. + bw.Write(WarthogConstants.Zero); + bw.Write(WarthogConstants.Zero); + bw.Write(WarthogConstants.Zero); + bw.Write((BitConverter.IsLittleEndian ? BitConverter.GetBytes(feeE8Encoded).ReverseInPlace() : BitConverter.GetBytes(feeE8Encoded))); // wart-node expects a big endian format. + + return stream.ToArray(); + } + } + + private byte[] SerializeAmount(ulong amount) + { + using(var stream = new MemoryStream(WarthogConstants.AmountByteSize)) + { + var bw = new BinaryWriter(stream); + + bw.Write((BitConverter.IsLittleEndian ? BitConverter.GetBytes(amount).ReverseInPlace() : BitConverter.GetBytes(amount))); // wart-node expects a big endian format. + + return stream.ToArray(); + } + } + + private byte[] SerializeSignature(byte[] pinHashBytes, byte[] pinHeightNonceIdFeeBytes, byte[] toAddressBytes, byte[] amountBytes) + { + using(var stream = new MemoryStream()) + { + stream.Write(pinHashBytes); + stream.Write(pinHeightNonceIdFeeBytes); + stream.Write(toAddressBytes); + stream.Write(amountBytes); + + return stream.ToArray(); + } + } + + private byte[] SerializeFullSignature(byte[] rBytes, byte[] sBytes, byte recid) + { + using(var stream = new MemoryStream(WarthogConstants.FullSignatureByteSize)) + { + var bw = new BinaryWriter(stream); + + bw.Write(rBytes); + bw.Write(sBytes); + bw.Write(recid); // wart-node expects a big endian format. + + return stream.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogPool.cs b/src/Miningcore/Blockchain/Warthog/WarthogPool.cs new file mode 100644 index 000000000..4ec178e50 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogPool.cs @@ -0,0 +1,411 @@ +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using Autofac; +using AutoMapper; +using Microsoft.IO; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Blockchain.Warthog; +using Miningcore.Blockchain.Warthog.Configuration; +using Miningcore.Configuration; +using Miningcore.Extensions; +using Miningcore.JsonRpc; +using Miningcore.Messaging; +using Miningcore.Mining; +using Miningcore.Nicehash; +using Miningcore.Notifications.Messages; +using Miningcore.Persistence; +using Miningcore.Persistence.Repositories; +using Miningcore.Stratum; +using Miningcore.Time; +using Newtonsoft.Json; +using NLog; +using static Miningcore.Util.ActionUtils; + +namespace Miningcore.Blockchain.Warthog; + +[CoinFamily(CoinFamily.Warthog)] +public class WarthogPool : PoolBase +{ + public WarthogPool(IComponentContext ctx, + JsonSerializerSettings serializerSettings, + IConnectionFactory cf, + IStatsRepository statsRepo, + IMapper mapper, + IMasterClock clock, + IMessageBus messageBus, + RecyclableMemoryStreamManager rmsm, + NicehashService nicehashService) : + base(ctx, serializerSettings, cf, statsRepo, mapper, clock, messageBus, rmsm, nicehashService) + { + } + + private object currentJobParams; + private WarthogJobManager manager; + private WarthogPoolConfigExtra extraPoolConfig; + private WarthogCoinTemplate coin; + + protected virtual async Task OnSubscribeAsync(StratumConnection connection, Timestamped tsRequest) + { + var request = tsRequest.Value; + + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + var context = connection.ContextAs(); + + var requestParams = request.ParamsAs(); + + if(requestParams?.Length < 1) + throw new StratumException(StratumError.Other, "invalid params"); + + context.UserAgent = requestParams.FirstOrDefault()?.Trim(); + + var subscriberData = manager.GetSubscriberData(connection); + var data = new object[] + { + new object[] + { + new object[] { BitcoinStratumMethods.MiningNotify, connection.ConnectionId } + } + } + .Concat(subscriberData) + .ToArray(); + + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(data, request.Id); + + if(context.IsNicehash || context.UserAgent.Contains(WarthogConstants.JanusMiner)) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + await connection.RespondAsync(response); + + // setup worker context + context.IsSubscribed = true; + + // Nicehash support + var nicehashDiff = await GetNicehashStaticMinDiff(context, coin.Name, coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + context.VarDiff = null; // disable vardiff + context.SetDifficulty(nicehashDiff.Value); + } + } + + protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + var context = connection.ContextAs(); + if(!context.IsSubscribed) + throw new StratumException(StratumError.NotSubscribed, "subscribe first please, we aren't savages"); + + var requestParams = request.ParamsAs(); + + if(requestParams?.Length < 1) + throw new StratumException(StratumError.Other, "invalid params"); + + var workerValue = requestParams?.Length > 0 ? requestParams[0] : null; + var password = requestParams?.Length > 1 ? requestParams[1] : null; + var passParts = password?.Split(PasswordControlVarsSeparator); + + // extract worker/miner + var split = workerValue?.Split('.'); + var minerName = split?.FirstOrDefault()?.Trim(); + var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; + + // assumes that minerName is an address + context.IsAuthorized = await manager.ValidateAddressAsync(minerName, ct); + context.Miner = minerName; + context.Worker = workerName; + + if(context.IsAuthorized) + { + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(context.IsAuthorized, request.Id); + + if(context.IsNicehash || context.UserAgent.Contains(WarthogConstants.JanusMiner)) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + // respond + await connection.RespondAsync(response); + + // log association + logger.Info(() => $"[{connection.ConnectionId}] Authorized worker {workerValue}"); + + // extract control vars from password + var staticDiff = GetStaticDiffFromPassparts(passParts); + + // Static diff + if(staticDiff.HasValue && + (context.VarDiff != null && staticDiff.Value >= context.VarDiff.Config.MinDiff || + context.VarDiff == null && staticDiff.Value > context.Difficulty)) + { + context.VarDiff = null; // disable vardiff + context.SetDifficulty(staticDiff.Value); + + logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); + } + + // send intial update + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + + else + { + await connection.RespondErrorAsync(StratumError.UnauthorizedWorker, "Authorization failed", request.Id, context.IsAuthorized); + + if(clusterConfig?.Banning?.BanOnLoginFailure is null or true) + { + // issue short-time ban if unauthorized to prevent DDos on daemon (validateaddress RPC) + logger.Info(() => $"[{connection.ConnectionId}] Banning unauthorized worker {minerName} for {loginFailureBanTimeout.TotalSeconds} sec"); + + banManager.Ban(connection.RemoteEndpoint.Address, loginFailureBanTimeout); + + Disconnect(connection); + } + } + } + + protected virtual async Task OnSubmitAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + var context = connection.ContextAs(); + + try + { + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + // check age of submission (aged submissions are usually caused by high server load) + var requestAge = clock.Now - tsRequest.Timestamp.UtcDateTime; + + if(requestAge > maxShareAge) + { + logger.Warn(() => $"[{connection.ConnectionId}] Dropping stale share submission request (server overloaded?)"); + return; + } + + // check worker state + context.LastActivity = clock.Now; + + // validate worker + if(!context.IsAuthorized) + throw new StratumException(StratumError.UnauthorizedWorker, "unauthorized worker"); + else if(!context.IsSubscribed) + throw new StratumException(StratumError.NotSubscribed, "not subscribed"); + + var requestParams = request.ParamsAs(); + + // submit + var share = await manager.SubmitShareAsync(connection, requestParams, ct); + + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(true, request.Id); + + if(context.IsNicehash || context.UserAgent.Contains(WarthogConstants.JanusMiner)) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + // respond + await connection.RespondAsync(response); + + // publish + messageBus.SendMessage(share); + + // telemetry + PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); + + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); + + // update pool stats + if(share.IsBlockCandidate) + poolStats.LastPoolBlockTime = clock.Now; + + // update client stats + context.Stats.ValidShares++; + + await UpdateVarDiffAsync(connection, false, ct); + } + + catch(StratumException ex) + { + // telemetry + PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, false); + + // update client stats + context.Stats.InvalidShares++; + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); + + // banning + ConsiderBan(connection, context, poolConfig.Banning); + + if(context.UserAgent.Contains(WarthogConstants.JanusMiner)) + { + // Little hack to allow JanusMiner to mine on our stratum without interruptions even though we should still be able to ban it just in case + // [Respect the goddamn standards though :(] + var response = new JsonRpcResponse(true, request.Id); + response.Extra = new Dictionary(); + response.Extra["error"] = null; + + // respond + await connection.RespondAsync(response); + } + else + throw; + } + } + + protected virtual async Task OnNewJobAsync(object jobParams) + { + currentJobParams = jobParams; + + logger.Info(() => $"Broadcasting job {((object[]) jobParams)[0]}"); + + await Guard(() => ForEachMinerAsync(async (connection, ct) => + { + var context = connection.ContextAs(); + + // varDiff: if the client has a pending difficulty change, apply it now + if(context.ApplyPendingDifficulty()) + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + + // send job + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + })); + } + + public override double HashrateFromShares(double shares, double interval) + { + var result = shares / interval; + + return result; + } + + public override double ShareMultiplier => 1; + + #region Overrides + + public override void Configure(PoolConfig pc, ClusterConfig cc) + { + coin = pc.Template.As(); + extraPoolConfig = pc.Extra.SafeExtensionDataAs(); + + base.Configure(pc, cc); + } + + protected override async Task SetupJobManager(CancellationToken ct) + { + var extraNonce1Size = extraPoolConfig?.ExtraNonce1Size ?? 4; + + manager = ctx.Resolve( + new TypedParameter(typeof(IExtraNonceProvider), new WarthogExtraNonceProvider(poolConfig.Id, extraNonce1Size, clusterConfig.InstanceId))); + + manager.Configure(poolConfig, clusterConfig); + + await manager.StartAsync(ct); + + if(poolConfig.EnableInternalStratum == true) + { + disposables.Add(manager.Jobs + .Select(job => Observable.FromAsync(() => + Guard(()=> OnNewJobAsync(job), + ex=> logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")))) + .Concat() + .Subscribe(_ => { }, ex => + { + logger.Debug(ex, nameof(OnNewJobAsync)); + })); + + // start with initial blocktemplate + await manager.Jobs.Take(1).ToTask(ct); + } + + else + { + // keep updating NetworkStats + disposables.Add(manager.Jobs.Subscribe()); + } + } + + protected override async Task InitStatsAsync(CancellationToken ct) + { + await base.InitStatsAsync(ct); + + blockchainStats = manager.BlockchainStats; + } + + protected override WorkerContextBase CreateWorkerContext() + { + return new WarthogWorkerContext(); + } + + protected override async Task OnRequestAsync(StratumConnection connection, + Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + + try + { + switch(request.Method) + { + case BitcoinStratumMethods.Authorize: + await OnAuthorizeAsync(connection, tsRequest, ct); + break; + + case BitcoinStratumMethods.Subscribe: + await OnSubscribeAsync(connection, tsRequest); + break; + + case BitcoinStratumMethods.SubmitShare: + await OnSubmitAsync(connection, tsRequest, ct); + break; + + default: + logger.Debug(() => $"[{connection.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); + + await connection.RespondErrorAsync(StratumError.Other, $"Unsupported request {request.Method}", request.Id); + break; + } + } + + catch(StratumException ex) + { + await connection.RespondErrorAsync(ex.Code, ex.Message, request.Id, false); + } + } + + protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff, CancellationToken ct) + { + await base.OnVarDiffUpdateAsync(connection, newDiff, ct); + + if(connection.Context.ApplyPendingDifficulty()) + { + await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { connection.Context.Difficulty }); + await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + } + + #endregion // Overrides +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogUtils.cs b/src/Miningcore/Blockchain/Warthog/WarthogUtils.cs new file mode 100644 index 000000000..b5c53bc47 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogUtils.cs @@ -0,0 +1,1095 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Text; +using Miningcore.Extensions; +using Miningcore.Util; + +#pragma warning disable 3021 // disable CLSCompliant attribute warnings - http://msdn.microsoft.com/en-us/library/1x9049cy(v=vs.90).aspx + +namespace Miningcore.Blockchain.Warthog; + +public static class WarthogUtils +{ + // Bit-mask used for extracting the exponent bits of a Double (0x7ff0000000000000). + public const long DBL_EXP_MASK = 0x7ff0000000000000L; + + // The number of bits in the mantissa of a Double, excludes the implicit leading 1 bit (52). + public const int DBL_MANT_BITS = 52; + + // Bit-mask used for extracting the sign bit of a Double (0x8000000000000000). + public const long DBL_SGN_MASK = -1 - 0x7fffffffffffffffL; + + // Bit-mask used for extracting the mantissa bits of a Double (0x000fffffffffffff). + public const long DBL_MANT_MASK = 0x000fffffffffffffL; + + // Bit-mask used for clearing the exponent bits of a Double (0x800fffffffffffff). + public const long DBL_EXP_CLR_MASK = DBL_SGN_MASK | DBL_MANT_MASK; + + public static double CalculateHashrate(WarthogCustomFloat sha256t, WarthogCustomFloat verus) + { + double hashrate = (10.0 / 3.0) * (double)sha256t * (Math.Pow(((double)WarthogConstants.ProofOfBalancedWorkC + (double)verus / (double)sha256t), 3.0) - Math.Pow((double)WarthogConstants.ProofOfBalancedWorkC, 3.0)); + + return hashrate; + } + + /* public static void Frexp(double value, out double significand, out int exponent) + { + // Special case for zero + if(value == 0.0) + { + significand = 0.0; + exponent = 0; + return; + } + + // Get the raw bits of the double + long bits = BitConverter.DoubleToInt64Bits(value); + + bool negative = (bits >> 63) != 0; + + // Extract the exponent (bits 52-62) + int rawExponent = (int)((bits >> 52) & 0x7FFL); + + // Extract the mantissa (bits 0-51) + long mantissa = bits & 0xFFFFFFFFFFFFFL; + + // If the exponent is zero, this is a subnormal number + if(rawExponent == 0) + rawExponent++; + // Add the implicit leading 1 bit to the mantissa + else + mantissa = mantissa | (1L << 52); + + // Adjust the exponent from the biased representation + exponent = rawExponent - 1023; + + // Calculate the significand + significand = mantissa / Math.Pow(2.0, 52); + + // Adjust the sign if necessary + if(negative) + significand = -significand; + }*/ + + public static void Frexp(double value, out double significand, out int exponent) + { + significand = value; + long bits = BitConverter.DoubleToInt64Bits(significand); + int exp = (int)((bits & DBL_EXP_MASK) >> DBL_MANT_BITS); + exponent = 0; + + if (exp == 0x7ff || significand == 0D) + significand += significand; + else + { + // Not zero and finite. + exponent = exp - 1022; + if (exp == 0) + { + // Subnormal, scale significand so that it is in [1, 2). + significand *= BitConverter.Int64BitsToDouble(0x4350000000000000L); // 2^54 + bits = BitConverter.DoubleToInt64Bits(significand); + exp = (int)((bits & DBL_EXP_MASK) >> DBL_MANT_BITS); + exponent = exp - 1022 - 54; + } + // Set exponent to -1 so that significand is in [0.5, 1). + significand = BitConverter.Int64BitsToDouble((bits & DBL_EXP_CLR_MASK) | 0x3fe0000000000000L); + } + } +} + +public class WarthogExponential +{ + public uint negExp { get; private set; } = 0; // negative exponent of 2 + public uint data { get; private set; } = 0x80000000; + + public WarthogExponential() { } + + public WarthogExponential(ReadOnlySpan hash) + { + negExp += 1; // we are considering hashes as number in (0,1), padded with infinite amount of trailing 1's + int i = 0; + for(; i < hash.Length; ++i) + { + if(hash[i] != 0) + break; + negExp += 8; + } + + ulong tmpData = 0; + for(int j = 0; ; ++j) + { + if(i < hash.Length) + tmpData |= hash[i++]; + else + tmpData |= 0xFFu; // "infinite amount of trailing 1's" + + if(j >= 3) + break; + tmpData <<= 8; + } + + while((tmpData & 0x80000000ul) == 0) + { + negExp += 1; + tmpData <<= 1; + } + + tmpData *= (ulong)data; + if(tmpData >= (ulong)(1ul << 63)) + { + tmpData >>= 1; + negExp -= 1; + } + + tmpData >>= 31; + + data = (uint)tmpData; + } +} + +public class WarthogTarget +{ + public bool IsJanusHash { get; private set; } = true; + public uint data { get; private set; } = 0u; + + public WarthogTarget(uint data, bool isJanusHash = true) + { + this.data = data; + this.IsJanusHash = isJanusHash; + } + + public WarthogTarget(byte[] data, bool isJanusHash = true) + { + this.data = uint.Parse(data.ToHexString(), NumberStyles.HexNumber); + this.IsJanusHash = isJanusHash; + } + + public WarthogTarget(double difficulty, bool isJanusHash = true) + { + if(difficulty < 1.0) + difficulty = 1.0; + + this.IsJanusHash = isJanusHash; + + WarthogUtils.Frexp(difficulty, out var coef, out var exp); + double inv = 1 / coef; + uint zeros; + uint digits; + + if(!this.IsJanusHash) + { + if(exp - 1 >= 256 - 24) + { + data = WarthogConstants.HardestTargetHost; + return; + } + + zeros = (uint)(exp - 1); + if(inv == 2.0) + { + Set(zeros, 0x00FFFFFF); + } + else + { + digits = (uint)Math.Floor(Math.ScaleB(inv, 23)); + if(digits < 0x00800000) + Set(zeros, 0x00800000); + else if(digits > 0x00FFFFFF) + Set(zeros, 0x00FFFFFF); + else + Set(zeros, digits); + } + } + else + { + zeros = (uint)(exp - 1); + if(zeros >= 3 * 256) + { + data = WarthogConstants.JanusHashMaxTargetHost; + return; + } + if(inv == 2.0) + { + Set(zeros, 0x003fffff); + } + else + { + digits = (uint)Math.Floor(Math.ScaleB(inv, 21)); + if(digits < 0x00200000) + Set(zeros, 0x00200000); + else if(digits > 0x003fffff) + Set(zeros, 0x003fffff); + else + Set(zeros, digits); + } + } + } + + private void Set(uint zeros, uint bytes) + { + data = (!this.IsJanusHash) ? (zeros << 24) | bytes : (zeros << 22) | bytes; + } + + public uint Zeros8() + { + return data >> 24; + } + + public uint Zeros10() + { + return data >> 22; + } + + public uint Bits22() + { + return data & 0x003FFFFF; + } + + public uint Bits24() + { + return data & 0x00FFFFFF; + } + + public double Difficulty() + { + int zeros; + double dbits; + + if(!IsJanusHash) + { + zeros = (int)Zeros8(); + dbits = Bits24(); + return Math.Pow(2, zeros + 24) / dbits; + } + else + { + zeros = (int)Zeros10(); + dbits = Bits22(); + return Math.Pow(2, zeros + 22) / dbits; + } + } + + public static bool operator <(WarthogCustomFloat wcf, WarthogTarget wt) + { + if(!wt.IsJanusHash) + return false; + else + { + uint zerosTarget = wt.Zeros10(); + int exp = wcf._exponent; + if(exp < 0) + exp = -exp; + uint zerosHashProduct = (uint)exp; + + if(zerosTarget > zerosHashProduct) + return false; + + if(zerosTarget < zerosHashProduct) + return true; + + ulong bits32 = wt.Bits22() << 10; + return wcf._mantissa < bits32; + } + } + + public static bool operator >(WarthogCustomFloat wcf, WarthogTarget wt) + { + if(!wt.IsJanusHash) + return false; + else + { + uint zerosTarget = wt.Zeros10(); + int exp = wcf._exponent; + if(exp < 0) + exp = -exp; + uint zerosHashProduct = (uint)exp; + + if(zerosTarget > zerosHashProduct) + return true; + + if(zerosTarget < zerosHashProduct) + return false; + + ulong bits32 = wt.Bits22() << 10; + return wcf._mantissa > bits32; + } + } + + public static bool operator <(ReadOnlySpan hash, WarthogTarget wt) + { + if(!wt.IsJanusHash) + { + uint zeros = wt.Zeros8(); + if(zeros > (256 - 4 * 8)) + return false; + uint bits = wt.Bits24(); + if((bits & 0x00800000) == 0) + return false; // first digit must be 1 + int zeroBytes = (int)(zeros / 8); // number of complete zero bytes + int shift = (int)(zeros & 0x07); + + for(int i = 0; i < zeroBytes; ++i) + if(hash[31 - i] != 0) + return false; // here we need zeros + + uint threshold = bits << (8 - shift); + byte[] dst = hash.ToArray().Skip(28 - zeroBytes).Take(4).ToArray(); + uint candidate = uint.Parse(dst.ToHexString(), NumberStyles.HexNumber); + if(candidate > threshold) + { + return false; + } + if(candidate < threshold) + { + return true; + } + for(int i = 0; i < 28 - zeroBytes; ++i) + if(hash[i] != 0) + return false; + return true; + } + else + { + WarthogExponential we = new WarthogExponential(hash); + uint zerosTarget = wt.Zeros10(); + uint zerosHashProduct = (uint)(we.negExp - 1); + + if(zerosTarget > zerosHashProduct) + return false; + + if(zerosTarget < zerosHashProduct) + return true; + + uint bits32 = wt.Bits22() << 10; + return we.data < bits32; + } + } + + public static bool operator >(ReadOnlySpan hash, WarthogTarget wt) + { + if(!wt.IsJanusHash) + { + uint zeros = wt.Zeros8(); + if(zeros < (256 - 4 * 8)) + return false; + uint bits = wt.Bits24(); + if((bits & 0x00800000) != 0) + return false; // first digit must be 1 + int zeroBytes = (int)(zeros / 8); // number of complete zero bytes + int shift = (int)(zeros & 0x07); + + for(int i = 0; i < zeroBytes; ++i) + if(hash[31 - i] == 0) + return false; // here we need zeros + + uint threshold = bits << (8 - shift); + byte[] dst = hash.ToArray().Skip(28 - zeroBytes).Take(4).ToArray(); + uint candidate = uint.Parse(dst.ToHexString(), NumberStyles.HexNumber); + if(candidate < threshold) + { + return false; + } + if(candidate > threshold) + { + return true; + } + for(int i = 0; i < 28 - zeroBytes; ++i) + if(hash[i] == 0) + return false; + return true; + } + else + { + WarthogExponential we = new WarthogExponential(hash); + uint zerosTarget = wt.Zeros10(); + uint zerosHashProduct = (uint)(we.negExp - 1); + + if(zerosTarget > zerosHashProduct) + return true; + + if(zerosTarget < zerosHashProduct) + return false; + + uint bits32 = wt.Bits22() << 10; + return we.data > bits32; + } + } +} + +// https://github.com/CoinFuMasterShifu/CustomFloat/blob/master/src/custom_float.hpp +[Serializable] +[ComVisible(false)] +public class WarthogCustomFloat : IComparable, IComparable, IDeserializationCallback, + IEquatable, + ISerializable +{ + // ---- SECTION: members supporting exposed properties -------------* + public int _exponent { get; private set; } = 0; + public uint _mantissa { get; private set; } = 0; + public bool _isPositive { get; private set; } = true; + + #region Public Properties + + public long Exponent + { + get => (long)this._exponent; + set + { + if(!(value < int.MaxValue && value > int.MinValue)) + throw new Exception($"Invalid value for Exponent: {value} < {int.MaxValue} && {value} > {int.MinValue}"); + + this._exponent = (int)value; + } + } + + public ulong Mantissa + { + get => (ulong)this._mantissa; + set + { + if(!(value < (ulong)(1ul << 32) && value != 0)) + throw new Exception($"Invalid value for Mantissa: {value} < {(ulong)(1ul << 32)} && {value} != 0"); + + this._mantissa = (uint)value; + } + } + + public bool IsPositive + { + get => this._isPositive; + set + { + this._isPositive = value; + } + } + + #endregion Public Properties + + // ---- SECTION: public instance methods --------------* + + #region Public Instance Methods + + private void SetAssert(long exponent, ulong mantissa) + { + this.Exponent = exponent; + + if(mantissa == 0) + this._mantissa = 0; + else + this.Mantissa = mantissa; + } + + public void ShiftLeft(long exponent, ulong mantissa) + { + while(mantissa < 0x80000000ul) + { + mantissa <<= 1; + exponent -= 1; + } + + this.SetAssert(exponent, mantissa); + } + + public void ShiftRight(long exponent, ulong mantissa) + { + while(mantissa >= (1ul << 32)) + { + mantissa >>= 1; + exponent += 1; + } + + this.SetAssert(exponent, mantissa); + } + + public override bool Equals(object obj) + { + return obj is WarthogCustomFloat customfloat && Equals(customfloat); + } + + public override int GetHashCode() + { + return ((double)this).GetHashCode(); + } + + // IComparable + int IComparable.CompareTo(object obj) + { + if(obj == null) + return 1; + if(obj is not WarthogCustomFloat customfloat) + throw new ArgumentException("Argument must be of type WarthogCustomFloat", "obj"); + return Compare(this, customfloat); + } + + // IComparable + public int CompareTo(WarthogCustomFloat other) + { + return Compare(this, other); + } + + // Object.ToString (x * 2^n) + public override string ToString() + { + double r = (double)this._mantissa / (ulong)(1ul << 32); + + if(!this._isPositive) + r = -r; + + var ret = new StringBuilder(); + ret.Append(r.ToString("F", CultureInfo.InvariantCulture)); + ret.Append(" * 2^"); + ret.Append(this._exponent.ToString("D", CultureInfo.InvariantCulture)); + return ret.ToString(); + } + + // IEquatable + // a/b = c/d, if ad = bc + public bool Equals(WarthogCustomFloat other) + { + if(this._exponent == other._exponent && this._mantissa == other._mantissa) + return this._isPositive == other._isPositive; + + long xMantissa = (long)this._mantissa; + if(!this._isPositive) + xMantissa = -xMantissa; + long yMantissa = other._mantissa; + if(!other._isPositive) + yMantissa = -yMantissa; + + return xMantissa * other._exponent == this._exponent * yMantissa; + } + + #endregion Public Instance Methods + + // -------- SECTION: constructors -----------------* + + #region Constructors + + public WarthogCustomFloat(int exponent, uint mantissa, bool isPositive) + { + this._exponent = exponent; + this._mantissa = mantissa; + this._isPositive = isPositive; + } + + public WarthogCustomFloat(ReadOnlySpan hash) + { + this._isPositive = true; + int exponent = 0; + int i = 0; + for(; i < hash.Length; ++i) + { + if(hash[i] != 0) + break; + exponent -= 8; + } + + ulong tmpData = 0; + + for(int j = 0; ; ++j) + { + if(i < hash.Length) + tmpData |= hash[i++]; + else + tmpData |= 0xFFu; // "infinite amount of trailing 1's" + + if(j >= 3) + break; + + tmpData <<= 8; + } + + ShiftLeft(exponent, tmpData); + } + + public WarthogCustomFloat(double d) + { + if(d == 0) + { + this._exponent = 0; + this._mantissa = 0; + this._isPositive = true; + } + else + { + WarthogUtils.Frexp(d, out var r, out var e); + + bool isPositive = r >= 0; + if(r < 0) + r = -r; + + r *= (ulong)(1ul << 32); + ulong m = (ulong)Math.Ceiling(r); + + this._exponent = e; + this.Mantissa = m; + this._isPositive = isPositive; + } + } + + public WarthogCustomFloat(int mantissa) + { + if(mantissa == 0) + { + this._exponent = 0; + this._mantissa = 0; + this._isPositive = true; + } + else + { + this.IsPositive = mantissa >= 0; + + if(mantissa < 0) + mantissa = -mantissa; + + ulong tmpData = (ulong)mantissa; + + ShiftLeft(32, tmpData); + } + } + + public WarthogCustomFloat(long exponent, bool isPositive) + { + if(exponent >= int.MaxValue || exponent <= int.MinValue) + throw new ArgumentException($"Invalid value for exponent: {exponent} < {int.MaxValue} && {exponent} > {int.MinValue}"); + + this.Exponent = exponent + 1; + this.Mantissa = 0x80000000ul; + this.IsPositive = isPositive; + } + + #endregion Constructors + + // -------- SECTION: public static methods -----------------* + + #region Public Static Methods + + public static WarthogCustomFloat Negate(WarthogCustomFloat wcf) + { + return new(wcf._exponent, wcf._mantissa, !wcf._isPositive); + } + + public static WarthogCustomFloat Add(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return wcf1 + wcf2; + } + + public static WarthogCustomFloat Subtract(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return wcf1 - wcf2; + } + + public static WarthogCustomFloat Multiply(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return wcf1 * wcf2; + } + + public static WarthogCustomFloat Divide(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return wcf1 / wcf2; + } + + public static WarthogCustomFloat Pow(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return Pow2(wcf2 * Log2(wcf1)); + } + + public static WarthogCustomFloat Zero() + { + return new(0, 0, true); + } + + public static WarthogCustomFloat One() + { + return new(0, 1, true); + } + + public static int Compare(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return ((double)wcf1).CompareTo((double)wcf2); + } + + #endregion Public Static Methods + + #region Operator Overloads + + public static bool operator ==(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return wcf1.Equals(wcf2); + } + + public static bool operator !=(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return !wcf1.Equals(wcf2); + } + + public static bool operator <(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + int expWcf2 = wcf2._exponent; + if(expWcf2 < 0) + expWcf2 = -expWcf2; + uint zerosWcf2 = (uint)expWcf2; + + int expWcf1 = wcf1._exponent; + if(expWcf1 < 0) + expWcf1 = -expWcf1; + uint zerosWcf1 = (uint)expWcf1; + + if(zerosWcf1 < zerosWcf2) + return false; + + if(zerosWcf1 > zerosWcf2) + return true; + + return wcf1._mantissa < wcf2._mantissa; + } + + public static bool operator <=(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + int expWcf2 = wcf2._exponent; + if(expWcf2 < 0) + expWcf2 = -expWcf2; + uint zerosWcf2 = (uint)expWcf2; + + int expWcf1 = wcf1._exponent; + if(expWcf1 < 0) + expWcf1 = -expWcf1; + uint zerosWcf1 = (uint)expWcf1; + + if(zerosWcf1 < zerosWcf2) + return false; + + if(zerosWcf1 >= zerosWcf2) + return true; + + return wcf1._mantissa <= wcf2._mantissa; + } + + public static bool operator >(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return !(wcf1 < wcf2); + } + + public static bool operator >=(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + return !(wcf1 <= wcf2); + } + + public static WarthogCustomFloat operator +(WarthogCustomFloat wcf) + { + return wcf; + } + + public static WarthogCustomFloat operator -(WarthogCustomFloat wcf) + { + return new(wcf._exponent, wcf._mantissa, !wcf._isPositive); + } + + public static WarthogCustomFloat operator ++(WarthogCustomFloat wcf) + { + return wcf + One(); + } + + public static WarthogCustomFloat operator --(WarthogCustomFloat wcf) + { + return wcf - One(); + } + + public static WarthogCustomFloat operator +(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + WarthogCustomFloat wcf3 = new(wcf1._exponent, wcf1._mantissa, wcf1._isPositive); + int e1 = wcf1._exponent; + int e2 = wcf2._exponent; + + if(wcf1._mantissa == 0) + { + wcf3._exponent = wcf2._exponent; + wcf3._mantissa = wcf2._mantissa; + wcf3._isPositive = wcf2._isPositive; + + return wcf3; + } + + if(wcf2._mantissa == 0) + return wcf3; + + if(e1 < e2) + return wcf2 + wcf1; + + if(e1 - e2 >= 64) + return wcf3; + + ulong tmp = wcf1._mantissa; + ulong operand = (ulong)wcf2._mantissa >> (e1 - e2); + + if(wcf1._isPositive == wcf2._isPositive) + { + tmp += operand; + + wcf3.ShiftRight(e1, tmp); + } + else + { + if(operand == tmp) + wcf3._mantissa = 0; + else if(operand > tmp) + { + wcf3._isPositive = wcf2._isPositive; // change sign + wcf3.ShiftLeft(e2, operand - tmp); + } + else + wcf3.ShiftLeft(e1, tmp - operand); + } + + return wcf3; + } + + public static WarthogCustomFloat operator -(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + WarthogCustomFloat wcf3 = new(wcf2._exponent, wcf2._mantissa, !wcf2._isPositive); + + return wcf1 + wcf3; + } + + public static WarthogCustomFloat operator *(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + WarthogCustomFloat wcf3 = new(wcf1._exponent, wcf1._mantissa, wcf1._isPositive); + if(wcf1._mantissa == 0 || wcf2._mantissa == 0) + { + wcf3._mantissa = 0; + return wcf3; + } + + wcf3.IsPositive = wcf1._isPositive == wcf2._isPositive; + int e1 = wcf1._exponent; + int e2 = wcf2._exponent; + long e = (long)(e1 + e2); + ulong tmp = (ulong)wcf1._mantissa * wcf2._mantissa; + + if(tmp < (1ul << 63)) + { + e -= 1; + tmp <<= 1; + } + + tmp >>= 32; + wcf3.SetAssert(e, tmp); + + return wcf3; + } + + public static WarthogCustomFloat operator /(WarthogCustomFloat wcf1, WarthogCustomFloat wcf2) + { + WarthogCustomFloat wcf3 = new(wcf1._exponent, wcf1._mantissa, wcf1._isPositive); + if (wcf2._mantissa == 0) + throw new DivideByZeroException("Attempt to divide by zero."); + + if (wcf1._mantissa == 0) + { + wcf3._mantissa = 0; + return wcf3; + } + + wcf3.IsPositive = wcf1._isPositive == wcf2._isPositive; + int e1 = wcf1._exponent; + int e2 = wcf2._exponent; + long e = (long)(e1 - e2); + + ulong dividend = (ulong)wcf1._mantissa; + ulong divisor = (ulong)wcf2._mantissa; + + ulong tmp = (ulong)Math.Ceiling((double)(dividend / divisor)); + + wcf3.SetAssert(e, tmp); + + return wcf3; + } + + #endregion Operator Overloads + + // ----- SECTION: explicit conversions from WarthogCustomFloat to numeric base types ----------------* + + #region explicit conversions from WarthogCustomFloat + + public static explicit operator double(WarthogCustomFloat value) + { + if(value._mantissa == 0) + return 0; + + double r = (double)value._mantissa / (ulong)(1ul << 32); + + if(!value._isPositive) + r = -r; + + // return Math.Pow(2, value._exponent) * r; + return Math.ScaleB(r, value._exponent); + } + + #endregion explicit conversions from WarthogCustomFloat + + // ----- SECTION: implicit conversions from numeric base types to WarthogCustomFloat ----------------* + + #region implicit conversions to WarthogCustomFloat + + #endregion implicit conversions to WarthogCustomFloat + + // ----- SECTION: private serialization instance methods ----------------* + + #region serialization + + void IDeserializationCallback.OnDeserialization(object sender) + { + try + { + // verify that the deserialized number is well formed + SetAssert(this._exponent, this._mantissa); + } + catch(ArgumentException e) + { + throw new SerializationException("invalid serialization data", e); + } + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if(info == null) + throw new ArgumentNullException(nameof(info)); + + info.AddValue("Exponent", this._exponent); + info.AddValue("Mantissa", this._mantissa); + info.AddValue("IsPositive", this._isPositive); + } + + private WarthogCustomFloat(SerializationInfo info, StreamingContext context) + { + if(info == null) + throw new ArgumentNullException(nameof(info)); + + this._exponent = (int) info.GetValue("Exponent", typeof(int)); + this._mantissa = (uint) info.GetValue("Mantissa", typeof(uint)); + this._isPositive = (bool) info.GetValue("IsPositive", typeof(bool)); + } + + #endregion serialization + + // ----- SECTION: private instance utility methods ----------------* + + #region instance helper methods + + #endregion instance helper methods + + // ----- SECTION: private static utility methods -----------------* + + #region static helper methods + + public static WarthogCustomFloat Pow2(WarthogCustomFloat wcf) + { + WarthogCustomFloat wcf1; + + if(wcf._mantissa == 0) + { + wcf1 = new(1); + return wcf1; + } + + int e_x = wcf._exponent; + uint m = wcf._mantissa; + + if(e_x == 32) + { + if(wcf._isPositive) + { + wcf1 = new((long)m, true); + return wcf1; + } + + wcf1 = new(-((long)m), true); + return wcf1; + } + else + { + if(e_x > 0) + { + long e = (long)(m >> (32 - e_x)); + uint m_frac = (uint)(m << e_x); + if(m_frac == 0) + { + if(wcf._isPositive) + { + wcf1 = new(e, true); + return wcf1; + } + + wcf1 = new(e, true); + wcf1.Exponent = -wcf1.Exponent; + wcf1.Exponent += 2; + + return wcf1; + } + + WarthogCustomFloat frac = Zero(); + frac.ShiftLeft(0, m_frac); + + if(wcf._isPositive) + { + wcf1 = Pow2Fraction(frac); + wcf1.Exponent += e; + + return wcf1; + } + + wcf1 = Pow2Fraction(new WarthogCustomFloat(1) - frac); + wcf1.Exponent += e - 1; + wcf1.Exponent = -wcf1.Exponent; + + return wcf1; + } + else + { + if(wcf._isPositive) + return Pow2Fraction(wcf); + + wcf1 = Pow2Fraction(new WarthogCustomFloat(1) + wcf); + wcf1.Exponent = -wcf1.Exponent; + wcf1.Exponent += 1; + + return wcf1; + } + } + } + + public static WarthogCustomFloat Log2(WarthogCustomFloat wcf) + { + WarthogCustomFloat wcf1 = new(wcf._exponent, wcf._mantissa, wcf._isPositive); + int e = wcf._exponent; + wcf1._exponent = 0; + WarthogCustomFloat c0 = new(1, 2872373668, true); // = 1.33755322 + WarthogCustomFloat c1 = new(3, 2377545675, false); // = -4.42852392 + WarthogCustomFloat c2 = new(3, 3384280813, true); // = 6.30371424 + WarthogCustomFloat c3 = new(2, 3451338727, false); // = -3.21430967 + WarthogCustomFloat d = c3 + wcf1 * (c2 + wcf1 * (c1 + wcf1 * c0)); + + return new WarthogCustomFloat(e) + d; + } + + public static WarthogCustomFloat Pow2Fraction(WarthogCustomFloat wcf) + { + WarthogCustomFloat wcf1 = new(wcf._exponent, wcf._mantissa, wcf._isPositive); + // I have modified constants from here: https://github.com/nadavrot/fast_log/blob/83bd112c330976c291300eaa214e668f809367ab/src/exp_approx.cc#L18 + // such that they don't compute the euler logartihm but logarithm to base 2. + WarthogCustomFloat c0 = new(-3, 3207796260, true); // = 0.09335915850659268 + WarthogCustomFloat c1 = new(-2, 3510493713, true); // = 0.2043376277254389 + WarthogCustomFloat c2 = new(0, 3014961390, true); // = 0.7019754011048444 + WarthogCustomFloat c3 = new(1, 2147933481, true); // = 1.00020947 + WarthogCustomFloat d = c3 + wcf1 * (c2 + wcf1 * (c1 + wcf1 * c0)); + + return d; + } + + #endregion static helper methods +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Warthog/WarthogWorkerContext.cs b/src/Miningcore/Blockchain/Warthog/WarthogWorkerContext.cs new file mode 100644 index 000000000..1bb476023 --- /dev/null +++ b/src/Miningcore/Blockchain/Warthog/WarthogWorkerContext.cs @@ -0,0 +1,21 @@ +using Miningcore.Mining; + +namespace Miningcore.Blockchain.Warthog; + +public class WarthogWorkerContext : WorkerContextBase +{ + /// + /// Usually a wallet address + /// + public override string Miner { get; set; } + + /// + /// Arbitrary worker identififer for miners using multiple rigs + /// + public override string Worker { get; set; } + + /// + /// Unique value assigned per worker + /// + public string ExtraNonce1 { get; set; } +} diff --git a/src/Miningcore/Configuration/ClusterConfig.cs b/src/Miningcore/Configuration/ClusterConfig.cs index f31b3e3c4..c1fcfc140 100644 --- a/src/Miningcore/Configuration/ClusterConfig.cs +++ b/src/Miningcore/Configuration/ClusterConfig.cs @@ -51,6 +51,9 @@ public enum CoinFamily [EnumMember(Value = "progpow")] Progpow, + + [EnumMember(Value = "warthog")] + Warthog, } public abstract partial class CoinTemplate @@ -160,6 +163,7 @@ public abstract partial class CoinTemplate {CoinFamily.Kaspa, typeof(KaspaCoinTemplate)}, {CoinFamily.Nexa, typeof(BitcoinTemplate)}, {CoinFamily.Progpow, typeof(ProgpowCoinTemplate)}, + {CoinFamily.Warthog, typeof(WarthogCoinTemplate)}, }; } @@ -196,8 +200,10 @@ public class BitcoinNetworkParams [JsonConverter(typeof(StringEnumConverter), true)] public BitcoinSubfamily Subfamily { get; set; } + public JObject MerkleTreeHasher { get; set; } public JObject CoinbaseHasher { get; set; } public JObject HeaderHasher { get; set; } + public JObject ShareHasher { get; set; } public JObject BlockHasher { get; set; } [JsonProperty("posBlockHasher")] @@ -233,6 +239,9 @@ public class BitcoinNetworkParams [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public bool HasCommunityAddress { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool HasCoinbaseDevReward { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] [DefaultValue(1.0d)] public double ShareMultiplier { get; set; } = 1.0d; @@ -656,6 +665,37 @@ public partial class EthereumCoinTemplate : CoinTemplate public partial class KaspaCoinTemplate : CoinTemplate { + /// + /// Prefix of a valid mainnet address + /// See: parameter -> Bech32PrefixKaspa in blob/master/util/address.go + /// + public string AddressBech32Prefix { get; set; } + + /// + /// Prefix of a valid devnet address + /// See: parameter -> Bech32PrefixKaspaDev in blob/master/util/address.go + /// + public string AddressBech32PrefixDevnet { get; set; } + + /// + /// Prefix of a valid simnet address + /// See: parameter -> Bech32PrefixKaspaSim in blob/master/util/address.go + /// + public string AddressBech32PrefixSimnet { get; set; } + + /// + /// Prefix of a valid testnet address + /// See: parameter -> Bech32PrefixKaspaTest in blob/master/util/address.go + /// + public string AddressBech32PrefixTestnet { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(1.0d)] + public double ShareMultiplier { get; set; } = 1.0d; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(4294967296.0d)] + public double HashrateMultiplier { get; set; } = 4294967296.0d; } public partial class ProgpowCoinTemplate : BitcoinTemplate @@ -666,6 +706,10 @@ public partial class ProgpowCoinTemplate : BitcoinTemplate public string Progpower { get; set; } = "kawpow"; } +public partial class WarthogCoinTemplate : CoinTemplate +{ +} + #endregion // Coin Definitions public enum PayoutScheme @@ -675,6 +719,7 @@ public enum PayoutScheme SOLO = 3, PPS = 4, PPBS = 5, + PPLNSBF = 6, } public partial class ClusterLoggingConfig @@ -885,6 +930,8 @@ public partial class PoolShareBasedBanningConfig public int CheckThreshold { get; set; } // Check stats when this many shares have been submitted public double InvalidPercent { get; set; } // What percent of invalid shares triggers ban public int Time { get; set; } // How many seconds to ban worker for + public double? MinerEffortPercent { get; set; } // What percent of effort triggers ban + public int? MinerEffortTime { get; set; } // How many seconds to ban worker for } public partial class PoolPaymentProcessingConfig diff --git a/src/Miningcore/Configuration/ClusterConfigExtensions.cs b/src/Miningcore/Configuration/ClusterConfigExtensions.cs index 519466ffc..62d295a6f 100644 --- a/src/Miningcore/Configuration/ClusterConfigExtensions.cs +++ b/src/Miningcore/Configuration/ClusterConfigExtensions.cs @@ -55,12 +55,18 @@ public partial class BitcoinTemplate { public BitcoinTemplate() { + merkleTreeHasherValue = new Lazy(() => + HashAlgorithmFactory.GetHash(ComponentContext, MerkleTreeHasher)); + coinbaseHasherValue = new Lazy(() => HashAlgorithmFactory.GetHash(ComponentContext, CoinbaseHasher)); headerHasherValue = new Lazy(() => HashAlgorithmFactory.GetHash(ComponentContext, HeaderHasher)); + shareHasherValue = new Lazy(() => + HashAlgorithmFactory.GetHash(ComponentContext, ShareHasher)); + blockHasherValue = new Lazy(() => HashAlgorithmFactory.GetHash(ComponentContext, BlockHasher)); @@ -68,15 +74,19 @@ public BitcoinTemplate() HashAlgorithmFactory.GetHash(ComponentContext, PoSBlockHasher)); } + private readonly Lazy merkleTreeHasherValue; private readonly Lazy coinbaseHasherValue; private readonly Lazy headerHasherValue; + private readonly Lazy shareHasherValue; private readonly Lazy blockHasherValue; private readonly Lazy posBlockHasherValue; public IComponentContext ComponentContext { get; [UsedImplicitly] init; } + public IHashAlgorithm MerkleTreeHasherValue => merkleTreeHasherValue.Value; public IHashAlgorithm CoinbaseHasherValue => coinbaseHasherValue.Value; public IHashAlgorithm HeaderHasherValue => headerHasherValue.Value; + public IHashAlgorithm ShareHasherValue => shareHasherValue.Value; public IHashAlgorithm BlockHasherValue => blockHasherValue.Value; public IHashAlgorithm PoSBlockHasherValue => posBlockHasherValue.Value; @@ -99,12 +109,20 @@ public BitcoinNetworkParams GetNetwork(ChainName chain) public override string GetAlgorithmName() { - var hash = HeaderHasherValue; + switch(Symbol) + { + case "HNS": + return HeaderHasherValue.GetType().Name + " + " + ShareHasherValue.GetType().Name; + case "KCN": + return HeaderHasherValue.GetType().Name; + default: + var hash = HeaderHasherValue; - if(hash.GetType() == typeof(DigestReverser)) - return ((DigestReverser) hash).Upstream.GetType().Name; + if(hash.GetType() == typeof(DigestReverser)) + return ((DigestReverser) hash).Upstream.GetType().Name; - return hash.GetType().Name; + return hash.GetType().Name; + } } #endregion @@ -258,14 +276,21 @@ public override string GetAlgorithmName() { switch(Symbol) { + case "AIX": + return "AstrixHash"; case "KLS": + return "Karlsenhashv2"; + case "CSS": case "NTL": case "NXL": + case "PUG": return "Karlsenhash"; case "CAS": case "HTN": case "PYI": return "Pyrinhash"; + case "SPR": + return " SpectreX"; default: // TODO: return variant return "kHeavyHash"; @@ -297,6 +322,18 @@ public override string GetAlgorithmName() #endregion } +public partial class WarthogCoinTemplate +{ + #region Overrides of CoinTemplate + + public override string GetAlgorithmName() + { + return "PoBW"; + } + + #endregion +} + public partial class PoolConfig { /// diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs b/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs index 2705e3835..1f75d4b6b 100644 --- a/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs +++ b/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs @@ -1,18 +1,32 @@ +using System; using Miningcore.Contracts; using Miningcore.Native; +using Miningcore.Time; +using NLog; namespace Miningcore.Crypto.Hashing.Algorithms; [Identifier("fishhash")] public unsafe class FishHash : IHashAlgorithm { - private bool enableFishHashPlus = false; + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + + public byte fishHashKernel { get; private set; } = 1; private IntPtr handle = IntPtr.Zero; private readonly object genLock = new(); - public FishHash(bool enableFishHashPlus = false, bool fullContext = false, uint threads = 4) + public const byte FishHashKernelV1 = 1; + public const byte FishHashKernelPlus = 2; + public const byte FishHashKernelV2 = 3; + + public FishHash(byte fishHashKernel = 1, bool fullContext = false, uint threads = 4) { - this.enableFishHashPlus = enableFishHashPlus; + Contract.Requires(fishHashKernel >= 1); + + this.fishHashKernel = fishHashKernel; + + var started = DateTime.Now; + logger.Debug(() => $"Generating light cache"); lock(genLock) { @@ -20,6 +34,8 @@ public FishHash(bool enableFishHashPlus = false, bool fullContext = false, uint if(fullContext) Multihash.fishhashPrebuildDataset(this.handle, threads); } + + logger.Debug(() => $"Done generating light cache after {DateTime.Now - started}"); } public void Digest(ReadOnlySpan data, Span result, params object[] extra) @@ -31,7 +47,7 @@ public void Digest(ReadOnlySpan data, Span result, params object[] e { fixed (byte* output = result) { - Multihash.fishhash(output, this.handle, input, (uint) data.Length, this.enableFishHashPlus); + Multihash.fishhash(output, this.handle, input, (uint) data.Length, this.fishHashKernel); } } } diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs b/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs index 01e6e867e..15cfe22a0 100644 --- a/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs +++ b/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs @@ -1,18 +1,28 @@ +using System; using Miningcore.Contracts; using Miningcore.Native; +using Miningcore.Time; +using NLog; namespace Miningcore.Crypto.Hashing.Algorithms; [Identifier("fishhashkarlsen")] public unsafe class FishHashKarlsen : IHashAlgorithm { - private bool enableFishHashPlus = false; + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + + public byte fishHashKernel { get; private set; } = 1; private IntPtr handle = IntPtr.Zero; private readonly object genLock = new(); - public FishHashKarlsen(bool enableFishHashPlus = false, bool fullContext = false, uint threads = 4) + public FishHashKarlsen(byte fishHashKernel = 1, bool fullContext = false, uint threads = 4) { - this.enableFishHashPlus = enableFishHashPlus; + Contract.Requires(fishHashKernel >= 1); + + this.fishHashKernel = fishHashKernel; + + var started = DateTime.Now; + logger.Debug(() => $"Generating light cache"); lock(genLock) { @@ -20,6 +30,8 @@ public FishHashKarlsen(bool enableFishHashPlus = false, bool fullContext = false if(fullContext) Multihash.fishhashPrebuildDataset(this.handle, threads); } + + logger.Debug(() => $"Done generating light cache after {DateTime.Now - started}"); } public void Digest(ReadOnlySpan data, Span result, params object[] extra) @@ -27,22 +39,11 @@ public void Digest(ReadOnlySpan data, Span result, params object[] e Contract.Requires(this.handle != IntPtr.Zero); Contract.Requires(result.Length >= 32); - // concat data in byte[64] - Span seedBytes = stackalloc byte[64]; - data.CopyTo(seedBytes); - - var mixHash = new Multihash.Fishhash_hash256(); - - var seed = new Multihash.Fishhash_hash512(); - seed.bytes = seedBytes.ToArray(); - - mixHash = (this.enableFishHashPlus) ? Multihash.fishhashplusKernel(this.handle, ref seed) : Multihash.fishhashKernel(this.handle, ref seed); - - fixed(byte* input = mixHash.bytes) + fixed(byte* input = data) { fixed (byte* output = result) { - Multihash.blake3(input, output, (uint) mixHash.bytes.Length, null, 0); + Multihash.fishhaskarlsen(output, this.handle, input, (uint) data.Length, this.fishHashKernel); } } } diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/Flex.cs b/src/Miningcore/Crypto/Hashing/Algorithms/Flex.cs new file mode 100644 index 000000000..2d872a7ef --- /dev/null +++ b/src/Miningcore/Crypto/Hashing/Algorithms/Flex.cs @@ -0,0 +1,22 @@ +using Miningcore.Contracts; +using Miningcore.Native; + +namespace Miningcore.Crypto.Hashing.Algorithms; + +[Identifier("flex")] + +public unsafe class Flex : IHashAlgorithm +{ + public void Digest(ReadOnlySpan data, Span result, params object[] extra) + { + Contract.Requires(result.Length >= 32); + + fixed (byte* input = data) + { + fixed (byte* output = result) + { + Multihash.flex(input, output); + } + } + } +} diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/Kezzak.cs b/src/Miningcore/Crypto/Hashing/Algorithms/Kezzak.cs index 51fd40398..158c6c597 100644 --- a/src/Miningcore/Crypto/Hashing/Algorithms/Kezzak.cs +++ b/src/Miningcore/Crypto/Hashing/Algorithms/Kezzak.cs @@ -4,7 +4,7 @@ namespace Miningcore.Crypto.Hashing.Algorithms; -[Identifier("groestl-myriad")] +[Identifier("kezzak")] public unsafe class Kezzak : IHashAlgorithm { public void Digest(ReadOnlySpan data, Span result, params object[] extra) diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/Sha256T.cs b/src/Miningcore/Crypto/Hashing/Algorithms/Sha256T.cs new file mode 100644 index 000000000..549dd6bbc --- /dev/null +++ b/src/Miningcore/Crypto/Hashing/Algorithms/Sha256T.cs @@ -0,0 +1,23 @@ +using System.Security.Cryptography; +using Miningcore.Contracts; + +namespace Miningcore.Crypto.Hashing.Algorithms; + +/// +/// Sha-256 triple round +/// +[Identifier("sha256t")] +public class Sha256T : IHashAlgorithm +{ + public void Digest(ReadOnlySpan data, Span result, params object[] extra) + { + Contract.Requires(result.Length >= 32); + + using(var hasher = SHA256.Create()) + { + hasher.TryComputeHash(data, result, out _); + hasher.TryComputeHash(result, result, out _); + hasher.TryComputeHash(result, result, out _); + } + } +} diff --git a/src/Miningcore/Mining/PoolBase.cs b/src/Miningcore/Mining/PoolBase.cs index 7de9a6f92..1c4c6926c 100644 --- a/src/Miningcore/Mining/PoolBase.cs +++ b/src/Miningcore/Mining/PoolBase.cs @@ -53,6 +53,8 @@ protected PoolBase(IComponentContext ctx, this.serializerSettings = serializerSettings; this.cf = cf; + blocksRepo = ctx.Resolve(); + shareRepo = ctx.Resolve(); this.statsRepo = statsRepo; this.mapper = mapper; this.nicehashService = nicehashService; @@ -61,6 +63,8 @@ protected PoolBase(IComponentContext ctx, protected PoolStats poolStats = new(); protected readonly JsonSerializerSettings serializerSettings; protected readonly IConnectionFactory cf; + protected readonly IBlockRepository blocksRepo; + protected readonly IShareRepository shareRepo; protected readonly IStatsRepository statsRepo; protected readonly IMapper mapper; protected readonly NicehashService nicehashService; @@ -210,6 +214,7 @@ await Parallel.ForEachAsync(connections, ct, async (kvp, _ct) => { if(!_ct.IsCancellationRequested && connection.IsAlive && connection.Context.IsAuthorized) { + await SuspiciousMinerEffortCheck(connection, ct); ZombieCheck(connection); await func(connection, _ct); @@ -225,6 +230,27 @@ await Parallel.ForEachAsync(connections, ct, async (kvp, _ct) => }); } + protected async Task SuspiciousMinerEffortCheck(StratumConnection connection, CancellationToken ct) + { + if(poolConfig.Banning?.Enabled == true && poolConfig.Banning?.MinerEffortPercent.HasValue == true && poolConfig.Banning?.MinerEffortTime.HasValue == true) + { + var lastBlockTime = await cf.Run(con => blocksRepo.GetLastPoolBlockTimeAsync(con, poolConfig.Id, ct)); + DateTime dateStart = (lastBlockTime.HasValue) ? lastBlockTime.Value : connection.Context.Created; + var minerEffort = await cf.Run(con => shareRepo.GetMinerEffortBetweenCreatedAsync(con, poolConfig.Id, connection.Context.Miner, dateStart, clock.Now, ct)); + if(minerEffort.HasValue) + { + logger.Debug(() => $"[{connection.Context.Miner}] Checking effort for worker: {minerEffort.Value}%"); + + if(minerEffort.Value >= poolConfig.Banning.MinerEffortPercent.Value) + { + banManager.Ban(connection.RemoteEndpoint.Address, TimeSpan.FromSeconds(poolConfig.Banning.MinerEffortTime.Value)); + + throw new Exception($"Detected suspicious over-sharing-worker: Current effort over {poolConfig.Banning.MinerEffortPercent.Value}%. Banning worker for {poolConfig.Banning.MinerEffortTime.Value} seconds"); + } + } + } + } + protected void ZombieCheck(StratumConnection connection) { if(poolConfig.ClientConnectionTimeout > 0) diff --git a/src/Miningcore/Mining/WorkerContextBase.cs b/src/Miningcore/Mining/WorkerContextBase.cs index 8ec8137e1..18068437b 100644 --- a/src/Miningcore/Mining/WorkerContextBase.cs +++ b/src/Miningcore/Mining/WorkerContextBase.cs @@ -33,6 +33,16 @@ public class WorkerContextBase /// public double? PreviousDifficulty { get; set; } + /// + /// Usually a wallet address + /// + public virtual string Miner { get; set; } + + /// + /// Arbitrary worker identififer for miners using multiple rigs + /// + public virtual string Worker { get; set; } + /// /// UserAgent reported by Stratum /// diff --git a/src/Miningcore/Miningcore.csproj b/src/Miningcore/Miningcore.csproj index 846867118..711225463 100644 --- a/src/Miningcore/Miningcore.csproj +++ b/src/Miningcore/Miningcore.csproj @@ -42,7 +42,7 @@ - + @@ -51,17 +51,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + @@ -70,23 +70,24 @@ - + + - + - + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Miningcore/Native/AstroBWTv3.cs b/src/Miningcore/Native/AstroBWTv3.cs new file mode 100644 index 000000000..49efca79f --- /dev/null +++ b/src/Miningcore/Native/AstroBWTv3.cs @@ -0,0 +1,36 @@ +using Miningcore.Contracts; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Miningcore.Extensions; +using Miningcore.Messaging; +using Miningcore.Native; +using Miningcore.Notifications.Messages; + +// ReSharper disable InconsistentNaming + +namespace Miningcore.Native; + +public unsafe class AstroBWTv3 +{ + internal static IMessageBus messageBus; + + [DllImport("libdero", EntryPoint = "astroBWTv3_export", CallingConvention = CallingConvention.Cdecl)] + public static extern void astroBWTv3(byte* input, int inputLength, void* output); + + public void Digest(ReadOnlySpan data, Span result, params object[] extra) + { + Contract.Requires(result.Length >= 32); + + var sw = Stopwatch.StartNew(); + + fixed (byte* input = data) + { + fixed (byte* output = result) + { + astroBWTv3(input, data.Length, output); + + messageBus?.SendTelemetry("AstroBWTv3", TelemetryCategory.Hash, sw.Elapsed, true); + } + } + } +} \ No newline at end of file diff --git a/src/Miningcore/Native/Multihash.cs b/src/Miningcore/Native/Multihash.cs index f8f0774f3..ebe9f3a5f 100644 --- a/src/Miningcore/Native/Multihash.cs +++ b/src/Miningcore/Native/Multihash.cs @@ -207,35 +207,19 @@ public static unsafe class Multihash [DllImport("libmultihash", EntryPoint = "yespowerTIDE_export", CallingConvention = CallingConvention.Cdecl)] public static extern void yespowerTIDE(byte* input, void* output, uint inputLength); - + + [DllImport("libmultihash", EntryPoint = "flex_export", CallingConvention = CallingConvention.Cdecl)] + public static extern void flex(byte* input, void* output); + [DllImport("libmultihash", EntryPoint = "fishhash_get_context", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr fishhashGetContext(bool fullContext = false); - - [DllImport("libmultihash", EntryPoint = "fishhash_kernel", CallingConvention = CallingConvention.Cdecl)] - public static extern Fishhash_hash256 fishhashKernel(IntPtr context, ref Fishhash_hash512 seed); - [DllImport("libmultihash", EntryPoint = "fishhashplus_kernel", CallingConvention = CallingConvention.Cdecl)] - public static extern Fishhash_hash256 fishhashplusKernel(IntPtr context, ref Fishhash_hash512 seed); - [DllImport("libmultihash", EntryPoint = "fishhash_prebuild_dataset", CallingConvention = CallingConvention.Cdecl)] public static extern void fishhashPrebuildDataset(IntPtr context, uint number_threads = 1); [DllImport("libmultihash", EntryPoint = "fishhash_hash", CallingConvention = CallingConvention.Cdecl)] - public static extern void fishhash(void* output, IntPtr context, byte* input, uint inputLength, bool enableFishHashPlus = false); - - [StructLayout(LayoutKind.Explicit)] - public struct Fishhash_hash256 - { - [FieldOffset(0)] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] bytes;//x32 - } - - [StructLayout(LayoutKind.Explicit)] - public struct Fishhash_hash512 - { - [FieldOffset(0)] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public byte[] bytes;//x64 - } + public static extern void fishhash(void* output, IntPtr context, byte* input, uint inputLength, byte fishHashKernel = 1); + + [DllImport("libmultihash", EntryPoint = "fishhaskarlsen_hash", CallingConvention = CallingConvention.Cdecl)] + public static extern void fishhaskarlsen(void* output, IntPtr context, byte* input, uint inputLength, byte fishHashKernel = 1); } diff --git a/src/Miningcore/Native/Verushash.cs b/src/Miningcore/Native/Verushash.cs index 0fb10949e..3851aaf3f 100644 --- a/src/Miningcore/Native/Verushash.cs +++ b/src/Miningcore/Native/Verushash.cs @@ -11,6 +11,9 @@ public unsafe class Verushash { [DllImport("libverushash", EntryPoint = "verushash2b2_export", CallingConvention = CallingConvention.Cdecl)] public static extern void verushash2b2(byte* input, byte* output, int input_length); + + [DllImport("libverushash", EntryPoint = "verushash2b2o_export", CallingConvention = CallingConvention.Cdecl)] + public static extern void verushash2b2o(byte* input, byte* output, int input_length); [DllImport("libverushash", EntryPoint = "verushash2b1_export", CallingConvention = CallingConvention.Cdecl)] public static extern void verushash2b1(byte* input, byte* output, int input_length); @@ -37,19 +40,23 @@ public void Digest(ReadOnlySpan data, Span result, string version = case VeruscoinConstants.HashVersion2b2: verushash2b2(input, output, data.Length); break; - + + case VeruscoinConstants.HashVersion2b2o: + verushash2b2o(input, output, data.Length); + break; + case VeruscoinConstants.HashVersion2b1: verushash2b1(input, output, data.Length); break; - + case VeruscoinConstants.HashVersion2b: verushash2b(input, output, data.Length); break; - + case VeruscoinConstants.HashVersion2: verushash2(input, output, data.Length); break; - + default: verushash(input, output, data.Length); break; diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSBFPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSBFPaymentScheme.cs new file mode 100644 index 000000000..6e3ee9caf --- /dev/null +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSBFPaymentScheme.cs @@ -0,0 +1,259 @@ +using System.Data; +using System.Data.Common; +using System.Net.Sockets; +using Miningcore.Configuration; +using Miningcore.Extensions; +using Miningcore.Mining; +using Miningcore.Persistence; +using Miningcore.Persistence.Model; +using Miningcore.Persistence.Repositories; +using Miningcore.Util; +using NLog; +using Polly; +using Contract = Miningcore.Contracts.Contract; + +namespace Miningcore.Payments.PaymentSchemes; + +/// +/// PPLNSBF payout scheme implementation +/// +// ReSharper disable once InconsistentNaming +public class PPLNSBFPaymentScheme : IPayoutScheme +{ + public PPLNSBFPaymentScheme( + IConnectionFactory cf, + IShareRepository shareRepo, + IBlockRepository blockRepo, + IBalanceRepository balanceRepo) + { + Contract.RequiresNonNull(cf); + Contract.RequiresNonNull(shareRepo); + Contract.RequiresNonNull(blockRepo); + Contract.RequiresNonNull(balanceRepo); + + this.cf = cf; + this.shareRepo = shareRepo; + this.blockRepo = blockRepo; + this.balanceRepo = balanceRepo; + + BuildFaultHandlingPolicy(); + } + + private readonly IBalanceRepository balanceRepo; + private readonly IBlockRepository blockRepo; + private readonly IConnectionFactory cf; + private readonly IShareRepository shareRepo; + private static readonly ILogger logger = LogManager.GetLogger("PPLNSBF Payment"); + + private const int RetryCount = 4; + private IAsyncPolicy shareReadFaultPolicy; + + private class Config + { + public decimal Factor { get; set; } + public decimal BlockFinderPercentage { get; set; } + } + + #region IPayoutScheme + + public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, + Block block, decimal blockReward, CancellationToken ct) + { + var poolConfig = pool.Config; + var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; + + // PPLNS window (see https://bitcointalk.org/index.php?topic=39832) + var window = payoutConfig?.ToObject()?.Factor ?? 2.0m; + + // calculate rewards + var shares = new Dictionary(); + var rewards = new Dictionary(); + var shareCutOffDate = await CalculateRewardsAsync(pool, payoutHandler, window, block, blockReward, shares, rewards, ct); + + // update balances + foreach(var address in rewards.Keys) + { + var amount = rewards[address]; + + if(amount > 0) + { + logger.Info(() => $"Crediting {address} with {payoutHandler.FormatAmount(amount)} for {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) shares for block {block.BlockHeight}"); + await balanceRepo.AddAmountAsync(con, tx, poolConfig.Id, address, amount, $"Reward for {FormatUtil.FormatQuantity(shares[address])} shares for block {block.BlockHeight}"); + } + } + + // delete discarded shares + if(shareCutOffDate.HasValue) + { + var cutOffCount = await shareRepo.CountSharesBeforeAsync(con, tx, poolConfig.Id, shareCutOffDate.Value, ct); + + if(cutOffCount > 0) + { + await LogDiscardedSharesAsync(ct, poolConfig, block, shareCutOffDate.Value); + + logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}"); + await shareRepo.DeleteSharesBeforeAsync(con, tx, poolConfig.Id, shareCutOffDate.Value, ct); + } + } + + // diagnostics + var totalShareCount = shares.Values.ToList().Sum(x => new decimal(x)); + var totalRewards = rewards.Values.ToList().Sum(x => x); + + if(totalRewards > 0) + logger.Info(() => $"{FormatUtil.FormatQuantity((double) totalShareCount)} ({Math.Round(totalShareCount, 2)}) shares contributed to a total payout of {payoutHandler.FormatAmount(totalRewards)} ({totalRewards / blockReward * 100:0.00}% of block reward) to {rewards.Keys.Count} addresses"); + } + + private async Task LogDiscardedSharesAsync(CancellationToken ct, PoolConfig poolConfig, Block block, DateTime value) + { + var before = value; + var pageSize = 50000; + var currentPage = 0; + var shares = new Dictionary(); + + while(true) + { + logger.Info(() => $"Fetching page {currentPage} of discarded shares for pool {poolConfig.Id}, block {block.BlockHeight}"); + + var page = await shareReadFaultPolicy.ExecuteAsync(() => + cf.Run(con => shareRepo.ReadSharesBeforeAsync(con, poolConfig.Id, before, false, pageSize, ct))); + + currentPage++; + + for(var i = 0; i < page.Length; i++) + { + var share = page[i]; + var address = share.Miner; + + // record attributed shares for diagnostic purposes + if(!shares.ContainsKey(address)) + shares[address] = share.Difficulty; + else + shares[address] += share.Difficulty; + } + + if(page.Length < pageSize) + break; + + before = page[^1].Created; + } + + if(shares.Keys.Count > 0) + { + // sort addresses by shares + var addressesByShares = shares.Keys.OrderByDescending(x => shares[x]); + + logger.Info(() => $"{FormatUtil.FormatQuantity(shares.Values.Sum())} ({shares.Values.Sum()}) total discarded shares, block {block.BlockHeight}"); + + foreach(var address in addressesByShares) + logger.Info(() => $"{address} = {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) discarded shares, block {block.BlockHeight}"); + } + } + + #endregion // IPayoutScheme + + private async Task CalculateRewardsAsync(IMiningPool pool, IPayoutHandler payoutHandler, decimal window, Block block, decimal blockReward, + Dictionary shares, Dictionary rewards, CancellationToken ct) + { + var poolConfig = pool.Config; + var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; + // calculate the block finder reward (% of the block reward) + var blockFinderPercentage = payoutConfig?.ToObject()?.BlockFinderPercentage ?? 5.0m; + var blockFinderReward = blockReward * (blockFinderPercentage / 100); + var done = false; + var before = block.Created; + var inclusive = true; + var pageSize = 50000; + var currentPage = 0; + var accumulatedScore = 0.0m; + var blockRewardRemaining = blockReward - blockFinderReward; + DateTime? shareCutOffDate = null; + + // log the block finder reward + logger.Info(() => $"Block finder reward: {payoutHandler.FormatAmount(blockFinderReward)} [{blockFinderPercentage}% of {payoutHandler.FormatAmount(blockReward)}] for block {block.BlockHeight} mined by {block.Miner}"); + + // give block finder reward + if(!rewards.ContainsKey(block.Miner)) + rewards[block.Miner] = blockFinderReward; + else + rewards[block.Miner] += blockFinderReward; + + while(!done && !ct.IsCancellationRequested) + { + logger.Info(() => $"Fetching page {currentPage} of shares for pool {poolConfig.Id}, block {block.BlockHeight}"); + + var page = await shareReadFaultPolicy.ExecuteAsync(() => + cf.Run(con => shareRepo.ReadSharesBeforeAsync(con, poolConfig.Id, before, inclusive, pageSize, ct))); + + inclusive = false; + currentPage++; + + for(var i = 0; !done && i < page.Length; i++) + { + var share = page[i]; + + var address = share.Miner; + var shareDiffAdjusted = payoutHandler.AdjustShareDifficulty(share.Difficulty); + + // record attributed shares for diagnostic purposes + if(!shares.ContainsKey(address)) + shares[address] = shareDiffAdjusted; + else + shares[address] += shareDiffAdjusted; + + var score = (decimal) (shareDiffAdjusted / share.NetworkDifficulty); + + // if accumulated score would cross threshold, cap it to the remaining value + if(accumulatedScore + score >= window) + { + score = window - accumulatedScore; + shareCutOffDate = share.Created; + done = true; + } + + // calculate reward + var reward = score * (blockReward - blockFinderReward) / window; + accumulatedScore += score; + blockRewardRemaining -= reward; + + // this should never happen + if(blockRewardRemaining <= 0 && !done) + throw new OverflowException("blockRewardRemaining < 0"); + + if(reward > 0) + { + // accumulate miner reward + if(!rewards.ContainsKey(address)) + rewards[address] = reward; + else + rewards[address] += reward; + } + } + + if(page.Length < pageSize) + break; + + before = page[^1].Created; + } + + logger.Info(() => $"Balance-calculation for pool {poolConfig.Id}, block {block.BlockHeight} completed with accumulated score {accumulatedScore:0.####} ({(accumulatedScore / window) * 100:0.#}%)"); + + return shareCutOffDate; + } + + private void BuildFaultHandlingPolicy() + { + var retry = Policy + .Handle() + .Or() + .Or() + .RetryAsync(RetryCount, OnPolicyRetry); + + shareReadFaultPolicy = retry; + } + + private static void OnPolicyRetry(Exception ex, int retry, object context) + { + logger.Warn(() => $"Retry {retry} due to {ex.Source}: {ex.GetType().Name} ({ex.Message})"); + } +} diff --git a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs index d2c5b1199..dc90ad31f 100644 --- a/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs +++ b/src/Miningcore/Payments/PaymentSchemes/PPLNSPaymentScheme.cs @@ -169,7 +169,7 @@ private async Task LogDiscardedSharesAsync(CancellationToken ct, PoolConfig pool logger.Info(() => $"Fetching page {currentPage} of shares for pool {poolConfig.Id}, block {block.BlockHeight}"); var page = await shareReadFaultPolicy.ExecuteAsync(() => - cf.Run(con => shareRepo.ReadSharesBeforeAsync(con, poolConfig.Id, before, inclusive, pageSize, ct))); //, sw, logger)); + cf.Run(con => shareRepo.ReadSharesBeforeAsync(con, poolConfig.Id, before, inclusive, pageSize, ct))); inclusive = false; currentPage++; diff --git a/src/Miningcore/Payments/PayoutManager.cs b/src/Miningcore/Payments/PayoutManager.cs index 69fcee656..cc333d609 100644 --- a/src/Miningcore/Payments/PayoutManager.cs +++ b/src/Miningcore/Payments/PayoutManager.cs @@ -169,6 +169,9 @@ await cf.RunTx(async (con, tx) => if(!block.Effort.HasValue) // fill block effort if empty await CalculateBlockEffortAsync(pool, poolConfig, block, handler, ct); + if(!block.MinerEffort.HasValue) // fill block miner effort if empty + await CalculateMinerEffortAsync(pool, poolConfig, block, handler, ct); + switch(block.Status) { case BlockStatus.Confirmed: @@ -247,6 +250,31 @@ private async Task CalculateBlockEffortAsync(IMiningPool pool, PoolConfig poolCo block.Effort = handler.AdjustBlockEffort(block.Effort.Value); } + private async Task CalculateMinerEffortAsync(IMiningPool pool, PoolConfig poolConfig, Block block, IPayoutHandler handler, CancellationToken ct) + { + // get share date-range + var from = DateTime.MinValue; + var to = block.Created; + + var miner = block.Miner; + + // get last block for pool + var lastBlock = await cf.Run(con => blockRepo.GetMinerBlockBeforeAsync(con, poolConfig.Id, miner, new[] + { + BlockStatus.Confirmed, + BlockStatus.Orphaned, + BlockStatus.Pending, + }, block.Created, ct)); + + if(lastBlock != null) + from = lastBlock.Created; + + block.MinerEffort = await cf.Run(con => shareRepo.GetMinerShareDifficultyBetweenAsync(con, pool.Config.Id, miner, from, to, ct)); + + if(block.MinerEffort.HasValue) + block.MinerEffort = handler.AdjustBlockEffort(block.MinerEffort.Value); + } + protected override async Task ExecuteAsync(CancellationToken ct) { try diff --git a/src/Miningcore/Persistence/Model/Block.cs b/src/Miningcore/Persistence/Model/Block.cs index acf027cc6..8979c045d 100644 --- a/src/Miningcore/Persistence/Model/Block.cs +++ b/src/Miningcore/Persistence/Model/Block.cs @@ -10,6 +10,7 @@ public class Block public string Type { get; set; } public double ConfirmationProgress { get; set; } public double? Effort { get; set; } + public double? MinerEffort { get; set; } public string TransactionConfirmationData { get; set; } public string Miner { get; set; } public decimal Reward { get; set; } diff --git a/src/Miningcore/Persistence/Model/Projections/MinerStats.cs b/src/Miningcore/Persistence/Model/Projections/MinerStats.cs index 898b29383..eb396ed13 100644 --- a/src/Miningcore/Persistence/Model/Projections/MinerStats.cs +++ b/src/Miningcore/Persistence/Model/Projections/MinerStats.cs @@ -19,6 +19,10 @@ public class MinerStats public decimal TotalPaid { get; init; } public decimal TodayPaid { get; init; } public Payment LastPayment { get; set; } + public double MinerEffort { get; set; } + public DateTime? LastMinerBlockTime { get; set; } public WorkerPerformanceStatsContainer Performance { get; set; } public MinerWorkerPerformanceStats[] PerformanceStats { get; init; } + public long TotalConfirmedBlocks { get; set; } + public long TotalPendingBlocks { get; set; } } diff --git a/src/Miningcore/Persistence/Postgres/Entities/Block.cs b/src/Miningcore/Persistence/Postgres/Entities/Block.cs index 6a233e495..bed7fb91f 100644 --- a/src/Miningcore/Persistence/Postgres/Entities/Block.cs +++ b/src/Miningcore/Persistence/Postgres/Entities/Block.cs @@ -10,6 +10,7 @@ public class Block public string Type { get; set; } public double ConfirmationProgress { get; set; } public double? Effort { get; set; } + public double? MinerEffort { get; set; } public string TransactionConfirmationData { get; set; } public string Miner { get; set; } public decimal Reward { get; set; } diff --git a/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs index 837438c9e..a7954f6d9 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/BlockRepository.cs @@ -21,9 +21,9 @@ public async Task InsertAsync(IDbConnection con, IDbTransaction tx, Block block) const string query = @"INSERT INTO blocks(poolid, blockheight, networkdifficulty, status, type, transactionconfirmationdata, - miner, reward, effort, confirmationprogress, source, hash, created) + miner, reward, effort, minereffort, confirmationprogress, source, hash, created) VALUES(@poolid, @blockheight, @networkdifficulty, @status, @type, @transactionconfirmationdata, - @miner, @reward, @effort, @confirmationprogress, @source, @hash, @created)"; + @miner, @reward, @effort, @minereffort, @confirmationprogress, @source, @hash, @created)"; await con.ExecuteAsync(query, mapped, tx); } @@ -39,7 +39,7 @@ public async Task UpdateBlockAsync(IDbConnection con, IDbTransaction tx, Block b var mapped = mapper.Map(block); const string query = @"UPDATE blocks SET blockheight = @blockheight, status = @status, type = @type, - reward = @reward, effort = @effort, confirmationprogress = @confirmationprogress, hash = @hash WHERE id = @id"; + reward = @reward, effort = @effort, minereffort = @minereffort, confirmationprogress = @confirmationprogress, hash = @hash WHERE id = @id"; await con.ExecuteAsync(query, mapped, tx); } @@ -117,6 +117,21 @@ public async Task GetBlockBeforeAsync(IDbConnection con, string poolId, B .Select(mapper.Map) .FirstOrDefault(); } + + public async Task GetMinerBlockBeforeAsync(IDbConnection con, string poolId, string miner, BlockStatus[] status, DateTime before, CancellationToken ct) + { + const string query = @"SELECT * FROM blocks WHERE poolid = @poolid AND miner = @miner AND status = ANY(@status) AND created < @before + ORDER BY created DESC FETCH NEXT 1 ROWS ONLY"; + return (await con.QueryAsync(new CommandDefinition(query, new + { + poolId, + miner, + before, + status = status.Select(x => x.ToString().ToLower()).ToArray() + }, cancellationToken: ct))) + .Select(mapper.Map) + .FirstOrDefault(); + } public async Task GetBlockBeforeCountAsync(IDbConnection con, string poolId, BlockStatus[] status, DateTime before) { @@ -136,7 +151,28 @@ public Task GetPoolBlockCountAsync(IDbConnection con, string poolId, Cance return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId }, cancellationToken: ct)); } - + + public Task GetTotalConfirmedBlocksAsync(IDbConnection con, string poolId, CancellationToken ct) + { + const string query = @"SELECT COUNT(*) FROM blocks WHERE poolid = @poolId AND status = 'confirmed'"; + + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId }, cancellationToken: ct)); + } + + public Task GetTotalPendingBlocksAsync(IDbConnection con, string poolId, CancellationToken ct) + { + const string query = @"SELECT COUNT(*) FROM blocks WHERE poolid = @poolId AND status = 'pending'"; + + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId }, cancellationToken: ct)); + } + + public Task GetLastConfirmedBlockRewardAsync(IDbConnection con, string poolId, CancellationToken ct) + { + const string query = @"SELECT reward FROM blocks WHERE poolid = @poolId AND status = 'confirmed' ORDER BY created DESC LIMIT 1"; + + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId }, cancellationToken: ct)); + } + public Task GetMinerBlockCountAsync(IDbConnection con, string poolId, string address, CancellationToken ct) { const string query = @"SELECT COUNT(*) FROM blocks WHERE poolid = @poolId AND miner = @address"; @@ -144,13 +180,19 @@ public Task GetMinerBlockCountAsync(IDbConnection con, string poolId, stri return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId, address }, cancellationToken: ct)); } - public Task GetLastPoolBlockTimeAsync(IDbConnection con, string poolId) + public Task GetLastPoolBlockTimeAsync(IDbConnection con, string poolId, CancellationToken ct) { const string query = @"SELECT created FROM blocks WHERE poolid = @poolId ORDER BY created DESC LIMIT 1"; - return con.ExecuteScalarAsync(query, new { poolId }); + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId }, cancellationToken: ct)); } - + + public Task GetLastMinerBlockTimeAsync(IDbConnection con, string poolId, string address, CancellationToken ct) + { + const string query = @"SELECT created FROM blocks WHERE poolid = @poolId AND miner = @address ORDER BY created DESC LIMIT 1"; + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId, address }, cancellationToken: ct)); + } + public async Task GetBlockByPoolHeightAndTypeAsync(IDbConnection con, string poolId, long height, string type) { const string query = @"SELECT * FROM blocks WHERE poolid = @poolId AND blockheight = @height AND type = @type"; diff --git a/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs index f06d6ed7f..90e75a07a 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs @@ -75,11 +75,18 @@ public Task CountSharesByMinerAsync(IDbConnection con, IDbTransaction tx, return con.QuerySingleAsync(new CommandDefinition(query, new { poolId, miner}, tx, cancellationToken: ct)); } - public Task GetEffortBetweenCreatedAsync(IDbConnection con, string poolId, double shareConst, DateTime start, DateTime end) + public Task GetEffortBetweenCreatedAsync(IDbConnection con, string poolId, double shareConst, DateTime start, DateTime end, CancellationToken ct) { const string query = "SELECT SUM((difficulty * @shareConst) / networkdifficulty) FROM shares WHERE poolid = @poolId AND created > @start AND created < @end"; - return con.QuerySingleAsync(query, new { poolId, shareConst, start, end }); + return con.QuerySingleAsync(new CommandDefinition(query, new { poolId, shareConst, start, end }, cancellationToken: ct)); + } + + public Task GetMinerEffortBetweenCreatedAsync(IDbConnection con, string poolId, string miner, DateTime start, DateTime end, CancellationToken ct) + { + const string query = "SELECT SUM(difficulty / networkdifficulty) FROM shares WHERE poolid = @poolId AND miner = @miner AND created > @start AND created < @end"; + + return con.QuerySingleAsync(new CommandDefinition(query, new { poolId, miner, start, end }, cancellationToken: ct)); } public async Task DeleteSharesByMinerAsync(IDbConnection con, IDbTransaction tx, string poolId, string miner, CancellationToken ct) @@ -103,6 +110,13 @@ public async Task DeleteSharesBeforeAsync(IDbConnection con, IDbTransaction tx, return con.QuerySingleAsync(new CommandDefinition(query, new { poolId, start, end }, cancellationToken: ct)); } + public Task GetMinerShareDifficultyBetweenAsync(IDbConnection con, string poolId, string miner, DateTime start, DateTime end, CancellationToken ct) + { + const string query = "SELECT SUM(difficulty / networkdifficulty) FROM shares WHERE poolid = @poolId AND miner = @miner AND created > @start AND created < @end"; + + return con.QuerySingleAsync(new CommandDefinition(query, new { poolId, miner, start, end }, cancellationToken: ct)); + } + public Task GetEffectiveAccumulatedShareDifficultyBetweenAsync(IDbConnection con, string poolId, DateTime start, DateTime end, CancellationToken ct) { const string query = "SELECT SUM(difficulty / networkdifficulty) FROM shares WHERE poolid = @poolId AND created > @start AND created < @end"; diff --git a/src/Miningcore/Persistence/Postgres/Repositories/StatsRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/StatsRepository.cs index b2a3088cd..46e00fa71 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/StatsRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/StatsRepository.cs @@ -354,4 +354,18 @@ public Task DeleteMinerStatsBeforeAsync(IDbConnection con, DateTime date, C return con.ExecuteAsync(new CommandDefinition(query, new { date }, cancellationToken: ct)); } + + public Task GetMinerTotalConfirmedBlocksAsync(IDbConnection con, string poolId, string miner, CancellationToken ct) + { + const string query = @"SELECT COUNT(*) FROM blocks WHERE poolid = @poolId AND miner = @miner AND status = 'confirmed'"; + + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId, miner }, cancellationToken: ct)); + } + + public Task GetMinerTotalPendingBlocksAsync(IDbConnection con, string poolId, string miner, CancellationToken ct) + { + const string query = @"SELECT COUNT(*) FROM blocks WHERE poolid = @poolId AND miner = @miner AND status = 'pending'"; + + return con.ExecuteScalarAsync(new CommandDefinition(query, new { poolId, miner }, cancellationToken: ct)); + } } diff --git a/src/Miningcore/Persistence/Postgres/Scripts/add_minereffort.sql b/src/Miningcore/Persistence/Postgres/Scripts/add_minereffort.sql new file mode 100644 index 000000000..fb306ae02 --- /dev/null +++ b/src/Miningcore/Persistence/Postgres/Scripts/add_minereffort.sql @@ -0,0 +1 @@ +ALTER TABLE blocks ADD COLUMN IF NOT EXISTS minereffort FLOAT NULL; \ No newline at end of file diff --git a/src/Miningcore/Persistence/Postgres/Scripts/createdb.sql b/src/Miningcore/Persistence/Postgres/Scripts/createdb.sql index 64b5258db..847644760 100644 --- a/src/Miningcore/Persistence/Postgres/Scripts/createdb.sql +++ b/src/Miningcore/Persistence/Postgres/Scripts/createdb.sql @@ -28,6 +28,7 @@ CREATE TABLE blocks type TEXT NULL, confirmationprogress FLOAT NOT NULL DEFAULT 0, effort FLOAT NULL, + minereffort FLOAT NULL, transactionconfirmationdata TEXT NOT NULL, miner TEXT NULL, reward decimal(28,12) NULL, diff --git a/src/Miningcore/Persistence/Repositories/IBlockRepository.cs b/src/Miningcore/Persistence/Repositories/IBlockRepository.cs index cdd80e863..9322df407 100644 --- a/src/Miningcore/Persistence/Repositories/IBlockRepository.cs +++ b/src/Miningcore/Persistence/Repositories/IBlockRepository.cs @@ -14,10 +14,15 @@ public interface IBlockRepository Task PageMinerBlocksAsync(IDbConnection con, string poolId, string address, BlockStatus[] status, int page, int pageSize, CancellationToken ct); Task GetPendingBlocksForPoolAsync(IDbConnection con, string poolId); Task GetBlockBeforeAsync(IDbConnection con, string poolId, BlockStatus[] status, DateTime before); + Task GetMinerBlockBeforeAsync(IDbConnection con, string poolId, string miner, BlockStatus[] status, DateTime before, CancellationToken ct); Task GetBlockBeforeCountAsync(IDbConnection con, string poolId, BlockStatus[] status, DateTime before); Task GetPoolBlockCountAsync(IDbConnection con, string poolId, CancellationToken ct); + Task GetTotalConfirmedBlocksAsync(IDbConnection con, string poolId, CancellationToken ct); + Task GetTotalPendingBlocksAsync(IDbConnection con, string poolId, CancellationToken ct); + Task GetLastConfirmedBlockRewardAsync(IDbConnection con, string poolId, CancellationToken ct); + Task GetLastMinerBlockTimeAsync(IDbConnection con, string poolId, string address, CancellationToken ct); Task GetMinerBlockCountAsync(IDbConnection con, string poolId, string address, CancellationToken ct); - Task GetLastPoolBlockTimeAsync(IDbConnection con, string poolId); + Task GetLastPoolBlockTimeAsync(IDbConnection con, string poolId, CancellationToken ct); Task GetBlockByPoolHeightAndTypeAsync(IDbConnection con, string poolId, long height, string type); Task GetPoolDuplicateBlockCountByPoolHeightNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, BlockStatus[] status); Task GetPoolDuplicateBlockBeforeCountByPoolHeightNoTypeAndStatusAsync(IDbConnection con, string poolId, long height, BlockStatus[] status, DateTime before); diff --git a/src/Miningcore/Persistence/Repositories/IShareRepository.cs b/src/Miningcore/Persistence/Repositories/IShareRepository.cs index e2357641c..ff43d8f1d 100644 --- a/src/Miningcore/Persistence/Repositories/IShareRepository.cs +++ b/src/Miningcore/Persistence/Repositories/IShareRepository.cs @@ -13,8 +13,10 @@ public interface IShareRepository Task CountSharesByMinerAsync(IDbConnection con, IDbTransaction tx, string poolId, string miner, CancellationToken ct); Task DeleteSharesByMinerAsync(IDbConnection con, IDbTransaction tx, string poolId, string miner, CancellationToken ct); Task GetAccumulatedShareDifficultyBetweenAsync(IDbConnection con, string poolId, DateTime start, DateTime end, CancellationToken ct); + Task GetMinerShareDifficultyBetweenAsync(IDbConnection con, string poolId, string miner, DateTime start, DateTime end, CancellationToken ct); Task GetEffectiveAccumulatedShareDifficultyBetweenAsync(IDbConnection con, string poolId, DateTime start, DateTime end, CancellationToken ct); - Task GetEffortBetweenCreatedAsync(IDbConnection con, string poolId, double shareConst, DateTime start, DateTime end); + Task GetEffortBetweenCreatedAsync(IDbConnection con, string poolId, double shareConst, DateTime start, DateTime end, CancellationToken ct); + Task GetMinerEffortBetweenCreatedAsync(IDbConnection con, string poolId, string miner,DateTime start, DateTime end, CancellationToken ct); Task GetHashAccumulationBetweenAsync(IDbConnection con, string poolId, DateTime start, DateTime end, CancellationToken ct); Task GetRecentyUsedIpAddressesAsync(IDbConnection con, IDbTransaction tx, string poolId, string miner, CancellationToken ct); diff --git a/src/Miningcore/Persistence/Repositories/IStatsRepository.cs b/src/Miningcore/Persistence/Repositories/IStatsRepository.cs index 02c6e4e50..a5bbc81e8 100644 --- a/src/Miningcore/Persistence/Repositories/IStatsRepository.cs +++ b/src/Miningcore/Persistence/Repositories/IStatsRepository.cs @@ -27,4 +27,6 @@ Task GetMinerPerformanceBetweenDailyAsync(IDb Task DeletePoolStatsBeforeAsync(IDbConnection con, DateTime date, CancellationToken ct); Task DeleteMinerStatsBeforeAsync(IDbConnection con, DateTime date, CancellationToken ct); + Task GetMinerTotalConfirmedBlocksAsync(IDbConnection con, string poolId, string miner, CancellationToken ct); + Task GetMinerTotalPendingBlocksAsync(IDbConnection con, string poolId, string miner, CancellationToken ct); } diff --git a/src/Miningcore/build-libs-linux.sh b/src/Miningcore/build-libs-linux.sh index 02d32e04c..f9d2181bd 100755 --- a/src/Miningcore/build-libs-linux.sh +++ b/src/Miningcore/build-libs-linux.sh @@ -2,6 +2,9 @@ OutDir=$1 +export UNAME_S=$(uname -s) +export UNAME_P=$(uname -m || uname -p) + AES=$(../Native/check_cpu.sh aes && echo -maes || echo) SSE2=$(../Native/check_cpu.sh sse2 && echo -msse2 || echo) SSE3=$(../Native/check_cpu.sh sse3 && echo -msse3 || echo) @@ -36,7 +39,8 @@ export HAVE_FEATURE="$HAVE_AES $HAVE_SSE2 $HAVE_SSE3 $HAVE_SSSE3 $HAVE_PCLMUL $H (cd ../Native/libfiropow && make clean && make) && mv ../Native/libfiropow/libfiropow.so "$OutDir" (cd ../Native/libkawpow && make clean && make) && mv ../Native/libkawpow/libkawpow.so "$OutDir" (cd ../Native/libmeowpow && make clean && make) && mv ../Native/libmeowpow/libmeowpow.so "$OutDir" +(cd ../Native/libdero && make clean && make) && mv ../Native/libdero/libdero.so "$OutDir" ((cd /tmp && rm -rf secp256k1 && git clone https://github.com/bitcoin-ABC/secp256k1 && cd secp256k1 && git checkout 04fabb44590c10a19e35f044d11eb5058aac65b2 && mkdir build && cd build && cmake -GNinja .. -DCMAKE_C_FLAGS=-fPIC -DSECP256K1_ENABLE_MODULE_RECOVERY=OFF -DSECP256K1_ENABLE_COVERAGE=OFF -DSECP256K1_ENABLE_MODULE_SCHNORR=ON && ninja) && (cd ../Native/libnexapow && cp /tmp/secp256k1/build/libsecp256k1.a . && make clean && make) && mv ../Native/libnexapow/libnexapow.so "$OutDir") -((cd /tmp && rm -rf RandomX && git clone https://github.com/tevador/RandomX && cd RandomX && git checkout tags/v1.1.10 && mkdir build && cd build && cmake -DARCH=native .. && make) && (cd ../Native/librandomx && cp /tmp/RandomX/build/librandomx.a . && make clean && make) && mv ../Native/librandomx/librandomx.so "$OutDir") -((cd /tmp && rm -rf RandomARQ && git clone https://github.com/arqma/RandomARQ && cd RandomARQ && git checkout 14850620439045b319fa6398f5a164715c4a66ce && mkdir build && cd build && cmake -DARCH=native .. && make) && (cd ../Native/librandomarq && cp /tmp/RandomARQ/build/librandomx.a . && make clean && make) && mv ../Native/librandomarq/librandomarq.so "$OutDir") +((cd /tmp && rm -rf RandomX && git clone https://github.com/tevador/RandomX && cd RandomX && git checkout tags/v1.2.1 && mkdir build && cd build && cmake -DARCH=native .. && make) && (cd ../Native/librandomx && cp /tmp/RandomX/build/librandomx.a . && make clean && make) && mv ../Native/librandomx/librandomx.so "$OutDir") +((cd /tmp && rm -rf RandomARQ && git clone https://github.com/arqma/RandomARQ && cd RandomARQ && git checkout 3bcb6bafe63d70f8e6f78a0d431e71be2b638083 && mkdir build && cd build && cmake -DARCH=native .. && make) && (cd ../Native/librandomarq && cp /tmp/RandomARQ/build/librandomx.a . && make clean && make) && mv ../Native/librandomarq/librandomarq.so "$OutDir") diff --git a/src/Miningcore/coins.json b/src/Miningcore/coins.json index 640001642..3353bb279 100644 --- a/src/Miningcore/coins.json +++ b/src/Miningcore/coins.json @@ -1338,12 +1338,18 @@ "twitter": "https://twitter.com/hns", "telegram": "https://t.me/handshake_hns", "discord": "https://handshake.org/discord", + "merkleTreeHasher": { + "hash": "blake2b" + }, "coinbaseHasher": { - "hash": "sha3-256" + "hash": "blake2b" }, "headerHasher": { "hash": "blake2b" }, + "shareHasher": { + "hash": "sha3-256" + }, "blockHasher": { "hash": "blake2b" }, @@ -1463,6 +1469,35 @@ "explorerTxLink": "http://157.245.248.32:3000/#/tx/{0}", "explorerAccountLink": "http://157.245.248.32:3000/#/address/{0}" }, + "kylacoin": { + "name": "Kylacoin", + "symbol": "KCN", + "family": "bitcoin", + "website": "https://kylacoin.com/", + "market": "https://coinpaprika.com/coin/kcn-kylacoin", + "twitter": "https://twitter.com/kylacoin", + "telegram": "https://t.me/kylacoingroup", + "discord": "https://discord.gg/SHZr5zQVDT", + "coinbaseHasher": { + "hash": "sha3-256d" + }, + "headerHasher": { + "hash": "flex" + }, + "blockHasher": { + "hash": "reverse", + "args": [ + { + "hash": "sha3-256d" + } + ] + }, + "payoutDecimalPlaces": 12, + "hasCoinbaseDevReward": true, + "explorerBlockLink": "https://kcnxp.com/block/$height$", + "explorerTxLink": "https://kcnxp.com/tx/{0}", + "explorerAccountLink": "https://kcnxp.com/address/{0}" + }, "lanacoin": { "name": "Lanacoin", "canonicalName": "Lana", @@ -2520,9 +2555,9 @@ "args": [ { "hash": "minotaurx" } ] }, "isPseudoPoS": true, - "explorerBlockLink": "https://explorer.pulsarcoin.org/block/$height$", - "explorerTxLink": "https://explorer.pulsarcoin.org/tx/{0}", - "explorerAccountLink": "https://explorer.pulsarcoin.org/address/{0}" + "explorerBlockLink": "https://explorer.pulsarcoin.org/block/?blockheight=$height$", + "explorerTxLink": "https://explorer.pulsarcoin.org/tx/?txid={0}", + "explorerAccountLink": "https://explorer.pulsarcoin.org/address/?address={0}" }, "vishai": { "name": "Vishai", @@ -4019,7 +4054,7 @@ "discord": "https://discord.com/invite/VRKMP2S", "networks": { "main": { - "diff1": "00000f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", + "diff1": "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", "solutionSize": 1344, "solutionPreambleSize": 3, "solver": { @@ -4040,7 +4075,7 @@ "saplingTxVersionGroupId": 2301567109 }, "test": { - "diff1": "0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "diff1": "07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "solutionSize": 1344, "solutionPreambleSize": 3, "solver": { @@ -4061,7 +4096,7 @@ "saplingTxVersionGroupId": 2301567109 }, "regtest": { - "diff1": "0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "diff1": "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", "solutionSize": 1344, "solutionPreambleSize": 3, "solver": { @@ -4664,6 +4699,33 @@ "explorerBlockLink": "https://explorer.conceal.network/index.html?hash=$hash$#blockchain_block", "explorerTxLink": "https://explorer.conceal.network/index.html?hash={0}#blockchain_transaction" }, + "salvium": { + "name": "Salvium", + "canonicalName": "Salvium", + "symbol": "SAL", + "family": "cryptonote", + "website": "https://salvium.io/", + "github": "https://github.com/salvium/salvium", + "market": "https://www.coingecko.com/en/coins/salvium", + "twitter": "https://x.com/salvium_io", + "telegram": "https://t.me/salviumprotocol", + "discord": "https://discord.com/invite/P3rrAjkyYs", + "hash": "randomx", + "hashVariant": 0, + "blobType": 15, + "smallestUnit": 100000000, + "addressPrefix": 4125464, + "addressPrefixTestnet": 364819224, + "addressPrefixStagenet": 345944856, + "addressPrefixIntegrated": 90108696, + "addressPrefixIntegratedTestnet": 55924667160, + "addressPrefixIntegratedStagenet": 65301033752, + "subAddressPrefix": 257880856, + "subAddressPrefixTestnet": 2778641176, + "subAddressPrefixStagenet": 12155007768, + "explorerBlockLink": "https://explorer.salvium.io/block/$height$", + "explorerTxLink": "https://explorer.salvium.io/tx/{0}" + }, "zephyr": { "name": "Zephyr", "canonicalName": "Zephyr", @@ -4880,6 +4942,60 @@ "explorerTxLink": "https://explorer.alephium.org/transactions/{0}", "explorerAccountLink": "https://explorer.alephium.org/addresses/{0}" }, + "astrix-network": { + "name": "Astrix Network", + "canonicalName": "Astrix Network", + "symbol": "AIX", + "family": "kaspa", + "website": "https://astrix-network.com/", + "market": "", + "twitter": "https://x.com/astrix_network", + "telegram": "https://t.me/astrix_network", + "discord": "https://discord.gg/cwfDZJ9dHx", + "addressBech32Prefix": "astrix", + "addressBech32PrefixDevnet": "astrixdev", + "addressBech32PrefixSimnet": "astrixsim", + "addressBech32PrefixTestnet": "astrixtest", + "explorerBlockLink": "https://explorer.astrix-network.com/blocks/$hash$", + "explorerTxLink": "https://explorer.astrix-network.com/txs/{0}", + "explorerAccountLink": "https://explorer.astrix-network.com/addresses/{0}" + }, + "bitmeme": { + "name": "Bitmeme", + "canonicalName": "Bitmeme", + "symbol": "BTM", + "family": "kaspa", + "website": "https://bitmeme.site/", + "market": "https://coinmarketcap.com/currencies/kaspa/", + "twitter": "https://x.com/bitmemebtm", + "telegram": "https://t.me/bitmemebtm", + "discord": "https://discord.com/invite/f3ugpwfD", + "addressBech32Prefix": "btm", + "addressBech32PrefixDevnet": "btmdev", + "addressBech32PrefixSimnet": "btmsim", + "addressBech32PrefixTestnet": "btmtest", + "explorerBlockLink": "https://explorer.bitmeme.world/blocks/$hash$", + "explorerTxLink": "https://explorer.bitmeme.world/txs/{0}", + "explorerAccountLink": "https://explorer.bitmeme.world/addresses/{0}" + }, + "brics": { + "name": "Brics", + "canonicalName": "Brics", + "symbol": "BRICS", + "family": "kaspa", + "website": "https://bricspro.site/", + "market": "https://coinmarketcap.com/currencies/kaspa/", + "twitter": "https://x.com/BricNetwor59084", + "telegram": "https://t.me/bricsnet", + "discord": "https://discord.com/invite/f3ugpwfD", + "addressBech32Prefix": "brics", + "addressBech32PrefixDevnet": "bricsdev", + "addressBech32PrefixSimnet": "bricssim", + "addressBech32PrefixTestnet": "bricstest", + "explorerBlockLink": "https://explorer.bricspro.site/blocks/$hash$", + "explorerTxLink": "https://explorer.bricspro.site/txs/{0}", + "explorerAccountLink": "https://explorer.bricspro.site/addresses/{0}" + }, "bugna": { "name": "Bugna Network", "canonicalName": "Bugna Network", @@ -4890,10 +5006,32 @@ "twitter": "https://twitter.com/bugnanetwork", "telegram": "", "discord": "https://discord.com/invite/YxDF2EMKTq", + "addressBech32Prefix": "bugna", + "addressBech32PrefixDevnet": "bugnadev", + "addressBech32PrefixSimnet": "bugnasim", + "addressBech32PrefixTestnet": "bugnatest", "explorerBlockLink": "https://explorer.bugna.org/blocks/$hash$", "explorerTxLink": "https://explorer.bugna.org/txs/{0}", "explorerAccountLink": "https://explorer.bugna.org/addresses/{0}" }, + "consensus": { + "name": "Consensus Network", + "canonicalName": "Consensus Network", + "symbol": "CSS", + "family": "kaspa", + "website": "https://consensus-network.com/", + "market": "", + "twitter": "https://twitter.com/Consensus_CSS", + "telegram": "https://t.me/consensus_network", + "discord": "", + "addressBech32Prefix": "consensus", + "addressBech32PrefixDevnet": "consensusdev", + "addressBech32PrefixSimnet": "consensussim", + "addressBech32PrefixTestnet": "consensustest", + "explorerBlockLink": "https://explorer.consensus-network.com/blocks/$hash$", + "explorerTxLink": "https://explorer.consensus-network.com/txs/{0}", + "explorerAccountLink": "https://explorer.consensus-network.com/addresses/{0}" + }, "hoosat": { "name": "Hoosat", "canonicalName": "Hoosat", @@ -4904,6 +5042,10 @@ "twitter": "https://twitter.com/HoosatNetwork", "telegram": "https://t.me/HoosatNetwork", "discord": "https://discord.com/invite/NzENEkxEqY", + "addressBech32Prefix": "hoosat", + "addressBech32PrefixDevnet": "htndev", + "addressBech32PrefixSimnet": "hoosatsim", + "addressBech32PrefixTestnet": "hoosattest", "explorerBlockLink": "https://explorer.hoosat.fi/blocks/$hash$", "explorerTxLink": "https://explorer.hoosat.fi/txs/{0}", "explorerAccountLink": "https://explorer.hoosat.fi/addresses/{0}" @@ -4918,10 +5060,32 @@ "twitter": "https://twitter.com/KaspaCurrency", "telegram": "https://t.me/Kaspaenglish", "discord": "https://discord.gg/kS3SK5F36R", + "addressBech32Prefix": "kaspa", + "addressBech32PrefixDevnet": "kaspadev", + "addressBech32PrefixSimnet": "kaspasim", + "addressBech32PrefixTestnet": "kaspatest", "explorerBlockLink": "https://explorer.kaspa.org/blocks/$hash$", "explorerTxLink": "https://explorer.kaspa.org/txs/{0}", "explorerAccountLink": "https://explorer.kaspa.org/addresses/{0}" }, + "kaspav2": { + "name": "Kaspav2", + "canonicalName": "Kaspav2", + "symbol": "KASV2", + "family": "kaspa", + "website": "http://kaspa-v2.com/", + "market": "https://coinmarketcap.com/currencies/kaspa/", + "twitter": "https://x.com/Kaspa_v2", + "telegram": "https://t.me/kaspav2", + "discord": "https://discord.gg/kaspav2", + "addressBech32Prefix": "kasv2", + "addressBech32PrefixDevnet": "kasv2dev", + "addressBech32PrefixSimnet": "kasv2sim", + "addressBech32PrefixTestnet": "kasv2test", + "explorerBlockLink": "https://explorer.kaspa-v2.services/blocks/$hash$", + "explorerTxLink": "https://explorer.kaspa-v2.services/txs/{0}", + "explorerAccountLink": "https://explorer.kaspa-v2.services/addresses/{0}" + }, "kaspaclassic": { "name": "Kaspa Classic", "canonicalName": "Kaspa Classic", @@ -4932,6 +5096,10 @@ "twitter": "https://twitter.com/kaspaclassic", "telegram": "https://t.me/kaspaclassic", "discord": "https://discord.com/invite/kaspaclassic1", + "addressBech32Prefix": "cas", + "addressBech32PrefixDevnet": "caspadev", + "addressBech32PrefixSimnet": "pyrinsim", + "addressBech32PrefixTestnet": "pyrintest", "explorerBlockLink": "https://explorer.kaspa-classic.online/blocks/$hash$", "explorerTxLink": "https://explorer.kaspa-classic.online/txs/{0}", "explorerAccountLink": "https://explorer.kaspa-classic.online/addresses/{0}" @@ -4946,6 +5114,10 @@ "twitter": "https://twitter.com/karlsennetwork", "telegram": "https://t.me/KarlsenNetwork", "discord": "https://discord.com/invite/NasfjsEm", + "addressBech32Prefix": "karlsen", + "addressBech32PrefixDevnet": "karlsendev", + "addressBech32PrefixSimnet": "karlsensim", + "addressBech32PrefixTestnet": "karlsentest", "explorerBlockLink": "https://explorer.karlsencoin.com/blocks/$hash$", "explorerTxLink": "https://explorer.karlsencoin.com/txs/{0}", "explorerAccountLink": "https://explorer.karlsencoin.com/addresses/{0}" @@ -4960,6 +5132,10 @@ "twitter": "https://twitter.com/nautilusNTL", "telegram": "", "discord": "https://discord.com/invite/qWcUUgww4d", + "addressBech32Prefix": "nautilus", + "addressBech32PrefixDevnet": "nautiliadev", + "addressBech32PrefixSimnet": "nautilussim", + "addressBech32PrefixTestnet": "nautilustest", "explorerBlockLink": "https://explorer.nautilus-network.net/blocks/$hash$", "explorerTxLink": "https://explorer.nautilus-network.net/txs/{0}", "explorerAccountLink": "https://explorer.nautilus-network.net/addresses/{0}" @@ -4974,9 +5150,31 @@ "twitter": "https://twitter.com/nexellia", "telegram": "https://t.me/NexelliaNetwork", "discord": "https://discord.com/invite/MHn4wStC4h", - "explorerBlockLink": "https://explorer.nexell-ia.net/blocks/$hash$", - "explorerTxLink": "https://explorer.nexell-ia.net/txs/{0}", - "explorerAccountLink": "https://explorer.nexell-ia.net/addresses/{0}" + "addressBech32Prefix": "nexellia", + "addressBech32PrefixDevnet": "nexelliadev", + "addressBech32PrefixSimnet": "nexelliasim", + "addressBech32PrefixTestnet": "nexelliatest", + "explorerBlockLink": "https://explorer.nexell-ai.com/blocks/$hash$", + "explorerTxLink": "https://explorer.nexell-ai.com/txs/{0}", + "explorerAccountLink": "https://explorer.nexell-ai.com/addresses/{0}" + }, + "pugdag": { + "name": "Pugdag", + "canonicalName": "Pugdag", + "symbol": "PUG", + "family": "kaspa", + "website": "https://pugdag.com/", + "market": "", + "twitter": "https://twitter.com/pug_dag", + "telegram": "https://t.me/pug_dag", + "discord": "https://discord.com/invite/pugdag", + "addressBech32Prefix": "pugdag", + "addressBech32PrefixDevnet": "pugdagdev", + "addressBech32PrefixSimnet": "pugdagsim", + "addressBech32PrefixTestnet": "pugdagtest", + "explorerBlockLink": "https://explorer.pugdag.com/blocks/$hash$", + "explorerTxLink": "https://explorer.pugdag.com/txs/{0}", + "explorerAccountLink": "https://explorer.pugdag.com/addresses/{0}" }, "pyrin": { "name": "Pyrin", @@ -4988,6 +5186,10 @@ "twitter": "https://twitter.com/pyrin_network", "telegram": "https://t.me/pyrin_network", "discord": "https://discord.gg/QQgWRntF", + "addressBech32Prefix": "pyrin", + "addressBech32PrefixDevnet": "pyipadev", + "addressBech32PrefixSimnet": "pyrinsim", + "addressBech32PrefixTestnet": "pyrintest", "explorerBlockLink": "https://explorer.pyrin.network/blocks/$hash$", "explorerTxLink": "https://explorer.pyrin.network/txs/{0}", "explorerAccountLink": "https://explorer.pyrin.network/addresses/{0}" @@ -5002,8 +5204,45 @@ "twitter": "https://twitter.com/SedraCoin", "telegram": "https://t.me/OfficialSedraCoin", "discord": "https://discord.gg/jWHrkKMSEr", + "addressBech32Prefix": "sedra", + "addressBech32PrefixDevnet": "sedradev", + "addressBech32PrefixSimnet": "sedrasim", + "addressBech32PrefixTestnet": "sedratest", "explorerBlockLink": "https://explorer.sedracoin.com/blocks/$hash$", "explorerTxLink": "https://explorer.sedracoin.com/txs/{0}", "explorerAccountLink": "https://explorer.sedracoin.com/addresses/{0}" + }, + "spectre-network": { + "name": "Spectre Network", + "canonicalName": "Spectre Network", + "symbol": "SPR", + "family": "kaspa", + "website": "https://spectre-network.org/", + "market": "", + "twitter": "https://twitter.com/SpectreNetwrk", + "telegram": "https://t.me/Spectre_Network", + "discord": "https://discord.spectre-network.org/", + "addressBech32Prefix": "spectre", + "addressBech32PrefixDevnet": "spectredev", + "addressBech32PrefixSimnet": "spectresim", + "addressBech32PrefixTestnet": "spectretest", + "hashrateMultiplier": 256, + "explorerBlockLink": "https://explorer.spectre-network.org/blocks/$hash$", + "explorerTxLink": "https://explorer.spectre-network.org/txs/{0}", + "explorerAccountLink": "https://explorer.spectre-network.org/addresses/{0}" + }, + "warthog": { + "name": "Warthog Network", + "canonicalName": "Warthog Network", + "symbol": "WART", + "family": "warthog", + "website": "https://www.warthog.network/", + "market": "https://www.coingecko.com/en/coins/warthog", + "twitter": "https://twitter.com/warthognetwork", + "telegram": "https://t.me/warthognetwork", + "discord": "https://discord.com/invite/QMDV8bGTdQ", + "explorerBlockLink": "https://wartscan.io/block/$height$", + "explorerTxLink": "https://wartscan.io/tx/{0}", + "explorerAccountLink": "https://wartscan.io/account/{0}" } } diff --git a/src/Miningcore/config.schema.json b/src/Miningcore/config.schema.json index 689ca1fc1..edb94bd08 100644 --- a/src/Miningcore/config.schema.json +++ b/src/Miningcore/config.schema.json @@ -531,6 +531,7 @@ "type": "string", "enum": [ "PPLNS", + "PPLNSBF", "PROP", "SOLO", "PPS", @@ -557,6 +558,18 @@ }, "time": { "type": "integer" + }, + "minerEffortPercent": { + "type": [ + "number", + "null" + ] + }, + "minerEffortTime": { + "type": [ + "integer", + "null" + ] } } }, diff --git a/src/Native/libbeamhash/libbeamhash.sln b/src/Native/libbeamhash/libbeamhash.sln index 8c6cddc1f..d75b3328c 100644 --- a/src/Native/libbeamhash/libbeamhash.sln +++ b/src/Native/libbeamhash/libbeamhash.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31229.75 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbeamhash", "libbeamhash.vcxproj", "{2DE74E14-BF6D-4046-951B-8EBC8A1BA009}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmultihash", "libbeamhash.vcxproj", "{2DE74E14-BF6D-4046-951B-8EBC8A1BA009}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Native/libcryptonote/Makefile b/src/Native/libcryptonote/Makefile index ec0119c7e..fe1f5b8cd 100644 --- a/src/Native/libcryptonote/Makefile +++ b/src/Native/libcryptonote/Makefile @@ -15,7 +15,8 @@ OBJECTS = exports.o \ crypto/hash.o \ crypto/keccak.o \ common/base58.o \ - zephyr_oracle/pricing_record.o + zephyr_oracle/pricing_record.o \ + salvium_oracle/pricing_record.o all: $(TARGET) diff --git a/src/Native/libcryptonote/Makefile.MSys2 b/src/Native/libcryptonote/Makefile.MSys2 index 7970e95be..a60a8e5d6 100644 --- a/src/Native/libcryptonote/Makefile.MSys2 +++ b/src/Native/libcryptonote/Makefile.MSys2 @@ -21,6 +21,7 @@ OBJECTS = exports.o \ crypto/keccak.o \ common/base58.o \ zephyr_oracle/pricing_record.o \ + salvium_oracle/pricing_record.o \ mingw_stubs.o all: $(TARGET) diff --git a/src/Native/libcryptonote/cryptonote_config.h b/src/Native/libcryptonote/cryptonote_config.h index 06b3beb02..3cfa237a0 100644 --- a/src/Native/libcryptonote/cryptonote_config.h +++ b/src/Native/libcryptonote/cryptonote_config.h @@ -34,4 +34,6 @@ enum BLOB_TYPE { BLOB_TYPE_CRYPTONOTE_XHV = 11, // Haven BLOB_TYPE_CRYPTONOTE_XTA = 12, // ITALO BLOB_TYPE_CRYPTONOTE_ZEPHYR = 13, // ZEPHYR + BLOB_TYPE_CRYPTONOTE_XLA = 14, // XLA + BLOB_TYPE_CRYPTONOTE_SALVIUM= 15, // Salvium }; diff --git a/src/Native/libcryptonote/cryptonote_core/cryptonote_basic.h b/src/Native/libcryptonote/cryptonote_core/cryptonote_basic.h index da8ff08bc..8e7763276 100644 --- a/src/Native/libcryptonote/cryptonote_core/cryptonote_basic.h +++ b/src/Native/libcryptonote/cryptonote_core/cryptonote_basic.h @@ -28,6 +28,7 @@ #include "cryptonote_protocol/blobdatatype.h" #include "offshore/pricing_record.h" #include "zephyr_oracle/pricing_record.h" +#include "salvium_oracle/pricing_record.h" namespace cryptonote @@ -48,6 +49,18 @@ namespace cryptonote typedef std::vector ring_signature; + enum salvium_transaction_type + { + UNSET = 0, + MINER = 1, + PROTOCOL = 2, + TRANSFER = 3, + CONVERT = 4, + BURN = 5, + STAKE = 6, + RETURN = 7, + MAX = 7 + }; /* outputs */ @@ -109,6 +122,41 @@ namespace cryptonote END_SERIALIZE() }; + // SALVIUM + struct txout_salvium_key + { + txout_salvium_key() { } + txout_salvium_key(const crypto::public_key &_key, const std::string &_asset_type, const uint64_t &_unlock_time) : + key(_key), asset_type(_asset_type), unlock_time(_unlock_time) { } + crypto::public_key key; + std::string asset_type; + uint64_t unlock_time; + + BEGIN_SERIALIZE_OBJECT() + FIELD(key) + FIELD(asset_type) + VARINT_FIELD(unlock_time) + END_SERIALIZE() + }; + + struct txout_salvium_tagged_key + { + txout_salvium_tagged_key() { } + txout_salvium_tagged_key(const crypto::public_key &_key, const std::string &_asset_type, const uint64_t &_unlock_time, const crypto::view_tag &_view_tag) : + key(_key), asset_type(_asset_type), unlock_time(_unlock_time), view_tag(_view_tag) { } + crypto::public_key key; + std::string asset_type; + uint64_t unlock_time; + crypto::view_tag view_tag; // optimization to reduce scanning time + + BEGIN_SERIALIZE_OBJECT() + FIELD(key) + FIELD(asset_type) + VARINT_FIELD(unlock_time) + FIELD(view_tag) + END_SERIALIZE() + }; + // ZEPHYR struct txout_zephyr_tagged_key { @@ -218,6 +266,21 @@ namespace cryptonote END_SERIALIZE() }; + struct txin_salvium_key + { + uint64_t amount; + std::string asset_type; + std::vector key_offsets; + crypto::key_image k_image; // double spending protection + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(asset_type) + FIELD(key_offsets) + FIELD(k_image) + END_SERIALIZE() + }; + struct txin_zephyr_key { uint64_t amount; @@ -234,10 +297,12 @@ namespace cryptonote }; typedef boost::variant txin_v; + typedef boost::variant txin_salvium_v; typedef boost::variant txout_target_v; typedef boost::variant txout_xhv_target_v; + typedef boost::variant txout_salvium_target_v; typedef boost::variant txout_stablero_target_v; struct tx_out @@ -262,6 +327,17 @@ namespace cryptonote END_SERIALIZE() }; + struct tx_out_salvium + { + uint64_t amount; + txout_salvium_target_v target; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(target) + END_SERIALIZE() + }; + struct tx_out_zephyr { uint64_t amount; @@ -293,7 +369,9 @@ namespace cryptonote uint64_t unlock_time; //number of block (or time), used as a limitation like: spend this tx not early then block/time std::vector vin; + std::vector vin_salvium; std::vector vout; + std::vector vout_salvium; std::vector vout_xhv; std::vector vout_zephyr; //extra @@ -306,6 +384,21 @@ namespace cryptonote uint64_t amount_minted; std::vector output_unlock_times; std::vector collateral_indices; + // SALVIUM-SPECIFIC FIELDS + // TX type + cryptonote::salvium_transaction_type tx_type; + // Return address + crypto::public_key return_address; + // Return TX public key + crypto::public_key return_pubkey; + // Source asset type + std::string source_asset_type; + // Destination asset type (this is only necessary for CONVERT transactions) + std::string destination_asset_type; + // Circulating supply information - already provided by Haven + //uint64_t amount_burnt; + // Slippage limit + uint64_t amount_slippage_limit; // // NOTE: Loki specific @@ -334,9 +427,15 @@ namespace cryptonote } if (blob_type != BLOB_TYPE_CRYPTONOTE_XHV || version < POU_TRANSACTION_VERSION) VARINT_FIELD(unlock_time) - FIELD(vin) - if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) + FIELD(vin_salvium) + else + FIELD(vin) + + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) + FIELD(vout_salvium) + else if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) FIELD(vout_zephyr) else if (blob_type == BLOB_TYPE_CRYPTONOTE_XHV) FIELD(vout_xhv) @@ -353,7 +452,19 @@ namespace cryptonote VARINT_FIELD(type) if (static_cast(type) >= loki_type_count) return false; } - if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + VARINT_FIELD(tx_type) + if (tx_type != cryptonote::salvium_transaction_type::PROTOCOL) { + VARINT_FIELD(amount_burnt) + if (tx_type != cryptonote::salvium_transaction_type::MINER) { + FIELD(return_address) + FIELD(return_pubkey) + FIELD(source_asset_type) + FIELD(destination_asset_type) + VARINT_FIELD(amount_slippage_limit) + } + } + } else if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { VARINT_FIELD(pricing_record_height) VARINT_FIELD(amount_burnt) VARINT_FIELD(amount_minted) @@ -434,17 +545,20 @@ namespace cryptonote else { ar.tag("rct_signatures"); - if (!vin.empty()) + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM ? !vin_salvium.empty() : !vin.empty()) { ar.begin_object(); - bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR ? vout_zephyr.size() : blob_type != BLOB_TYPE_CRYPTONOTE_XHV ? vout.size() : vout_xhv.size()); + bool r = rct_signatures.serialize_rctsig_base(ar, blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM ? vin_salvium.size() : vin.size(), blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR ? vout_zephyr.size() : blob_type != BLOB_TYPE_CRYPTONOTE_XHV ? vout.size() : vout_xhv.size()); if (!r || !ar.stream().good()) return false; ar.end_object(); if (rct_signatures.type != rct::RCTTypeNull) { ar.tag("rctsig_prunable"); ar.begin_object(); - if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + r = rct_signatures.p.serialize_rctsig_prunable(ar, rct_signatures.type, vin_salvium.size(), vout_salvium.size(), + vin_salvium[0].type() == typeid(txin_salvium_key) ? boost::get(vin_salvium[0]).key_offsets.size() - 1 : 0); + } else if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { r = rct_signatures.p.serialize_rctsig_prunable(ar, rct_signatures.type, vin.size(), vout_zephyr.size(), vin[0].type() == typeid(txin_zephyr_key) ? boost::get(vin[0]).key_offsets.size() - 1 : 0); } else if (blob_type == BLOB_TYPE_CRYPTONOTE_XHV) { @@ -486,7 +600,9 @@ namespace cryptonote version = 0; unlock_time = 0; vin.clear(); + vin_salvium.clear(); vout.clear(); + vout_salvium.clear(); vout_xhv.clear(); vout_zephyr.clear(); extra.clear(); @@ -497,6 +613,13 @@ namespace cryptonote amount_minted = 0; output_unlock_times.clear(); collateral_indices.clear(); + // Salvium-specific fields + type = cryptonote::salvium_transaction_type::UNSET; + return_address = null_pkey; + return_pubkey = null_pkey; + source_asset_type.clear(); + destination_asset_type.clear(); + amount_slippage_limit = 0; } inline @@ -511,6 +634,7 @@ namespace cryptonote size_t operator()(const txin_offshore& txin) const {return txin.key_offsets.size();} size_t operator()(const txin_onshore& txin) const {return txin.key_offsets.size();} size_t operator()(const txin_xasset& txin) const {return txin.key_offsets.size();} + size_t operator()(const txin_salvium_key& txin) const {return txin.key_offsets.size();} size_t operator()(const txin_zephyr_key& txin) const {return txin.key_offsets.size();} }; @@ -633,9 +757,11 @@ namespace cryptonote uint64_t nonce8; offshore::pricing_record pricing_record; zephyr_oracle::pricing_record zephyr_pricing_record; + salvium_oracle::pricing_record salvium_pricing_record; crypto::cycle cycle; crypto::cycle40 cycle40; crypto::cycle48 cycle48; + crypto::signature signature; BEGIN_SERIALIZE() VARINT_FIELD(major_version) @@ -658,11 +784,27 @@ namespace cryptonote if (blob_type == BLOB_TYPE_CRYPTONOTE_XTA) FIELD(cycle48) if (blob_type == BLOB_TYPE_CRYPTONOTE_XHV) FIELD(pricing_record) - if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { - if (major_version >= 3) + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + if (major_version >= 2) FIELD(salvium_pricing_record) + } else if (blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { + if (major_version >= 4) { FIELD_N("pricing_record", zephyr_pricing_record) } + else if (major_version >= 3) + { + zephyr_oracle::pricing_record_v2 pr_v2; + if (!typename Archive::is_saving()) + { + FIELD(pr_v2) + pr_v2.write_to_pr(zephyr_pricing_record); + } + else + { + pr_v2.read_from_pr(zephyr_pricing_record); + FIELD(pr_v2) + } + } else { zephyr_oracle::pricing_record_v1 pr_v1; @@ -678,6 +820,7 @@ namespace cryptonote } } } + if (blob_type == BLOB_TYPE_CRYPTONOTE_XLA && major_version >= 13) FIELD(signature) END_SERIALIZE() }; @@ -687,10 +830,11 @@ namespace cryptonote bytecoin_block parent_block; transaction miner_tx; + transaction protocol_tx; std::vector tx_hashes; mutable crypto::hash uncle = cryptonote::null_hash; - void set_blob_type(enum BLOB_TYPE bt) { miner_tx.blob_type = blob_type = bt; } + void set_blob_type(enum BLOB_TYPE bt) { miner_tx.blob_type = protocol_tx.blob_type = blob_type = bt; } BEGIN_SERIALIZE_OBJECT() FIELDS(*static_cast(this)) @@ -700,6 +844,10 @@ namespace cryptonote FIELD_N("parent_block", sbb); } FIELD(miner_tx) + if (blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) + { + FIELD(protocol_tx) + } FIELD(tx_hashes) if (blob_type == BLOB_TYPE_CRYPTONOTE3) { @@ -765,6 +913,7 @@ VARIANT_TAG(binary_archive, cryptonote::txin_gen, 0xff); VARIANT_TAG(binary_archive, cryptonote::txin_to_script, 0x0); VARIANT_TAG(binary_archive, cryptonote::txin_to_scripthash, 0x1); VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::txin_salvium_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txin_zephyr_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txin_offshore, 0x3); VARIANT_TAG(binary_archive, cryptonote::txin_onshore, 0x4); @@ -772,6 +921,8 @@ VARIANT_TAG(binary_archive, cryptonote::txin_xasset, 0x5); VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0); VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::txout_salvium_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::txout_salvium_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::txout_zephyr_tagged_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::txout_offshore, 0x3); diff --git a/src/Native/libcryptonote/cryptonote_core/cryptonote_format_utils.cpp b/src/Native/libcryptonote/cryptonote_core/cryptonote_format_utils.cpp index 943727f13..d6a3a37b0 100644 --- a/src/Native/libcryptonote/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/Native/libcryptonote/cryptonote_core/cryptonote_format_utils.cpp @@ -166,41 +166,64 @@ namespace cryptonote bool get_inputs_money_amount(const transaction& tx, uint64_t& money) { money = 0; - BOOST_FOREACH(const auto& in, tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); - money += tokey_in.amount; + if (tx.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + BOOST_FOREACH(const auto& in, tx.vin_salvium) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_salvium_key, tokey_in, false); + money += tokey_in.amount; + } + } else { + BOOST_FOREACH(const auto& in, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + money += tokey_in.amount; + } } return true; } //--------------------------------------------------------------- uint64_t get_block_height(const block& b) { - CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1"); - CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], const txin_gen, coinbase_in, 0); - return coinbase_in.height; + if (b.miner_tx.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + CHECK_AND_ASSERT_MES(b.miner_tx.vin_salvium.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin_salvium.size() != 1"); + CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin_salvium[0], const txin_gen, coinbase_in, 0); + return coinbase_in.height; + } else { + CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1"); + CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], const txin_gen, coinbase_in, 0); + return coinbase_in.height; + } } //--------------------------------------------------------------- bool check_inputs_types_supported(const transaction& tx) { - BOOST_FOREACH(const auto& in, tx.vin) - { - if (tx.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { - CHECK_AND_ASSERT_MES(in.type() == typeid(txin_zephyr_key), false, "wrong variant type: " - << in.type().name() << ", expected " << typeid(txin_zephyr_key).name() - << ", in transaction id=" << get_transaction_hash(tx)); - } else if (tx.blob_type == BLOB_TYPE_CRYPTONOTE_XHV) { - CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key) || in.type() == typeid(txin_offshore) || in.type() == typeid(txin_onshore) || in.type() == typeid(txin_xasset), false, "wrong variant type: " - << in.type().name() << ", expected " << typeid(txin_to_key).name() - << "or " << typeid(txin_offshore).name() - << "or " << typeid(txin_onshore).name() - << "or " << typeid(txin_xasset).name() - << ", in transaction id=" << get_transaction_hash(tx)); - } else { - CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), false, "wrong variant type: " - << in.type().name() << ", expected " << typeid(txin_to_key).name() + if (tx.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + BOOST_FOREACH(const auto& in, tx.vin_salvium) + { + CHECK_AND_ASSERT_MES(in.type() == typeid(txin_salvium_key), false, "wrong variant type: " + << in.type().name() << ", expected " << typeid(txin_salvium_key).name() << ", in transaction id=" << get_transaction_hash(tx)); } + } else { + BOOST_FOREACH(const auto& in, tx.vin) + { + if (tx.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { + CHECK_AND_ASSERT_MES(in.type() == typeid(txin_zephyr_key), false, "wrong variant type: " + << in.type().name() << ", expected " << typeid(txin_zephyr_key).name() + << ", in transaction id=" << get_transaction_hash(tx)); + } else if (tx.blob_type == BLOB_TYPE_CRYPTONOTE_XHV) { + CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key) || in.type() == typeid(txin_offshore) || in.type() == typeid(txin_onshore) || in.type() == typeid(txin_xasset), false, "wrong variant type: " + << in.type().name() << ", expected " << typeid(txin_to_key).name() + << "or " << typeid(txin_offshore).name() + << "or " << typeid(txin_onshore).name() + << "or " << typeid(txin_xasset).name() + << ", in transaction id=" << get_transaction_hash(tx)); + } else { + CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), false, "wrong variant type: " + << in.type().name() << ", expected " << typeid(txin_to_key).name() + << ", in transaction id=" << get_transaction_hash(tx)); + } + } } return true; } @@ -262,8 +285,8 @@ namespace cryptonote { std::stringstream ss; binary_archive ba(ss); - const size_t inputs = t.vin.size(); - const size_t outputs = t.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR ? t.vout_zephyr.size() : t.blob_type != BLOB_TYPE_CRYPTONOTE_XHV ? t.vout.size() : t.vout_xhv.size(); + const size_t inputs = t.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM ? t.vin_salvium.size() : t.vin.size(); + const size_t outputs = t.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM ? t.vout_salvium.size() : (t.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR ? t.vout_zephyr.size() : (t.blob_type != BLOB_TYPE_CRYPTONOTE_XHV ? t.vout.size() : t.vout_xhv.size())); bool r = tt.rct_signatures.serialize_rctsig_base(ba, inputs, outputs); CHECK_AND_ASSERT_MES(r, false, "Failed to serialize rct signatures base"); cryptonote::get_blob_hash(ss.str(), hashes[1]); @@ -278,10 +301,12 @@ namespace cryptonote { std::stringstream ss; binary_archive ba(ss); - const size_t inputs = t.vin.size(); - const size_t outputs = t.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR ? t.vout_zephyr.size() : t.blob_type != BLOB_TYPE_CRYPTONOTE_XHV ? t.vout.size() : t.vout_xhv.size(); + const size_t inputs = t.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM ? t.vin_salvium.size() : t.vin.size(); + const size_t outputs = t.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM ? t.vout_salvium.size() : (t.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR ? t.vout_zephyr.size() : (t.blob_type != BLOB_TYPE_CRYPTONOTE_XHV ? t.vout.size() : t.vout_xhv.size())); size_t mixin; - if (t.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { + if (t.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + mixin = t.vin_salvium.empty() ? 0 : t.vin_salvium[0].type() == typeid(txin_salvium_key) ? boost::get(t.vin_salvium[0]).key_offsets.size() - 1 : 0; + } else if (t.blob_type == BLOB_TYPE_CRYPTONOTE_ZEPHYR) { mixin = t.vin.empty() ? 0 : t.vin[0].type() == typeid(txin_zephyr_key) ? boost::get(t.vin[0]).key_offsets.size() - 1 : 0; } else if (t.blob_type == BLOB_TYPE_CRYPTONOTE_XHV) { mixin = t.vin.empty() ? 0 : @@ -454,6 +479,12 @@ namespace cryptonote crypto::hash h = null_hash; size_t bl_sz = 0; get_transaction_hash(b.miner_tx, h, bl_sz); + if (b.blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM) { + txs_ids.push_back(h); + h = null_hash; + bl_sz = 0; + get_transaction_hash(b.protocol_tx, h, bl_sz); + } txs_ids.push_back(h); BOOST_FOREACH(auto& th, b.tx_hashes) txs_ids.push_back(th); diff --git a/src/Native/libcryptonote/ringct/rctTypes.h b/src/Native/libcryptonote/ringct/rctTypes.h index 641cd56a0..b51ed4900 100644 --- a/src/Native/libcryptonote/ringct/rctTypes.h +++ b/src/Native/libcryptonote/ringct/rctTypes.h @@ -87,6 +87,8 @@ namespace rct { typedef std::vector keyV; //vector of keys typedef std::vector keyM; //matrix of keys (indexed by column first) + static key null_key = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + //containers For CT operations //if it's representing a private ctkey then "dest" contains the secret key of the address // while "mask" contains a where C = aG + bH is CT pedersen commitment and b is the amount @@ -236,11 +238,48 @@ namespace rct { END_SERIALIZE() }; + struct BulletproofPlus + { + rct::keyV V; + rct::key A, A1, B; + rct::key r1, s1, d1; + rct::keyV L, R; + + BulletproofPlus() {} + BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V(V), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + + bool operator==(const BulletproofPlus &other) const { return V == other.V && A == other.A && A1 == other.A1 && B == other.B && r1 == other.r1 && s1 == other.s1 && d1 == other.d1 && L == other.L && R == other.R; } + + BEGIN_SERIALIZE_OBJECT() + // Commitments aren't saved, they're restored via outPk + // FIELD(V) + FIELD(A) + FIELD(A1) + FIELD(B) + FIELD(r1) + FIELD(s1) + FIELD(d1) + FIELD(L) + FIELD(R) + + if (L.empty() || L.size() != R.size()) + return false; + END_SERIALIZE() + }; + size_t n_bulletproof_amounts(const Bulletproof &proof); size_t n_bulletproof_max_amounts(const Bulletproof &proof); size_t n_bulletproof_amounts(const std::vector &proofs); size_t n_bulletproof_max_amounts(const std::vector &proofs); + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_amounts(const std::vector &proofs); + size_t n_bulletproof_plus_max_amounts(const std::vector &proofs); + //A container to hold all signatures necessary for RingCT // rangeSigs holds all the rangeproof data of a transaction // MG holds the MLSAG signature of a transaction @@ -257,11 +296,19 @@ namespace rct { RCTTypeCLSAG = 5, RCTTypeCLSAGN = 6, RCTTypeHaven2 = 7, // Add public mask sum terms, remove extraneous fields (txnFee_usd,txnFee_xasset,txnOffshoreFee_usd,txnOffshoreFee_xasset) + RCTTypeHaven3 = 8, // Add public mask sum term for collateral + RCTTypeBulletproofPlus = 9, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { RangeProofType range_proof_type; int bp_version; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + VARINT_FIELD(range_proof_type) + VARINT_FIELD(bp_version) + END_SERIALIZE() }; struct rctSigBase { uint8_t type; @@ -279,7 +326,8 @@ namespace rct { xmr_amount txnOffshoreFee = 0; xmr_amount txnOffshoreFee_usd = 0; xmr_amount txnOffshoreFee_xasset = 0; - keyV maskSums; // contains 2 elements. 1. is the sum of masks of inputs. 2. is the sum of masks of changes. + keyV maskSums; // contains 2 or 3 elements. 1. is the sum of masks of inputs. 2. is the sum of masks of change outputs. 3. mask of the col output. + key p_r; template class Archive> bool serialize_rctsig_base(Archive &ar, size_t inputs, size_t outputs) @@ -287,10 +335,70 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) return ar.stream().good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeCLSAGN && type != RCTTypeHaven2) + if (type != RCTTypeBulletproofPlus) + return serialize_rctsig_base_old(ar, inputs, outputs); + VARINT_FIELD(txnFee) + VARINT_FIELD(txnOffshoreFee) + // inputs/outputs not saved, only here for serialization help + // FIELD(message) - not serialized, it can be reconstructed + // FIELD(mixRing) - not serialized, it can be reconstructed + ar.tag("ecdhInfo"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(outputs, ecdhInfo); + if (ecdhInfo.size() != outputs) + return false; + for (size_t i = 0; i < outputs; ++i) + { + ar.begin_object(); + if (!typename Archive::is_saving()) + memset(ecdhInfo[i].amount.bytes, 0, sizeof(ecdhInfo[i].amount.bytes)); + crypto::hash8 &amount = (crypto::hash8&)ecdhInfo[i].amount; + FIELD(amount); + ar.end_object(); + if (outputs - i > 1) + ar.delimit_array(); + } + ar.end_array(); + + ar.tag("outPk"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(outputs, outPk); + if (outPk.size() != outputs) + return false; + for (size_t i = 0; i < outputs; ++i) + { + FIELDS(outPk[i].mask) + if (outputs - i > 1) + ar.delimit_array(); + } + ar.end_array(); + + // if txnOffshoreFee is not 0, it is a conversion tx + if (txnOffshoreFee) { + ar.tag("maskSums"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(3, maskSums); + if (maskSums.size() != 3) + return false; + FIELDS(maskSums[0]) + ar.delimit_array(); + FIELDS(maskSums[1]) + ar.delimit_array(); + FIELDS(maskSums[2]) + ar.end_array(); + } + if (crypto_verify_32(p_r.bytes, null_key.bytes)) + FIELD(p_r) + return ar.stream().good(); + } + + template class Archive> + bool serialize_rctsig_base_old(Archive &ar, size_t inputs, size_t outputs) + { + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeCLSAGN && type != RCTTypeHaven2 && type != RCTTypeHaven3) return false; VARINT_FIELD(txnFee) - if (type == RCTTypeHaven2) { + if (type == RCTTypeHaven2 || type == RCTTypeHaven3) { // serialize offshore fee VARINT_FIELD(txnOffshoreFee) } else if (type == RCTTypeCLSAG || type == RCTTypeCLSAGN) { @@ -338,7 +446,7 @@ namespace rct { return false; for (size_t i = 0; i < outputs; ++i) { - if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3) { ar.begin_object(); if (!typename Archive::is_saving()) @@ -369,7 +477,22 @@ namespace rct { } ar.end_array(); - if (type == RCTTypeHaven2) { + // if txnOffshoreFee is not 0, it is a conversion tx + if (type == RCTTypeHaven3 && txnOffshoreFee) { + + ar.tag("maskSums"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(3, maskSums); + if (maskSums.size() != 3) + return false; + FIELDS(maskSums[0]) + ar.delimit_array(); + FIELDS(maskSums[1]) + ar.delimit_array(); + FIELDS(maskSums[2]) + ar.end_array(); + + } else if (type == RCTTypeHaven2) { ar.tag("maskSums"); ar.begin_array(); @@ -416,10 +539,23 @@ namespace rct { } return ar.stream().good(); } + + BEGIN_SERIALIZE_OBJECT() + FIELD(type) + FIELD(message) + FIELD(mixRing) + FIELD(pseudoOuts) + FIELD(ecdhInfo) + FIELD(outPk) + VARINT_FIELD(txnFee) + VARINT_FIELD(txnOffshoreFee) + FIELD(maskSums) + END_SERIALIZE() }; struct rctSigPrunable { std::vector rangeSigs; std::vector bulletproofs; + std::vector bulletproofs_plus; std::vector MGs; // simple rct has N, full has 1 std::vector CLSAGs; keyV pseudoOuts; //C - for simple rct @@ -428,14 +564,39 @@ namespace rct { template class Archive> bool serialize_rctsig_prunable(Archive &ar, uint8_t type, size_t inputs, size_t outputs, size_t mixin) { + if (inputs >= 0xffffffff) + return false; + if (outputs >= 0xffffffff) + return false; + if (mixin >= 0xffffffff) + return false; if (type == RCTTypeNull) return ar.stream().good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeCLSAGN && type != RCTTypeHaven2) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeCLSAGN && type != RCTTypeHaven2 && type != RCTTypeHaven3 && type != RCTTypeBulletproofPlus) return false; - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2) + if (type == RCTTypeBulletproofPlus) + { + uint32_t nbp = bulletproofs_plus.size(); + VARINT_FIELD(nbp) + ar.tag("bpp"); + ar.begin_array(); + if (nbp > outputs) + return false; + PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus); + for (size_t i = 0; i < nbp; ++i) + { + FIELDS(bulletproofs_plus[i]) + if (nbp - i > 1) + ar.delimit_array(); + } + if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs) + return false; + ar.end_array(); + } + else if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3) { uint32_t nbp = bulletproofs.size(); - if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3) VARINT_FIELD(nbp) else FIELD(nbp) @@ -470,7 +631,7 @@ namespace rct { ar.end_array(); } - if ((type == RCTTypeCLSAG) || (type == RCTTypeCLSAGN) || (type == RCTTypeHaven2)) + if (type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus) { ar.tag("CLSAGs"); ar.begin_array(); @@ -561,7 +722,7 @@ namespace rct { } ar.end_array(); } - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -579,19 +740,32 @@ namespace rct { return ar.stream().good(); } + BEGIN_SERIALIZE_OBJECT() + FIELD(rangeSigs) + FIELD(bulletproofs) + FIELD(bulletproofs_plus) + FIELD(MGs) + FIELD(CLSAGs) + FIELD(pseudoOuts) + END_SERIALIZE() }; struct rctSig: public rctSigBase { rctSigPrunable p; keyV& get_pseudo_outs() { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } + + BEGIN_SERIALIZE_OBJECT() + FIELDS((rctSigBase&)*this) + FIELD(p) + END_SERIALIZE() }; //other basepoint H = toPoint(cn_fast_hash(G)), G the basepoint @@ -697,7 +871,9 @@ namespace rct { bool is_rct_simple(int type); bool is_rct_bulletproof(int type); + bool is_rct_bulletproof_plus(int type); bool is_rct_borromean(int type); + bool is_rct_clsag(int type); static inline const rct::key &pk2rct(const crypto::public_key &pk) { return (const rct::key&)pk; } static inline const rct::key &sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; } @@ -753,5 +929,6 @@ VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(binary_archive, rct::clsag, 0x9f); +VARIANT_TAG(binary_archive, rct::BulletproofPlus, 0xa0); #endif /* RCTTYPES_H */ diff --git a/src/Native/libcryptonote/salvium_oracle/asset_types.h b/src/Native/libcryptonote/salvium_oracle/asset_types.h new file mode 100644 index 000000000..f8cf135e5 --- /dev/null +++ b/src/Native/libcryptonote/salvium_oracle/asset_types.h @@ -0,0 +1,77 @@ +// Copyright (c) 2021, Haven Protocol +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include +#include + +namespace salvium_oracle { + + const std::vector ASSET_TYPES = {"SAL", "VSD", "BURN"}; + + class asset_type_counts + { + + public: + + // Fields + uint64_t SAL; + uint64_t VSD; + uint64_t BURN; + + asset_type_counts() noexcept + : SAL(0) + , VSD(0) + , BURN(0) + { + } + + uint64_t operator[](const std::string asset_type) const noexcept + { + if (asset_type == "SAL") { + return SAL; + } else if (asset_type == "VSD") { + return VSD; + } else if (asset_type == "BURN") { + return BURN; + } + + return 0; + } + + void add(const std::string asset_type, const uint64_t val) + { + if (asset_type == "SAL") { + SAL += val; + } else if (asset_type == "VSD") { + VSD += val; + } else if (asset_type == "BURN") { + BURN += val; + } + } + }; +} diff --git a/src/Native/libcryptonote/salvium_oracle/pricing_record.cpp b/src/Native/libcryptonote/salvium_oracle/pricing_record.cpp new file mode 100644 index 000000000..38f1ca9e1 --- /dev/null +++ b/src/Native/libcryptonote/salvium_oracle/pricing_record.cpp @@ -0,0 +1,246 @@ +// Copyright (c) 2019, Haven Protocol +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Portions of this code based upon code Copyright (c) 2019, The Monero Project + +#include +#include "pricing_record.h" + +#include "serialization/keyvalue_serialization.h" +#include "storages/portable_storage.h" + +#include "string_tools.h" +namespace salvium_oracle +{ + + namespace + { + struct asset_data_serialized + { + std::string asset_type; + uint64_t spot_price; + uint64_t ma_price; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(asset_type) + KV_SERIALIZE(spot_price) + KV_SERIALIZE(ma_price) + END_KV_SERIALIZE_MAP() + }; + + struct supply_data_serialized + { + uint64_t SAL; + uint64_t VSD; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(SAL) + KV_SERIALIZE(VSD) + END_KV_SERIALIZE_MAP() + }; + + struct pr_serialized + { + uint64_t pr_version; + uint64_t height; + supply_data supply; + std::vector assets; + uint64_t timestamp; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(pr_version) + KV_SERIALIZE(height) + KV_SERIALIZE(supply) + KV_SERIALIZE(assets) + KV_SERIALIZE(timestamp) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + } + + pricing_record::pricing_record() noexcept + : pr_version(0) + , height(0) + , supply() + , assets() + , timestamp(0) + , signature() + { + } + + pricing_record::~pricing_record() noexcept + { + } + + bool supply_data::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent) + { + supply_data_serialized in{}; + if (in._load(src, hparent)) + { + // Copy everything into the local instance + sal = in.SAL; + vsd = in.VSD; + return true; + } + // Report error here? + return false; + } + + bool supply_data::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const + { + const supply_data_serialized out{sal, vsd}; + return out.store(dest, hparent); + } + + bool asset_data::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent) + { + asset_data_serialized in{}; + if (in._load(src, hparent)) + { + // Copy everything into the local instance + asset_type = in.asset_type; + spot_price = in.spot_price; + ma_price = in.ma_price; + return true; + } + // Report error here? + return false; + } + + bool asset_data::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const + { + const asset_data_serialized out{asset_type, spot_price, ma_price}; + return out.store(dest, hparent); + } + + bool pricing_record::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent) + { + pr_serialized in{}; + if (in._load(src, hparent)) + { + // Copy everything into the local instance + pr_version = in.pr_version; + height = in.height; + supply = in.supply; + assets = in.assets; + timestamp = in.timestamp; + + // Signature arrives in HEX format, but needs to be used in BINARY format - convert it here + signature.resize(0); + assert(in.signature.size()%2 == 0); + signature.reserve(in.signature.size() >> 1); + for (unsigned int i = 0; i < in.signature.size(); i += 2) { + std::string byteString = in.signature.substr(i, 2); + signature.emplace_back((uint8_t)strtol(byteString.c_str(), NULL, 16)); + } + return true; + } + + // Report error here? + return false; + } + + bool pricing_record::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const + { + std::string sig_hex; + for (size_t i=0; iempty()) + return true; + + // validate the timestmap + if (this->timestamp > bl_timestamp + PRICING_RECORD_VALID_TIME_DIFF_FROM_BLOCK) { + LOG_ERROR("Pricing record timestamp is too far in the future."); + return false; + } + + if (this->timestamp <= last_bl_timestamp) { + LOG_ERROR("Pricing record timestamp: " << this->timestamp << ", block timestamp: " << bl_timestamp); + LOG_ERROR("Pricing record timestamp is too old."); + return false; + } + + return true; + } +} diff --git a/src/Native/libcryptonote/salvium_oracle/pricing_record.h b/src/Native/libcryptonote/salvium_oracle/pricing_record.h new file mode 100644 index 000000000..fa01044c4 --- /dev/null +++ b/src/Native/libcryptonote/salvium_oracle/pricing_record.h @@ -0,0 +1,159 @@ +// Copyright (c) 2019, Haven Protocol +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Portions of this code based upon code Copyright (c) 2019, The Monero Project + +#pragma once +#include "common/pod-class.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "serialization/vector.h" + +#include "cryptonote_config.h" +#include "crypto/hash.h" + +namespace epee +{ + namespace serialization + { + class portable_storage; + struct section; + } +} + +namespace salvium_oracle +{ + #pragma pack(push, 1) + POD_CLASS pricing_record_pre { + uint64_t pr_version; + uint64_t price; + uint64_t timestamp; + }; + #pragma pack(pop) + + struct supply_data { + uint64_t sal; + uint64_t vsd; + + //! Load from epee p2p format + bool _load(epee::serialization::portable_storage& src, epee::serialization::section* hparent); + //! Store in epee p2p format + bool store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(sal) + VARINT_FIELD(vsd) + END_SERIALIZE() + }; + + inline bool operator==(const supply_data& a, const supply_data& b) noexcept + { + return (a.sal == b.sal && + a.vsd == b.vsd); + } + + struct asset_data { + std::string asset_type; + uint64_t spot_price; + uint64_t ma_price; + + //! Load from epee p2p format + bool _load(epee::serialization::portable_storage& src, epee::serialization::section* hparent); + //! Store in epee p2p format + bool store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const; + + BEGIN_SERIALIZE_OBJECT() + FIELD(asset_type) + VARINT_FIELD(spot_price) + VARINT_FIELD(ma_price) + END_SERIALIZE() + }; + + inline bool operator==(const asset_data& a, const asset_data& b) noexcept + { + return (a.asset_type == b.asset_type && + a.spot_price == b.spot_price && + a.ma_price == b.ma_price); + } + + struct pricing_record + { + // Fields + uint64_t pr_version; + uint64_t height; + supply_data supply; + std::vector assets; + uint64_t timestamp; + std::vector signature; + + // Default c'tor + pricing_record() noexcept; + //! Load from epee p2p format + bool _load(epee::serialization::portable_storage& src, epee::serialization::section* hparent); + //! Store in epee p2p format + bool store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const; + pricing_record(const pricing_record& orig) noexcept; + ~pricing_record() noexcept; + bool equal(const pricing_record& other) const noexcept; + bool empty() const noexcept; + bool valid(uint32_t hf_version, uint64_t bl_timestamp, uint64_t last_bl_timestamp) const; + + pricing_record& operator=(const pricing_record& orig) noexcept; + uint64_t operator[](const std::string& asset_type) const; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(pr_version) + VARINT_FIELD(height) + FIELD(supply) + FIELD(assets) + VARINT_FIELD(timestamp) + FIELD(signature) + END_SERIALIZE() + }; + + inline bool operator==(const pricing_record& a, const pricing_record& b) noexcept + { + return a.equal(b); + } + + inline bool operator!=(const pricing_record& a, const pricing_record& b) noexcept + { + return !a.equal(b); + } + +} // salvium_oracle diff --git a/src/Native/libcryptonote/serialization/serialization.h b/src/Native/libcryptonote/serialization/serialization.h index 6802cdcf9..8b943083b 100644 --- a/src/Native/libcryptonote/serialization/serialization.h +++ b/src/Native/libcryptonote/serialization/serialization.h @@ -109,6 +109,13 @@ inline bool do_serialize(Archive &ar, bool &v) ar.serialize_varint(f); \ if (!ar.stream().good()) return false; \ } while(0); +#define VERSION_FIELD(v) \ + uint32_t version = v; \ + do { \ + ar.tag("version"); \ + ar.serialize_varint(version); \ + if (!ar.stream().good()) return false; \ + } while(0); namespace serialization { namespace detail diff --git a/src/Native/libcryptonote/serialization/zephyr_pricing_record.h b/src/Native/libcryptonote/serialization/zephyr_pricing_record.h index df8302fc9..cea3f8338 100644 --- a/src/Native/libcryptonote/serialization/zephyr_pricing_record.h +++ b/src/Native/libcryptonote/serialization/zephyr_pricing_record.h @@ -40,31 +40,46 @@ template