Skip to content

Commit fdffce5

Browse files
authored
dgb: Stage 4d mining_submit decision SSOT (submit_classify.hpp) (#296)
Add coin/submit_classify.hpp: the single place mining_submit places a submission Scrypt pow_hash into one of three outcome classes. Tighten-first ladder (block target checked before share target, so a won block -- a superset of an accepted share -- wins the classification): pow_hash <= block_target -> WonBlock (submit_block_fn_ broadcast) pow_hash <= share_target -> ShareAccept (try_mint_share sharechain mint) otherwise -> Reject Inclusive at BOTH boundaries via the same pow<=target == !(pow>target) gate the header_chain satisfaction check and the nonce grinder run, matching DigiByte Core CheckProofOfWork and p2pool-merged-v36 share-accept -- keeps c2pool-dgb bit-compatible with the daemon and the Python reference. Header-only, depends only on dgb_arith256.hpp (u256): no scrypt, no core, no dgb OBJECT lib -- a pure decision over three integers. 7/7 KAT pins the ladder, both inclusive boundaries, the zero-hash superset case, and a safety invariant that an inverted (malformed) target pair can only mis-Reject, never spurious WonBlock. Registered in both build.yml --target allowlists (#143 trap). mining_submit still returns the stage-4d low-difficulty stub -- no behavior change. The header-reconstruct -> scrypt_pow_hash -> classify -> dispatch wiring that makes this the live-invocation point is the following slice. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent d0ef185 commit fdffce5

4 files changed

Lines changed: 157 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
test_address_resolution test_compute_share_target \
8383
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 \
8484
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 \
85-
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 \
85+
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 \
8686
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
8787
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 \
8888
-j$(nproc)
@@ -214,7 +214,7 @@ jobs:
214214
test_address_resolution test_compute_share_target \
215215
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 \
216216
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 \
217-
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 \
217+
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 \
218218
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
219219
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 \
220220
v37_test \
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
// ---------------------------------------------------------------------------
3+
// submit_classify.hpp -- the Stage 4d mining_submit decision SSOT.
4+
//
5+
// mining_submit (stratum/work_source.cpp) reconstructs the 80-byte block header
6+
// from the miner submission, runs scrypt_pow_hash (#286 SSOT) to obtain the
7+
// DGB-Scrypt pow_hash u256, then must place the result in EXACTLY ONE of three
8+
// outcome classes. THIS header is the single place that three-way decision is
9+
// made, so the hot path and its KATs can never disagree on the boundary
10+
// semantics, and the eventual dual-path broadcaster + sharechain-mint dispatch
11+
// both read their trigger from one function.
12+
//
13+
// The ladder, in strict tighten-first order. The block target is always <= the
14+
// share target, so a hash that meets the block target necessarily meets the
15+
// share target: a won block is a SUPERSET of an accepted share, never a
16+
// disjoint class. Check the block target FIRST so the rarer, higher-value
17+
// outcome wins the classification:
18+
//
19+
// pow_hash <= block_target -> WonBlock (submit_block_fn_: broadcast)
20+
// pow_hash <= share_target -> ShareAccept (try_mint_share: sharechain mint)
21+
// otherwise -> Reject (low-difficulty stratum error)
22+
//
23+
// The comparison is the EXACT inclusive gate the header_chain satisfaction
24+
// check and the nonce grinder run: pow <= target == !(pow > target), MSB-first
25+
// via u256::operator>. Inclusive at BOTH boundaries -- a hash exactly equal to
26+
// a target satisfies it. This matches Bitcoin / DigiByte Core CheckProofOfWork
27+
// (hash > target is the ONLY reject) and p2pool s share-accept gate, so
28+
// c2pool-dgb stays bit-compatible with the daemon AND with p2pool-merged-v36.
29+
//
30+
// Header-only, depends ONLY on dgb_arith256.hpp (u256): no core, no daemon, no
31+
// scrypt (the caller supplies the already-computed digest). Same no-link
32+
// discipline as scrypt_pow.hpp / nonce_grinder.hpp -- a pure decision over
33+
// three integers, trivially testable without the stratum string machinery.
34+
// ---------------------------------------------------------------------------
35+
36+
#include <impl/dgb/coin/dgb_arith256.hpp> // dgb::coin::u256
37+
38+
namespace dgb::coin {
39+
40+
// The three mutually-exclusive outcomes of a stratum submission, in increasing
41+
// difficulty order. Values are stable (logged / asserted by the KATs).
42+
enum class SubmitClass {
43+
Reject = 0, // pow_hash > share_target: below share difficulty
44+
ShareAccept = 1, // share_target >= pow_hash > block_target: mint a share
45+
WonBlock = 2, // pow_hash <= block_target: a full network block was found
46+
};
47+
48+
// Classify a submission s Scrypt pow_hash against the two targets.
49+
//
50+
// Precondition (caller-guaranteed): block_target <= share_target -- the share
51+
// target is always looser. The tighten-first ladder is SAFE even if a malformed
52+
// job inverts them: a hash is only ever called WonBlock when it genuinely meets
53+
// the block target, so an inverted pair can never promote a share into a
54+
// spurious block broadcast (it would at worst mis-Reject, never mis-WonBlock).
55+
inline SubmitClass classify_submission(const u256& pow_hash,
56+
const u256& block_target,
57+
const u256& share_target)
58+
{
59+
if (!(pow_hash > block_target)) return SubmitClass::WonBlock; // <= block
60+
if (!(pow_hash > share_target)) return SubmitClass::ShareAccept; // <= share
61+
return SubmitClass::Reject;
62+
}
63+
64+
} // namespace dgb::coin

src/impl/dgb/test/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,17 @@ if (BUILD_TESTING AND GTest_FOUND)
406406
GTest::gtest_main GTest::gtest)
407407
gtest_add_tests(dgb_won_block_finalize_test "" AUTO)
408408

409+
# --- #82 Stage 4d: mining_submit decision SSOT (coin/submit_classify.hpp) --
410+
# Pins classify_submission: the three-way pow_hash ladder (WonBlock /
411+
# ShareAccept / Reject) with BOTH boundaries inclusive, mirroring DigiByte
412+
# Core CheckProofOfWork (hash>target = the only reject) and p2pool-merged-v36
413+
# share-accept. Tighten-first order so block beats share; safety KAT proves an
414+
# inverted (malformed) target pair can only mis-Reject, never spurious-WonBlock.
415+
# Pure header-only u256 -- no scrypt, no core, no dgb OBJECT lib. MUST stay in
416+
# BOTH build.yml --target allowlists (#143 trap).
417+
add_executable(dgb_submit_classify_test submit_classify_test.cpp)
418+
target_link_libraries(dgb_submit_classify_test PRIVATE
419+
GTest::gtest_main GTest::gtest)
420+
gtest_add_tests(dgb_submit_classify_test "" AUTO)
421+
409422
endif()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// ---------------------------------------------------------------------------
2+
// submit_classify_test.cpp -- KAT for the Stage 4d mining_submit decision SSOT
3+
// (coin/submit_classify.hpp). Pins the three-way ladder + BOTH inclusive
4+
// boundaries so the hot path (work_source.cpp mining_submit) and the dual-path
5+
// broadcaster / sharechain-mint dispatch can never drift from the documented
6+
// pow<=target gate, which itself mirrors DigiByte Core CheckProofOfWork and
7+
// p2pool-merged-v36 share-accept. Pure u256 arithmetic -- no scrypt, no link.
8+
// ---------------------------------------------------------------------------
9+
#include <gtest/gtest.h>
10+
11+
#include <impl/dgb/coin/submit_classify.hpp>
12+
13+
using dgb::coin::u256;
14+
using dgb::coin::SubmitClass;
15+
using dgb::coin::classify_submission;
16+
17+
namespace {
18+
19+
// Realistic ordering: block target is TIGHTER (smaller) than the share target.
20+
// Use round magnitudes so the boundaries are unambiguous.
21+
// block_target = 1000, share_target = 1000000.
22+
u256 block_t() { return u256::from_u64(1000); }
23+
u256 share_t() { return u256::from_u64(1000000); }
24+
25+
u256 plus1(u256 v) { v += u256::from_u64(1); return v; }
26+
27+
} // namespace
28+
29+
// A hash STRICTLY below the block target is a won block (rarer outcome wins).
30+
TEST(DgbSubmitClassify, BelowBlockTargetIsWonBlock) {
31+
EXPECT_EQ(classify_submission(u256::from_u64(500), block_t(), share_t()),
32+
SubmitClass::WonBlock);
33+
}
34+
35+
// Inclusive at the BLOCK boundary: pow == block_target satisfies it (won).
36+
TEST(DgbSubmitClassify, EqualBlockTargetIsWonBlockInclusive) {
37+
EXPECT_EQ(classify_submission(block_t(), block_t(), share_t()),
38+
SubmitClass::WonBlock);
39+
}
40+
41+
// One unit above the block target, still under the share target -> share mint.
42+
TEST(DgbSubmitClassify, JustAboveBlockTargetIsShareAccept) {
43+
EXPECT_EQ(classify_submission(plus1(block_t()), block_t(), share_t()),
44+
SubmitClass::ShareAccept);
45+
}
46+
47+
// Inclusive at the SHARE boundary: pow == share_target is still an accept.
48+
TEST(DgbSubmitClassify, EqualShareTargetIsShareAcceptInclusive) {
49+
EXPECT_EQ(classify_submission(share_t(), block_t(), share_t()),
50+
SubmitClass::ShareAccept);
51+
}
52+
53+
// One unit above the share target -> below share difficulty -> reject.
54+
TEST(DgbSubmitClassify, JustAboveShareTargetIsReject) {
55+
EXPECT_EQ(classify_submission(plus1(share_t()), block_t(), share_t()),
56+
SubmitClass::Reject);
57+
}
58+
59+
// pow_hash == 0 trivially satisfies every target -> won block (superset rule:
60+
// a won block always also clears the share target; block is checked first).
61+
TEST(DgbSubmitClassify, ZeroHashIsWonBlockNotDoubleCounted) {
62+
EXPECT_EQ(classify_submission(u256::from_u64(0), block_t(), share_t()),
63+
SubmitClass::WonBlock);
64+
}
65+
66+
// Safety under an inverted (malformed) job where share_target < block_target:
67+
// the tighten-first ladder must NEVER promote a non-block hash to WonBlock.
68+
// Here pow=2000 is below the (looser-looking) block_t=1000? no -- it is ABOVE
69+
// 1000, so it is not a block; with share=500 it is also above 500 -> Reject.
70+
// The invariant under test: an inverted pair can only mis-Reject, never
71+
// mis-WonBlock.
72+
TEST(DgbSubmitClassify, InvertedTargetsNeverSpuriousWonBlock) {
73+
const u256 tight = u256::from_u64(500); // passed as share (wrong)
74+
const u256 loose = u256::from_u64(1000); // passed as block (wrong)
75+
// pow just above the real block magnitude (1000): not a block.
76+
EXPECT_NE(classify_submission(u256::from_u64(1500), loose, tight),
77+
SubmitClass::WonBlock);
78+
}

0 commit comments

Comments
 (0)