Skip to content

Commit 8d700a8

Browse files
authored
Merge pull request #130 from frstrtr/dash/s6-conformance
dash(s6): merkle-root-equality conformance precondition
2 parents fc4ee9a + 03ffa33 commit 8d700a8

4 files changed

Lines changed: 793 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
test_threading test_weights \
6060
test_header_chain test_mempool test_template_builder \
6161
test_doge_chain test_compact_blocks test_dash_x11_kat \
62-
test_dash_header_chain test_dash_block_replay \
62+
test_dash_header_chain test_dash_block_replay test_dash_conformance \
6363
test_multiaddress_pplns test_pplns_stress \
6464
test_hash_link test_decay_pplns \
6565
test_pplns_consensus \
@@ -186,7 +186,7 @@ jobs:
186186
test_threading test_weights \
187187
test_header_chain test_mempool test_template_builder \
188188
test_doge_chain test_compact_blocks test_dash_x11_kat \
189-
test_dash_header_chain test_dash_block_replay \
189+
test_dash_header_chain test_dash_block_replay test_dash_conformance \
190190
test_hash_link test_decay_pplns \
191191
test_pplns_consensus \
192192
test_v36_script_sorting test_v36_cross_impl_refhash \
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#pragma once
2+
3+
// Dash share-version transition negotiation (older-than-v35 -> v36).
4+
//
5+
// Conforms to frstrtr/p2pool-dash (OLDER oracle) — Option A, not a c2pool
6+
// reinterpretation. Two distinct tallies, deliberately kept apart (the F10
7+
// version-gate trap):
8+
//
9+
// * get_desired_version_counts() — a PLAIN tally (one vote per share) over a
10+
// chain window. Reference: p2pool-dash data.py get_desired_version_counts
11+
// as consumed by Share.check()'s confirmed-state guard and by the
12+
// AutoRatchet desired-version selection. It is NOT weighted in place.
13+
//
14+
// * get_desired_version_weights() — the SEPARATE WEIGHTED variant
15+
// (weight = target_to_average_attempts(target), i.e. expected hashes) the
16+
// v36 activation gate consumes. Reference: p2pool-merged-v36 work.py
17+
// v36_active = weight[36] / Sum(weight) >= 0.95.
18+
//
19+
// The confirmed-state guard (Share.check): a SUCCESSOR-version share may follow
20+
// its predecessor only if the new version already holds >= 60% of the PLAIN
21+
// votes in the [9/10 .. 10/10] tail of the CHAIN_LENGTH window; a switch with
22+
// fewer than CHAIN_LENGTH ancestors is rejected ("without enough history").
23+
24+
#include "share_chain.hpp" // dash::ShareChain, dash::DashShare
25+
26+
#include <core/target_utils.hpp> // chain::target_to_average_attempts, bits_to_target
27+
#include <core/uint256.hpp> // uint256, uint288
28+
29+
#include <algorithm>
30+
#include <cstdint>
31+
#include <map>
32+
#include <optional>
33+
34+
namespace dash::version_negotiation
35+
{
36+
37+
// PLAIN desired-version tally over the `dist` shares ending at `start_hash`
38+
// (inclusive, walking back via prev_hash). One vote per share. Mirrors the
39+
// older p2pool-dash get_desired_version_counts as used by the SUCCESSOR
40+
// tail-guard / AutoRatchet — do NOT add work weighting here (F10 trap).
41+
inline std::map<uint64_t, uint64_t>
42+
get_desired_version_counts(ShareChain& chain, const uint256& start_hash, uint64_t dist)
43+
{
44+
std::map<uint64_t, uint64_t> res;
45+
if (!chain.contains(start_hash)) return res;
46+
const uint64_t n = std::min<uint64_t>(dist,
47+
static_cast<uint64_t>(chain.get_height(start_hash)));
48+
for (auto&& [h, data] : chain.get_chain(start_hash, n)) {
49+
(void)h;
50+
data.share.invoke([&](auto* obj) {
51+
using S = std::remove_pointer_t<decltype(obj)>;
52+
if constexpr (std::is_same_v<S, dash::DashShare>)
53+
res[obj->m_desired_version] += 1;
54+
});
55+
}
56+
return res;
57+
}
58+
59+
// WEIGHTED desired-version tally: each share contributes its expected hash
60+
// count (target_to_average_attempts of its target), matching ShareIndex::work.
61+
// This is the variant the v36 activation gate consumes — kept separate from the
62+
// plain count above.
63+
inline std::map<uint64_t, uint288>
64+
get_desired_version_weights(ShareChain& chain, const uint256& start_hash, uint64_t dist)
65+
{
66+
std::map<uint64_t, uint288> res;
67+
if (!chain.contains(start_hash)) return res;
68+
const uint64_t n = std::min<uint64_t>(dist,
69+
static_cast<uint64_t>(chain.get_height(start_hash)));
70+
for (auto&& [h, data] : chain.get_chain(start_hash, n)) {
71+
(void)h;
72+
data.share.invoke([&](auto* obj) {
73+
using S = std::remove_pointer_t<decltype(obj)>;
74+
if constexpr (std::is_same_v<S, dash::DashShare>) {
75+
uint288 w = chain::target_to_average_attempts(
76+
chain::bits_to_target(obj->m_bits));
77+
res[obj->m_desired_version] += w;
78+
}
79+
});
80+
}
81+
return res;
82+
}
83+
84+
// 60% confirmed-state guard (p2pool-dash data.py Share.check). A SUCCESSOR
85+
// share is accepted only when its PLAIN vote count reaches floor(total*60/100).
86+
// The floor matches the oracle's integer `sum*60//100` exactly (e.g. 4/7 votes
87+
// clears thr=4, 3/7 does not).
88+
inline bool
89+
successor_switch_allowed(const std::map<uint64_t, uint64_t>& plain_counts,
90+
uint64_t successor_version)
91+
{
92+
uint64_t total = 0;
93+
for (const auto& [v, c] : plain_counts) total += c;
94+
if (total == 0) return false;
95+
const uint64_t threshold = (total * 60) / 100; // floor, as in the oracle
96+
auto it = plain_counts.find(successor_version);
97+
const uint64_t have = (it == plain_counts.end()) ? 0 : it->second;
98+
return have >= threshold;
99+
}
100+
101+
// v36 activation gate (p2pool-merged-v36 work.py): v36 is active once its
102+
// WEIGHTED signaling reaches >= 95% of total work. Evaluated as the exact
103+
// rational w36*100 >= total*95 — integer uint288, no IEEE-double fragility.
104+
inline bool
105+
v36_active(const std::map<uint64_t, uint288>& weights, uint64_t v36_version = 36)
106+
{
107+
uint288 total(0);
108+
for (const auto& [v, w] : weights) total += w;
109+
if (total == uint288(0)) return false;
110+
auto it = weights.find(v36_version);
111+
uint288 w36 = (it == weights.end()) ? uint288(0) : it->second;
112+
return w36 * uint288(100) >= total * uint288(95);
113+
}
114+
115+
// Window helper mirroring Share.check: negotiation looks at the [9/10 .. 10/10]
116+
// tail of the CHAIN_LENGTH ancestry behind `prev_hash`. Returns nullopt when
117+
// fewer than `chain_length` ancestors exist ("switch without enough history").
118+
struct Window { uint256 start_hash; uint64_t dist; };
119+
inline std::optional<Window>
120+
negotiation_window(ShareChain& chain, const uint256& prev_hash, uint64_t chain_length)
121+
{
122+
if (chain.get_height(prev_hash) < static_cast<int64_t>(chain_length))
123+
return std::nullopt;
124+
const uint64_t back = (chain_length * 9) / 10;
125+
const uint64_t dist = chain_length / 10;
126+
uint256 start = chain.get_nth_parent_key(prev_hash, static_cast<int32_t>(back));
127+
return Window{start, dist};
128+
}
129+
130+
} // namespace dash::version_negotiation

test/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,20 @@ if (BUILD_TESTING AND GTest_FOUND)
213213
target_link_libraries(test_dash_block_replay PRIVATE c2pool_payout c2pool_hashrate c2pool_merged_mining) # OBJECT-lib SCC direct-naming (#22/#39): core stratum/web_server TUs
214214
gtest_add_tests(test_dash_block_replay "" AUTO)
215215

216+
# DASH V36 conformance -- merkle-root-equality precondition (S6 slice).
217+
# share_check.hpp (check_merkle_link) + coinbase_builder.hpp
218+
# (merkle_branches_raw) are header-only over the dash_x11 + core link set;
219+
# no ltc/pool SCC dependency. Mirrors the test_dash_header_chain link set.
220+
add_executable(test_dash_conformance test_dash_conformance.cpp)
221+
target_link_libraries(test_dash_conformance PRIVATE
222+
GTest::gtest_main GTest::gtest
223+
dash_x11 core
224+
nlohmann_json::nlohmann_json
225+
${Boost_LIBRARIES}
226+
)
227+
target_link_libraries(test_dash_conformance PRIVATE c2pool_payout c2pool_hashrate c2pool_merged_mining) # OBJECT-lib SCC direct-naming (#22/#39): core stratum/web_server TUs
228+
gtest_add_tests(test_dash_conformance "" AUTO)
229+
216230
add_executable(test_compact_blocks test_compact_blocks.cpp)
217231
target_link_libraries(test_compact_blocks PRIVATE
218232
GTest::gtest_main GTest::gtest

0 commit comments

Comments
 (0)