Skip to content

Commit b886efb

Browse files
authored
dgb: dev-only flag to relax algo softfork readiness gate on isolated testnet (G3b slice-ii(b)) (#514)
1 parent 2655f74 commit b886efb

8 files changed

Lines changed: 195 additions & 18 deletions

File tree

src/c2pool/main_dgb.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ void print_banner(const char* argv0, const core::CoinParams& p)
101101
<< " [--version] [--help] [--selftest] [--run] [--stratum [H:]P]\n"
102102
<< " [--coin-daemon H:P] [--coin-magic HEX] [--regtest]\n"
103103
<< " [--regtest-force-won-share] [--no-p2p-relay]\n"
104-
<< " [--redistribute SPEC] [--sharechain-port P]\n\n"
104+
<< " [--redistribute SPEC] [--sharechain-port P]\n"
105+
<< " [--dev-relax-algo-softforks]\n\n"
105106
<< "Status: pool/sharechain pillars live (Phase B); run-loop up\n"
106107
<< " (--run: io_context + sharechain peer + Stratum standup).\n"
107108
<< " --stratum [HOST:]PORT binds a miner-facing TCP listener\n"
@@ -112,6 +113,11 @@ void print_banner(const char* argv0, const core::CoinParams& p)
112113
<< " --regtest AND --coin-daemon) drives ONE forced won\n"
113114
<< " share through the live dual-path broadcaster;\n"
114115
<< " --no-p2p-relay isolates the submitblock arm.\n"
116+
<< " --dev-relax-algo-softforks (DEV-ONLY; OFF by default)\n"
117+
<< " relaxes the algo-softfork readiness gate (odo/reservealgo/\n"
118+
<< " nversionbips) so the node can boot against an isolated\n"
119+
<< " tuned testnet. NEVER relaxes mainnet (gate stays absolute\n"
120+
<< " when chain==main) and does NOT touch taproot.\n"
115121
<< "Network: " << network_summary(p) << "\n";
116122
}
117123

@@ -165,7 +171,8 @@ int run_node(const core::CoinParams& params, bool testnet,
165171
const std::string& rpc_conf_path,
166172
bool regtest = false, bool force_won_share = false,
167173
bool no_p2p_relay = false,
168-
const std::string& redistribute_spec = "")
174+
const std::string& redistribute_spec = "",
175+
bool dev_relax_algo_softforks = false)
169176
{
170177
io::io_context ioc;
171178

@@ -186,6 +193,11 @@ int run_node(const core::CoinParams& params, bool testnet,
186193
dgb::Config config(net_subdir);
187194
config.pool()->m_prefix = ParseHexBytes(dgb::PoolConfig::DEFAULT_PREFIX_HEX);
188195
config.m_testnet = testnet;
196+
// Wire the dev-only algo-softfork relax from argv into the coin config so
197+
// NodeRPC::check() can see it in --run (Config::init() is skipped above, so
198+
// the YAML path never sets it here). OFF by default; the only flip is the
199+
// explicit --dev-relax-algo-softforks marker. Mainnet stays fail-closed.
200+
config.coin()->m_dev_relax_algo_softforks = dev_relax_algo_softforks;
189201
// DEFAULT_BOOTSTRAP_HOSTS is empty until DGB p2pool nodes come online, so
190202
// there are no outbound seeds to dial this slice — the node binds its
191203
// listener and waits for inbound sharechain peers.
@@ -1118,6 +1130,12 @@ int main(int argc, char** argv)
11181130
bool regtest = false; // --regtest (dev/regtest marker; gates the forced seam)
11191131
bool force_won_share = false; // --regtest-force-won-share (regtest-ONLY won-block live seam)
11201132
bool no_p2p_relay = false; // --no-p2p-relay (suppress ARM A to isolate ARM B)
1133+
// --dev-relax-algo-softforks (DEV-ONLY, OFF by default): the ONLY argv path
1134+
// that flips CoinConfig::m_dev_relax_algo_softforks. Relaxes the algo-softfork
1135+
// readiness gate for an isolated tuned-testnet boot; NodeRPC::check() keeps it
1136+
// FAIL-CLOSED on mainnet (ignored when chain==main) and it does NOT extend to
1137+
// taproot (a real consensus floor; operator-gated, not relaxed here).
1138+
bool dev_relax_algo_softforks = false; // --dev-relax-algo-softforks (dev boot aid)
11211139
for (int i = 1; i < argc; ++i) {
11221140
if (std::strcmp(argv[i], "--version") == 0) {
11231141
std::cout << "c2pool-dgb " << C2POOL_VERSION << "\n";
@@ -1156,6 +1174,7 @@ int main(int argc, char** argv)
11561174
if (std::strcmp(argv[i], "--regtest-force-won-share") == 0)
11571175
force_won_share = true; // gated below: regtest-ONLY
11581176
if (std::strcmp(argv[i], "--no-p2p-relay") == 0) no_p2p_relay = true;
1177+
if (std::strcmp(argv[i], "--dev-relax-algo-softforks") == 0) dev_relax_algo_softforks = true;
11591178
if (std::strcmp(argv[i], "--redistribute") == 0 && i + 1 < argc) {
11601179
redistribute_spec = argv[++i]; // pplns|fee|boost|donate or hybrid "boost:70,donate:20"
11611180
}
@@ -1182,7 +1201,7 @@ int main(int argc, char** argv)
11821201
coin_daemon, coin_magic, coin_genesis,
11831202
rpc_endpoint, rpc_conf_path,
11841203
regtest, force_won_share, no_p2p_relay,
1185-
redistribute_spec);
1204+
redistribute_spec, dev_relax_algo_softforks);
11861205

11871206
// --selftest, or a bare invocation: drive the live score path so the
11881207
// binary exercises real consensus code, then exit cleanly.

src/impl/dgb/coin/node.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ class Node : public dgb::interfaces::Node
102102
// node.hpp; testnet drives the chain-identity genesis probe in
103103
// NodeRPC::check(). connect() (transport bring-up) is driven by the
104104
// pool-layer seam once an RPC endpoint is configured.
105-
m_rpc = std::make_unique<NodeRPC>(m_context, this, m_config->m_testnet);
105+
m_rpc = std::make_unique<NodeRPC>(m_context, this, m_config->m_testnet,
106+
m_config->m_dev_relax_algo_softforks);
106107
}
107108

108109
NodeRPC* rpc() { return m_rpc.get(); }

src/impl/dgb/coin/rpc.cpp

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ namespace coin
1717
// check() probes getblockheader(dgb_genesis_hash(IS_TESTNET)) to confirm the
1818
// daemon is a real digibyted on the selected network.
1919

20-
NodeRPC::NodeRPC(io::io_context* context, dgb::interfaces::Node* coin, bool testnet)
21-
: m_context(context), IS_TESTNET(testnet), m_resolver(*context), m_stream(*context),
20+
NodeRPC::NodeRPC(io::io_context* context, dgb::interfaces::Node* coin, bool testnet,
21+
bool dev_relax_algo_softforks)
22+
: m_context(context), IS_TESTNET(testnet),
23+
DEV_RELAX_ALGO_SOFTFORKS(dev_relax_algo_softforks),
24+
m_resolver(*context), m_stream(*context),
2225
m_client(*this, RPC_VER), m_coin(coin)
2326
{
2427
}
@@ -281,18 +284,26 @@ bool NodeRPC::check()
281284
}
282285
}
283286

284-
// Regtest does not deploy the DGB-specific algo softforks (reservealgo, odo)
285-
// nor nversionbips — those are mainnet/testnet deployments, so a regtest
286-
// digibyted legitimately signals only csv/segwit/taproot. Gating startup on
287-
// deployments the connected chain cannot carry would make the regtest
288-
// won-block path (and CI against regtest) impossible to start, with no
289-
// consensus benefit. Relax ONLY on regtest; mainnet/testnet keep the full
290-
// SSOT requirement set. Non-consensus startup readiness gate only.
291-
std::set<std::string> required = dgb::PoolConfig::SOFTFORKS_REQUIRED;
292-
if (blockchaininfo.value("chain", std::string{}) == "regtest")
287+
// Effective required-softfork set. Regtest always drops the DGB-specific
288+
// algo deployments (reservealgo/odo) and nversionbips — a regtest daemon
289+
// cannot carry them, and gating on them would make the regtest won-block
290+
// path unstartable. The EXPLICIT, off-by-default DEV_RELAX_ALGO_SOFTFORKS
291+
// flag extends that same relaxation to an isolated tuned testnet for
292+
// development boot only; mainnet is never relaxed. Policy lives in the pure,
293+
// unit-tested SSOT below. Non-consensus startup readiness gate only.
294+
const std::string chain = blockchaininfo.value("chain", std::string{});
295+
std::set<std::string> required = dgb::coin::compute_required_softforks(
296+
dgb::PoolConfig::SOFTFORKS_REQUIRED, chain, DEV_RELAX_ALGO_SOFTFORKS);
297+
298+
// Loud, un-suppressable signal when the dev flag actually drops algo
299+
// deployments on a non-regtest chain. A node booted this way is NOT a valid
300+
// V36 crossing-soak — development only.
301+
if (DEV_RELAX_ALGO_SOFTFORKS && chain != "regtest" && chain != "main")
293302
{
294-
for (const char* algo_fork : {"reservealgo", "odo", "nversionbips"})
295-
required.erase(algo_fork);
303+
LOG_WARNING << "DEV-ONLY: dev_relax_algo_softforks is set — relaxing the "
304+
"DGB algo softfork gate (reservealgo/odo/nversionbips) on "
305+
"chain '" << chain << "'. This node is NOT a valid "
306+
"crossing-soak; development boot only.";
296307
}
297308

298309
std::vector<std::string> missing;

src/impl/dgb/coin/rpc.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ class NodeRPC : public jsonrpccxx::IClientConnector
5656
const jsonrpccxx::version RPC_VER = jsonrpccxx::version::v2;
5757

5858
const bool IS_TESTNET;
59+
60+
// Dev-only boot aid (off by default): when set, NodeRPC::check() relaxes the
61+
// DGB algo softfork gate on non-regtest, non-main chains. Never weakens the
62+
// gate on mainnet. See dgb::coin::compute_required_softforks.
63+
const bool DEV_RELAX_ALGO_SOFTFORKS;
5964
private:
6065
dgb::interfaces::Node* m_coin;
6166

@@ -77,7 +82,8 @@ class NodeRPC : public jsonrpccxx::IClientConnector
7782
nlohmann::json CallAPIMethod(const std::string& method, const jsonrpccxx::positional_parameter& params = {});
7883

7984
public:
80-
NodeRPC(io::io_context* context, dgb::interfaces::Node* coin, bool testnet);
85+
NodeRPC(io::io_context* context, dgb::interfaces::Node* coin, bool testnet,
86+
bool dev_relax_algo_softforks = false);
8187
~NodeRPC();
8288

8389
void connect(NetService address, std::string userpass);

src/impl/dgb/coin/softfork_check.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,52 @@ inline void collect_deployment_names(const nlohmann::json& getdeploymentinfo_res
6262
}
6363
}
6464

65+
/**
66+
* Algo deployments that a regtest digibyted legitimately never signals
67+
* (reservealgo, odo are DigiByte-unique) plus nversionbips. These are the only
68+
* names the readiness gate is ever permitted to drop from its required set.
69+
*/
70+
inline const std::set<std::string>& relaxable_algo_softforks()
71+
{
72+
static const std::set<std::string> kRelaxable = {
73+
"reservealgo", "odo", "nversionbips"};
74+
return kRelaxable;
75+
}
76+
77+
/**
78+
* Effective required-softfork set for NodeRPC::check(), given the connected
79+
* chain and an EXPLICIT, off-by-default developer relaxation flag.
80+
*
81+
* - regtest : always drops the relaxable algo deployments — a
82+
* regtest daemon cannot carry them and gating on them
83+
* would make the regtest won-block path unstartable.
84+
* - non-main, non-regtest (e.g. an isolated tuned testnet):
85+
* drops the relaxable deployments ONLY when
86+
* dev_relax_algo_softforks is explicitly set. This is a
87+
* development boot-aid; it is off by default, so a real
88+
* testnet crossing-soak (which never sets it) keeps the
89+
* full SSOT requirement set and still demands active
90+
* forks.
91+
* - main : NEVER relaxed under any flag value. The dev flag
92+
* cannot weaken the readiness gate on mainnet.
93+
*
94+
* Pure (no I/O, no consensus surface) so it is exhaustively unit-testable
95+
* without a live daemon.
96+
*/
97+
inline std::set<std::string> compute_required_softforks(
98+
const std::set<std::string>& base,
99+
const std::string& chain,
100+
bool dev_relax_algo_softforks)
101+
{
102+
std::set<std::string> required = base;
103+
// Hard floor: mainnet is never relaxed, regardless of the dev flag.
104+
if (chain == "main")
105+
return required;
106+
const bool relax = (chain == "regtest") || dev_relax_algo_softforks;
107+
if (relax)
108+
for (const auto& name : relaxable_algo_softforks())
109+
required.erase(name);
110+
return required;
111+
}
112+
65113
} // namespace dgb::coin

src/impl/dgb/config_coin.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ void CoinConfig::load()
3030

3131
PARSE_CONFIG(node, share_period, int);
3232
PARSE_CONFIG(node, testnet, bool);
33+
34+
// Dev-only boot aid (off by default). Optional key — absent in every normal
35+
// and production config, so it cannot be silently inherited by a real
36+
// crossing-soak. Never weakens the gate on mainnet (see
37+
// dgb::coin::compute_required_softforks). Deliberately NOT emitted by
38+
// get_default(): you must consciously add it to opt in.
39+
if (node["dev_relax_algo_softforks"])
40+
m_dev_relax_algo_softforks = node["dev_relax_algo_softforks"].as<bool>();
3341
}
3442

3543
} // namespace dgb

src/impl/dgb/config_coin.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ class CoinConfig : protected core::Fileconfig
200200
std::string m_symbol = "DGB";
201201
int m_share_period{};
202202
bool m_testnet{false};
203+
204+
// Dev-only boot aid — DO NOT set on any real network. When true, relaxes the
205+
// DGB algo softfork readiness gate (reservealgo/odo/nversionbips) on
206+
// non-regtest, non-main chains so c2pool-dgb can boot against an isolated
207+
// tuned testnet for development. Off by default and absent from the
208+
// auto-written default config, so a real crossing-soak cannot silently inherit
209+
// it; never weakens the gate on mainnet. See
210+
// dgb::coin::compute_required_softforks / NodeRPC::check().
211+
bool m_dev_relax_algo_softforks{false};
203212
};
204213

205214
} // namespace dgb

src/impl/dgb/test/softfork_check_test.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,78 @@ TEST(DgbDeploymentInfo, AccumulatesIntoExistingSet)
185185
collect_deployment_names(json::parse(R"({"deployments":{"segwit":{}}})"), out);
186186
EXPECT_EQ(out, (std::set<std::string>{"preexisting", "segwit"}));
187187
}
188+
189+
// ---------------------------------------------------------------------------
190+
// compute_required_softforks() — the readiness-gate relaxation policy SSOT.
191+
//
192+
// Pins the dev-only-flag contract (slice (b)): the EXPLICIT, off-by-default
193+
// dev_relax_algo_softforks flag may relax the DGB algo deployments
194+
// (reservealgo/odo/nversionbips) on an isolated dev net, but:
195+
// * a real testnet crossing-soak (flag OFF) keeps the FULL requirement set;
196+
// * mainnet is NEVER relaxed, regardless of the flag (un-inheritable safety);
197+
// * regtest is relaxed unconditionally (pre-existing behaviour);
198+
// * the non-relaxable forks (csv/segwit/taproot) always survive.
199+
// ---------------------------------------------------------------------------
200+
201+
using dgb::coin::compute_required_softforks;
202+
using dgb::coin::relaxable_algo_softforks;
203+
204+
namespace {
205+
// Mirrors dgb::PoolConfig::SOFTFORKS_REQUIRED (kept local so this guard links
206+
// no OBJECT lib). DGB_share_test.OracleSoftforksRequired pins the real SSOT.
207+
const std::set<std::string> kBaseRequired = {
208+
"nversionbips", "csv", "segwit", "reservealgo", "odo", "taproot"};
209+
const std::set<std::string> kRelaxed = {"csv", "segwit", "taproot"};
210+
} // namespace
211+
212+
TEST(DgbRequiredSoftforks, RelaxableSetIsExactlyThreeAlgoForks)
213+
{
214+
EXPECT_EQ(relaxable_algo_softforks(),
215+
(std::set<std::string>{"reservealgo", "odo", "nversionbips"}));
216+
}
217+
218+
TEST(DgbRequiredSoftforks, RegtestAlwaysRelaxesRegardlessOfFlag)
219+
{
220+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "regtest", false), kRelaxed);
221+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "regtest", true), kRelaxed);
222+
}
223+
224+
TEST(DgbRequiredSoftforks, TestnetFlagOffKeepsFullSet)
225+
{
226+
// The real-crossing-soak guard: an honest testnet node (flag off) still
227+
// demands every fork, exactly as before this slice.
228+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "test", false), kBaseRequired);
229+
}
230+
231+
TEST(DgbRequiredSoftforks, TestnetFlagOnRelaxesAlgoForks)
232+
{
233+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "test", true), kRelaxed);
234+
}
235+
236+
TEST(DgbRequiredSoftforks, MainnetNeverRelaxedEvenWithFlag)
237+
{
238+
// Un-inheritable safety: the dev flag cannot weaken the gate on mainnet.
239+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "main", true), kBaseRequired);
240+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "main", false), kBaseRequired);
241+
}
242+
243+
TEST(DgbRequiredSoftforks, NonRelaxableForksAlwaysSurvive)
244+
{
245+
for (bool flag : {false, true})
246+
for (const char* chain : {"main", "test", "regtest"})
247+
{
248+
auto req = compute_required_softforks(kBaseRequired, chain, flag);
249+
for (const char* keep : {"csv", "segwit", "taproot"})
250+
EXPECT_TRUE(req.count(keep))
251+
<< "dropped non-relaxable fork " << keep
252+
<< " on chain=" << chain << " flag=" << flag;
253+
}
254+
}
255+
256+
TEST(DgbRequiredSoftforks, UnknownChainFollowsFlag)
257+
{
258+
// Defensive: an unrecognised chain string is treated as non-main/non-regtest
259+
// — relaxed only when the explicit flag is set.
260+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "", false), kBaseRequired);
261+
EXPECT_EQ(compute_required_softforks(kBaseRequired, "", true), kRelaxed);
262+
}

0 commit comments

Comments
 (0)