Gate block template on protocol_tx RPC

Wait for getblocktemplate RPC response before delivering templates
  to miners, ensuring the protocol_tx hash is correct in the PoW
  merkle root. All error paths fall back to empty protocol_tx so
  mining is never permanently stalled.

  Also bump ver to 4.16 and include upstream whitespace fix in tcp_server
This commit is contained in:
Matt Hess
2026-02-02 19:48:15 +00:00
parent 5ab43ff6d9
commit 3b85ec3aba
3 changed files with 54 additions and 30 deletions
+49 -25
View File
@@ -1346,15 +1346,18 @@ void p2pool::update_block_template()
if (m_updateSeed.exchange(false)) {
m_hasher->set_seed_async(data.seed_hash);
}
// If we don't have the real protocol_tx yet, fetch it first.
// The RPC callback will trigger update_block_template again once it has the data.
if (!data.protocol_tx_loaded) {
fetch_block_template();
return;
}
m_blockTemplate->update(data, *m_mempool, m_params, in_donation_mode(data.height));
stratum_on_block();
api_update_pool_stats();
// Fetch real protocol_tx from daemon if we don't have it yet for this height
if (!data.protocol_tx_loaded) {
fetch_block_template();
}
#if defined(WITH_RANDOMX) && !defined(P2POOL_UNIT_TESTS)
if (m_isAlternativeBlock.exchange(false)) {
MutexLock lock(m_minerLock);
@@ -2154,11 +2157,18 @@ void p2pool::fetch_block_template()
{
parse_block_template_rpc(data, size, height);
},
[](const char* data, size_t size, double)
[this](const char* data, size_t size, double)
{
if (size > 0) {
LOGWARN(1, "getblocktemplate RPC request failed: " << log::const_buf(data, size));
}
// RPC failed - fall back to building template without real protocol_tx
// Mark as loaded (with empty blob) so update_block_template proceeds
{
WriteLock lock(m_minerDataLock);
m_minerData.protocol_tx_loaded = true;
}
update_block_template_async();
});
}
@@ -2166,11 +2176,21 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
{
if (m_stopped) return;
// On any failure, fall back to empty protocol_tx and proceed with template build
auto fallback = [this]() {
{
WriteLock lock(m_minerDataLock);
m_minerData.protocol_tx_loaded = true;
}
update_block_template_async();
};
rapidjson::Document doc;
doc.Parse(data, size);
if (doc.HasParseError() || !doc.IsObject()) {
LOGWARN(1, "getblocktemplate RPC response is not valid JSON");
fallback();
return;
}
@@ -2181,6 +2201,7 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
} else {
LOGWARN(1, "getblocktemplate RPC response has no result");
}
fallback();
return;
}
@@ -2189,18 +2210,21 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
auto it_blob = result.FindMember("blocktemplate_blob");
if (it_blob == result.MemberEnd() || !it_blob->value.IsString()) {
LOGWARN(1, "getblocktemplate RPC response missing blocktemplate_blob");
fallback();
return;
}
auto it_height = result.FindMember("height");
if (it_height == result.MemberEnd() || !it_height->value.IsUint64()) {
LOGWARN(1, "getblocktemplate RPC response missing height");
fallback();
return;
}
const uint64_t height = it_height->value.GetUint64();
if (height != expected_height) {
LOGINFO(5, "getblocktemplate height " << height << " doesn't match expected " << expected_height << ", ignoring");
fallback();
return;
}
@@ -2210,6 +2234,7 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
std::vector<uint8_t> blob;
if (!from_hex(hex_str, hex_len, blob)) {
LOGWARN(1, "getblocktemplate: failed to decode blocktemplate_blob hex");
fallback();
return;
}
@@ -2217,36 +2242,35 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
std::vector<uint8_t> protocol_tx_blob;
if (!extract_protocol_tx_from_blob(blob.data(), blob.size(), protocol_tx_blob)) {
LOGWARN(1, "getblocktemplate: failed to extract protocol_tx from blob");
fallback();
return;
}
// Count vout in the protocol_tx to determine if it has outputs
// The protocol_tx starts with: version(varint) + unlock_time(varint) + vin_count(varint) + vin_data + vout_count(varint)
// We need to read vout_count to know if there are outputs
// Count vout in the protocol_tx
const uint8_t* p = protocol_tx_blob.data();
const uint8_t* p_end = p + protocol_tx_blob.size();
uint64_t dummy;
p = readVarint(p, p_end, dummy); // version
if (!p) return;
if (!p) { fallback(); return; }
p = readVarint(p, p_end, dummy); // unlock_time
if (!p) return;
if (!p) { fallback(); return; }
uint64_t vin_count;
p = readVarint(p, p_end, vin_count); // vin count
if (!p) return;
// Skip vin entries
p = readVarint(p, p_end, vin_count);
if (!p) { fallback(); return; }
for (uint64_t i = 0; i < vin_count; ++i) {
if (p >= p_end) return;
if (p >= p_end) { fallback(); return; }
uint8_t tag = *(p++);
if (tag == 0xff) {
p = readVarint(p, p_end, dummy); // txin_gen height
if (!p) return;
p = readVarint(p, p_end, dummy);
if (!p) { fallback(); return; }
} else {
return; // Unexpected vin type in protocol_tx
fallback();
return;
}
}
uint64_t vout_count;
p = readVarint(p, p_end, vout_count);
if (!p) return;
if (!p) { fallback(); return; }
// Compute protocol_tx hash
hash protocol_tx_hash;
@@ -2254,7 +2278,11 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
LOGINFO(4, "getblocktemplate: protocol_tx for height " << height << " has " << vout_count << " outputs, hash " << protocol_tx_hash);
// Check if current miner data is still for this height
if (vout_count > 0) {
LOGINFO(2, "Protocol TX for height " << height << " has " << vout_count << " outputs");
}
// Store protocol_tx and trigger template build
{
WriteLock lock(m_minerDataLock);
if (m_minerData.height != height) {
@@ -2266,11 +2294,7 @@ void p2pool::parse_block_template_rpc(const char* data, size_t size, uint64_t ex
m_minerData.protocol_tx_loaded = true;
}
// If protocol_tx has outputs, trigger a template update so miners get the correct block
if (vout_count > 0) {
LOGINFO(2, "Protocol TX for height " << height << " has " << vout_count << " outputs, updating block template");
update_block_template_async();
}
update_block_template_async();
}
bool p2pool::parse_block_header(const char* data, size_t size, ChainMain& c)
+4 -4
View File
@@ -319,9 +319,9 @@ bool TCPServer::connect_to_peer(bool is_v6, const raw_ip& ip, int port)
bool TCPServer::connect_to_peer(const std::string& domain, int port)
{
if (m_socks5Proxy.empty()) {
LOGERR(1, "Can't connect to " << domain << ": SOCKS5 proxy is required");
return false;
}
LOGERR(1, "Can't connect to " << domain << ": SOCKS5 proxy is required");
return false;
}
if (m_finished.load()) {
return false;
@@ -337,7 +337,7 @@ bool TCPServer::connect_to_peer(const std::string& domain, int port)
s << domain << ':' << port << '\0';
if (s.m_spilled) {
LOGERR(1, "Can't connect to " << domain << ": too long domain name");
LOGERR(1, "Can't connect to " << domain << ": too long domain name");
return false;
}
+1 -1
View File
@@ -35,7 +35,7 @@
namespace p2pool {
#define P2POOL_VERSION_MAJOR 4
#define P2POOL_VERSION_MINOR 15
#define P2POOL_VERSION_MINOR 16
#define P2POOL_VERSION_PATCH 0
constexpr uint32_t P2POOL_VERSION = (P2POOL_VERSION_MAJOR << 16) | (P2POOL_VERSION_MINOR << 8) | P2POOL_VERSION_PATCH;