Fix merkle tree calculation for mempool transactions

- Fixed m_transactionHashes to preserve mempool txs across template updates
- Changed merkle calculation to use merkle_hash() with all transactions
- Removed hardcoded 2-transaction merkle assumption for HF10+
- Synchronized m_poolBlockTemplate->m_transactions with m_transactionHashes
- Commented out premature m_transactionHashes.clear()
- Updated monerod references to salviumd
- Enhanced difficulty debug logging (lo/hi components)
This commit is contained in:
Matt Hess
2025-12-08 23:03:32 +00:00
parent 728d6c2cbf
commit 1d319325e5
3 changed files with 51 additions and 67 deletions
+46 -62
View File
@@ -639,14 +639,23 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
// First, calculate and store the miner TX hash
hash miner_tx_hash = calc_miner_tx_hash(0);
// Clear and rebuild m_transactionHashes with both hashes
// m_transactionHashes currently has: [zeros placeholder][mempool tx 0][mempool tx 1]...
// We need: [miner tx][protocol tx][mempool tx 0][mempool tx 1]...
// Save mempool txs (everything after position 0)
std::vector<uint8_t> mempool_txs;
if (m_transactionHashes.size() > HASH_SIZE) {
mempool_txs.assign(m_transactionHashes.begin() + HASH_SIZE, m_transactionHashes.end());
}
// Rebuild with correct order
m_transactionHashes.clear();
m_transactionHashes.reserve(HASH_SIZE * 2);
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);
LOGINFO(3, "Stored miner TX hash at position 0: " << miner_tx_hash);
// Write protocol tx bytes to blob
writeVarint(4, m_blockTemplateBlob); // version
writeVarint(60, m_blockTemplateBlob); // unlock_time
@@ -659,36 +668,36 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
m_blockTemplateBlob.push_back(0x00); // extra[1]
writeVarint(2, m_blockTemplateBlob); // type PROTOCOL
m_blockTemplateBlob.push_back(0); // RCT type
// Calculate protocol tx hash and store in member variable
calculate_protocol_tx_hash(data.height, m_protocolTxHash);
LOGINFO(3, "Protocol TX hash: " << m_protocolTxHash);
// Add to transaction list after miner tx
// Add protocol tx hash after miner tx
m_transactionHashes.insert(m_transactionHashes.end(), m_protocolTxHash.h, m_protocolTxHash.h + HASH_SIZE);
// Add mempool txs back
if (!mempool_txs.empty()) {
m_transactionHashes.insert(m_transactionHashes.end(), mempool_txs.begin(), mempool_txs.end());
}
// Now write tx_hashes section
// For HF10+, blob tx_count excludes protocol tx (it's implicit like miner tx)
const uint64_t blob_tx_count = (data.major_version >= 10) ? (m_numTransactionHashes >= 2 ? m_numTransactionHashes - 2 : 0) : m_numTransactionHashes;
const uint64_t blob_tx_count = m_numTransactionHashes;
writeVarint(blob_tx_count, m_blockTemplateBlob);
// Miner tx hash is skipped here because it's not a part of block template
m_blockTemplateBlob.insert(m_blockTemplateBlob.end(), m_transactionHashes.begin() + HASH_SIZE * 2, m_transactionHashes.end());
// Build m_transactions directly from m_transactionHashes to ensure they match
m_poolBlockTemplate->m_transactions.clear();
m_poolBlockTemplate->m_transactions.resize(1);
m_poolBlockTemplate->m_transactions.reserve(m_mempoolTxsOrder.size() + 1);
for (size_t i = 0, n = m_mempoolTxsOrder.size(); i < n; ++i) {
m_poolBlockTemplate->m_transactions.push_back(m_mempoolTxs[m_mempoolTxsOrder[i]].id);
}
// For Carrot v1 blocks, insert protocol TX at position 1
if (m_poolBlockTemplate->m_majorVersion >= 10) {
hash protocol_tx_hash;
calculate_protocol_tx_hash(m_poolBlockTemplate->m_txinGenHeight, protocol_tx_hash);
m_poolBlockTemplate->m_transactions.insert(
m_poolBlockTemplate->m_transactions.begin() + 1,
static_cast<indexed_hash>(protocol_tx_hash));
m_poolBlockTemplate->m_transactions.resize(1); // Placeholder for coinbase at [0]
// m_transactionHashes layout: [miner_tx][protocol_tx][mempool_tx_0][mempool_tx_1]...
// Copy protocol_tx and all mempool txs (skip miner_tx at position 0)
for (size_t i = HASH_SIZE; i < m_transactionHashes.size(); i += HASH_SIZE) {
hash h;
memcpy(h.h, m_transactionHashes.data() + i, HASH_SIZE);
m_poolBlockTemplate->m_transactions.push_back(static_cast<indexed_hash>(h));
}
m_poolBlockTemplate->m_minerWallet = params->m_miningWallet;
@@ -849,7 +858,7 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
m_minerTx.clear();
m_blockHeader.clear();
m_minerTxExtra.clear();
m_transactionHashes.clear();
// m_transactionHashes.clear();
m_transactionHashesSet.clear();
m_rewards.clear();
m_mempoolTxs.clear();
@@ -1562,49 +1571,24 @@ uint32_t BlockTemplate::get_hashing_blob_nolock(uint32_t extra_nonce, uint8_t* b
// Block header
memcpy(p, m_blockTemplateBlob.data(), m_blockHeaderSize);
p += m_blockHeaderSize;
// Merkle tree hash - build from all transactions and use merkle_hash()
hash miner_tx_hash = calc_miner_tx_hash(extra_nonce);
const size_t num_hashes = m_transactionHashes.size() / HASH_SIZE;
// Merkle tree hash
hash root_hash = calc_miner_tx_hash(extra_nonce);
// For Carrot v1 (HF10+) with protocol TX, simple 2-transaction merkle tree
if (m_majorVersion >= 10) {
// Just hash miner TX hash + protocol TX hash
uint8_t merkle_data[HASH_SIZE * 2];
memcpy(merkle_data, root_hash.h, HASH_SIZE);
// Protocol TX hash is the first (and only) entry in transaction hashes after miner TX
memcpy(merkle_data + HASH_SIZE, m_protocolTxHash.h, HASH_SIZE);
// DEBUG: Show what we're actually hashing
LOGINFO(6, "DEBUG: About to hash merkle data (raw bytes):");
LOGINFO(6, " Full 64 bytes: " << log::hex_buf(merkle_data, HASH_SIZE * 2));
LOGINFO(6, " First 32 bytes (miner): " << log::hex_buf(merkle_data, HASH_SIZE));
LOGINFO(6, " Second 32 bytes (protocol): " << log::hex_buf(merkle_data + HASH_SIZE, HASH_SIZE));
LOGINFO(6, " m_transactionHashes size: " << m_transactionHashes.size());
keccak(merkle_data, HASH_SIZE * 2, root_hash.h);
// DEBUG: Verify the result
LOGINFO(6, " Result merkle root: " << log::hex_buf(root_hash.h, HASH_SIZE));
// DEBUG: Manually verify by re-hashing
hash verify_hash;
keccak(merkle_data, HASH_SIZE * 2, verify_hash.h);
LOGINFO(6, " Verify merkle root: " << log::hex_buf(verify_hash.h, HASH_SIZE));
} else {
// Pre-Carrot: use merkle branch logic
for (size_t i = 0; i < m_merkleTreeMainBranch.size(); i += HASH_SIZE) {
uint8_t h[HASH_SIZE * 2];
memcpy(h, root_hash.h, HASH_SIZE);
memcpy(h + HASH_SIZE, m_merkleTreeMainBranch.data() + i, HASH_SIZE);
keccak(h, HASH_SIZE * 2, root_hash.h);
}
std::vector<hash> hashes(num_hashes);
hashes[0] = miner_tx_hash; // miner tx with current extra_nonce
for (size_t i = 1; i < num_hashes; ++i) {
memcpy(hashes[i].h, m_transactionHashes.data() + i * HASH_SIZE, HASH_SIZE);
}
root_hash merkle_root;
merkle_hash(hashes, merkle_root);
memcpy(p, root_hash.h, HASH_SIZE);
LOGINFO(6, " m_transactionHashes size: " << m_transactionHashes.size() << " num_hashes: " << num_hashes);
LOGINFO(6, " Result merkle root: " << static_cast<const hash&>(merkle_root));
memcpy(p, merkle_root.h, HASH_SIZE);
p += HASH_SIZE;
// Total number of transactions in this block (including the miner tx)
// FOR HF10+, include both miner tx and protocol tx
const uint64_t tx_count_in_header = m_numTransactionHashes + (m_majorVersion >= 10 ? 2 : 1);
+4 -4
View File
@@ -556,7 +556,7 @@ void p2pool::get_missing_heights()
m_missingHeights.pop_back();
}
LOGWARN(3, "Mainchain data for height " << h << " is missing, requesting it from monerod again");
LOGWARN(3, "Mainchain data for height " << h << " is missing, requesting it from salviumd again");
char buf[log::Stream::BUF_SIZE + 1] = {};
log::Stream s(buf);
@@ -1644,7 +1644,7 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
}
if (info.busy_syncing || !info.synchronized) {
LOGINFO(1, "monerod is " << (info.busy_syncing ? "busy syncing" : "not synchronized") << ", trying again in 1 second");
LOGINFO(1, "salviumd is " << (info.busy_syncing ? "busy syncing" : "not synchronized") << ", trying again in 1 second");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
switch_host();
get_info();
@@ -1660,7 +1660,7 @@ void p2pool::parse_get_info_rpc(const char* data, size_t size)
const NetworkType sidechain_network = m_sideChain->network_type();
if (monero_network != sidechain_network) {
LOGERR(1, "monerod is on " << monero_network << ", but you're mining to a " << sidechain_network << " sidechain");
LOGERR(1, "salviumd is on " << monero_network << ", but you're mining to a " << sidechain_network << " sidechain");
PANIC_STOP();
}
@@ -1732,7 +1732,7 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size)
const uint64_t version_lo = version & 65535;
const uint64_t required_version_hi = required >> 16;
const uint64_t required_version_lo = required & 65535;
LOGERR(1, "monerod RPC v" << version_hi << '.' << version_lo << " is incompatible, update to RPC >= v" << required_version_hi << '.' << required_version_lo << " (Monero v0.18.0.0 or newer)");
LOGERR(1, "salviumd RPC v" << version_hi << '.' << version_lo << " is incompatible, update to RPC >= v" << required_version_hi << '.' << required_version_lo << " (Monero v0.18.0.0 or newer)");
PANIC_STOP();
}
+1 -1
View File
@@ -650,7 +650,7 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& missing_
}
}
LOGINFO(0, "DEBUG PoW check: sidechainHeight=" << block.m_sidechainHeight << " m_difficulty=" << block.m_difficulty << " m_powHash=" << block.m_powHash);
LOGINFO(0, "DEBUG PoW check: sidechainHeight=" << block.m_sidechainHeight << " m_difficulty.lo=" << block.m_difficulty.lo << " m_difficulty.hi=" << block.m_difficulty.hi << " m_powHash=" << block.m_powHash);
if (!block.m_difficulty.check_pow(block.m_powHash)) {
LOGWARN(3,