Skip to content

Commit 6b5d60e

Browse files
committed
Fix LTC block rejection: atomic snapshot for block body data
send_notify_work() read template, merkle branches, tx_data, and coinbase via separate mutex acquisitions. refresh_work() could update the template between reads, causing witness commitment / merkle root mismatch in the assembled block — litecoind rejected every block containing mempool txs as duplicate-invalid. Fix: extend WorkSnapshot to include tx_data, merkle_branches, and the full GBT template. build_connection_coinbase() captures them atomically under m_work_mutex. send_notify_work() overrides all header fields, branches, and tx_data from the snapshot after coinbase build.
1 parent e3121e1 commit 6b5d60e

3 files changed

Lines changed: 58 additions & 7 deletions

File tree

src/core/stratum_server.cpp

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,7 +1226,7 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
12261226
ntime_ss << std::hex << std::setw(8) << std::setfill('0') << curtime;
12271227
std::string ntime = ntime_ss.str();
12281228

1229-
// Merkle branches
1229+
// Merkle branches — will be overridden with snapshot data after coinbase build
12301230
merkle_branches_vec = mining_interface_->get_stratum_merkle_branches();
12311231
for (const auto& h : merkle_branches_vec)
12321232
merkle_branches.push_back(h);
@@ -1278,6 +1278,45 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
12781278
}
12791279
}
12801280

1281+
// ── ATOMIC SNAPSHOT OVERRIDE ──
1282+
// Replace header fields, merkle branches, and tx_data with data from the
1283+
// atomic snapshot captured by build_connection_coinbase(). This prevents
1284+
// merkle root / witness commitment mismatch when refresh_work() updates
1285+
// the template between the separate get_current_work_template(),
1286+
// get_stratum_merkle_branches(), and build_connection_coinbase() calls.
1287+
if (!cbr.snapshot.template_json.is_null()) {
1288+
const auto& snap_tmpl = cbr.snapshot.template_json;
1289+
1290+
// Re-derive header fields from the snapshot template
1291+
gbt_prevhash = snap_tmpl.value("previousblockhash", "");
1292+
prevhash = gbt_to_stratum_prevhash(gbt_prevhash);
1293+
1294+
version_u32 = static_cast<uint32_t>(snap_tmpl.value("version", 0x20000000));
1295+
{
1296+
std::ostringstream ss;
1297+
ss << std::hex << std::setw(8) << std::setfill('0') << version_u32;
1298+
version = ss.str();
1299+
}
1300+
1301+
if (snap_tmpl.contains("bits"))
1302+
gbt_block_nbits = snap_tmpl["bits"].get<std::string>();
1303+
nbits = gbt_block_nbits;
1304+
1305+
if (snap_tmpl.contains("curtime"))
1306+
curtime = static_cast<uint32_t>(snap_tmpl["curtime"].get<uint64_t>());
1307+
{
1308+
std::ostringstream ss;
1309+
ss << std::hex << std::setw(8) << std::setfill('0') << curtime;
1310+
ntime = ss.str();
1311+
}
1312+
1313+
// Override merkle branches with snapshot
1314+
merkle_branches_vec = cbr.snapshot.merkle_branches;
1315+
merkle_branches = nlohmann::json::array();
1316+
for (const auto& h : merkle_branches_vec)
1317+
merkle_branches.push_back(h);
1318+
}
1319+
12811320
// clean_jobs = true when prevhash changed OR forced (e.g. after authorize)
12821321
bool clean_jobs = force_clean || (prevhash != last_prevhash_);
12831322
last_prevhash_ = prevhash;
@@ -1331,12 +1370,10 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
13311370
je.desired_version = cbr.snapshot.frozen_ref.desired_version;
13321371
je.has_frozen = true;
13331372

1334-
if (!tmpl.empty() && !tmpl.is_null() && tmpl.contains("transactions")) {
1335-
for (const auto& tx : tmpl["transactions"]) {
1336-
if (tx.contains("data"))
1337-
je.tx_data.push_back(tx["data"].get<std::string>());
1338-
}
1339-
}
1373+
// Use tx_data from the atomic snapshot — NOT from the potentially stale
1374+
// tmpl fetched at the top of send_notify_work(). This ensures the block
1375+
// body transactions match the witness commitment and merkle branches.
1376+
je.tx_data = cbr.snapshot.tx_data;
13401377
}
13411378

13421379
// VARDIFF: do NOT override per-connection difficulty with pool share_bits.

src/core/web_server.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,15 @@ MiningInterface::build_connection_coinbase(
16411641
snap.witness_commitment_hex = ws.witness_commitment;
16421642
snap.witness_root = ws.witness_root;
16431643
snap.frozen_ref = rhr;
1644+
// Freeze block body data atomically — prevents merkle root mismatch
1645+
// when refresh_work() updates the template between separate reads.
1646+
snap.merkle_branches = ws.merkle_branches;
1647+
snap.template_json = ws.tmpl;
1648+
if (ws.tmpl.contains("transactions")) {
1649+
for (const auto& tx : ws.tmpl["transactions"])
1650+
if (tx.contains("data"))
1651+
snap.tx_data.push_back(tx["data"].get<std::string>());
1652+
}
16441653
return {std::move(cb1), std::move(cb2), std::move(snap)};
16451654
}
16461655

src/core/web_server.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,11 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server
382382
uint256 witness_root;
383383
// Frozen share fields from ref_hash_fn
384384
RefHashResult frozen_ref;
385+
// Block body data — must be captured atomically with witness_commitment
386+
// to prevent merkle root mismatch when refresh_work() updates the template.
387+
std::vector<std::string> tx_data; // raw tx hex from template
388+
std::vector<std::string> merkle_branches; // stratum merkle branches
389+
nlohmann::json template_json; // full GBT template (for prevhash, version, bits)
385390
};
386391

387392
// Build per-connection coinbase parts: computes ref_hash using the ref_hash callback,

0 commit comments

Comments
 (0)