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:
Matt Hess
2025-12-07 23:57:46 +00:00
parent 00fb078004
commit 728d6c2cbf
5 changed files with 121 additions and 28 deletions
+28 -12
View File
@@ -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 = {};