Skip to content

Commit 8d39fad

Browse files
committed
doge: correct submitblock/submitauxblock reward accounting (#744)
Re-audit (Fable review of the prior revision) showed submitauxblock is NOT a viable fallback for a PPLNS merged block: DOGE ships multiaddress=true and rebuild_cached_blocks commits current_work.block_hash to c2pool's self-built PPLNS block hash, which dogecoind's AuxpowMiner never minted via createauxblock, so submitauxblock of it is always rejected -- and the daemon's own template pays its aux address, violating the trustless merged-payout invariant. The viable DOGE dual-path is full-block submitblock (ARM B) + the independent embedded P2P relay (ARM A), which master already wires; there is no aux fallback to add. This revision drops the misguided aux-fallback wiring and instead fixes the genuine latent reward-accounting bugs it surfaced: - try_submit_merged_blocks: keep the P2P relay UNCONDITIONAL (fires even when the local submitblock is rejected -- it is an independent arm that can still land the block via peers). Documented why submitauxblock is not a valid fallback. - AuxChainRPC::submit_block: BIP22 -- a submitblock REJECTION is a non-null string RESULT, not a JSON-RPC error, so the old 'return true unless throw' mis-recorded a rejected block as WON. Inspect the result: null => accepted, duplicate/inconclusive => accepted, any other non-null string => rejected. - AuxChainRPC::submit_aux_block: honor the BOOLEAN result (true=accept, false=reject) instead of assuming success on no-throw (latent; no run-path caller today, corrected for safety). Broadcast/RPC-result path only -- no PoW, share format, aux commitment, or PPLNS math touched (consensus-neutral). Builds clean (c2pool_merged_mining + c2pool-ltc).
1 parent c4bd8ef commit 8d39fad

1 file changed

Lines changed: 56 additions & 6 deletions

File tree

src/c2pool/merged/merged_mining.cpp

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,31 @@ AuxWork AuxChainRPC::create_aux_block(const std::string& address)
443443
bool AuxChainRPC::submit_block(const std::string& block_hex)
444444
{
445445
try {
446-
call("submitblock", nlohmann::json::array({block_hex}));
447-
LOG_INFO << "[MM:" << m_config.symbol << "] Block submitted successfully!";
448-
return true;
446+
auto result = call("submitblock", nlohmann::json::array({block_hex}));
447+
// BIP22: submitblock returns a NULL result on ACCEPT; a REJECTION is
448+
// delivered as a non-null string RESULT ("rejected", "high-hash",
449+
// "bad-txnmrklroot", ...), NOT a JSON-RPC error -- so a
450+
// template-divergence reject does NOT throw. Interpreting throw-or-not as
451+
// accept-or-not (the previous behaviour) mis-records a rejected block as
452+
// WON. Treat any non-null result as a rejection, except the
453+
// duplicate/inconclusive verdicts which mean the daemon already has (or
454+
// may still accept) the block -> a win.
455+
if (result.is_null()) {
456+
LOG_INFO << "[MM:" << m_config.symbol << "] submitblock ACCEPTED";
457+
return true;
458+
}
459+
const std::string reason =
460+
result.is_string() ? result.get<std::string>() : result.dump();
461+
if (reason == "duplicate" || reason == "duplicate-invalid" ||
462+
reason == "duplicate-inconclusive" || reason == "inconclusive") {
463+
LOG_INFO << "[MM:" << m_config.symbol
464+
<< "] submitblock duplicate/inconclusive (" << reason
465+
<< ") -- treated as accepted";
466+
return true;
467+
}
468+
LOG_ERROR << "[MM:" << m_config.symbol
469+
<< "] submitblock REJECTED by daemon: " << reason;
470+
return false;
449471
} catch (const std::exception& e) {
450472
LOG_WARNING << "[MM:" << m_config.symbol << "] submitblock failed: " << e.what();
451473
return false;
@@ -455,9 +477,22 @@ bool AuxChainRPC::submit_block(const std::string& block_hex)
455477
bool AuxChainRPC::submit_aux_block(const uint256& block_hash, const std::string& auxpow_hex)
456478
{
457479
try {
458-
call("submitauxblock", nlohmann::json::array({block_hash.GetHex(), auxpow_hex}));
459-
LOG_INFO << "[MM:" << m_config.symbol << "] Aux block ACCEPTED by daemon!";
460-
return true;
480+
auto result = call("submitauxblock",
481+
nlohmann::json::array({block_hash.GetHex(), auxpow_hex}));
482+
// submitauxblock returns a BOOLEAN result: true = accepted, false =
483+
// rejected (e.g. "block hash unknown" for a hash the daemon never minted
484+
// via createauxblock). Honor it -- the previous code discarded the result
485+
// and returned true unless call() threw, so a false-result rejection was
486+
// mis-recorded as a won block. (No run-path caller today: the DOGE
487+
// PPLNS-committed hash is structurally not createauxblock-minted, so this
488+
// path is not wired as a fallback -- corrected here for latent safety.)
489+
const bool accepted = result.is_boolean() ? result.get<bool>() : false;
490+
if (accepted)
491+
LOG_INFO << "[MM:" << m_config.symbol << "] submitauxblock ACCEPTED by daemon";
492+
else
493+
LOG_ERROR << "[MM:" << m_config.symbol
494+
<< "] submitauxblock REJECTED by daemon: " << result.dump();
495+
return accepted;
461496
} catch (const std::exception& e) {
462497
LOG_WARNING << "[MM:" << m_config.symbol << "] submitauxblock failed: " << e.what();
463498
return false;
@@ -1027,6 +1062,21 @@ void MergedMiningManager::try_submit_merged_blocks(
10271062
std::ofstream f(path);
10281063
if (f.is_open()) { f << block_hex; f.close(); }
10291064
}
1065+
// DOGE dual-path broadcast (the viable pair for a PPLNS merged
1066+
// block): ARM B = full-block submitblock to a local daemon; ARM A =
1067+
// the INDEPENDENT embedded P2P relay (m_block_relay_fn, bound in
1068+
// main_ltc). The P2P relay fires UNCONDITIONALLY -- including when
1069+
// the local submitblock is REJECTED for a transient/local reason --
1070+
// because it is an independent arm that can still land the block on
1071+
// the network; gating it on `ok` would drop a working reward-safety
1072+
// arm. #744 note: submitauxblock is NOT a viable fallback here.
1073+
// DOGE ships multiaddress=true and rebuild_cached_blocks commits
1074+
// current_work.block_hash to c2pool's self-built PPLNS block hash,
1075+
// which the daemon's AuxpowMiner never minted via createauxblock, so
1076+
// submitauxblock of it is always rejected ("block hash unknown") --
1077+
// and the daemon's own template would pay its aux address, violating
1078+
// the trustless merged-payout invariant. So the canonical aux path
1079+
// is deliberately NOT wired as a fallback.
10301080
bool ok = chain.rpc->submit_block(block_hex);
10311081
record_discovered_block(chain, ok, parent_hash.GetHex());
10321082
if (m_block_relay_fn) {

0 commit comments

Comments
 (0)