Import current p2pool-salvium snapshot
C/C++ CI / build-alpine-static (map[arch:aarch64 branch:latest-stable flags:-ffunction-sections -mfix-cortex-a53-835769 -mfix-cortex-a53-843419]) (push) Has been cancelled
C/C++ CI / build-alpine-static (map[arch:riscv64 branch:latest-stable flags:-ffunction-sections]) (push) Has been cancelled
C/C++ CI / build-alpine-static (map[arch:x86_64 branch:latest-stable flags:-ffunction-sections]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-10 cpp:g++-10 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-11 cpp:g++-11 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-12 cpp:g++-12 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-13 cpp:g++-13 flags: os:ubuntu-24.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-14 cpp:g++-14 flags: os:ubuntu-24.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-9 cpp:g++-9 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu-static-libs (map[flags:-fuse-linker-plugin -ffunction-sections]) (push) Has been cancelled
C/C++ CI / build-ubuntu-aarch64 (map[flags:-fuse-linker-plugin -ffunction-sections -mfix-cortex-a53-835769 -mfix-cortex-a53-843419 os:ubuntu-22.04-arm]) (push) Has been cancelled
C/C++ CI / build-ubuntu-riscv64 (map[flags:-fuse-linker-plugin -ffunction-sections os:ubuntu-24.04]) (push) Has been cancelled
C/C++ CI / build-windows-msys2 (map[c:gcc cxx:g++ flags:-ffunction-sections -Wno-error=maybe-uninitialized -Wno-error=attributes -Wno-attributes]) (push) Has been cancelled
C/C++ CI / build-windows-msbuild (map[grpc:OFF os:2022 rx:ON tls:ON upnp:ON vs:Visual Studio 17 2022 vspath:C:\Program Files\Microsoft Visual Studio\2022\Enterprise]) (push) Has been cancelled
C/C++ CI / build-windows-msbuild (map[grpc:ON os:2022 rx:ON tls:ON upnp:ON vs:Visual Studio 17 2022 vspath:C:\Program Files\Microsoft Visual Studio\2022\Enterprise]) (push) Has been cancelled
C/C++ CI / build-macos (push) Has been cancelled
C/C++ CI / build-macos-aarch64 (push) Has been cancelled
C/C++ CI / build-freebsd (map[architecture:x86-64 host:ubuntu-latest name:freebsd version:13.3]) (push) Has been cancelled
C/C++ CI / build-openbsd (map[architecture:x86-64 host:ubuntu-latest name:openbsd version:7.4]) (push) Has been cancelled
C/C++ CI / build-windows-msys2 (map[c:clang cxx:clang++ flags:-fuse-ld=lld -Wno-unused-command-line-argument -Wno-nan-infinity-disabled -Wno-attributes]) (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
Code coverage / coverage (push) Has been cancelled
cppcheck / cppcheck-ubuntu (push) Has been cancelled
cppcheck / cppcheck-windows (push) Has been cancelled
Microsoft C++ Code Analysis / Analyze (push) Has been cancelled
source-snapshot / source-snapshot (push) Has been cancelled
Sync test (macOS) / sync-test-macos-intel (push) Has been cancelled
Sync test (macOS) / sync-test-macos-arm64 (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-tsan (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-msan (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-ubsan (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-asan (push) Has been cancelled
Sync test (Windows) / sync-test-windows-debug-asan (push) Has been cancelled
Sync test (Windows) / sync-test-windows-leaks (push) Has been cancelled
C/C++ CI / build-alpine-static (map[arch:aarch64 branch:latest-stable flags:-ffunction-sections -mfix-cortex-a53-835769 -mfix-cortex-a53-843419]) (push) Has been cancelled
C/C++ CI / build-alpine-static (map[arch:riscv64 branch:latest-stable flags:-ffunction-sections]) (push) Has been cancelled
C/C++ CI / build-alpine-static (map[arch:x86_64 branch:latest-stable flags:-ffunction-sections]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-10 cpp:g++-10 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-11 cpp:g++-11 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-12 cpp:g++-12 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-13 cpp:g++-13 flags: os:ubuntu-24.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-14 cpp:g++-14 flags: os:ubuntu-24.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu (map[c:gcc-9 cpp:g++-9 flags: os:ubuntu-22.04]) (push) Has been cancelled
C/C++ CI / build-ubuntu-static-libs (map[flags:-fuse-linker-plugin -ffunction-sections]) (push) Has been cancelled
C/C++ CI / build-ubuntu-aarch64 (map[flags:-fuse-linker-plugin -ffunction-sections -mfix-cortex-a53-835769 -mfix-cortex-a53-843419 os:ubuntu-22.04-arm]) (push) Has been cancelled
C/C++ CI / build-ubuntu-riscv64 (map[flags:-fuse-linker-plugin -ffunction-sections os:ubuntu-24.04]) (push) Has been cancelled
C/C++ CI / build-windows-msys2 (map[c:gcc cxx:g++ flags:-ffunction-sections -Wno-error=maybe-uninitialized -Wno-error=attributes -Wno-attributes]) (push) Has been cancelled
C/C++ CI / build-windows-msbuild (map[grpc:OFF os:2022 rx:ON tls:ON upnp:ON vs:Visual Studio 17 2022 vspath:C:\Program Files\Microsoft Visual Studio\2022\Enterprise]) (push) Has been cancelled
C/C++ CI / build-windows-msbuild (map[grpc:ON os:2022 rx:ON tls:ON upnp:ON vs:Visual Studio 17 2022 vspath:C:\Program Files\Microsoft Visual Studio\2022\Enterprise]) (push) Has been cancelled
C/C++ CI / build-macos (push) Has been cancelled
C/C++ CI / build-macos-aarch64 (push) Has been cancelled
C/C++ CI / build-freebsd (map[architecture:x86-64 host:ubuntu-latest name:freebsd version:13.3]) (push) Has been cancelled
C/C++ CI / build-openbsd (map[architecture:x86-64 host:ubuntu-latest name:openbsd version:7.4]) (push) Has been cancelled
C/C++ CI / build-windows-msys2 (map[c:clang cxx:clang++ flags:-fuse-ld=lld -Wno-unused-command-line-argument -Wno-nan-infinity-disabled -Wno-attributes]) (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
Code coverage / coverage (push) Has been cancelled
cppcheck / cppcheck-ubuntu (push) Has been cancelled
cppcheck / cppcheck-windows (push) Has been cancelled
Microsoft C++ Code Analysis / Analyze (push) Has been cancelled
source-snapshot / source-snapshot (push) Has been cancelled
Sync test (macOS) / sync-test-macos-intel (push) Has been cancelled
Sync test (macOS) / sync-test-macos-arm64 (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-tsan (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-msan (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-ubsan (push) Has been cancelled
Sync test (Ubuntu) / sync-test-ubuntu-asan (push) Has been cancelled
Sync test (Windows) / sync-test-windows-debug-asan (push) Has been cancelled
Sync test (Windows) / sync-test-windows-leaks (push) Has been cancelled
This commit is contained in:
+1002
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,21 @@ Decentralized pool for Salvium mining. Originally forked from [SChernykh's P2Poo
|
||||
|
||||
### Build Status
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
[](https://gitlab.com/whiskyrelaxing-group/p2pool-salvium/-/pipelines)
|
||||
|
||||
# Contents
|
||||
- [Pool mining vs Solo mining vs P2Pool mining](#pool-mining-vs-solo-mining-vs-p2pool-mining)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)
|
||||
set(CMAKE_STRIP /usr/bin/aarch64-linux-gnu-strip)
|
||||
+2
-2
@@ -38,7 +38,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES GNU)
|
||||
else()
|
||||
set(OPTIMIZATION_FLAGS "-Og")
|
||||
endif()
|
||||
set(OPTIMIZATION_FLAGS "${OPTIMIZATION_FLAGS} -g3 -ftrapv")
|
||||
set(OPTIMIZATION_FLAGS "${OPTIMIZATION_FLAGS} -g3")
|
||||
else()
|
||||
set(OPTIMIZATION_FLAGS "-O3 -ffast-math -s")
|
||||
endif()
|
||||
@@ -130,7 +130,7 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES Clang)
|
||||
else()
|
||||
set(OPTIMIZATION_FLAGS "-Og")
|
||||
endif()
|
||||
set(OPTIMIZATION_FLAGS "${OPTIMIZATION_FLAGS} -g3 -ftrapv")
|
||||
set(OPTIMIZATION_FLAGS "${OPTIMIZATION_FLAGS} -g3")
|
||||
else()
|
||||
set(OPTIMIZATION_FLAGS "-O3 -ffast-math -funroll-loops -fmerge-all-constants")
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR riscv64)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
set(triple riscv64-linux-musl)
|
||||
|
||||
set(CMAKE_C_COMPILER ${triple}-gcc)
|
||||
set(CMAKE_CXX_COMPILER ${triple}-g++)
|
||||
set(CMAKE_AR ${triple}-ar)
|
||||
set(CMAKE_RANLIB ${triple}-ranlib)
|
||||
set(CMAKE_STRIP ${triple}-strip)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
@@ -0,0 +1,19 @@
|
||||
@echo off
|
||||
"C:\Cppcheck\cppcheck.exe" -j 4 ../src/*.cpp -D__cppcheck__ -DSIZE_MAX=UINT64_MAX -DRAPIDJSON_ENDIAN=RAPIDJSON_LITTLEENDIAN -D_WIN32=1 -D_WIN64=1 -DWIN32=1 -D_WINDOWS=1 -DNDEBUG=1 -DWITH_GRPC=1 -DPROTOBUF_ENABLE_DEBUG_LOGGING_MAY_LEAK_PII=0 -DWITH_RANDOMX=1 -DWITH_UPNP=1 -DWITH_TLS=1 -DWITH_MERGE_MINING_DONATION=1 -DCURL_STATICLIB=1 -DWIN32_LEAN_AND_MEAN=1 -D_WIN32_WINNT=0x0600 -D_DISABLE_VECTOR_ANNOTATION=1 -D_DISABLE_STRING_ANNOTATION=1 -DZMQ_STATIC=1 -DZMQ_VERSION=40306 -DHAVE_BITSCANREVERSE64=1 -DRAPIDJSON_PARSE_DEFAULT_FLAGS=kParseTrailingCommasFlag -DMINIUPNP_STATICLIB=1 -DCARES_STATICLIB=1 -DCMAKE_INTDIR="Release" -D__SSE2__=1 -D_MSC_VER=1929 --platform=win64 --std=c++17 --enable=all --inconclusive --inline-suppr --template="{file}:{line}:{id}{inconclusive: INCONCLUSIVE} {message}" --includes-file=includes.txt --suppressions-list=suppressions.txt --output-file=errors_full.txt --max-ctu-depth=3 --check-level=exhaustive --checkers-report=checkers_report.txt
|
||||
|
||||
findstr /V /C:"external\src" errors_full.txt > errors_filtered0.txt
|
||||
findstr /V /C:":checkersReport" errors_filtered0.txt > errors_filtered.txt
|
||||
|
||||
findstr /C:"There were critical errors" checkers_report.txt > checkers_report_filtered.txt
|
||||
|
||||
for /f %%i in ("errors_filtered.txt") do set size=%%~zi
|
||||
if %size% gtr 0 (
|
||||
type errors_filtered.txt
|
||||
exit 1
|
||||
)
|
||||
|
||||
for /f %%i in ("checkers_report_filtered.txt") do set size2=%%~zi
|
||||
if %size2% gtr 0 (
|
||||
type checkers_report_filtered.txt
|
||||
exit 1
|
||||
)
|
||||
Vendored
+6
@@ -61,6 +61,9 @@ const hardfork_t mainnet_hard_forks[] = {
|
||||
|
||||
// version 10 Carrot - including treasury mint - starts from block 334750, which is on or around the 13th of October, 2025. Fork time finalised on 2025-09-29. No fork voting occurs for the v10 fork.
|
||||
{10, 334750, 0, 1759142500 },
|
||||
|
||||
// version 11 Two Milestone 1 - starts from block 465000, which is on or around the 13th of April, 2026. Fork time finalised on 2026-03-20. No fork voting occurs for the v11 fork.
|
||||
{11, 465000, 0, 1774000000 },
|
||||
};
|
||||
const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]);
|
||||
const uint64_t mainnet_hard_fork_version_1_till = ((uint64_t)-1);
|
||||
@@ -95,6 +98,9 @@ const hardfork_t testnet_hard_forks[] = {
|
||||
|
||||
// version 10 Carrot - including treasury mint - starts from block 1100
|
||||
{10, 1100, 0, 1739780005 },
|
||||
|
||||
// version 11
|
||||
{11, 1200, 0, 1739880005 },
|
||||
};
|
||||
const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]);
|
||||
const uint64_t testnet_hard_fork_version_1_till = ((uint64_t)-1);
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# Generate a shields.io-style SVG badge as a CI artifact.
|
||||
# Usage: gen-badge.sh <label> <status> <color> <output_file>
|
||||
# Example: gen-badge.sh "clang-tidy" "passing" "brightgreen" "clang-tidy.svg"
|
||||
|
||||
LABEL="$1"
|
||||
STATUS="$2"
|
||||
COLOR="$3"
|
||||
OUTPUT="$4"
|
||||
|
||||
LABEL_WIDTH=$(( ${#LABEL} * 7 + 10 ))
|
||||
STATUS_WIDTH=$(( ${#STATUS} * 7 + 10 ))
|
||||
TOTAL_WIDTH=$(( LABEL_WIDTH + STATUS_WIDTH ))
|
||||
|
||||
cat > "$OUTPUT" << SVGEOF
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="${TOTAL_WIDTH}" height="20">
|
||||
<linearGradient id="a" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<rect rx="3" width="${TOTAL_WIDTH}" height="20" fill="#555"/>
|
||||
<rect rx="3" x="${LABEL_WIDTH}" width="${STATUS_WIDTH}" height="20" fill="${COLOR}"/>
|
||||
<rect rx="3" width="${TOTAL_WIDTH}" height="20" fill="url(#a)"/>
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||
<text x="$(( LABEL_WIDTH / 2 ))" y="15" fill="#010101" fill-opacity=".3">${LABEL}</text>
|
||||
<text x="$(( LABEL_WIDTH / 2 ))" y="14">${LABEL}</text>
|
||||
<text x="$(( LABEL_WIDTH + STATUS_WIDTH / 2 ))" y="15" fill="#010101" fill-opacity=".3">${STATUS}</text>
|
||||
<text x="$(( LABEL_WIDTH + STATUS_WIDTH / 2 ))" y="14">${STATUS}</text>
|
||||
</g>
|
||||
</svg>
|
||||
SVGEOF
|
||||
+65
-53
@@ -28,7 +28,6 @@
|
||||
#include "p2pool.h"
|
||||
#include "side_chain.h"
|
||||
#include "pool_block.h"
|
||||
#include "protocol_tx_hash.h"
|
||||
#include "merkle.h"
|
||||
#include <zmq.hpp>
|
||||
#include <ctime>
|
||||
@@ -67,6 +66,7 @@ BlockTemplate::BlockTemplate(SideChain* sidechain, RandomX_Hasher_Base* hasher)
|
||||
, m_minerTxKeccakStateInputLength(0)
|
||||
, m_sidechainHashKeccakState{}
|
||||
, m_sidechainHashInputLength(0)
|
||||
, m_sidechainExtraNonceOffset(0)
|
||||
, m_rng(RandomDeviceSeed::instance)
|
||||
{
|
||||
// Diffuse the initial state in case it has low quality
|
||||
@@ -115,15 +115,8 @@ BlockTemplate::BlockTemplate(const BlockTemplate& b)
|
||||
*this = b;
|
||||
}
|
||||
|
||||
// cppcheck-suppress operatorEqVarError
|
||||
BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
|
||||
void BlockTemplate::copy_from_nolock(const BlockTemplate& b)
|
||||
{
|
||||
if (this == &b) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
WriteLock lock(m_lock);
|
||||
|
||||
m_sidechain = b.m_sidechain;
|
||||
m_hasher = b.m_hasher;
|
||||
m_templateId = b.m_templateId;
|
||||
@@ -158,11 +151,13 @@ BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
|
||||
|
||||
m_sidechainHashKeccakState = b.m_sidechainHashKeccakState;
|
||||
m_sidechainHashInputLength = b.m_sidechainHashInputLength;
|
||||
m_sidechainExtraNonceOffset = b.m_sidechainExtraNonceOffset;
|
||||
|
||||
m_minerTx.clear();
|
||||
m_blockHeader.clear();
|
||||
m_minerTxExtra.clear();
|
||||
m_transactionHashes.clear();
|
||||
m_transactionHashesSet.clear();
|
||||
m_rewards.clear();
|
||||
m_mempoolTxs.clear();
|
||||
m_mempoolTxsOrder.clear();
|
||||
@@ -174,7 +169,17 @@ BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
|
||||
#if TEST_MEMPOOL_PICKING_ALGORITHM
|
||||
m_knapsack.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
// cppcheck-suppress operatorEqVarError ; m_lock and m_oldTemplates are per-instance, must not be copied
|
||||
BlockTemplate& BlockTemplate::operator=(const BlockTemplate& b)
|
||||
{
|
||||
if (this == &b) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
WriteLock lock(m_lock);
|
||||
copy_from_nolock(b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -252,7 +257,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
|
||||
auto use_old_template = [this]() {
|
||||
const uint32_t id = m_templateId - 1;
|
||||
LOGWARN(4, "using old block template with ID = " << id);
|
||||
*this = *m_oldTemplates[id % array_size(&BlockTemplate::m_oldTemplates)];
|
||||
copy_from_nolock(*m_oldTemplates[id % array_size(&BlockTemplate::m_oldTemplates)]);
|
||||
};
|
||||
|
||||
m_height = data.height;
|
||||
@@ -296,6 +301,15 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
|
||||
|
||||
m_poolBlockTemplate->m_minerWallet = params->m_miningWallet;
|
||||
|
||||
// Protocol TX blob is always required for Carrot v1+ blocks.
|
||||
// update_block_template() ensures it's loaded from the daemon before calling update().
|
||||
m_poolBlockTemplate->m_protocolTxBlob = data.protocol_tx_blob;
|
||||
m_protocolTxHash = data.protocol_tx_hash;
|
||||
|
||||
// Detect and handle hardfork transitions before building the sidechain data.
|
||||
// This purges the stale sidechain and enables genesis creation on the new chain.
|
||||
m_sidechain->handle_hardfork_transition(data.major_version);
|
||||
|
||||
if (!m_sidechain->fill_sidechain_data(*m_poolBlockTemplate, m_shares)) {
|
||||
use_old_template();
|
||||
return;
|
||||
@@ -562,9 +576,17 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
|
||||
#endif
|
||||
}
|
||||
|
||||
// Salvium: Calculate full reward, then apply 80/20 split
|
||||
// Salvium: Calculate full reward, then apply reward split
|
||||
full_block_reward = final_reward; // Full reward including fees
|
||||
final_reward = final_reward - (final_reward / 5); // 80% to miners
|
||||
if (data.major_version >= 11) {
|
||||
// HF11: 60% miner + 25% treasury + 15% staker
|
||||
const uint64_t treasury_reward = full_block_reward * 25 / 100;
|
||||
const uint64_t staker_reward = (full_block_reward - treasury_reward) * 20 / 100;
|
||||
final_reward = full_block_reward - treasury_reward - staker_reward;
|
||||
} else {
|
||||
// HF10: 80% miner + 20% staker
|
||||
final_reward = final_reward - (final_reward / 5);
|
||||
}
|
||||
if (!SideChain::split_reward(final_reward, m_shares, m_rewards)) {
|
||||
use_old_template();
|
||||
return;
|
||||
@@ -655,31 +677,10 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
|
||||
m_transactionHashes.reserve(HASH_SIZE * 2 + mempool_txs.size());
|
||||
m_transactionHashes.insert(m_transactionHashes.end(), miner_tx_hash.h, miner_tx_hash.h + HASH_SIZE);
|
||||
|
||||
// Write protocol tx to blob (for sidechain consensus)
|
||||
// Write protocol tx to block template blob (already set on pool block template at top of update())
|
||||
m_protocolTxOffsetInTemplate = m_blockTemplateBlob.size();
|
||||
if (m_poolBlockTemplate->m_sidechainHeight >= SIDECHAIN_PROTOCOL_TX_HARDFORK_HEIGHT && data.protocol_tx_loaded && !data.protocol_tx_blob.empty()) {
|
||||
// Real protocol_tx from daemon - use it directly
|
||||
m_blockTemplateBlob.insert(m_blockTemplateBlob.end(), data.protocol_tx_blob.begin(), data.protocol_tx_blob.end());
|
||||
m_protocolTxSize = m_blockTemplateBlob.size() - m_protocolTxOffsetInTemplate;
|
||||
m_protocolTxHash = data.protocol_tx_hash;
|
||||
m_poolBlockTemplate->m_protocolTxBlob = data.protocol_tx_blob;
|
||||
} else {
|
||||
// Stub protocol_tx (pre-hardfork or blob not yet available)
|
||||
writeVarint(4, m_blockTemplateBlob); // version
|
||||
writeVarint(60, m_blockTemplateBlob); // unlock_time
|
||||
writeVarint(1, m_blockTemplateBlob); // vin count
|
||||
m_blockTemplateBlob.push_back(0xff); // TXIN_GEN
|
||||
writeVarint(data.height, m_blockTemplateBlob); // height
|
||||
writeVarint(0, m_blockTemplateBlob); // vout count
|
||||
writeVarint(2, m_blockTemplateBlob); // extra size
|
||||
m_blockTemplateBlob.push_back(0x02); // extra[0]
|
||||
m_blockTemplateBlob.push_back(0x00); // extra[1]
|
||||
writeVarint(2, m_blockTemplateBlob); // type PROTOCOL
|
||||
m_blockTemplateBlob.push_back(0); // RCT type
|
||||
m_protocolTxSize = m_blockTemplateBlob.size() - m_protocolTxOffsetInTemplate;
|
||||
calculate_protocol_tx_hash(data.height, m_protocolTxHash);
|
||||
m_poolBlockTemplate->m_protocolTxBlob.clear();
|
||||
}
|
||||
m_blockTemplateBlob.insert(m_blockTemplateBlob.end(), m_poolBlockTemplate->m_protocolTxBlob.begin(), m_poolBlockTemplate->m_protocolTxBlob.end());
|
||||
m_protocolTxSize = m_blockTemplateBlob.size() - m_protocolTxOffsetInTemplate;
|
||||
|
||||
// Add protocol tx hash after miner tx
|
||||
m_transactionHashes.insert(m_transactionHashes.end(), m_protocolTxHash.h, m_protocolTxHash.h + HASH_SIZE);
|
||||
@@ -759,17 +760,21 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
|
||||
|
||||
init_merge_mining_merkle_proof();
|
||||
|
||||
const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data();
|
||||
size_t sidechain_extra_nonce_offset_in_sc;
|
||||
const std::vector<uint8_t> sidechain_data = m_poolBlockTemplate->serialize_sidechain_data(&sidechain_extra_nonce_offset_in_sc);
|
||||
const std::vector<uint8_t>& consensus_id = m_sidechain->consensus_id();
|
||||
|
||||
m_sidechainHashBlob = m_poolBlockTemplate->serialize_mainchain_data();
|
||||
const size_t mainchain_size = m_sidechainHashBlob.size();
|
||||
m_sidechainHashBlob.insert(m_sidechainHashBlob.end(), sidechain_data.begin(), sidechain_data.end());
|
||||
m_sidechainHashBlob.insert(m_sidechainHashBlob.end(), consensus_id.begin(), consensus_id.end());
|
||||
|
||||
m_sidechainExtraNonceOffset = mainchain_size + sidechain_extra_nonce_offset_in_sc;
|
||||
|
||||
{
|
||||
m_sidechainHashKeccakState = {};
|
||||
|
||||
const size_t extra_nonce_offset = m_sidechainHashBlob.size() - HASH_SIZE - EXTRA_NONCE_SIZE;
|
||||
const size_t extra_nonce_offset = m_sidechainExtraNonceOffset;
|
||||
if (extra_nonce_offset >= KeccakParams::HASH_DATA_AREA) {
|
||||
// Sidechain data is big enough to cache keccak state up to extra_nonce
|
||||
m_sidechainHashInputLength = (extra_nonce_offset / KeccakParams::HASH_DATA_AREA) * KeccakParams::HASH_DATA_AREA;
|
||||
@@ -1022,8 +1027,8 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
const size_t num_outputs = num_miner_outputs + num_protocol_outputs;
|
||||
m_minerTx.reserve(num_outputs * 60 + 55);
|
||||
|
||||
// For Carrot v1 (HF10+), use version 4
|
||||
m_minerTx.push_back(4); // TRANSACTION_VERSION_CARROT
|
||||
// For Carrot v1 (HF10+), use version 4; HF11+ uses version 5
|
||||
m_minerTx.push_back(data.major_version >= 11 ? TX_VERSION_ENABLE_TOKENS : TX_VERSION);
|
||||
|
||||
writeVarint(MINER_REWARD_UNLOCK_TIME, m_minerTx);
|
||||
m_minerTx.push_back(1); // Number of inputs
|
||||
@@ -1058,6 +1063,7 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
hash eph_pubkey; // D_e (per-output ephemeral pubkey)
|
||||
uint8_t view_tag[3];
|
||||
uint8_t encrypted_anchor[16];
|
||||
std::string asset_type; // "SAL1" for miner outputs, variable for protocol outputs
|
||||
};
|
||||
std::vector<CarrotOutput> outputs;
|
||||
outputs.reserve(num_outputs);
|
||||
@@ -1072,6 +1078,8 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
memset(out.view_tag, 0, 3);
|
||||
memset(out.encrypted_anchor, 0, 16);
|
||||
|
||||
out.asset_type = "SAL1";
|
||||
|
||||
if (!dry_run) {
|
||||
// Derive anchor from wallet's spend public key (position-independent)
|
||||
uint8_t anchor[16];
|
||||
@@ -1135,6 +1143,7 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
out.eph_pubkey = po.eph_pubkey;
|
||||
memcpy(out.view_tag, po.view_tag, 3);
|
||||
memcpy(out.encrypted_anchor, po.anchor_enc, 16);
|
||||
out.asset_type = po.asset_type;
|
||||
outputs.push_back(out);
|
||||
}
|
||||
|
||||
@@ -1164,20 +1173,14 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
|
||||
if (dry_run) {
|
||||
m_minerTx.insert(m_minerTx.end(), HASH_SIZE, 0);
|
||||
m_minerTx.push_back(4);
|
||||
m_minerTx.push_back('S');
|
||||
m_minerTx.push_back('A');
|
||||
m_minerTx.push_back('L');
|
||||
m_minerTx.push_back('1');
|
||||
writeVarint(out.asset_type.size(), m_minerTx);
|
||||
m_minerTx.insert(m_minerTx.end(), out.asset_type.begin(), out.asset_type.end());
|
||||
m_minerTx.insert(m_minerTx.end(), 3, 0);
|
||||
m_minerTx.insert(m_minerTx.end(), 16, 0);
|
||||
} else {
|
||||
m_minerTx.insert(m_minerTx.end(), out.onetime_address.h, out.onetime_address.h + HASH_SIZE);
|
||||
m_minerTx.push_back(4);
|
||||
m_minerTx.push_back('S');
|
||||
m_minerTx.push_back('A');
|
||||
m_minerTx.push_back('L');
|
||||
m_minerTx.push_back('1');
|
||||
writeVarint(out.asset_type.size(), m_minerTx);
|
||||
m_minerTx.insert(m_minerTx.end(), out.asset_type.begin(), out.asset_type.end());
|
||||
m_minerTx.insert(m_minerTx.end(), out.view_tag, out.view_tag + 3);
|
||||
m_minerTx.insert(m_minerTx.end(), out.encrypted_anchor, out.encrypted_anchor + 16);
|
||||
|
||||
@@ -1189,6 +1192,7 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
po.amount = out.amount;
|
||||
memcpy(po.view_tag, out.view_tag, 3);
|
||||
memcpy(po.anchor_enc, out.encrypted_anchor, 16);
|
||||
po.asset_type = out.asset_type;
|
||||
m_poolBlockTemplate->m_protocolOutputs.push_back(po);
|
||||
} else {
|
||||
// Save miner output to pool block template
|
||||
@@ -1283,8 +1287,16 @@ int BlockTemplate::create_miner_tx(const MinerData& data, const std::vector<Mine
|
||||
// type = MINER (1)
|
||||
writeVarint(1, m_minerTx);
|
||||
|
||||
// amount_burnt = 20% of total block reward
|
||||
uint64_t amount_burnt = full_block_reward / 5;
|
||||
// amount_burnt = staker share of total block reward
|
||||
uint64_t amount_burnt;
|
||||
if (data.major_version >= 11) {
|
||||
// HF11: staker gets 20% of (block_reward - treasury) = 15% of total
|
||||
const uint64_t treasury_reward = full_block_reward * 25 / 100;
|
||||
amount_burnt = (full_block_reward - treasury_reward) * 20 / 100;
|
||||
} else {
|
||||
// HF10: staker gets 20% of total
|
||||
amount_burnt = full_block_reward / 5;
|
||||
}
|
||||
writeVarint(amount_burnt, m_minerTx);
|
||||
m_poolBlockTemplate->m_amountBurnt = amount_burnt;
|
||||
|
||||
@@ -1301,7 +1313,7 @@ hash BlockTemplate::calc_sidechain_hash(uint32_t sidechain_extra_nonce) const
|
||||
const size_t size = m_sidechainHashBlob.size();
|
||||
const size_t N = m_sidechainHashInputLength;
|
||||
|
||||
const size_t sidechain_extra_nonce_offset = size - HASH_SIZE - EXTRA_NONCE_SIZE;
|
||||
const size_t sidechain_extra_nonce_offset = m_sidechainExtraNonceOffset;
|
||||
const uint8_t sidechain_extra_nonce_buf[EXTRA_NONCE_SIZE] = {
|
||||
static_cast<uint8_t>(sidechain_extra_nonce >> 0),
|
||||
static_cast<uint8_t>(sidechain_extra_nonce >> 8),
|
||||
|
||||
@@ -74,6 +74,7 @@ private:
|
||||
RandomX_Hasher_Base* m_hasher;
|
||||
|
||||
private:
|
||||
void copy_from_nolock(const BlockTemplate& b);
|
||||
void select_mempool_transactions(const Mempool& mempool);
|
||||
int create_miner_tx(const MinerData& data, const std::vector<MinerShare>& shares, uint64_t max_reward_amounts_weight, bool dry_run, uint64_t full_block_reward);
|
||||
hash calc_sidechain_hash(uint32_t sidechain_extra_nonce) const;
|
||||
@@ -132,6 +133,7 @@ private:
|
||||
std::vector<uint8_t> m_sidechainHashBlob;
|
||||
std::array<uint64_t, 25> m_sidechainHashKeccakState;
|
||||
size_t m_sidechainHashInputLength;
|
||||
size_t m_sidechainExtraNonceOffset;
|
||||
|
||||
std::vector<uint8_t> m_blockHeader;
|
||||
std::vector<uint8_t> m_minerTxExtra;
|
||||
|
||||
+17
-2
@@ -117,12 +117,13 @@ extern "C" void __nss_module_disable_loading();
|
||||
namespace p2pool {
|
||||
|
||||
constexpr size_t HASH_SIZE = 32;
|
||||
constexpr uint8_t HARDFORK_SUPPORTED_VERSION = 10;
|
||||
constexpr uint8_t HARDFORK_SUPPORTED_VERSION = 11;
|
||||
constexpr uint8_t MINER_REWARD_UNLOCK_TIME = 60;
|
||||
constexpr uint8_t NONCE_SIZE = 4;
|
||||
constexpr uint8_t EXTRA_NONCE_SIZE = 4;
|
||||
constexpr uint8_t EXTRA_NONCE_MAX_SIZE = EXTRA_NONCE_SIZE + 10;
|
||||
constexpr uint8_t TX_VERSION = 4;
|
||||
constexpr uint8_t TX_VERSION_ENABLE_TOKENS = 5;
|
||||
constexpr uint8_t TXIN_GEN = 0xFF;
|
||||
constexpr uint8_t TXOUT_TO_TAGGED_KEY = 4; // Changed from 3 to 4 for Carrot v1
|
||||
constexpr uint8_t TXOUT_TO_CARROT_V1 = 4;
|
||||
@@ -131,6 +132,19 @@ constexpr uint8_t TX_EXTRA_TAG_ADDITIONAL_PUBKEYS = 4;
|
||||
constexpr uint8_t TX_EXTRA_NONCE = 2;
|
||||
constexpr uint8_t TX_EXTRA_MERGE_MINING_TAG = 3;
|
||||
|
||||
// Salvium transaction types
|
||||
constexpr uint8_t TX_TYPE_UNSET = 0;
|
||||
constexpr uint8_t TX_TYPE_MINER = 1;
|
||||
constexpr uint8_t TX_TYPE_PROTOCOL = 2;
|
||||
constexpr uint8_t TX_TYPE_TRANSFER = 3;
|
||||
constexpr uint8_t TX_TYPE_CONVERT = 4;
|
||||
constexpr uint8_t TX_TYPE_BURN = 5;
|
||||
constexpr uint8_t TX_TYPE_STAKE = 6;
|
||||
constexpr uint8_t TX_TYPE_RETURN = 7;
|
||||
constexpr uint8_t TX_TYPE_AUDIT = 8;
|
||||
constexpr uint8_t TX_TYPE_CREATE_TOKEN = 9;
|
||||
constexpr uint8_t TX_TYPE_ROLLUP = 10;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define umul128 _umul128
|
||||
#define udiv128 _udiv128
|
||||
@@ -534,8 +548,9 @@ struct ProtocolOutput
|
||||
uint64_t amount;
|
||||
uint8_t view_tag[3];
|
||||
uint8_t anchor_enc[16];
|
||||
std::string asset_type; // "SAL1" by default, variable at HF11+
|
||||
|
||||
FORCEINLINE ProtocolOutput() : onetime_address{}, eph_pubkey{}, amount(0), view_tag{}, anchor_enc{} {}
|
||||
FORCEINLINE ProtocolOutput() : onetime_address{}, eph_pubkey{}, amount(0), view_tag{}, anchor_enc{}, asset_type("SAL1") {}
|
||||
|
||||
FORCEINLINE bool operator==(const ProtocolOutput& rhs) const {
|
||||
return onetime_address == rhs.onetime_address;
|
||||
|
||||
@@ -273,7 +273,7 @@ static void do_status(p2pool *m_pool, const char * /* args */)
|
||||
|
||||
if (tip && (data.height < tip->m_txinGenHeight)) {
|
||||
node_health -= 5;
|
||||
comments.push_back("Your Salvium node is lagging");
|
||||
comments.push_back("Mainchain data is stale (miner_data height behind sidechain tip)");
|
||||
}
|
||||
|
||||
if (stratum && (stratum->num_connections() == 0)) {
|
||||
|
||||
+181
-97
@@ -85,6 +85,7 @@ P2PServer::P2PServer(p2pool* pool)
|
||||
, m_numOnionConnections(0)
|
||||
, m_lookForMissingBlocks(true)
|
||||
, m_fastestPeer(nullptr)
|
||||
, m_maxPeerTipHeight(0)
|
||||
, m_newP2PoolVersionDetected(false)
|
||||
, m_auxJobLastMessageTimestamp(0)
|
||||
{
|
||||
@@ -166,29 +167,12 @@ P2PServer::P2PServer(p2pool* pool)
|
||||
m_cache->load_all(m_pool->side_chain(), *this);
|
||||
m_cacheLoaded = true;
|
||||
|
||||
// Bootstrap sidechain from cache - add blocks in height order
|
||||
if (m_cachedBlocks && !m_cachedBlocks->empty()) {
|
||||
LOGINFO(1, "bootstrapping sidechain from " << m_cachedBlocks->size() << " cached blocks");
|
||||
|
||||
// Collect and sort by height ascending
|
||||
std::vector<PoolBlock*> sorted_blocks;
|
||||
sorted_blocks.reserve(m_cachedBlocks->size());
|
||||
for (const auto& it : *m_cachedBlocks) {
|
||||
sorted_blocks.push_back(it.second);
|
||||
}
|
||||
std::sort(sorted_blocks.begin(), sorted_blocks.end(),
|
||||
[](const PoolBlock* a, const PoolBlock* b) {
|
||||
return a->m_sidechainHeight < b->m_sidechainHeight;
|
||||
});
|
||||
|
||||
// Add in height order so parents exist before children
|
||||
std::vector<hash> missing;
|
||||
for (PoolBlock* block : sorted_blocks) {
|
||||
(void)m_pool->side_chain().add_external_block(*block, missing);
|
||||
}
|
||||
|
||||
LOGINFO(1, "cache bootstrap complete, chain tip height = " <<
|
||||
(m_pool->side_chain().chainTip() ? m_pool->side_chain().chainTip()->m_sidechainHeight : 0));
|
||||
// Cache loaded — blocks are available via m_cachedBlocks for on-demand
|
||||
// lookup in download_missing_blocks. We don't bootstrap eagerly here
|
||||
// because mainchain data isn't available yet (salviumd not connected),
|
||||
// so add_external_block would defer and forget every block.
|
||||
if (m_cachedBlocks) {
|
||||
LOGINFO(1, "loaded " << m_cachedBlocks->size() << " cached blocks (available for on-demand sync)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +341,7 @@ void P2PServer::update_peer_connections()
|
||||
|
||||
unordered_set<raw_ip> connected_clients;
|
||||
unordered_set<std::string_view> connected_clients_domain;
|
||||
std::vector<uint64_t> peer_tip_heights;
|
||||
|
||||
connected_clients.reserve(m_numConnections);
|
||||
|
||||
@@ -405,6 +390,9 @@ void P2PServer::update_peer_connections()
|
||||
|
||||
if (client->is_good()) {
|
||||
has_good_peers = true;
|
||||
if (client->m_broadcastMaxHeight > 0) {
|
||||
peer_tip_heights.push_back(client->m_broadcastMaxHeight);
|
||||
}
|
||||
if ((client->m_pingTime >= 0) && (!m_fastestPeer || (m_fastestPeer->m_pingTime > client->m_pingTime))) {
|
||||
m_fastestPeer = client;
|
||||
}
|
||||
@@ -419,6 +407,21 @@ void P2PServer::update_peer_connections()
|
||||
|
||||
m_newP2PoolVersionDetected = newer_version_p2pool * 5 > total_outgoing_p2pool;
|
||||
|
||||
// Update max peer tip height (read safely from any thread via atomic)
|
||||
{
|
||||
uint64_t max_h = 0;
|
||||
for (uint64_t h : peer_tip_heights) {
|
||||
if (h > max_h) max_h = h;
|
||||
}
|
||||
m_maxPeerTipHeight.store(max_h);
|
||||
}
|
||||
|
||||
// Check if we've diverged from peer consensus (e.g., after isolation)
|
||||
// Run every 30 seconds to avoid unnecessary overhead
|
||||
if ((m_timerCounter % 30) == 15) {
|
||||
m_pool->side_chain().check_peer_consensus(peer_tip_heights);
|
||||
}
|
||||
|
||||
std::vector<Peer> peer_list;
|
||||
{
|
||||
MutexLock lock(m_peerListLock);
|
||||
@@ -1469,6 +1472,19 @@ void P2PServer::on_timer()
|
||||
}
|
||||
|
||||
flush_cache();
|
||||
|
||||
// Periodically re-enable missing block search as a safety net
|
||||
// Prevents permanent stalls if the flag was incorrectly cleared
|
||||
if ((t % 15) == 0) {
|
||||
m_lookForMissingBlocks = true;
|
||||
}
|
||||
|
||||
// Periodically retry verifying unverified blocks (every 10 seconds)
|
||||
// Blocks added without mainchain data sit unverified until this runs
|
||||
if ((t % 5) == 3) {
|
||||
m_pool->side_chain().retry_unverified_blocks();
|
||||
}
|
||||
|
||||
download_missing_blocks();
|
||||
update_peer_list();
|
||||
save_peer_list_async();
|
||||
@@ -1526,6 +1542,17 @@ void P2PServer::download_missing_blocks()
|
||||
unordered_set<hash> missing_blocks;
|
||||
m_pool->side_chain().get_missing_blocks(missing_blocks);
|
||||
|
||||
// Merge checkpoint-driven sync targets into missing blocks.
|
||||
// Remove targets we've already received, add the rest to the request set.
|
||||
for (auto it = m_syncTargetBlocks.begin(); it != m_syncTargetBlocks.end();) {
|
||||
if (m_pool->side_chain().find_block(*it)) {
|
||||
it = m_syncTargetBlocks.erase(it);
|
||||
} else {
|
||||
missing_blocks.insert(*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (missing_blocks.empty()) {
|
||||
m_lookForMissingBlocks = false;
|
||||
m_missingBlockRequests.clear();
|
||||
@@ -1533,6 +1560,18 @@ void P2PServer::download_missing_blocks()
|
||||
return;
|
||||
}
|
||||
|
||||
// Expire stale request entries older than 10 seconds so we can retry with different peers
|
||||
{
|
||||
const uint64_t now = seconds_since_epoch();
|
||||
for (auto it = m_missingBlockRequests.begin(); it != m_missingBlockRequests.end();) {
|
||||
if (now - it->second >= 10) {
|
||||
it = m_missingBlockRequests.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_numConnections == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1560,14 +1599,19 @@ void P2PServer::download_missing_blocks()
|
||||
// Group blocks for batch requests to 1.6+ clients
|
||||
std::map<P2PClient*, std::vector<hash>> batch_requests;
|
||||
|
||||
// Try to download each block from a random client
|
||||
// Shuffle clients so we distribute requests across peers
|
||||
for (size_t i = clients.size(); i > 1; --i) {
|
||||
std::swap(clients[i - 1], clients[get_random64() % i]);
|
||||
}
|
||||
|
||||
// Try to download each missing block, cycling through all peers until one accepts
|
||||
for (const hash& id : missing_blocks) {
|
||||
// Check cache first
|
||||
if (m_cachedBlocks) {
|
||||
auto it = m_cachedBlocks->find(id);
|
||||
if (it != m_cachedBlocks->end()) {
|
||||
LOGINFO(5, "using cached block for id = " << id);
|
||||
P2PClient* client = clients[get_random64() % clients.size()];
|
||||
P2PClient* client = clients[0];
|
||||
if (!client->handle_incoming_block_async(it->second)) {
|
||||
LOGERR(5, "download_missing_blocks: handle_incoming_block_async failed");
|
||||
}
|
||||
@@ -1575,84 +1619,76 @@ void P2PServer::download_missing_blocks()
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer 1.6+ clients for batch requests
|
||||
P2PClient* client;
|
||||
if (!clients_v16.empty()) {
|
||||
client = clients_v16[get_random64() % clients_v16.size()];
|
||||
} else {
|
||||
client = clients[get_random64() % clients.size()];
|
||||
}
|
||||
// Try each peer until one accepts the request
|
||||
for (P2PClient* client : clients) {
|
||||
if (client->m_blockPendingRequests.size() >= 25) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (client->m_blockPendingRequests.size() >= 25) {
|
||||
// Too many pending requests to this peer
|
||||
continue;
|
||||
}
|
||||
if (!m_missingBlockRequests.emplace(std::make_pair(client->m_peerId, *id.u64()), seconds_since_epoch()).second) {
|
||||
// Already asked this peer for this block (and request hasn't expired yet)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_missingBlockRequests.emplace(client->m_peerId, *id.u64()).second) {
|
||||
// We already asked this peer about this block
|
||||
continue;
|
||||
}
|
||||
// Use batch requests for 1.6+ peers
|
||||
if (client->m_protocolVersion >= PROTOCOL_VERSION_1_6) {
|
||||
batch_requests[client].push_back(id);
|
||||
if (batch_requests[client].size() >= BLOCK_BATCH_MAX_COUNT) {
|
||||
auto& batch = batch_requests[client];
|
||||
const bool result = send(client,
|
||||
[&batch, client](uint8_t* buf, size_t buf_size) -> size_t
|
||||
{
|
||||
LOGINFO(5, "[download_missing_blocks] sending BLOCK_BATCH_REQUEST for " << batch.size() << " blocks to " << static_cast<char*>(client->m_addrString));
|
||||
|
||||
// Use batch requests for 1.6+ peers
|
||||
if (client->m_protocolVersion >= PROTOCOL_VERSION_1_6) {
|
||||
batch_requests[client].push_back(id);
|
||||
// Limit batch size
|
||||
if (batch_requests[client].size() >= BLOCK_BATCH_MAX_COUNT) {
|
||||
// Send this batch now
|
||||
auto& batch = batch_requests[client];
|
||||
const size_t needed = 2 + batch.size() * HASH_SIZE;
|
||||
if (buf_size < needed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* p = buf;
|
||||
*(p++) = static_cast<uint8_t>(MessageId::BLOCK_BATCH_REQUEST);
|
||||
*(p++) = static_cast<uint8_t>(batch.size());
|
||||
|
||||
for (const hash& h : batch) {
|
||||
memcpy(p, h.h, HASH_SIZE);
|
||||
p += HASH_SIZE;
|
||||
}
|
||||
|
||||
return p - buf;
|
||||
});
|
||||
|
||||
if (result) {
|
||||
for (const hash& h : batch) {
|
||||
client->m_blockPendingRequests.push_back(*h.u64());
|
||||
}
|
||||
}
|
||||
batch.clear();
|
||||
}
|
||||
} else {
|
||||
const bool result = send(client,
|
||||
[&batch, client](uint8_t* buf, size_t buf_size) -> size_t
|
||||
[&id, client](uint8_t* buf, size_t buf_size) -> size_t
|
||||
{
|
||||
LOGINFO(5, "[download_missing_blocks] sending BLOCK_BATCH_REQUEST for " << batch.size() << " blocks to " << static_cast<char*>(client->m_addrString));
|
||||
LOGINFO(5, "[download_missing_blocks] sending BLOCK_REQUEST for id = " << id << " to " << static_cast<char*>(client->m_addrString));
|
||||
|
||||
const size_t needed = 2 + batch.size() * HASH_SIZE;
|
||||
if (buf_size < needed) {
|
||||
if (buf_size < 1 + HASH_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* p = buf;
|
||||
*(p++) = static_cast<uint8_t>(MessageId::BLOCK_BATCH_REQUEST);
|
||||
*(p++) = static_cast<uint8_t>(batch.size());
|
||||
|
||||
for (const hash& h : batch) {
|
||||
memcpy(p, h.h, HASH_SIZE);
|
||||
p += HASH_SIZE;
|
||||
}
|
||||
*(p++) = static_cast<uint8_t>(MessageId::BLOCK_REQUEST);
|
||||
|
||||
memcpy(p, id.h, HASH_SIZE);
|
||||
p += HASH_SIZE;
|
||||
|
||||
return p - buf;
|
||||
});
|
||||
|
||||
if (result) {
|
||||
for (const hash& h : batch) {
|
||||
client->m_blockPendingRequests.push_back(*h.u64());
|
||||
}
|
||||
client->m_blockPendingRequests.push_back(*id.u64());
|
||||
}
|
||||
batch.clear();
|
||||
}
|
||||
} else {
|
||||
// Legacy single block request for older peers
|
||||
const bool result = send(client,
|
||||
[&id, client](uint8_t* buf, size_t buf_size) -> size_t
|
||||
{
|
||||
LOGINFO(5, "[download_missing_blocks] sending BLOCK_REQUEST for id = " << id << " to " << static_cast<char*>(client->m_addrString));
|
||||
|
||||
if (buf_size < 1 + HASH_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t* p = buf;
|
||||
|
||||
*(p++) = static_cast<uint8_t>(MessageId::BLOCK_REQUEST);
|
||||
|
||||
memcpy(p, id.h, HASH_SIZE);
|
||||
p += HASH_SIZE;
|
||||
|
||||
return p - buf;
|
||||
});
|
||||
|
||||
if (result) {
|
||||
client->m_blockPendingRequests.push_back(*id.u64());
|
||||
}
|
||||
break; // Found a peer for this block, move to the next block
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3076,10 +3112,8 @@ bool P2PServer::P2PClient::on_block_response(const uint8_t* buf, uint32_t size,
|
||||
return false;
|
||||
}
|
||||
|
||||
const SideChain& side_chain = server->m_pool->side_chain();
|
||||
const uint64_t max_time_delta = side_chain.precalcFinished() ? (side_chain.block_time() * side_chain.chain_window_size() * 4) : 0;
|
||||
|
||||
return handle_incoming_block_async(block, max_time_delta);
|
||||
// No timestamp check on requested blocks — we asked for these
|
||||
return handle_incoming_block_async(block, 0);
|
||||
}
|
||||
|
||||
bool P2PServer::P2PClient::on_block_batch_request(const uint8_t* buf, uint32_t size)
|
||||
@@ -3253,10 +3287,8 @@ bool P2PServer::P2PClient::on_block_batch_response(const uint8_t* buf, uint32_t
|
||||
m_blockPendingRequests.pop_front();
|
||||
}
|
||||
|
||||
const SideChain& side_chain = server->m_pool->side_chain();
|
||||
const uint64_t max_time_delta = side_chain.precalcFinished() ? (side_chain.block_time() * side_chain.chain_window_size() * 4) : 0;
|
||||
|
||||
if (!handle_incoming_block_async(block, max_time_delta)) {
|
||||
// No timestamp check on requested blocks — we asked for these
|
||||
if (!handle_incoming_block_async(block, 0)) {
|
||||
LOGWARN(4, "failed to handle block from batch response");
|
||||
}
|
||||
|
||||
@@ -3313,7 +3345,7 @@ bool P2PServer::P2PClient::on_block_broadcast(const uint8_t* buf, uint32_t size,
|
||||
}
|
||||
else if (peer_height > our_height) {
|
||||
if (peer_height >= our_height + 2) {
|
||||
LOGWARN(3, "peer " << static_cast<char*>(m_addrString) << " is ahead on mainchain (height " << peer_height << ", your height " << our_height << "). Is your salviumd stuck or lagging?");
|
||||
LOGWARN(3, "peer " << static_cast<char*>(m_addrString) << " is ahead on mainchain (height " << peer_height << ", your height " << our_height << "). Mainchain data may be stale — check ZMQ connection");
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -3483,6 +3515,31 @@ void P2PServer::P2PClient::on_peer_list_response(const uint8_t* buf)
|
||||
);
|
||||
}
|
||||
|
||||
// Reject peers running protocol versions that produce incompatible block serialization
|
||||
if (m_protocolVersion < MIN_SUPPORTED_PROTOCOL_VERSION) {
|
||||
LOGWARN(3, "peer " << static_cast<char*>(m_addrString)
|
||||
<< " protocol version " << (m_protocolVersion >> 16) << '.' << (m_protocolVersion & 0xFFFF)
|
||||
<< " is below minimum required " << (MIN_SUPPORTED_PROTOCOL_VERSION >> 16) << '.' << (MIN_SUPPORTED_PROTOCOL_VERSION & 0xFFFF)
|
||||
<< " — disconnecting to prevent sidechain corruption");
|
||||
ban(DEFAULT_BAN_TIME);
|
||||
server->remove_peer_from_list(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// At HF11+, reject peers running software older than v4.18
|
||||
// (they lack variable asset_type support and would corrupt sidechain state)
|
||||
if (m_SoftwareVersion && m_SoftwareVersion < MIN_VERSION_HF11) {
|
||||
if (server->m_pool->miner_data().major_version >= 11) {
|
||||
LOGWARN(3, "peer " << static_cast<char*>(m_addrString)
|
||||
<< " software version "
|
||||
<< (m_SoftwareVersion >> 16) << '.' << ((m_SoftwareVersion >> 8) & 0xFF) << '.' << (m_SoftwareVersion & 0xFF)
|
||||
<< " is too old for HF11 — disconnecting");
|
||||
ban(DEFAULT_BAN_TIME);
|
||||
server->remove_peer_from_list(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We know this peer's protocol version now. Send protocol-specific messages here.
|
||||
if ((m_protocolVersion >= PROTOCOL_VERSION_1_3) && !server->m_auxJobLastMessage.empty()) {
|
||||
if (static_cast<uint64_t>(time(nullptr)) < server->m_auxJobLastMessageTimestamp + AUX_JOB_TIMEOUT) {
|
||||
@@ -3554,7 +3611,7 @@ void P2PServer::P2PClient::on_block_notify(const uint8_t* buf)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server->m_blockNotifyRequests.insert(*id.u64()).second || !server->m_missingBlockRequests.emplace(m_peerId, *id.u64()).second) {
|
||||
if (!server->m_blockNotifyRequests.insert(*id.u64()).second || !server->m_missingBlockRequests.emplace(std::make_pair(m_peerId, *id.u64()), seconds_since_epoch()).second) {
|
||||
LOGINFO(6, "BLOCK_REQUEST for id = " << id << " was already sent");
|
||||
return;
|
||||
}
|
||||
@@ -3801,6 +3858,25 @@ bool P2PServer::P2PClient::on_checkpoint_response(const uint8_t* buf, uint32_t c
|
||||
<< static_cast<char*>(m_addrString));
|
||||
}
|
||||
|
||||
// Use checkpoint data for sync bootstrapping.
|
||||
// Store checkpoint block hashes we don't have yet — they serve as entry points
|
||||
// for the backward-walking sync, giving us multiple starting points instead of
|
||||
// depending solely on the chain tip request.
|
||||
if (!side_chain.is_ready_to_mine()) {
|
||||
uint32_t new_targets = 0;
|
||||
for (const Checkpoint& cp : peer_checkpoints) {
|
||||
if (!side_chain.find_block(cp.id)) {
|
||||
if (server->m_syncTargetBlocks.insert(cp.id).second) {
|
||||
++new_targets;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (new_targets > 0) {
|
||||
server->m_lookForMissingBlocks = true;
|
||||
LOGINFO(3, "Checkpoint sync: added " << new_targets << " checkpoint blocks as sync targets (total " << server->m_syncTargetBlocks.size() << ")");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3970,7 +4046,7 @@ bool P2PServer::P2PClient::on_monero_block_broadcast(const uint8_t* buf, uint32_
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buf[data.header_size] != TX_VERSION) {
|
||||
if (buf[data.header_size] != TX_VERSION && buf[data.header_size] != TX_VERSION_ENABLE_TOKENS) {
|
||||
LOGWARN(3, "Invalid MONERO_BLOCK_BROADCAST: TX_VERSION byte not found");
|
||||
return false;
|
||||
}
|
||||
@@ -4311,6 +4387,14 @@ bool P2PServer::P2PClient::handle_incoming_block_async(const PoolBlock* block, u
|
||||
void P2PServer::P2PClient::handle_incoming_block(p2pool* pool, PoolBlock& block, std::vector<hash>& missing_blocks, bool& result)
|
||||
{
|
||||
result = pool->side_chain().add_external_block(block, missing_blocks);
|
||||
|
||||
// If block wasn't actually added to the sidechain (PoW failure, too_low_diff, etc.),
|
||||
// clear incoming tracking so re-download attempts aren't silently dropped.
|
||||
// Without this, a block that fails processing stays "seen" for 10 minutes,
|
||||
// and all BLOCK_REQUEST responses for it are silently discarded.
|
||||
if (!pool->side_chain().find_block(block.m_sidechainId)) {
|
||||
pool->side_chain().forget_incoming_block(block);
|
||||
}
|
||||
}
|
||||
|
||||
void P2PServer::P2PClient::post_handle_incoming_block(p2pool* pool, const PoolBlock& block, const uint32_t reset_counter, bool is_v6, const raw_ip& addr, std::vector<hash>& missing_blocks, const bool result)
|
||||
@@ -4376,7 +4460,7 @@ void P2PServer::P2PClient::post_handle_incoming_block(p2pool* pool, const PoolBl
|
||||
}
|
||||
}
|
||||
|
||||
if (!server->m_missingBlockRequests.emplace(m_peerId, *id.u64()).second) {
|
||||
if (!server->m_missingBlockRequests.emplace(std::make_pair(m_peerId, *id.u64()), seconds_since_epoch()).second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -48,6 +48,13 @@ static constexpr uint32_t PROTOCOL_VERSION_1_6 = 0x00010006UL;
|
||||
|
||||
static constexpr uint32_t SUPPORTED_PROTOCOL_VERSION = PROTOCOL_VERSION_1_6;
|
||||
|
||||
// Minimum protocol version required to connect. Peers below this are disconnected
|
||||
// during handshake. v1.6 is required for correct Carrot v1 block serialization.
|
||||
static constexpr uint32_t MIN_SUPPORTED_PROTOCOL_VERSION = PROTOCOL_VERSION_1_6;
|
||||
|
||||
// Minimum software version required at HF11+ (v4.18 adds variable asset_type support)
|
||||
static constexpr uint32_t MIN_VERSION_HF11 = (4 << 16) | (18 << 8);
|
||||
|
||||
// Batch block request limits
|
||||
static constexpr uint8_t BLOCK_BATCH_MAX_COUNT = 10;
|
||||
|
||||
@@ -209,6 +216,8 @@ public:
|
||||
};
|
||||
|
||||
void broadcast(const PoolBlock& block, const PoolBlock* parent);
|
||||
void set_look_for_missing_blocks() { m_lookForMissingBlocks = true; }
|
||||
[[nodiscard]] uint64_t max_peer_tip_height() const { return m_maxPeerTipHeight.load(); }
|
||||
[[nodiscard]] uint64_t get_random64();
|
||||
[[nodiscard]] uint64_t get_peerId(bool is_tor) const { return is_tor ? m_peerId_TOR : m_peerId; }
|
||||
|
||||
@@ -315,11 +324,16 @@ private:
|
||||
uv_async_t m_broadcastAsync;
|
||||
std::vector<Broadcast*> m_broadcastQueue;
|
||||
|
||||
bool m_lookForMissingBlocks;
|
||||
unordered_set<std::pair<uint64_t, uint64_t>> m_missingBlockRequests;
|
||||
std::atomic<bool> m_lookForMissingBlocks;
|
||||
unordered_map<std::pair<uint64_t, uint64_t>, uint64_t> m_missingBlockRequests;
|
||||
unordered_set<uint64_t> m_blockNotifyRequests;
|
||||
|
||||
// Checkpoint-driven sync: block hashes from peer checkpoint responses
|
||||
// Used as additional sync entry points alongside get_missing_blocks results
|
||||
unordered_set<hash> m_syncTargetBlocks;
|
||||
|
||||
P2PClient* m_fastestPeer;
|
||||
std::atomic<uint64_t> m_maxPeerTipHeight;
|
||||
std::atomic<bool> m_newP2PoolVersionDetected;
|
||||
|
||||
static void on_broadcast(uv_async_t* handle) { reinterpret_cast<P2PServer*>(handle->data)->on_broadcast(); }
|
||||
|
||||
+72
-51
@@ -68,7 +68,7 @@ constexpr char FOUND_BLOCKS_FILE[] = "p2pool.blocks";
|
||||
|
||||
namespace p2pool {
|
||||
|
||||
static uint64_t BLOCK_HEADERS_REQUIRED = 720;
|
||||
static uint64_t BLOCK_HEADERS_REQUIRED = 2000;
|
||||
|
||||
p2pool::p2pool(const Params& params)
|
||||
: m_stopped(false)
|
||||
@@ -91,9 +91,9 @@ p2pool::p2pool(const Params& params)
|
||||
LOGINFO(1, "Connected to Redis at " << m_params.m_redisHost << ":" << m_params.m_redisPort << " (db " << m_params.m_redisDb << ")");
|
||||
#endif
|
||||
|
||||
// P2Pool-nano requires more Monero blocks for the initial sync
|
||||
// P2Pool-nano requires more blocks for the initial sync
|
||||
if (m_params.m_nano) {
|
||||
BLOCK_HEADERS_REQUIRED = 1440;
|
||||
BLOCK_HEADERS_REQUIRED = 4000;
|
||||
}
|
||||
|
||||
bkg_jobs_tracker = new BackgroundJobTracker();
|
||||
@@ -564,20 +564,20 @@ void p2pool::get_missing_heights()
|
||||
{
|
||||
ChainMain block;
|
||||
if (!parse_block_header(data, size, block)) {
|
||||
LOGERR(1, "couldn't download block header for height " << h);
|
||||
|
||||
MutexLock lock(m_missingHeightsLock);
|
||||
m_missingHeights.push_back(h);
|
||||
LOGWARN(3, "couldn't download block header for height " << h);
|
||||
// Don't re-queue: retry_unverified_blocks re-detects missing heights
|
||||
// every 5s from the P2P timer. Re-queuing here caused an infinite
|
||||
// tight loop that overwhelmed the daemon with requests.
|
||||
}
|
||||
else {
|
||||
m_sideChain->retry_unverified_blocks();
|
||||
}
|
||||
get_missing_heights();
|
||||
},
|
||||
[this, h](const char* data, size_t size, double)
|
||||
{
|
||||
if (size > 0) {
|
||||
LOGERR(1, "couldn't download block header for height " << h << ", error " << log::const_buf(data, size));
|
||||
|
||||
MutexLock lock(m_missingHeightsLock);
|
||||
m_missingHeights.push_back(h);
|
||||
LOGWARN(3, "couldn't download block header for height " << h << ", error " << log::const_buf(data, size));
|
||||
}
|
||||
get_missing_heights();
|
||||
});
|
||||
@@ -1401,7 +1401,7 @@ void p2pool::download_block_headers2(uint64_t current_height)
|
||||
void p2pool::download_block_headers3(uint64_t start_height, uint64_t current_height)
|
||||
{
|
||||
// Workaround the restricted RPC limit
|
||||
constexpr uint64_t RESTRICTED_BLOCK_HEADER_RANGE = 1000;
|
||||
constexpr uint64_t RESTRICTED_BLOCK_HEADER_RANGE = 500;
|
||||
|
||||
if (current_height - start_height > RESTRICTED_BLOCK_HEADER_RANGE + 1) {
|
||||
char buf[log::Stream::BUF_SIZE + 1] = {};
|
||||
@@ -1459,35 +1459,11 @@ void p2pool::download_block_headers4(uint64_t start_height, uint64_t current_hei
|
||||
LOGINFO(0, log::LightCyan() << "########################################################");
|
||||
|
||||
m_p2pServer.store(new P2PServer(this));
|
||||
m_sideChain->set_p2p_start_time(seconds_since_epoch());
|
||||
|
||||
// Wait for sidechain to be ready before starting stratum/mining
|
||||
int genesis_wait_count = 0;
|
||||
bool genesis_escape = false;
|
||||
while (!m_sideChain->is_ready_to_mine()) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
genesis_wait_count++;
|
||||
|
||||
// Genesis node escape: if we created genesis and have no peers, proceed
|
||||
const P2PServer* server = m_p2pServer.load();
|
||||
if (server && server->num_connections() == 0 && genesis_wait_count >= 24) {
|
||||
LOGINFO(0, log::LightGreen() << "########################################");
|
||||
LOGINFO(0, log::LightGreen() << "GENESIS NODE - MINING IS NOW ENABLED");
|
||||
LOGINFO(0, log::LightGreen() << "########################################");
|
||||
// Clear any stale checkpoints from a previous run so they
|
||||
// don't block mining with an unsatisfiable validation loop
|
||||
m_sideChain->clear_checkpoints();
|
||||
m_sideChain->set_ready_to_mine(true);
|
||||
genesis_escape = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!genesis_escape) {
|
||||
LOGINFO(0, log::LightGreen() << "########################################");
|
||||
LOGINFO(0, log::LightGreen() << "SIDECHAIN LOADED - MINING IS NOW ENABLED");
|
||||
LOGINFO(0, log::LightGreen() << "########################################");
|
||||
}
|
||||
|
||||
// Start all services immediately — mining is gated by
|
||||
// m_readyToMine/preflight_check(), which runs on every
|
||||
// chain tip update and prune cycle. No spin loop needed.
|
||||
m_stratumServer = new StratumServer(this);
|
||||
#if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS)
|
||||
if (m_params.m_minerThreads) {
|
||||
@@ -1736,7 +1712,9 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
|
||||
|
||||
if (doc.HasParseError() || !doc.IsObject() || !doc.HasMember("result")) {
|
||||
LOGWARN(1, "get_info RPC response is invalid (\"result\" not found), trying again in 1 second");
|
||||
if (m_stopped) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
if (m_stopped) return;
|
||||
switch_host();
|
||||
get_info();
|
||||
return;
|
||||
@@ -1754,7 +1732,9 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
|
||||
!PARSE(result, info, testnet) ||
|
||||
!PARSE(result, info, stagenet)) {
|
||||
LOGWARN(1, "get_info RPC response is invalid, trying again in 1 second");
|
||||
if (m_stopped) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
if (m_stopped) return;
|
||||
switch_host();
|
||||
get_info();
|
||||
return;
|
||||
@@ -1762,7 +1742,9 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
|
||||
|
||||
if (info.busy_syncing || !info.synchronized) {
|
||||
LOGINFO(1, "salviumd is " << (info.busy_syncing ? "busy syncing" : "not synchronized") << ", trying again in 1 second");
|
||||
if (m_stopped) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
if (m_stopped) return;
|
||||
switch_host();
|
||||
get_info();
|
||||
return;
|
||||
@@ -1816,7 +1798,9 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size)
|
||||
|
||||
if (doc.HasParseError() || !doc.IsObject() || !doc.HasMember("result")) {
|
||||
LOGWARN(1, "get_version RPC response is invalid (\"result\" not found), trying again in 1 second");
|
||||
if (m_stopped) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
if (m_stopped) return;
|
||||
get_version();
|
||||
return;
|
||||
}
|
||||
@@ -1828,14 +1812,18 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size)
|
||||
|
||||
if (!parseValue(result, "status", status) || !parseValue(result, "version", version)) {
|
||||
LOGWARN(1, "get_version RPC response is invalid, trying again in 1 second");
|
||||
if (m_stopped) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
if (m_stopped) return;
|
||||
get_version();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != "OK") {
|
||||
LOGWARN(1, "get_version RPC failed, trying again in 1 second");
|
||||
if (m_stopped) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
if (m_stopped) return;
|
||||
get_version();
|
||||
return;
|
||||
}
|
||||
@@ -1955,7 +1943,7 @@ void p2pool::parse_get_miner_data_rpc(const char* data, size_t size)
|
||||
}
|
||||
|
||||
// Extract protocol outputs (treasury, etc.) from the daemon's miner_tx in a blocktemplate_blob.
|
||||
// The miner's own reward output is identified by: amount == expected_reward - expected_reward/5
|
||||
// The miner's own reward output is identified by amount matching the expected miner share (80% at HF10, 60% at HF11+)
|
||||
// All other outputs are "protocol outputs" (treasury, etc.) to be boomeranged into p2pool's miner_tx.
|
||||
static bool extract_protocol_outputs_from_blob(const uint8_t* data, size_t size, uint64_t expected_reward, std::vector<ProtocolOutput>& protocol_outputs_out)
|
||||
{
|
||||
@@ -1977,7 +1965,8 @@ static bool extract_protocol_outputs_from_blob(const uint8_t* data, size_t size,
|
||||
|
||||
// Parse miner_tx
|
||||
// version, unlock_time
|
||||
if (!read_varint_fn(dummy) || !read_varint_fn(dummy)) return false;
|
||||
uint64_t tx_version;
|
||||
if (!read_varint_fn(tx_version) || !read_varint_fn(dummy)) return false;
|
||||
// vin
|
||||
uint64_t vin_count;
|
||||
if (!read_varint_fn(vin_count)) return false;
|
||||
@@ -1996,9 +1985,19 @@ static bool extract_protocol_outputs_from_blob(const uint8_t* data, size_t size,
|
||||
if (!read_varint_fn(vout_count)) return false;
|
||||
if (vout_count == 0) return true; // no outputs at all, nothing to extract
|
||||
|
||||
// Compute what the miner's output amount should be:
|
||||
// miner_amount = expected_reward - expected_reward/5 (80% of full reward)
|
||||
const uint64_t miner_amount = (expected_reward > 0) ? (expected_reward - expected_reward / 5) : 0;
|
||||
// Compute what the miner's output amount should be
|
||||
uint64_t miner_amount = 0;
|
||||
if (expected_reward > 0) {
|
||||
if (tx_version >= TX_VERSION_ENABLE_TOKENS) {
|
||||
// HF11: 60% miner + 25% treasury + 15% staker
|
||||
const uint64_t treasury_reward = expected_reward * 25 / 100;
|
||||
const uint64_t staker_reward = (expected_reward - treasury_reward) * 20 / 100;
|
||||
miner_amount = expected_reward - treasury_reward - staker_reward;
|
||||
} else {
|
||||
// HF10: 80% miner + 20% staker
|
||||
miner_amount = expected_reward - expected_reward / 5;
|
||||
}
|
||||
}
|
||||
|
||||
// Read all outputs, store them temporarily
|
||||
struct TmpOutput {
|
||||
@@ -2006,6 +2005,7 @@ static bool extract_protocol_outputs_from_blob(const uint8_t* data, size_t size,
|
||||
hash K_o;
|
||||
uint8_t view_tag[3];
|
||||
uint8_t anchor_enc[16];
|
||||
std::string asset_type;
|
||||
};
|
||||
std::vector<TmpOutput> tmp_outputs;
|
||||
tmp_outputs.reserve(static_cast<size_t>(vout_count));
|
||||
@@ -2023,11 +2023,13 @@ static bool extract_protocol_outputs_from_blob(const uint8_t* data, size_t size,
|
||||
memcpy(t.K_o.h, data, HASH_SIZE);
|
||||
data += HASH_SIZE;
|
||||
|
||||
// asset_type: length byte + "SAL1"
|
||||
// asset_type: varint length + asset string
|
||||
uint64_t asset_len;
|
||||
if (!read_varint_fn(asset_len)) return false;
|
||||
if (asset_len != 4) return false;
|
||||
if (!skip_bytes_fn(4)) return false; // "SAL1"
|
||||
if (asset_len == 0 || asset_len > 32) return false;
|
||||
if (static_cast<size_t>(blob_end - data) < asset_len) return false;
|
||||
t.asset_type.assign(reinterpret_cast<const char*>(data), static_cast<size_t>(asset_len));
|
||||
data += asset_len;
|
||||
|
||||
// view_tag (3 bytes)
|
||||
if (static_cast<size_t>(blob_end - data) < 3) return false;
|
||||
@@ -2101,6 +2103,7 @@ static bool extract_protocol_outputs_from_blob(const uint8_t* data, size_t size,
|
||||
po.amount = t.amount;
|
||||
memcpy(po.view_tag, t.view_tag, 3);
|
||||
memcpy(po.anchor_enc, t.anchor_enc, 16);
|
||||
po.asset_type = t.asset_type;
|
||||
// Get D_e from tx_extra at matching index
|
||||
if (i < de_values.size()) {
|
||||
po.eph_pubkey = de_values[i];
|
||||
@@ -2223,13 +2226,13 @@ static bool extract_protocol_tx_from_blob(const uint8_t* data, size_t size, std:
|
||||
|
||||
// Conditional fields based on type
|
||||
// type 0 = UNSET, 1 = MINER, 2 = PROTOCOL
|
||||
if (tx_type != 0 && tx_type != 2) {
|
||||
if (tx_type != TX_TYPE_UNSET && tx_type != TX_TYPE_PROTOCOL) {
|
||||
// amount_burnt
|
||||
if (!skip_varint()) return false;
|
||||
if (tx_type != 1) {
|
||||
if (tx_type != TX_TYPE_MINER) {
|
||||
// For TRANSFER, STAKE, etc: additional fields
|
||||
// return_address/return_address_list, return_pubkey, source_asset, dest_asset, slippage
|
||||
// This is complex - but for getblocktemplate, miner_tx is always type MINER(1)
|
||||
// This is complex - but for getblocktemplate, miner_tx is always type MINER
|
||||
// so we should never reach here
|
||||
return false;
|
||||
}
|
||||
@@ -2624,6 +2627,24 @@ void p2pool::fetch_mainchain_block(uint64_t height)
|
||||
);
|
||||
}
|
||||
|
||||
void p2pool::queue_missing_mainchain_heights(std::vector<uint64_t>&& heights)
|
||||
{
|
||||
MutexLock lock(m_missingHeightsLock);
|
||||
std::vector<uint64_t> moved_heights = std::move(heights);
|
||||
for (const uint64_t h : moved_heights) {
|
||||
// Deduplicate
|
||||
if (std::find(m_missingHeights.begin(), m_missingHeights.end(), h) == m_missingHeights.end()) {
|
||||
m_missingHeights.push_back(h);
|
||||
}
|
||||
}
|
||||
if (!m_missingHeights.empty()) {
|
||||
const int err = uv_async_send(&m_getMissingHeightsAsync);
|
||||
if (err) {
|
||||
LOGERR(1, "uv_async_send failed, error " << uv_err_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void p2pool::api_update_network_stats()
|
||||
{
|
||||
if (!m_api || m_stopped) {
|
||||
|
||||
@@ -121,6 +121,7 @@ public:
|
||||
void download_block_headers4(uint64_t start_height, uint64_t current_height);
|
||||
void prefetch_mainchain_blocks(uint64_t current_height);
|
||||
void fetch_mainchain_block(uint64_t height);
|
||||
void queue_missing_mainchain_heights(std::vector<uint64_t>&& heights);
|
||||
|
||||
bool chainmain_get_by_hash(const hash& id, ChainMain& data) const;
|
||||
|
||||
|
||||
+58
-42
@@ -169,7 +169,7 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
}
|
||||
|
||||
// Miner tx
|
||||
data.push_back(TX_VERSION);
|
||||
data.push_back(m_majorVersion >= 11 ? TX_VERSION_ENABLE_TOKENS : TX_VERSION);
|
||||
writeVarint(MINER_REWARD_UNLOCK_TIME, data);
|
||||
data.push_back(1);
|
||||
data.push_back(TXIN_GEN);
|
||||
@@ -187,6 +187,7 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
uint64_t amount;
|
||||
uint8_t view_tag[3];
|
||||
uint8_t anchor[16];
|
||||
std::string asset_type;
|
||||
};
|
||||
std::vector<MergedOutput> merged;
|
||||
merged.reserve(m_outputAmounts.size() + m_protocolOutputs.size());
|
||||
@@ -205,18 +206,37 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
memcpy(mo.anchor, m_encryptedAnchors[i].data(), 16);
|
||||
else
|
||||
memset(mo.anchor, 0, 16);
|
||||
mo.asset_type = "SAL1";
|
||||
merged.push_back(mo);
|
||||
}
|
||||
|
||||
// Protocol outputs
|
||||
for (const auto& po : m_protocolOutputs) {
|
||||
MergedOutput mo;
|
||||
mo.K_o = po.onetime_address;
|
||||
mo.D_e = po.eph_pubkey;
|
||||
mo.amount = po.amount;
|
||||
memcpy(mo.view_tag, po.view_tag, 3);
|
||||
memcpy(mo.anchor, po.anchor_enc, 16);
|
||||
merged.push_back(mo);
|
||||
// Protocol outputs — only add if not already included in m_outputAmounts.
|
||||
// After parsing from the wire, m_outputAmounts contains ALL outputs (miner + protocol
|
||||
// interleaved), while m_protocolOutputs is populated separately from sidechain data.
|
||||
// Blindly merging both would double the protocol outputs and corrupt the blob.
|
||||
// Detect this by checking if the first protocol output's K_o already appears in m_ephPublicKeys.
|
||||
bool protocol_already_in_outputs = false;
|
||||
if (!m_protocolOutputs.empty()) {
|
||||
const hash& first_po_Ko = m_protocolOutputs[0].onetime_address;
|
||||
for (size_t i = 0, n = m_ephPublicKeys.size(); i < n; ++i) {
|
||||
if (m_ephPublicKeys[i] == first_po_Ko) {
|
||||
protocol_already_in_outputs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!protocol_already_in_outputs) {
|
||||
for (const auto& po : m_protocolOutputs) {
|
||||
MergedOutput mo;
|
||||
mo.K_o = po.onetime_address;
|
||||
mo.D_e = po.eph_pubkey;
|
||||
mo.amount = po.amount;
|
||||
memcpy(mo.view_tag, po.view_tag, 3);
|
||||
memcpy(mo.anchor, po.anchor_enc, 16);
|
||||
mo.asset_type = po.asset_type;
|
||||
merged.push_back(mo);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort all outputs by K_o
|
||||
@@ -232,11 +252,8 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
writeVarint(mo.amount, data);
|
||||
data.push_back(TXOUT_TO_CARROT_V1);
|
||||
data.insert(data.end(), mo.K_o.h, mo.K_o.h + HASH_SIZE);
|
||||
data.push_back(4);
|
||||
data.push_back('S');
|
||||
data.push_back('A');
|
||||
data.push_back('L');
|
||||
data.push_back('1');
|
||||
writeVarint(mo.asset_type.size(), data);
|
||||
data.insert(data.end(), mo.asset_type.begin(), mo.asset_type.end());
|
||||
data.insert(data.end(), mo.view_tag, mo.view_tag + 3);
|
||||
data.insert(data.end(), mo.anchor, mo.anchor + 16);
|
||||
}
|
||||
@@ -296,7 +313,7 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
// For Carrot v1+ (major_version >= 10), add type and amount_burnt instead of vin_rct_type
|
||||
if (m_majorVersion >= 10) {
|
||||
// type = MINER
|
||||
writeVarint(1, data);
|
||||
writeVarint(TX_TYPE_MINER, data);
|
||||
|
||||
writeVarint(m_amountBurnt, data);
|
||||
|
||||
@@ -312,25 +329,12 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
*miner_tx_size = data.size() - header_size0;
|
||||
}
|
||||
|
||||
// Protocol tx (Salvium Carrot v1+)
|
||||
// Protocol tx (Salvium Carrot v1+) — must always have the real blob
|
||||
if (m_majorVersion >= 10) {
|
||||
if (m_sidechainHeight >= SIDECHAIN_PROTOCOL_TX_HARDFORK_HEIGHT && !m_protocolTxBlob.empty()) {
|
||||
// Real protocol_tx from daemon
|
||||
data.insert(data.end(), m_protocolTxBlob.begin(), m_protocolTxBlob.end());
|
||||
} else {
|
||||
// Stub protocol_tx (pre-hardfork or blob not yet available)
|
||||
writeVarint(4, data); // version = TRANSACTION_VERSION_CARROT
|
||||
writeVarint(60, data); // unlock_time = 60
|
||||
writeVarint(1, data); // vin.size() = 1
|
||||
data.push_back(TXIN_GEN);
|
||||
writeVarint(m_txinGenHeight, data);
|
||||
writeVarint(0, data); // vout.size() = 0
|
||||
writeVarint(2, data); // extra.size() = 2
|
||||
data.push_back(0x02);
|
||||
data.push_back(0x00);
|
||||
writeVarint(2, data); // transaction_type::PROTOCOL = 2
|
||||
data.push_back(0); // RCT
|
||||
if (m_protocolTxBlob.empty()) {
|
||||
LOGERR(1, "serialize_mainchain_data: m_protocolTxBlob is empty for Carrot v1 block at height " << m_sidechainHeight << " — this will break merkle proof verification");
|
||||
}
|
||||
data.insert(data.end(), m_protocolTxBlob.begin(), m_protocolTxBlob.end());
|
||||
}
|
||||
if (include_tx_hashes) {
|
||||
writeVarint(m_transactions.size() - 1, data);
|
||||
@@ -360,7 +364,7 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> PoolBlock::serialize_sidechain_data() const
|
||||
std::vector<uint8_t> PoolBlock::serialize_sidechain_data(size_t* extra_nonce_offset) const
|
||||
{
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
@@ -408,6 +412,11 @@ std::vector<uint8_t> PoolBlock::serialize_sidechain_data() const
|
||||
const uint8_t* p = reinterpret_cast<const uint8_t*>(m_sidechainExtraBuf);
|
||||
data.insert(data.end(), p, p + sizeof(m_sidechainExtraBuf));
|
||||
|
||||
// The sidechain extra nonce is m_sidechainExtraBuf[3], the last 4 bytes of the 16-byte buffer
|
||||
if (extra_nonce_offset) {
|
||||
*extra_nonce_offset = data.size() - EXTRA_NONCE_SIZE;
|
||||
}
|
||||
|
||||
// Append protocol outputs (new format, appended after sidechainExtraBuf)
|
||||
if (!m_protocolOutputs.empty()) {
|
||||
writeVarint(m_protocolOutputs.size(), data);
|
||||
@@ -417,6 +426,10 @@ std::vector<uint8_t> PoolBlock::serialize_sidechain_data() const
|
||||
writeVarint(po.amount, data);
|
||||
data.insert(data.end(), po.view_tag, po.view_tag + 3);
|
||||
data.insert(data.end(), po.anchor_enc, po.anchor_enc + 16);
|
||||
if (m_majorVersion >= 11) {
|
||||
writeVarint(po.asset_type.size(), data);
|
||||
data.insert(data.end(), po.asset_type.begin(), po.asset_type.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,18 +497,21 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
|
||||
size_t blob_size = 0;
|
||||
|
||||
{
|
||||
// For Carrot v1 blocks, ensure m_transactions has space for protocol TX before serializing
|
||||
// For Carrot v1 blocks, ensure m_transactions has space for protocol TX.
|
||||
// IMPORTANT: Don't override m_transactions[1] if it was already parsed from block data.
|
||||
// The original hash must be preserved for PoW verification — the block creator may
|
||||
// have used a different protocol TX hash (stub vs real blob).
|
||||
if (m_majorVersion >= 10) {
|
||||
if (m_transactions.size() < 2) {
|
||||
m_transactions.resize(2);
|
||||
hash protocol_tx_hash;
|
||||
if (!m_protocolTxBlob.empty()) {
|
||||
calculate_protocol_tx_hash_from_blob(m_protocolTxBlob, protocol_tx_hash);
|
||||
} else {
|
||||
calculate_protocol_tx_hash(m_txinGenHeight, protocol_tx_hash);
|
||||
}
|
||||
m_transactions[1] = static_cast<indexed_hash>(protocol_tx_hash);
|
||||
}
|
||||
hash protocol_tx_hash;
|
||||
if (m_sidechainHeight >= SIDECHAIN_PROTOCOL_TX_HARDFORK_HEIGHT && !m_protocolTxBlob.empty()) {
|
||||
calculate_protocol_tx_hash_from_blob(m_protocolTxBlob, protocol_tx_hash);
|
||||
} else {
|
||||
calculate_protocol_tx_hash(m_txinGenHeight, protocol_tx_hash);
|
||||
}
|
||||
m_transactions[1] = static_cast<indexed_hash>(protocol_tx_hash);
|
||||
}
|
||||
size_t header_size, miner_tx_size;
|
||||
const std::vector<uint8_t> mainchain_data = serialize_mainchain_data(&header_size, &miner_tx_size, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
+3
-2
@@ -69,7 +69,8 @@ static constexpr difficulty_type MAX_CUMULATIVE_DIFFICULTY{ 13019633956666736640
|
||||
// 1000 years at 1 block/second. It should be enough for any normal use.
|
||||
static constexpr uint64_t MAX_SIDECHAIN_HEIGHT = 31556952000ULL;
|
||||
|
||||
// Sidechain height at which real protocol_tx data replaces the stub in mainchain data
|
||||
// Legacy: sidechain height at which real protocol_tx data replaced the stub.
|
||||
// No longer used as a gate — protocol TX is always required from the daemon.
|
||||
static constexpr uint64_t SIDECHAIN_PROTOCOL_TX_HARDFORK_HEIGHT = 800001ULL;
|
||||
|
||||
// Limited by the format of the Merkle tree parameters in tx_extra
|
||||
@@ -205,7 +206,7 @@ struct PoolBlock
|
||||
std::vector<ProtocolOutput> m_protocolOutputs;
|
||||
|
||||
std::vector<uint8_t> serialize_mainchain_data(size_t* header_size = nullptr, size_t* miner_tx_size = nullptr, int* outputs_offset = nullptr, int* outputs_blob_size = nullptr, const uint32_t* nonce = nullptr, const uint32_t* extra_nonce = nullptr, bool include_tx_hashes = true) const;
|
||||
std::vector<uint8_t> serialize_sidechain_data() const;
|
||||
std::vector<uint8_t> serialize_sidechain_data(size_t* extra_nonce_offset = nullptr) const;
|
||||
|
||||
[[nodiscard]] int deserialize(const uint8_t* data, size_t size, const SideChain& sidechain, uv_loop_t* loop, bool compact);
|
||||
void reset_offchain_data();
|
||||
|
||||
+48
-31
@@ -75,10 +75,10 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
const int nonce_offset = static_cast<int>(data - data_begin);
|
||||
READ_BUF(&m_nonce, NONCE_SIZE);
|
||||
|
||||
// Verify TX_VERSION (currently 4)
|
||||
// Verify TX_VERSION (4 for HF10, 5 for HF11+)
|
||||
uint8_t tx_version;
|
||||
READ_BYTE(tx_version);
|
||||
if (tx_version != TX_VERSION) return __LINE__;
|
||||
if (tx_version != TX_VERSION && tx_version != TX_VERSION_ENABLE_TOKENS) return __LINE__;
|
||||
|
||||
uint64_t unlock_height;
|
||||
READ_VARINT(unlock_height);
|
||||
@@ -100,9 +100,9 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
int outputs_blob_size;
|
||||
|
||||
if (num_outputs > 0) {
|
||||
// Carrot v1 outputs: each output is at least 89 bytes
|
||||
// 1 byte amount, 1 byte tx_type(0x04), 32 bytes K_o, 4 bytes SAL1,
|
||||
// 3 bytes view_tag, 16 bytes anchor, 32 bytes D_e
|
||||
// Carrot v1 outputs: each output is at least 58 bytes
|
||||
// 1 byte amount, 1 byte tx_type(0x04), 32 bytes K_o, 1+ bytes asset_type (varint len + string),
|
||||
// 3 bytes view_tag, 16 bytes anchor (D_e is in tx_extra, not per-output)
|
||||
constexpr uint64_t MIN_OUTPUT_SIZE = 58;
|
||||
|
||||
if (num_outputs > std::numeric_limits<uint64_t>::max() / MIN_OUTPUT_SIZE) return __LINE__;
|
||||
@@ -136,17 +136,16 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
hash onetime_address;
|
||||
READ_BUF(onetime_address.h, HASH_SIZE);
|
||||
|
||||
// asset_type string: length byte + "SAL1"
|
||||
uint8_t asset_len;
|
||||
READ_BYTE(asset_len);
|
||||
if (asset_len != 4) {
|
||||
// asset_type string: varint length + asset bytes
|
||||
uint64_t asset_len;
|
||||
READ_VARINT(asset_len);
|
||||
if (asset_len == 0 || asset_len > 32) {
|
||||
return __LINE__;
|
||||
}
|
||||
uint8_t sal1_marker[4];
|
||||
READ_BUF(sal1_marker, 4);
|
||||
if (memcmp(sal1_marker, "SAL1", 4) != 0) {
|
||||
if (static_cast<uint64_t>(data_end - data) < asset_len) {
|
||||
return __LINE__;
|
||||
}
|
||||
data += asset_len;
|
||||
|
||||
// 3-byte view tag
|
||||
uint8_t view_tag_bytes[3];
|
||||
@@ -295,13 +294,13 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
uint64_t first_val;
|
||||
const uint8_t* peek1 = readVarint(data, data_end, first_val);
|
||||
|
||||
// Check if this is protocol_tx by looking for signature: version(4) + unlock_time(60)
|
||||
// This avoids false positive when num_transactions = 4
|
||||
if (peek1 && first_val == 4) {
|
||||
// Check if this is protocol_tx by looking for signature: version(4 or 5) + unlock_time(60)
|
||||
// This avoids false positive when num_transactions = 4 or 5
|
||||
if (peek1 && (first_val == 4 || first_val == 5)) {
|
||||
uint64_t second_val;
|
||||
const uint8_t* peek2 = readVarint(peek1, data_end, second_val);
|
||||
|
||||
// Only parse as protocol_tx if we see version=4 AND unlock_time=60
|
||||
// Only parse as protocol_tx if we see version=4 or 5 AND unlock_time=60
|
||||
if (peek2 && second_val == 60) {
|
||||
// This is a protocol_tx, parse it
|
||||
data = peek1; // Consume version varint
|
||||
@@ -344,7 +343,12 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
if (!ptx_skip(static_cast<size_t>(asset_type_len))) { data = saved_pos; goto skip_protocol_tx; }
|
||||
|
||||
// Type-specific additional fields
|
||||
if (ptx_out_type == 3) {
|
||||
if (ptx_out_type == 2) {
|
||||
// txout_to_key: unlock_time(varint)
|
||||
uint64_t ptx_unlock;
|
||||
data = readVarint(data, data_end, ptx_unlock);
|
||||
if (!data) { data = saved_pos; goto skip_protocol_tx; }
|
||||
} else if (ptx_out_type == 3) {
|
||||
// txout_to_tagged_key: unlock_time(varint) + view_tag(1)
|
||||
uint64_t ptx_unlock;
|
||||
data = readVarint(data, data_end, ptx_unlock);
|
||||
@@ -353,11 +357,10 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
} else if (ptx_out_type == 4) {
|
||||
// txout_to_carrot_v1: view_tag(3) + encrypted_janus_anchor(16)
|
||||
if (!ptx_skip(19)) { data = saved_pos; goto skip_protocol_tx; }
|
||||
} else if (ptx_out_type != 2) {
|
||||
} else {
|
||||
// Unknown output type
|
||||
data = saved_pos; goto skip_protocol_tx;
|
||||
}
|
||||
// type 2 (txout_to_key): nothing more to skip
|
||||
}
|
||||
|
||||
// extra (vector<uint8_t>)
|
||||
@@ -376,10 +379,10 @@ int PoolBlock::deserialize(const uint8_t* data, size_t size, const SideChain& si
|
||||
if (!read_byte(rct)) { data = saved_pos; goto skip_protocol_tx; }
|
||||
if (rct != 0) { data = saved_pos; goto skip_protocol_tx; }
|
||||
|
||||
// Capture the real protocol_tx blob for height >= hardfork
|
||||
if (m_sidechainHeight >= SIDECHAIN_PROTOCOL_TX_HARDFORK_HEIGHT) {
|
||||
m_protocolTxBlob.assign(saved_pos, data);
|
||||
}
|
||||
// Always capture the real protocol_tx blob for round-trip fidelity.
|
||||
// Note: m_sidechainHeight is not yet parsed at this point (it's in sidechain data),
|
||||
// so we cannot gate on it here. The serializer will use this blob if available.
|
||||
m_protocolTxBlob.assign(saved_pos, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -603,6 +606,16 @@ skip_protocol_tx:
|
||||
READ_VARINT(po.amount);
|
||||
READ_BUF(po.view_tag, 3);
|
||||
READ_BUF(po.anchor_enc, 16);
|
||||
if (m_majorVersion >= 11) {
|
||||
uint64_t asset_len;
|
||||
READ_VARINT(asset_len);
|
||||
if (asset_len == 0 || asset_len > 32) return __LINE__;
|
||||
if (static_cast<uint64_t>(data_end - data) < asset_len) return __LINE__;
|
||||
po.asset_type.assign(reinterpret_cast<const char*>(data), static_cast<size_t>(asset_len));
|
||||
data += asset_len;
|
||||
} else {
|
||||
po.asset_type = "SAL1";
|
||||
}
|
||||
m_protocolOutputs.push_back(po);
|
||||
}
|
||||
}
|
||||
@@ -719,18 +732,22 @@ skip_protocol_tx:
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
// For Carrot v1 blocks, ensure protocol TX hash is populated
|
||||
// For Carrot v1 blocks, ensure protocol TX hash is populated.
|
||||
// IMPORTANT: Only fill in the hash if it wasn't already parsed from the block data.
|
||||
// Overriding the original hash breaks PoW verification when the block creator used
|
||||
// a different hash method (e.g., stub hash vs real blob hash).
|
||||
if (m_majorVersion >= 10) {
|
||||
if (m_transactions.size() < 2) {
|
||||
m_transactions.resize(2);
|
||||
// Only compute if the slot was just created (no hash from block data)
|
||||
hash protocol_tx_hash;
|
||||
if (!m_protocolTxBlob.empty()) {
|
||||
calculate_protocol_tx_hash_from_blob(m_protocolTxBlob, protocol_tx_hash);
|
||||
} else {
|
||||
calculate_protocol_tx_hash(m_txinGenHeight, protocol_tx_hash);
|
||||
}
|
||||
m_transactions[1] = static_cast<indexed_hash>(protocol_tx_hash);
|
||||
}
|
||||
hash protocol_tx_hash;
|
||||
if (m_sidechainHeight >= SIDECHAIN_PROTOCOL_TX_HARDFORK_HEIGHT && !m_protocolTxBlob.empty()) {
|
||||
calculate_protocol_tx_hash_from_blob(m_protocolTxBlob, protocol_tx_hash);
|
||||
} else {
|
||||
calculate_protocol_tx_hash(m_txinGenHeight, protocol_tx_hash);
|
||||
}
|
||||
m_transactions[1] = static_cast<indexed_hash>(protocol_tx_hash);
|
||||
}
|
||||
|
||||
// Post-process: remove protocol outputs from m_outputAmounts and related arrays.
|
||||
|
||||
+588
-221
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,7 @@ public:
|
||||
SideChain(p2pool* pool, NetworkType type, const char* pool_name = nullptr, const Wallet* dev_wallet = nullptr);
|
||||
~SideChain();
|
||||
|
||||
void handle_hardfork_transition(uint8_t new_major_version);
|
||||
[[nodiscard]] bool fill_sidechain_data(PoolBlock& block, std::vector<MinerShare>& shares) const;
|
||||
|
||||
[[nodiscard]] bool incoming_block_seen(const PoolBlock& block);
|
||||
@@ -114,6 +115,9 @@ public:
|
||||
void check_and_run_deferred_recovery();
|
||||
bool is_in_recovery() const { return m_recoveryMode.load(); }
|
||||
|
||||
// Peer consensus check: detect if we're ahead of the network and recover
|
||||
void check_peer_consensus(const std::vector<uint64_t>& peer_tips);
|
||||
|
||||
// Mainchain reorg handling
|
||||
void on_mainchain_reorg(uint64_t split_height, const hash& old_block_hash);
|
||||
bool is_in_reorg_grace_period() const;
|
||||
@@ -127,6 +131,8 @@ public:
|
||||
[[nodiscard]] uint64_t last_updated() const;
|
||||
[[nodiscard]] bool is_ready_to_mine() const { return m_readyToMine.load(); }
|
||||
void set_ready_to_mine(bool ready) { m_readyToMine.store(ready); }
|
||||
void set_p2p_start_time(uint64_t t) { m_p2pStartTime.store(t); }
|
||||
bool preflight_check();
|
||||
[[nodiscard]] bool is_default() const;
|
||||
[[nodiscard]] bool is_mini() const;
|
||||
[[nodiscard]] bool is_nano() const;
|
||||
@@ -192,6 +198,9 @@ private:
|
||||
|
||||
// Sync state - prevents mining/bans during startup
|
||||
std::atomic<bool> m_readyToMine{ false };
|
||||
std::atomic<bool> m_preflightPassed{ false };
|
||||
std::atomic<uint64_t> m_p2pStartTime{ 0 };
|
||||
|
||||
|
||||
std::map<uint64_t, std::vector<PoolBlock*>> m_blocksByHeight;
|
||||
unordered_map<hash, PoolBlock*> m_blocksById;
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
namespace p2pool {
|
||||
|
||||
#define P2POOL_VERSION_MAJOR 4
|
||||
#define P2POOL_VERSION_MINOR 16
|
||||
#define P2POOL_VERSION_MINOR 19
|
||||
#define P2POOL_VERSION_PATCH 0
|
||||
|
||||
constexpr uint32_t P2POOL_VERSION = (P2POOL_VERSION_MAJOR << 16) | (P2POOL_VERSION_MINOR << 8) | P2POOL_VERSION_PATCH;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "wallet.h"
|
||||
#include "keccak.h"
|
||||
#include "params.h"
|
||||
#include "protocol_tx_hash.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace p2pool {
|
||||
@@ -34,6 +35,24 @@ static hash H(const char* s)
|
||||
return result;
|
||||
};
|
||||
|
||||
// Build a protocol TX blob matching what the daemon would provide for a given height
|
||||
static void build_protocol_tx(uint64_t height, std::vector<uint8_t>& blob, hash& tx_hash)
|
||||
{
|
||||
blob.clear();
|
||||
writeVarint(4, blob); // version
|
||||
writeVarint(60, blob); // unlock_time
|
||||
writeVarint(1, blob); // vin count
|
||||
blob.push_back(0xff); // TXIN_GEN
|
||||
writeVarint(height, blob); // height
|
||||
writeVarint(0, blob); // vout count
|
||||
writeVarint(2, blob); // extra size
|
||||
blob.push_back(0x02); // extra[0]
|
||||
blob.push_back(0x00); // extra[1]
|
||||
writeVarint(2, blob); // type PROTOCOL
|
||||
blob.push_back(0); // RCT type
|
||||
calculate_protocol_tx_hash_from_blob(blob, tx_hash);
|
||||
}
|
||||
|
||||
TEST(block_template, update)
|
||||
{
|
||||
init_crypto_cache();
|
||||
@@ -51,6 +70,8 @@ TEST(block_template, update)
|
||||
data.median_weight = 300000;
|
||||
data.already_generated_coins = 6887387843126525ULL; // Current Salvium supply
|
||||
data.median_timestamp = (1ULL << 35) - 2;
|
||||
build_protocol_tx(data.height, data.protocol_tx_blob, data.protocol_tx_hash);
|
||||
data.protocol_tx_loaded = true;
|
||||
|
||||
Mempool mempool;
|
||||
Params params;
|
||||
@@ -61,7 +82,7 @@ TEST(block_template, update)
|
||||
ASSERT_EQ(tpl.get_reward(), 8813943601ULL);
|
||||
|
||||
const PoolBlock* b = tpl.pool_block_template();
|
||||
ASSERT_EQ(b->m_sidechainId, H("559b3c4ce0f85fb74862b21872c0e7652cd238cda0868bb5e109e45bba824987"));
|
||||
ASSERT_EQ(b->m_sidechainId, H("061da16d9a2d3a31ebc1075f5d4e84fd7be7f55f6c4c1fc62a7e15ef344baf76"));
|
||||
|
||||
std::vector<uint8_t> blobs;
|
||||
uint64_t height;
|
||||
@@ -80,7 +101,7 @@ TEST(block_template, update)
|
||||
|
||||
hash blobs_hash;
|
||||
keccak(blobs.data(), static_cast<int>(blobs.size()), blobs_hash.h);
|
||||
ASSERT_EQ(blobs_hash, H("a0aa8de98e412b38856d3c3a60f4b24159c94bb7d0f847186c729538b2ed3b29"));
|
||||
ASSERT_EQ(blobs_hash, H("d5c2782a239b702cd08bb2843a53e45996a924ae370cbacdf4be951b9dad4166"));
|
||||
|
||||
// Test 2: mempool with high fee and low fee transactions, it must choose high fee transactions
|
||||
for (uint64_t i = 0; i < 513; ++i) {
|
||||
@@ -115,7 +136,7 @@ TEST(block_template, update)
|
||||
tpl.update(data, mempool, ¶ms);
|
||||
ASSERT_EQ(tpl.get_reward(), 20408427350ULL);
|
||||
|
||||
ASSERT_EQ(b->m_sidechainId, H("b141fb9d1ac6a0a657b342ca7febf8c3bd4fa26e5fcfe314698e5ed8a189b8b0"));
|
||||
ASSERT_EQ(b->m_sidechainId, H("2508573073e47476fbcc6b78c0a9c07f880bb04d4e791c2312d5d620f5e0c2d6"));
|
||||
ASSERT_EQ(b->m_transactions.size(), 258);
|
||||
|
||||
// Transaction selection algorithm differs with Salvium parameters
|
||||
@@ -134,7 +155,7 @@ TEST(block_template, update)
|
||||
ASSERT_EQ(template_id, 2U);
|
||||
|
||||
keccak(blobs.data(), static_cast<int>(blobs.size()), blobs_hash.h);
|
||||
ASSERT_EQ(blobs_hash, H("dd2d21ae05f434fb776d39a85e6a517315e70054681a10086343eae620ce301c"));
|
||||
ASSERT_EQ(blobs_hash, H("89d72d7d990c38356920a5e4225e9c1d6da1cdaae838365509cdde24359d0924"));
|
||||
|
||||
// Test 3: small but not empty mempool, and aux chains
|
||||
/*
|
||||
@@ -200,7 +221,7 @@ TEST(block_template, update)
|
||||
tpl.update(data, mempool, ¶ms);
|
||||
ASSERT_EQ(tpl.get_reward(), 29290189890ULL);
|
||||
|
||||
ASSERT_EQ(b->m_sidechainId, H("785a5525982ac554fcfeaa61efa71c0b479d418422ed2028021bf7e8e6b79412"));
|
||||
ASSERT_EQ(b->m_sidechainId, H("81de1eb4007bd91e8f7a63bd2da9391b10cd3a725ef10e1beaf502d530c3adc4"));
|
||||
ASSERT_EQ(b->m_transactions.size(), 277);
|
||||
|
||||
tpl.get_hashing_blobs(0, 1000, blobs, height, diff, aux_diff, sidechain_diff, seed_hash, nonce_offset, template_id);
|
||||
@@ -213,7 +234,7 @@ TEST(block_template, update)
|
||||
ASSERT_EQ(template_id, 3U);
|
||||
|
||||
keccak(blobs.data(), static_cast<int>(blobs.size()), blobs_hash.h);
|
||||
ASSERT_EQ(blobs_hash, H("ca4057ffed2822185c0f8ab6c30ec124c8eb024db56132d47c504b86607d45d7"));
|
||||
ASSERT_EQ(blobs_hash, H("8e2e3a9119c1b37771f37b6cc7f8637fe92a7e37fcc2b6affaa61c98f9154754"));
|
||||
}
|
||||
destroy_crypto_cache();
|
||||
|
||||
@@ -248,6 +269,8 @@ TEST(block_template, submit_sidechain_block)
|
||||
data.median_weight = 300000;
|
||||
data.already_generated_coins = 6887387843126525ULL; // Current Salvium supply
|
||||
data.median_timestamp = (1ULL << 35) - (sidechain.chain_window_size() * 2 + 10) * sidechain.block_time() - 3600;
|
||||
build_protocol_tx(data.height, data.protocol_tx_blob, data.protocol_tx_hash);
|
||||
data.protocol_tx_loaded = true;
|
||||
|
||||
Mempool mempool;
|
||||
Params params;
|
||||
@@ -289,7 +312,7 @@ TEST(block_template, submit_sidechain_block)
|
||||
ASSERT_EQ(tip->m_txinGenHeight, data.height);
|
||||
ASSERT_EQ(tip->m_sidechainHeight, sidechain.chain_window_size() * 3 - 1);
|
||||
|
||||
ASSERT_EQ(tip->m_sidechainId, H("1f1264edf6756741774ed89af60a8d7edafb397eda07dc05aabc2ced929e4d06"));
|
||||
ASSERT_EQ(tip->m_sidechainId, H("64e7b577ef4c10e12c32c81a1148a1e31a728133d189602533c10ffacc751461"));
|
||||
}
|
||||
destroy_crypto_cache();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user