Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_coinbase_value_parity_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_coinbase_value_parity_test dgb_submit_classify_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test v37_test \
-j$(nproc)
Expand Down Expand Up @@ -214,7 +214,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_coinbase_value_parity_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_coinbase_value_parity_test dgb_submit_classify_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
Expand Down
64 changes: 64 additions & 0 deletions src/impl/dgb/coin/submit_classify.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once
// ---------------------------------------------------------------------------
// submit_classify.hpp -- the Stage 4d mining_submit decision SSOT.
//
// mining_submit (stratum/work_source.cpp) reconstructs the 80-byte block header
// from the miner submission, runs scrypt_pow_hash (#286 SSOT) to obtain the
// DGB-Scrypt pow_hash u256, then must place the result in EXACTLY ONE of three
// outcome classes. THIS header is the single place that three-way decision is
// made, so the hot path and its KATs can never disagree on the boundary
// semantics, and the eventual dual-path broadcaster + sharechain-mint dispatch
// both read their trigger from one function.
//
// The ladder, in strict tighten-first order. The block target is always <= the
// share target, so a hash that meets the block target necessarily meets the
// share target: a won block is a SUPERSET of an accepted share, never a
// disjoint class. Check the block target FIRST so the rarer, higher-value
// outcome wins the classification:
//
// pow_hash <= block_target -> WonBlock (submit_block_fn_: broadcast)
// pow_hash <= share_target -> ShareAccept (try_mint_share: sharechain mint)
// otherwise -> Reject (low-difficulty stratum error)
//
// The comparison is the EXACT inclusive gate the header_chain satisfaction
// check and the nonce grinder run: pow <= target == !(pow > target), MSB-first
// via u256::operator>. Inclusive at BOTH boundaries -- a hash exactly equal to
// a target satisfies it. This matches Bitcoin / DigiByte Core CheckProofOfWork
// (hash > target is the ONLY reject) and p2pool s share-accept gate, so
// c2pool-dgb stays bit-compatible with the daemon AND with p2pool-merged-v36.
//
// Header-only, depends ONLY on dgb_arith256.hpp (u256): no core, no daemon, no
// scrypt (the caller supplies the already-computed digest). Same no-link
// discipline as scrypt_pow.hpp / nonce_grinder.hpp -- a pure decision over
// three integers, trivially testable without the stratum string machinery.
// ---------------------------------------------------------------------------

#include <impl/dgb/coin/dgb_arith256.hpp> // dgb::coin::u256

namespace dgb::coin {

// The three mutually-exclusive outcomes of a stratum submission, in increasing
// difficulty order. Values are stable (logged / asserted by the KATs).
enum class SubmitClass {
Reject = 0, // pow_hash > share_target: below share difficulty
ShareAccept = 1, // share_target >= pow_hash > block_target: mint a share
WonBlock = 2, // pow_hash <= block_target: a full network block was found
};

// Classify a submission s Scrypt pow_hash against the two targets.
//
// Precondition (caller-guaranteed): block_target <= share_target -- the share
// target is always looser. The tighten-first ladder is SAFE even if a malformed
// job inverts them: a hash is only ever called WonBlock when it genuinely meets
// the block target, so an inverted pair can never promote a share into a
// spurious block broadcast (it would at worst mis-Reject, never mis-WonBlock).
inline SubmitClass classify_submission(const u256& pow_hash,
const u256& block_target,
const u256& share_target)
{
if (!(pow_hash > block_target)) return SubmitClass::WonBlock; // <= block
if (!(pow_hash > share_target)) return SubmitClass::ShareAccept; // <= share
return SubmitClass::Reject;
}

} // namespace dgb::coin
13 changes: 13 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -406,4 +406,17 @@ if (BUILD_TESTING AND GTest_FOUND)
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_won_block_finalize_test "" AUTO)

# --- #82 Stage 4d: mining_submit decision SSOT (coin/submit_classify.hpp) --
# Pins classify_submission: the three-way pow_hash ladder (WonBlock /
# ShareAccept / Reject) with BOTH boundaries inclusive, mirroring DigiByte
# Core CheckProofOfWork (hash>target = the only reject) and p2pool-merged-v36
# share-accept. Tighten-first order so block beats share; safety KAT proves an
# inverted (malformed) target pair can only mis-Reject, never spurious-WonBlock.
# Pure header-only u256 -- no scrypt, no core, no dgb OBJECT lib. MUST stay in
# BOTH build.yml --target allowlists (#143 trap).
add_executable(dgb_submit_classify_test submit_classify_test.cpp)
target_link_libraries(dgb_submit_classify_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_submit_classify_test "" AUTO)

endif()
78 changes: 78 additions & 0 deletions src/impl/dgb/test/submit_classify_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// ---------------------------------------------------------------------------
// submit_classify_test.cpp -- KAT for the Stage 4d mining_submit decision SSOT
// (coin/submit_classify.hpp). Pins the three-way ladder + BOTH inclusive
// boundaries so the hot path (work_source.cpp mining_submit) and the dual-path
// broadcaster / sharechain-mint dispatch can never drift from the documented
// pow<=target gate, which itself mirrors DigiByte Core CheckProofOfWork and
// p2pool-merged-v36 share-accept. Pure u256 arithmetic -- no scrypt, no link.
// ---------------------------------------------------------------------------
#include <gtest/gtest.h>

#include <impl/dgb/coin/submit_classify.hpp>

using dgb::coin::u256;
using dgb::coin::SubmitClass;
using dgb::coin::classify_submission;

namespace {

// Realistic ordering: block target is TIGHTER (smaller) than the share target.
// Use round magnitudes so the boundaries are unambiguous.
// block_target = 1000, share_target = 1000000.
u256 block_t() { return u256::from_u64(1000); }
u256 share_t() { return u256::from_u64(1000000); }

u256 plus1(u256 v) { v += u256::from_u64(1); return v; }

} // namespace

// A hash STRICTLY below the block target is a won block (rarer outcome wins).
TEST(DgbSubmitClassify, BelowBlockTargetIsWonBlock) {
EXPECT_EQ(classify_submission(u256::from_u64(500), block_t(), share_t()),
SubmitClass::WonBlock);
}

// Inclusive at the BLOCK boundary: pow == block_target satisfies it (won).
TEST(DgbSubmitClassify, EqualBlockTargetIsWonBlockInclusive) {
EXPECT_EQ(classify_submission(block_t(), block_t(), share_t()),
SubmitClass::WonBlock);
}

// One unit above the block target, still under the share target -> share mint.
TEST(DgbSubmitClassify, JustAboveBlockTargetIsShareAccept) {
EXPECT_EQ(classify_submission(plus1(block_t()), block_t(), share_t()),
SubmitClass::ShareAccept);
}

// Inclusive at the SHARE boundary: pow == share_target is still an accept.
TEST(DgbSubmitClassify, EqualShareTargetIsShareAcceptInclusive) {
EXPECT_EQ(classify_submission(share_t(), block_t(), share_t()),
SubmitClass::ShareAccept);
}

// One unit above the share target -> below share difficulty -> reject.
TEST(DgbSubmitClassify, JustAboveShareTargetIsReject) {
EXPECT_EQ(classify_submission(plus1(share_t()), block_t(), share_t()),
SubmitClass::Reject);
}

// pow_hash == 0 trivially satisfies every target -> won block (superset rule:
// a won block always also clears the share target; block is checked first).
TEST(DgbSubmitClassify, ZeroHashIsWonBlockNotDoubleCounted) {
EXPECT_EQ(classify_submission(u256::from_u64(0), block_t(), share_t()),
SubmitClass::WonBlock);
}

// Safety under an inverted (malformed) job where share_target < block_target:
// the tighten-first ladder must NEVER promote a non-block hash to WonBlock.
// Here pow=2000 is below the (looser-looking) block_t=1000? no -- it is ABOVE
// 1000, so it is not a block; with share=500 it is also above 500 -> Reject.
// The invariant under test: an inverted pair can only mis-Reject, never
// mis-WonBlock.
TEST(DgbSubmitClassify, InvertedTargetsNeverSpuriousWonBlock) {
const u256 tight = u256::from_u64(500); // passed as share (wrong)
const u256 loose = u256::from_u64(1000); // passed as block (wrong)
// pow just above the real block magnitude (1000): not a block.
EXPECT_NE(classify_submission(u256::from_u64(1500), loose, tight),
SubmitClass::WonBlock);
}
Loading