Fix Carrot v1 protocol TX handling for peer synchronization
Root cause: Merkle verification failures (error 659) and peer bans occurred because protocol TX was not consistently present in m_transactions[1]. Parser fix (pool_block_parser.inl): - Skip dummy transactions[0] entry when populating m_transactions - Add protocol TX computation after parsing for Carrot v1 blocks Block template fix (block_template.cpp): - Insert protocol TX at position [1] during block creation in update() - Insert protocol TX during select_mempool_transactions() get_pow_hash fix (pool_block.cpp): - Ensure protocol TX is populated before serialize_mainchain_data() The protocol TX must be at m_transactions[1] before ANY serialization occurs, otherwise sender and receiver compute different merkle roots. Tested: Multiple restart cycles with two nodes, no bans, chains stay synced.
This commit is contained in:
@@ -682,6 +682,15 @@ void BlockTemplate::update(const MinerData& data, const Mempool& mempool, const
|
||||
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_minerWallet = params->m_miningWallet;
|
||||
|
||||
// Layout: [software id, version, random number, sidechain extra_nonce]
|
||||
@@ -952,6 +961,12 @@ void BlockTemplate::select_mempool_transactions(const Mempool& mempool)
|
||||
PoolBlock* b = m_poolBlockTemplate;
|
||||
b->m_transactions.clear();
|
||||
b->m_transactions.resize(1);
|
||||
// For Carrot v1 blocks, protocol TX takes a slot
|
||||
if (b->m_majorVersion >= 10) {
|
||||
hash protocol_tx_hash;
|
||||
calculate_protocol_tx_hash(b->m_txinGenHeight, protocol_tx_hash);
|
||||
b->m_transactions.push_back(static_cast<indexed_hash>(protocol_tx_hash));
|
||||
}
|
||||
b->m_ephPublicKeys.clear();
|
||||
b->m_outputAmounts.clear();
|
||||
b->m_viewTags.clear();
|
||||
|
||||
+45
-10
@@ -180,6 +180,24 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
|
||||
LOGINFO(0, "DEBUG serialize: numOutputs=" << m_outputAmounts.size() << " numEphKeys=" << m_ephPublicKeys.size() << " numViewTags=" << m_viewTags.size() << " numEncAnchors=" << m_encryptedAnchors.size() << " sidechainHeight=" << m_sidechainHeight);
|
||||
|
||||
if (!m_ephPublicKeys.empty()) {
|
||||
LOGINFO(0, "DEBUG serialize K_o[0]=" << m_ephPublicKeys[0]);
|
||||
}
|
||||
|
||||
if (!m_viewTags.empty() && m_viewTags[0].size() >= 3) {
|
||||
char buf[16]; snprintf(buf, sizeof(buf), "%02x%02x%02x", m_viewTags[0][0], m_viewTags[0][1], m_viewTags[0][2]);
|
||||
LOGINFO(0, "DEBUG serialize viewTag[0]=" << (const char*)buf);
|
||||
}
|
||||
if (!m_encryptedAnchors.empty() && m_encryptedAnchors[0].size() >= 16) {
|
||||
std::string hex;
|
||||
for (int i = 0; i < 16; ++i) { char buf[4]; snprintf(buf, sizeof(buf), "%02x", m_encryptedAnchors[0][i]); hex += buf; }
|
||||
LOGINFO(0, "DEBUG serialize encAnchor[0]=" << hex);
|
||||
}
|
||||
|
||||
LOGINFO(0, "DEBUG serialize D_e=" << m_txkeyPub);
|
||||
|
||||
LOGINFO(0, "DEBUG serialize merkleRoot=" << static_cast<const hash&>(m_merkleRoot));
|
||||
|
||||
for (size_t i = 0, n = m_outputAmounts.size(); i < n; ++i) {
|
||||
const TxOutput& output = m_outputAmounts[i];
|
||||
|
||||
@@ -302,6 +320,11 @@ std::vector<uint8_t> PoolBlock::serialize_mainchain_data(size_t* header_size, si
|
||||
}
|
||||
if (include_tx_hashes) {
|
||||
writeVarint(m_transactions.size() - 1, data);
|
||||
|
||||
if (m_transactions.size() > 1) {
|
||||
LOGINFO(0, "DEBUG serialize tx: m_transactions[1]=" << m_transactions[1] << " sidechainHeight=" << m_sidechainHeight);
|
||||
}
|
||||
|
||||
#ifdef WITH_INDEXED_HASHES
|
||||
for (size_t i = 1, n = m_transactions.size(); i < n; ++i) {
|
||||
const hash h = m_transactions[i];
|
||||
@@ -434,9 +457,18 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
|
||||
uint8_t blob[128];
|
||||
size_t blob_size = 0;
|
||||
|
||||
{
|
||||
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);
|
||||
{
|
||||
// For Carrot v1 blocks, ensure m_transactions has space for protocol TX before serializing
|
||||
if (m_majorVersion >= 10) {
|
||||
if (m_transactions.size() < 2) {
|
||||
m_transactions.resize(2);
|
||||
}
|
||||
hash protocol_tx_hash;
|
||||
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);
|
||||
|
||||
if (!header_size || !miner_tx_size || (mainchain_data.size() < header_size + miner_tx_size)) {
|
||||
LOGERR(1, "tried to calculate PoW of uninitialized block");
|
||||
@@ -473,13 +505,6 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
|
||||
// Save the coinbase tx hash into the first element of m_transactions
|
||||
m_transactions[0] = static_cast<indexed_hash>(tmp);
|
||||
|
||||
// For Carrot v1 blocks, compute and store protocol TX hash at position 1
|
||||
if ((m_majorVersion >= 10) && (m_transactions.size() >= 2)) {
|
||||
hash protocol_tx_hash;
|
||||
calculate_protocol_tx_hash(m_txinGenHeight, protocol_tx_hash);
|
||||
m_transactions[1] = static_cast<indexed_hash>(protocol_tx_hash);
|
||||
}
|
||||
|
||||
// DEBUG: dump m_transactions for comparison
|
||||
LOGINFO(0, "get_pow_hash: m_transactions.size()=" << m_transactions.size());
|
||||
for (size_t dbg_i = 0; dbg_i < m_transactions.size(); ++dbg_i) {
|
||||
@@ -517,6 +542,16 @@ bool PoolBlock::get_pow_hash(RandomX_Hasher_Base* hasher, uint64_t height, const
|
||||
// cppcheck-suppress danglingLifetime
|
||||
m_hashingBlob.assign(blob, blob + blob_size);
|
||||
|
||||
{
|
||||
std::string hex;
|
||||
for (size_t i = 0; i < blob_size; ++i) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x", blob[i]);
|
||||
hex += buf;
|
||||
}
|
||||
LOGINFO(0, "DEBUG hashing blob (" << blob_size << " bytes): " << hex);
|
||||
}
|
||||
|
||||
return hasher->calculate(blob, blob_size, height, seed_hash, pow_hash, force_light_mode);
|
||||
}
|
||||
|
||||
|
||||
@@ -446,10 +446,6 @@ skip_protocol_tx:
|
||||
|
||||
const uint64_t n = transactions.size();
|
||||
|
||||
if (n > 0) {
|
||||
m_transactions.emplace_back(transactions[0]);
|
||||
}
|
||||
|
||||
for (uint64_t i = 1; i < n; ++i) {
|
||||
const uint64_t parent_index = parent_indices[i];
|
||||
if (parent_index) {
|
||||
@@ -465,8 +461,8 @@ skip_protocol_tx:
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const hash& h : transactions) {
|
||||
m_transactions.emplace_back(h);
|
||||
for (size_t i = 1; i < transactions.size(); ++i) {
|
||||
m_transactions.emplace_back(transactions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,6 +571,12 @@ skip_protocol_tx:
|
||||
|
||||
const uint8_t* transactions_blob = reinterpret_cast<uint8_t*>(transactions.data() + 1);
|
||||
|
||||
if (transactions_blob_size > 0) {
|
||||
hash tx_h;
|
||||
memcpy(tx_h.h, transactions_blob, HASH_SIZE);
|
||||
LOGINFO(0, "DEBUG transactions_blob first hash: " << tx_h);
|
||||
}
|
||||
|
||||
#if POOL_BLOCK_DEBUG
|
||||
memcpy(m_mainChainDataDebug.data() + outputs_offset, outputs_blob.data(), outputs_blob_size);
|
||||
memcpy(m_mainChainDataDebug.data() + transactions_offset + outputs_blob_size_diff, transactions_blob, transactions_blob_size);
|
||||
@@ -584,6 +586,8 @@ skip_protocol_tx:
|
||||
const std::vector<uint8_t>& consensus_id = sidechain.consensus_id();
|
||||
const int data_size = static_cast<int>((data_end - data_begin) + outputs_blob_size_diff + transactions_blob_size_diff);
|
||||
|
||||
LOGINFO(0, "DEBUG hash params: data_size=" << data_size << " outputs_offset=" << outputs_offset << " outputs_blob_size=" << outputs_blob_size << " outputs_blob_size_diff=" << outputs_blob_size_diff << " transactions_offset=" << transactions_offset << " transactions_blob_size=" << transactions_blob_size << " transactions_blob_size_diff=" << transactions_blob_size_diff << " nonce_offset=" << nonce_offset << " extra_nonce_offset=" << extra_nonce_offset << " mm_root_hash_offset=" << mm_root_hash_offset << " consensus_id_size=" << consensus_id.size());
|
||||
|
||||
if (data_size > static_cast<int>(MAX_BLOCK_SIZE)) {
|
||||
return __LINE__;
|
||||
}
|
||||
@@ -660,6 +664,16 @@ skip_protocol_tx:
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
// For Carrot v1 blocks, ensure protocol TX is populated
|
||||
if (m_majorVersion >= 10) {
|
||||
if (m_transactions.size() < 2) {
|
||||
m_transactions.resize(2);
|
||||
}
|
||||
hash protocol_tx_hash;
|
||||
calculate_protocol_tx_hash(m_txinGenHeight, protocol_tx_hash);
|
||||
m_transactions[1] = static_cast<indexed_hash>(protocol_tx_hash);
|
||||
}
|
||||
|
||||
reset_offchain_data();
|
||||
return 0;
|
||||
}
|
||||
|
||||
+28
-12
@@ -625,6 +625,8 @@ bool SideChain::add_external_block(PoolBlock& block, std::vector<hash>& missing_
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGINFO(0, "DEBUG get_pow_hash: seed=" << block.m_seed << " txinGenHeight=" << block.m_txinGenHeight);
|
||||
|
||||
if (!block.get_pow_hash(m_pool->hasher(), block.m_txinGenHeight, block.m_seed, block.m_powHash)) {
|
||||
LOGWARN(3, "add_external_block: couldn't get PoW hash for height = " << block.m_sidechainHeight << ", mainchain height " << block.m_txinGenHeight << ". Ignoring it.");
|
||||
forget_incoming_block(block);
|
||||
@@ -648,6 +650,8 @@ 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);
|
||||
|
||||
if (!block.m_difficulty.check_pow(block.m_powHash)) {
|
||||
LOGWARN(3,
|
||||
"add_external_block mined by " << block.m_minerWallet <<
|
||||
@@ -2491,19 +2495,26 @@ bool SideChain::consider_peer_genesis(const hash& genesis_id, uint64_t timestamp
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a genesis and peer's is older (or same timestamp but lower hash), we need to yield
|
||||
if (m_genesisDecisionMade && our_genesis_timestamp > 0) {
|
||||
const bool peer_wins = (timestamp < our_genesis_timestamp) ||
|
||||
(timestamp == our_genesis_timestamp && genesis_id < our_genesis_id);
|
||||
|
||||
if (peer_wins) {
|
||||
LOGWARN(3, "Peer has older genesis (theirs=" << timestamp
|
||||
<< " ours=" << our_genesis_timestamp << "), purging to re-sync");
|
||||
purge_sidechain();
|
||||
// Fall through to adopt peer's genesis
|
||||
// If we have a genesis and peer's is older (or same timestamp but lower hash), we need to yield
|
||||
if (m_genesisDecisionMade) {
|
||||
if (our_genesis_timestamp > 0) {
|
||||
// We have an actual genesis block - check if peer's is older
|
||||
const bool peer_wins = (timestamp < our_genesis_timestamp) ||
|
||||
(timestamp == our_genesis_timestamp && genesis_id < our_genesis_id);
|
||||
if (peer_wins) {
|
||||
LOGWARN(3, "Peer has older genesis (theirs=" << timestamp
|
||||
<< " ours=" << our_genesis_timestamp << "), purging to re-sync");
|
||||
purge_sidechain();
|
||||
// Fall through to adopt peer's genesis
|
||||
} else {
|
||||
// Our genesis is older or same, keep it
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Our genesis is older or same, keep it
|
||||
return true;
|
||||
// We decided to create our own genesis but haven't mined it yet
|
||||
// Reset and adopt peer's genesis instead
|
||||
LOGINFO(3, "Canceling own genesis creation, will adopt peer's genesis");
|
||||
m_genesisDecisionMade = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2540,6 +2551,11 @@ void SideChain::purge_sidechain()
|
||||
// Clear difficulty data
|
||||
m_difficultyData.clear();
|
||||
|
||||
{
|
||||
WriteLock diff_lock(m_curDifficultyLock);
|
||||
m_curDifficulty = m_minDifficulty;
|
||||
}
|
||||
|
||||
// Reset genesis state to allow new genesis adoption
|
||||
m_genesisDecisionMade = false;
|
||||
m_adoptedGenesisId = {};
|
||||
|
||||
@@ -983,6 +983,19 @@ void StratumServer::on_share_found(uv_work_t* req)
|
||||
}
|
||||
|
||||
hash pow_hash;
|
||||
|
||||
LOGINFO(0, "DEBUG stratum hash: height=" << height << " seed=" << seed_hash << " blob_size=" << blob_size);
|
||||
|
||||
{
|
||||
std::string hex;
|
||||
for (size_t i = 0; i < blob_size; ++i) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x", blob[i]);
|
||||
hex += buf;
|
||||
}
|
||||
LOGINFO(0, "DEBUG stratum blob: " << hex);
|
||||
}
|
||||
|
||||
if (!pool->calculate_hash(blob, blob_size, height, seed_hash, pow_hash, false)) {
|
||||
LOGWARN(3, "client " << static_cast<char*>(share->m_clientAddrString) << " couldn't check share PoW");
|
||||
share->m_result = SubmittedShare::Result::COULDNT_CHECK_POW;
|
||||
|
||||
Reference in New Issue
Block a user