From 758227c8d537cf3b65087c34398ba0a286ee81eb Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 09:31:34 -0600 Subject: [PATCH 01/14] Update validation.cpp --- src/validation.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/validation.cpp b/src/validation.cpp index ac4f088e03..96a07608fb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4798,6 +4798,14 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Check proof of work const Consensus::Params& consensusParams = chainman.GetConsensus(); int algo = block.GetAlgo(); + + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_DIGIDOLLAR)) { + if (algo == ALGO_UNKNOWN || !IsAlgoActive(pindexPrev, consensusParams, algo)) { + return state.Invalid(BlockValidationResult::BLOCK_INVALID_ALGO, "bad-algo", + "block uses a deactivated mining algorithm"); + } + } + unsigned int nBitsExpected = GetNextWorkRequired(pindexPrev, &block, consensusParams, algo); // Debug logging for early blocks From 23414ce4a0a680e474a5ed53471b5ec4cd1517f2 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:48:46 -0600 Subject: [PATCH 02/14] Add feature_digibyte_groestl_deactivation.py --- .../feature_digibyte_groestl_deactivation.py | 82 +++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 83 insertions(+) create mode 100755 test/functional/feature_digibyte_groestl_deactivation.py diff --git a/test/functional/feature_digibyte_groestl_deactivation.py b/test/functional/feature_digibyte_groestl_deactivation.py new file mode 100755 index 0000000000..330e396fa8 --- /dev/null +++ b/test/functional/feature_digibyte_groestl_deactivation.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 The DigiByte Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Validator-level enforcement of active mining algorithms. + +A block mined with a deactivated algorithm (Groestl, retired at the Odocrypt +fork) must be rejected at block acceptance, not merely refused by the local +miner. This exercises the submit/validate path with externally-crafted blocks, +which is the path the local miner does not cover. + +On regtest DEPLOYMENT_DIGIDOLLAR is ALWAYS_ACTIVE and Odocrypt activates at +height 600, so above height 600 Groestl is deactivated and crafted Groestl +blocks must be rejected; below it Groestl is still active and accepted. +""" + +from test_framework.test_framework import DigiByteTestFramework +from test_framework.blocktools import create_block, create_coinbase +from test_framework.util import assert_equal + +ODOCRYPT_HEIGHT = 600 # Groestl deactivation / Odocrypt activation (regtest) + +BLOCK_VERSION_GROESTL = 0x20000402 +BLOCK_VERSION_SHA256D = 0x20000202 + + +class GroestlDeactivationTest(DigiByteTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [["-easypow=1"]] + self.mining_address = "dgbrt1qtmp74ayg7p24uslctssvjm06q5phz4yrgndnyh" + + def skip_test_if_missing_module(self): + pass + + def craft(self, node, version): + tip = node.getbestblockhash() + hdr = node.getblockheader(tip) + height = node.getblockcount() + 1 + return create_block(int(tip, 16), create_coinbase(height, nValue=0), + hdr["time"] + 1, version=version) + + def submit_until_pow_ok(self, node, block): + # -easypow keeps the target at powLimit, so ~half of nonces satisfy any + # algorithm's PoW. The node computes the algo-specific hash, so no Python + # implementation of Groestl is needed: grind until PoW passes, then return + # whatever the validator says next. + for nonce in range(2000): + block.nNonce = nonce + res = node.submitblock(block.serialize().hex()) + if res != "high-hash": + return res + raise AssertionError("could not satisfy easypow PoW") + + def run_test(self): + node = self.nodes[0] + + self.log.info("Below Odocrypt: Groestl is active, crafted Groestl block is accepted") + self.generatetoaddress(node, 150, self.mining_address, 1000000, "scrypt") + assert_equal(node.getblockcount(), 150) + accepted = self.craft(node, BLOCK_VERSION_GROESTL) + assert_equal(self.submit_until_pow_ok(node, accepted), None) + assert_equal(node.getblockcount(), 151) + + self.log.info("Above Odocrypt: Groestl is deactivated, crafted Groestl block is rejected") + self.generatetoaddress(node, ODOCRYPT_HEIGHT + 5 - node.getblockcount(), + self.mining_address, 1000000, "scrypt") + assert node.getblockcount() > ODOCRYPT_HEIGHT + tip = node.getbestblockhash() + rejected = self.craft(node, BLOCK_VERSION_GROESTL) + assert_equal(self.submit_until_pow_ok(node, rejected), "bad-algo") + assert_equal(node.getbestblockhash(), tip) + + self.log.info("Control: an active algorithm via the same submit path is accepted") + control = self.craft(node, BLOCK_VERSION_SHA256D) + assert_equal(self.submit_until_pow_ok(node, control), None) + assert node.getbestblockhash() != tip + + +if __name__ == '__main__': + GroestlDeactivationTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 7968ce2f2d..c9e0359dfc 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -104,6 +104,7 @@ # DigiByte: Multi-Algorithm Mining Tests 'feature_digibyte_multialgo_mining.py', + 'feature_digibyte_groestl_deactivation.py', # vv Tests less than 5m vv 'feature_fee_estimation.py', From 7987f44743301dd697d41163dd250090e5a581a4 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:20:22 -0600 Subject: [PATCH 03/14] Update pow_tests.cpp and mining_basic.py --- src/test/pow_tests.cpp | 42 +++++++++++++++++++++++++++++++++ test/functional/mining_basic.py | 7 ++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 4003c28331..32333a6a86 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -11,6 +11,9 @@ #include #include // For GetVersionForAlgo +#include // For IsAlgoActive + +#include BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup) @@ -294,4 +297,43 @@ BOOST_AUTO_TEST_CASE(digibyte_difficulty_versions_test) } } +BOOST_AUTO_TEST_CASE(digibyte_isalgoactive_matrix) +{ + // The set of mining algorithms accepted at a given height. Groestl is part of + // the original MultiAlgo set but is deactivated at the Odocrypt fork; the + // consensus rule rejecting deactivated algorithms relies on this predicate. + const auto chainParams = CreateChainParams(*m_node.args, ChainType::REGTEST); + const auto& params = chainParams->GetConsensus(); + + // regtest fork heights: MultiAlgo at 100, Odocrypt/Groestl-swap at 600. + auto is_active = [&](int prev_height, int algo) { + CBlockIndex prev; + prev.nHeight = prev_height; + return IsAlgoActive(&prev, params, algo); + }; + + // Before the MultiAlgo fork: Scrypt only. + BOOST_CHECK(is_active(50, ALGO_SCRYPT)); + for (int algo : {ALGO_SHA256D, ALGO_GROESTL, ALGO_SKEIN, ALGO_QUBIT, ALGO_ODO}) { + BOOST_CHECK(!is_active(50, algo)); + } + + // MultiAlgo era (100..599): five algorithms including Groestl, excluding Odocrypt. + for (int algo : {ALGO_SHA256D, ALGO_SCRYPT, ALGO_GROESTL, ALGO_SKEIN, ALGO_QUBIT}) { + BOOST_CHECK(is_active(150, algo)); + } + BOOST_CHECK(!is_active(150, ALGO_ODO)); + BOOST_CHECK(is_active(599, ALGO_GROESTL)); // last block before the swap + + // Odocrypt era (>=600): Groestl is deactivated, Odocrypt is active. + BOOST_CHECK(!is_active(600, ALGO_GROESTL)); + BOOST_CHECK(!is_active(700, ALGO_GROESTL)); + for (int algo : {ALGO_SHA256D, ALGO_SCRYPT, ALGO_SKEIN, ALGO_QUBIT, ALGO_ODO}) { + BOOST_CHECK(is_active(700, algo)); + } + + // An unknown algorithm is never active. + BOOST_CHECK(!is_active(700, ALGO_UNKNOWN)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 12ddcbb471..a3cc37fb31 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -37,6 +37,9 @@ VERSIONBITS_TOP_BITS = 0x20000000 VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 27 VERSIONBITS_DEPLOYMENT_TAPROOT_BIT = 0x02 # DigiByte taproot bit +# DigiByte encodes the mining algorithm in version bits 8-11, so a block version +# must select a valid algorithm. Use a version whose algo nibble is SHA256D. +BLOCKVERSION_OVERRIDE = 0x20000202 DEFAULT_BLOCK_MIN_TX_FEE = 100000 # default `-blockmintxfee` setting [sat/kvB] - DigiByte uses higher fees @@ -73,9 +76,9 @@ def mine_chain(self): self.log.info('test blockversion') mock_time = TIME_GENESIS_BLOCK + block_count * 15 - self.restart_node(0, extra_args=[f'-mocktime={mock_time}', '-blockversion=1337', '-dandelion=0']) + self.restart_node(0, extra_args=[f'-mocktime={mock_time}', f'-blockversion={BLOCKVERSION_OVERRIDE}', '-dandelion=0']) self.connect_nodes(0, 1) - assert_equal(1337, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version']) + assert_equal(BLOCKVERSION_OVERRIDE, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version']) self.restart_node(0, extra_args=[f'-mocktime={mock_time}', '-dandelion=0']) self.connect_nodes(0, 1) assert_equal(VERSIONBITS_TOP_BITS + (1 << VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT) + VERSIONBITS_DEPLOYMENT_TAPROOT_BIT, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version']) From 81ab1603a0c81ce76b3e37854bdf4f9314b5f1f1 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:49:56 -0600 Subject: [PATCH 04/14] Update params.h, chainparams.cpp, validation.cpp --- src/consensus/params.h | 4 ++++ src/kernel/chainparams.cpp | 2 ++ src/validation.cpp | 2 +- test/functional/feature_digibyte_groestl_deactivation.py | 7 ++++--- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/consensus/params.h b/src/consensus/params.h index 76e3c2398f..d3e9cc6927 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -110,6 +110,10 @@ struct Params { /** * Block height at which Odocrypt got activated */ int OdoHeight; + /** + * Block height at which blocks using a deactivated mining algorithm + * (e.g. the retired Groestl) or an unknown algorithm are rejected. */ + int nGroestlDeactivationHeight{std::numeric_limits::max()}; /** Don't warn about unknown BIP 9 activations below this height. * This prevents us from warning about the CSV and segwit activations. */ int MinBIP9WarningHeight; diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index d13fef1a97..9a7c4caf6d 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -124,6 +124,7 @@ class CMainParams : public CChainParams { consensus.workComputationChangeTarget = 1430000; // Block 1,430,000 DigiSpeed Hard Fork consensus.algoSwapChangeTarget = 9100000; // Block 9,100,000 Odo PoW Hard Fork consensus.OdoHeight = 9112320; // 906b712a7b1f54f10b0faf86111e832ddb7b8ce86ac71a4edd2c61e5ccfe9428 + consensus.nGroestlDeactivationHeight = 23766000; // v9.26.2 flag day: reject reactivated Groestl / unknown algos consensus.ReserveAlgoBitsHeight = 8547840; // d2c03966aeef35f739b222c8332b68df2676204d49c390b3a2544b967c46163f // DigiByte-specific difficulty adjustment parameters @@ -1024,6 +1025,7 @@ class CRegTestParams : public CChainParams consensus.SegwitHeight = 0; // Always active unless overridden consensus.ReserveAlgoBitsHeight = 0; // DigiByte ReserveAlgoBits consensus.OdoHeight = 600; // DigiByte Odocrypt height + consensus.nGroestlDeactivationHeight = 0; // enforce deactivated-algo rejection from genesis on regtest consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // Set initial targets for all algorithms (easy difficulty for regtest) diff --git a/src/validation.cpp b/src/validation.cpp index 96a07608fb..b010c4e334 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4799,7 +4799,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio const Consensus::Params& consensusParams = chainman.GetConsensus(); int algo = block.GetAlgo(); - if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_DIGIDOLLAR)) { + if (nHeight >= consensusParams.nGroestlDeactivationHeight) { if (algo == ALGO_UNKNOWN || !IsAlgoActive(pindexPrev, consensusParams, algo)) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_ALGO, "bad-algo", "block uses a deactivated mining algorithm"); diff --git a/test/functional/feature_digibyte_groestl_deactivation.py b/test/functional/feature_digibyte_groestl_deactivation.py index 330e396fa8..cf288f5fee 100755 --- a/test/functional/feature_digibyte_groestl_deactivation.py +++ b/test/functional/feature_digibyte_groestl_deactivation.py @@ -9,9 +9,10 @@ miner. This exercises the submit/validate path with externally-crafted blocks, which is the path the local miner does not cover. -On regtest DEPLOYMENT_DIGIDOLLAR is ALWAYS_ACTIVE and Odocrypt activates at -height 600, so above height 600 Groestl is deactivated and crafted Groestl -blocks must be rejected; below it Groestl is still active and accepted. +On regtest the deactivated-algorithm rule is enforced from genesis +(nGroestlDeactivationHeight = 0) and Odocrypt activates at height 600, so above +height 600 Groestl is deactivated and crafted Groestl blocks must be rejected; +below it Groestl is still active and accepted. """ from test_framework.test_framework import DigiByteTestFramework From 888e3219e072d5f202205031559e03f7b824d2b6 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:06:36 -0600 Subject: [PATCH 05/14] Add ALGOLOCK BIP9 deployment + reindex-safe algo enforcement --- src/consensus/params.h | 1 + src/deploymentinfo.cpp | 4 +++ src/kernel/chainparams.cpp | 21 +++++++++++++- src/rpc/blockchain.cpp | 1 + src/validation.cpp | 21 +++++++++++++- .../feature_digibyte_groestl_deactivation.py | 29 ++++++++++++++++++- 6 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/consensus/params.h b/src/consensus/params.h index d3e9cc6927..3b324d8b33 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -41,6 +41,7 @@ enum DeploymentPos : uint16_t { DEPLOYMENT_TESTDUMMY, DEPLOYMENT_TAPROOT, // Deployment of Schnorr/Taproot (BIPs 340-342) DEPLOYMENT_DIGIDOLLAR, // Deployment of DigiDollar stablecoin features + DEPLOYMENT_ALGOLOCK, // Reject blocks mined with a deactivated (e.g. retired Groestl) or unknown algorithm // NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp MAX_VERSION_BITS_DEPLOYMENTS }; diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 8fd0cea5c8..c74efc47a6 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -24,6 +24,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.name =*/ "digidollar", /*.gbt_force =*/ true, }, + { + /*.name =*/ "algolock", + /*.gbt_force =*/ true, + }, }; std::string DeploymentName(Consensus::BuriedDeployment dep) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 9a7c4caf6d..a89d3533ef 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -124,7 +124,7 @@ class CMainParams : public CChainParams { consensus.workComputationChangeTarget = 1430000; // Block 1,430,000 DigiSpeed Hard Fork consensus.algoSwapChangeTarget = 9100000; // Block 9,100,000 Odo PoW Hard Fork consensus.OdoHeight = 9112320; // 906b712a7b1f54f10b0faf86111e832ddb7b8ce86ac71a4edd2c61e5ccfe9428 - consensus.nGroestlDeactivationHeight = 23766000; // v9.26.2 flag day: reject reactivated Groestl / unknown algos + consensus.nGroestlDeactivationHeight = 23782000; // v9.26.2 backstop (~3 days): reject reactivated Groestl / unknown algos consensus.ReserveAlgoBitsHeight = 8547840; // d2c03966aeef35f739b222c8332b68df2676204d49c390b3a2544b967c46163f // DigiByte-specific difficulty adjustment parameters @@ -179,6 +179,13 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nStartTime = 1780272000; // June 1, 2026 consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nTimeout = 1811808000; // June 1, 2027 consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].min_activation_height = 23627520; // Aligned to confirmation window (586 * 40320) + // ALGOLOCK: reject reactivated Groestl / unknown-algo blocks. BIP9 bit 0, signalling + // starts immediately so miners can lock in early; nGroestlDeactivationHeight is the + // mandatory unconditional backstop so activation cannot be vetoed or stalled. + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].bit = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nStartTime = 1782691200; // June 29, 2026 (signalling open) + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nTimeout = 1814227200; // June 29, 2027 + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].min_activation_height = 0; // may activate as soon as it locks in // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000001cae290ed41eb2efd4804c"); @@ -512,6 +519,10 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nStartTime = 1780156800; // testnet26 genesis timestamp consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nTimeout = 1830297600; // Jan 1, 2028 consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].min_activation_height = 600; // Activation delayed until block 600 + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].bit = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].min_activation_height = 0; consensus.nMinimumChainWork = uint256S("0x00"); consensus.defaultAssumeValid = uint256S("0x00"); //1079274 @@ -964,6 +975,10 @@ class SigNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].min_activation_height = 0; // No activation delay for ALWAYS_ACTIVE + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].bit = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].min_activation_height = 0; // message start is defined as the first 4 bytes of the sha256d of the block script HashWriter h{}; @@ -1095,6 +1110,10 @@ class CRegTestParams : public CChainParams consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR].min_activation_height = 0; // No activation delay for ALWAYS_ACTIVE + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].bit = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_ALGOLOCK].min_activation_height = 0; consensus.nMinimumChainWork = uint256{}; consensus.defaultAssumeValid = uint256{}; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c7e8c737bf..503ef99764 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1396,6 +1396,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY); SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT); SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DIGIDOLLAR); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ALGOLOCK); return softforks; } } // anon namespace diff --git a/src/validation.cpp b/src/validation.cpp index b010c4e334..92da1023dd 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2849,6 +2849,24 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); } + // Reject blocks mined with a deactivated (e.g. retired Groestl) or unknown + // algorithm once ALGOLOCK is in force. This mirrors the ContextualCheckBlockHeader + // gate and is repeated here, at connection time, precisely to close the upgrade + // gap described above: -reindex-chainstate skips the header check, and a node that + // accepted a post-activation deactivated-algo block while running older software + // must not carry it forward on replay/reconnection. Blocks below the activation + // point are grandfathered identically to the header-path check. + if (pindex->pprev) { + const Consensus::Params& algo_consensus = params.GetConsensus(); + const int algo = block.GetAlgo(); + if ((DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_ALGOLOCK) || + pindex->nHeight >= algo_consensus.nGroestlDeactivationHeight) && + (algo == ALGO_UNKNOWN || !IsAlgoActive(pindex->pprev, algo_consensus, algo))) { + return state.Invalid(BlockValidationResult::BLOCK_INVALID_ALGO, "bad-algo", + "block uses a deactivated mining algorithm"); + } + } + if (!OracleDataValidator::ValidateBlockOracleData(block, pindex->pprev, params.GetConsensus(), state)) { return error("%s: OracleDataValidator::ValidateBlockOracleData: %s", __func__, state.ToString()); } @@ -4799,7 +4817,8 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio const Consensus::Params& consensusParams = chainman.GetConsensus(); int algo = block.GetAlgo(); - if (nHeight >= consensusParams.nGroestlDeactivationHeight) { + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_ALGOLOCK) || + nHeight >= consensusParams.nGroestlDeactivationHeight) { if (algo == ALGO_UNKNOWN || !IsAlgoActive(pindexPrev, consensusParams, algo)) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_ALGO, "bad-algo", "block uses a deactivated mining algorithm"); diff --git a/test/functional/feature_digibyte_groestl_deactivation.py b/test/functional/feature_digibyte_groestl_deactivation.py index cf288f5fee..d239296a78 100755 --- a/test/functional/feature_digibyte_groestl_deactivation.py +++ b/test/functional/feature_digibyte_groestl_deactivation.py @@ -12,7 +12,13 @@ On regtest the deactivated-algorithm rule is enforced from genesis (nGroestlDeactivationHeight = 0) and Odocrypt activates at height 600, so above height 600 Groestl is deactivated and crafted Groestl blocks must be rejected; -below it Groestl is still active and accepted. +below it Groestl is still active and accepted (grandfathered). + +The rule is enforced both in ContextualCheckBlockHeader (header acceptance) and in +ConnectBlock (connection/replay). The latter is exercised by reindexing: a block +that was valid when mined (a pre-Odocrypt Groestl block) must survive -reindex and +-reindex-chainstate, proving the connection-time guard grandfathers by height +rather than rejecting the algorithm outright. """ from test_framework.test_framework import DigiByteTestFramework @@ -78,6 +84,27 @@ def run_test(self): assert_equal(self.submit_until_pow_ok(node, control), None) assert node.getbestblockhash() != tip + self.log.info("Signalling flag: the algolock BIP9 deployment is exposed for tracking") + deployments = node.getdeploymentinfo()["deployments"] + assert "algolock" in deployments, "algolock deployment must be visible via getdeploymentinfo" + + self.log.info("Reindex-safety: the pre-Odocrypt Groestl block is grandfathered through replay") + # The chain contains a Groestl block at height 151 that was valid when mined + # (pre-Odocrypt). The ConnectBlock guard must grandfather it by height, so a + # full replay must reproduce the exact same tip and height. + final_tip = node.getbestblockhash() + final_height = node.getblockcount() + # Confirm height 151 really is the grandfathered Groestl block. + assert_equal(node.getblockheader(node.getblockhash(151))["version"], BLOCK_VERSION_GROESTL) + + self.restart_node(0, extra_args=["-easypow=1", "-reindex=1"]) + assert_equal(node.getbestblockhash(), final_tip) + assert_equal(node.getblockcount(), final_height) + + self.restart_node(0, extra_args=["-easypow=1", "-reindex-chainstate=1"]) + assert_equal(node.getbestblockhash(), final_tip) + assert_equal(node.getblockcount(), final_height) + if __name__ == '__main__': GroestlDeactivationTest().main() From 1ef91d999f042d9b3007ded573d4e4e1015e2725 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:14:41 -0600 Subject: [PATCH 06/14] Update rpc_blockchain.py expected deployments for algolock --- test/functional/rpc_blockchain.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index c9e2d354ee..0e8f25b055 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -250,6 +250,19 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, }, 'height': 0, 'active': True + }, + 'algolock': { + 'type': 'bip9', + 'bip9': { + 'start_time': -1, + 'timeout': 9223372036854775807, + 'min_activation_height': 0, + 'status': 'active', + 'status_next': 'active', + 'since': 0, + }, + 'height': 0, + 'active': True } } }) From 7838000ef4c466b167c78cd6d325b978dfebc712 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:49:11 -0600 Subject: [PATCH 07/14] Set Groestl deactivation activation height to ~7 day window --- src/kernel/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index a89d3533ef..b0cf6486a8 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -124,7 +124,7 @@ class CMainParams : public CChainParams { consensus.workComputationChangeTarget = 1430000; // Block 1,430,000 DigiSpeed Hard Fork consensus.algoSwapChangeTarget = 9100000; // Block 9,100,000 Odo PoW Hard Fork consensus.OdoHeight = 9112320; // 906b712a7b1f54f10b0faf86111e832ddb7b8ce86ac71a4edd2c61e5ccfe9428 - consensus.nGroestlDeactivationHeight = 23782000; // v9.26.2 backstop (~3 days): reject reactivated Groestl / unknown algos + consensus.nGroestlDeactivationHeight = 23808000; // v9.26.2 activation (~7 days for miner upgrade): reject reactivated Groestl / unknown algos consensus.ReserveAlgoBitsHeight = 8547840; // d2c03966aeef35f739b222c8332b68df2676204d49c390b3a2544b967c46163f // DigiByte-specific difficulty adjustment parameters From 1ac80345f80553af739b06b87911722bb78988c3 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 19:42:26 -0600 Subject: [PATCH 08/14] Pin exact Groestl deactivation boundary (600 accepted, 601 rejected) in test --- .../feature_digibyte_groestl_deactivation.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/functional/feature_digibyte_groestl_deactivation.py b/test/functional/feature_digibyte_groestl_deactivation.py index d239296a78..c63e9afe67 100755 --- a/test/functional/feature_digibyte_groestl_deactivation.py +++ b/test/functional/feature_digibyte_groestl_deactivation.py @@ -70,6 +70,25 @@ def run_test(self): assert_equal(self.submit_until_pow_ok(node, accepted), None) assert_equal(node.getblockcount(), 151) + # Pin the exact boundary. IsAlgoActive() keys off pindexPrev->nHeight, so the + # Groestl block AT the Odocrypt height (600, prev 599 < 600) is still active and + # accepted; rejection begins one block later, at height 601 (prev 600). Asserting + # only "rejected somewhere above 600" would let an off-by-one in the height math + # slip through, so we exercise both edges. + self.log.info("At Odocrypt height: Groestl block 600 is still accepted (prev 599 < 600)") + self.generatetoaddress(node, ODOCRYPT_HEIGHT - 1 - node.getblockcount(), + self.mining_address, 1000000, "scrypt") + assert_equal(node.getblockcount(), ODOCRYPT_HEIGHT - 1) # tip 599 + boundary = self.craft(node, BLOCK_VERSION_GROESTL) + assert_equal(self.submit_until_pow_ok(node, boundary), None) # block 600 accepted + assert_equal(node.getblockcount(), ODOCRYPT_HEIGHT) + + self.log.info("One past Odocrypt height: the first rejected Groestl block is 601") + boundary_tip = node.getbestblockhash() + rejected601 = self.craft(node, BLOCK_VERSION_GROESTL) + assert_equal(self.submit_until_pow_ok(node, rejected601), "bad-algo") # block 601 rejected + assert_equal(node.getbestblockhash(), boundary_tip) + self.log.info("Above Odocrypt: Groestl is deactivated, crafted Groestl block is rejected") self.generatetoaddress(node, ODOCRYPT_HEIGHT + 5 - node.getblockcount(), self.mining_address, 1000000, "scrypt") From 1589e1fd85e61062684805a90eacbd8863fb1fbe Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 19:49:07 -0600 Subject: [PATCH 09/14] chainparams: add Bastian oracle seed peer --- src/kernel/chainparams.cpp | 10 ++++++++++ src/kernel/chainparams.h | 3 +++ src/rpc/digidollar.cpp | 11 +++++++++++ src/test/oracle_config_tests.cpp | 26 ++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index b0cf6486a8..8375a0a677 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -302,6 +302,16 @@ class CMainParams : public CChainParams { // Initialize DigiDollar Oracle Nodes (35 active slots) InitializeOracleNodes(); + // Public mainnet peers that oracle operators can use to bootstrap + // DigiDollar P2P connectivity. These are operational seed peers, not + // consensus trust anchors; oracle security comes from the hardcoded + // public keys and 7-of-35 MuSig2 validation. + vOracleSeedPeers = { + "oracle1.digibyte.io:12024", // DigiByte.io / Jared Tate + "digihash.digibyte.io:12024", // DigiHash Mining Pool + "oracleseed.digibyte.link:12024", // Bastian Driessen + }; + // Mainnet-specific oracle and activation settings consensus.nDDOracleEpochBlocks = 40; // Rotate oracle signing epochs every 40 blocks (~10 minutes) consensus.nDDOracleUpdateInterval = 4; // Update price every 4 blocks (~1 minute) diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index 7e252da845..8db0c9b3a5 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -121,6 +121,8 @@ class CChainParams ChainType GetChainType() const { return m_chain_type; } /** Return the list of hostnames to look up for DNS seeds */ const std::vector& DNSSeeds() const { return vSeeds; } + /** Return public mainnet peers operators can use to bootstrap DigiDollar oracle P2P traffic */ + const std::vector& OracleSeedPeers() const { return vOracleSeedPeers; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP() const { return bech32_hrp; } const std::vector& FixedSeeds() const { return vFixedSeeds; } @@ -200,6 +202,7 @@ class CChainParams uint64_t m_assumed_blockchain_size; uint64_t m_assumed_chain_state_size; std::vector vSeeds; + std::vector vOracleSeedPeers; std::vector base58Prefixes[MAX_BASE58_TYPES]; std::string bech32_hrp; ChainType m_chain_type; diff --git a/src/rpc/digidollar.cpp b/src/rpc/digidollar.cpp index 11c1c1bc21..5bc910f023 100644 --- a/src/rpc/digidollar.cpp +++ b/src/rpc/digidollar.cpp @@ -1116,6 +1116,11 @@ static RPCHelpMan getdigidollardeploymentinfo() {RPCResult::Type::NUM, "oracle_pubkey_count", "Number of consensus oracle public keys configured for MuSig2 (nOraclePubkeyCount)"}, {RPCResult::Type::NUM, "oracle_consensus_required", "MuSig2 quorum size required to satisfy a v0x03 bundle (nOracleConsensusRequired)"}, {RPCResult::Type::NUM, "oracle_total_slots", "Total oracle slots configured in chainparams (vOracleNodes.size); oracle_id values must be below oracle_pubkey_count to vote"}, + {RPCResult::Type::ARR, "oracle_seed_peers", "Public mainnet peers operators can use to bootstrap DigiDollar oracle P2P connectivity", + { + {RPCResult::Type::STR, "peer", "Host:port seed peer"}, + } + }, {RPCResult::Type::OBJ, "musig2_session", "Current MuSig2 signing session status (operator diagnostic)", { {RPCResult::Type::NUM, "epoch", "Current epoch number (block_height / nDDOracleEpochBlocks)"}, @@ -1203,6 +1208,12 @@ static RPCHelpMan getdigidollardeploymentinfo() result.pushKV("oracle_consensus_required", consensusParams.nOracleConsensusRequired); result.pushKV("oracle_total_slots", static_cast(Params().GetOracleNodes().size())); + UniValue oracle_seed_peers(UniValue::VARR); + for (const auto& peer : Params().OracleSeedPeers()) { + oracle_seed_peers.push_back(peer); + } + result.pushKV("oracle_seed_peers", oracle_seed_peers); + // Wave 10 (Agent C): expose the orchestrator's MuSig2 session // status for the current epoch so operators can diagnose stuck // sessions (timeout / sub-quorum / liveness drift). The state diff --git a/src/test/oracle_config_tests.cpp b/src/test/oracle_config_tests.cpp index 085df8167c..cfffd2facd 100644 --- a/src/test/oracle_config_tests.cpp +++ b/src/test/oracle_config_tests.cpp @@ -14,6 +14,8 @@ #include #include +#include + BOOST_FIXTURE_TEST_SUITE(oracle_config_tests, BasicTestingSetup) /** @@ -386,6 +388,30 @@ BOOST_AUTO_TEST_CASE(mainnet_oracle_endpoints_use_mainnet_p2p_port) SelectParams(ChainType::MAIN); } +BOOST_AUTO_TEST_CASE(mainnet_oracle_seed_peers_include_launch_bootstrap_nodes) +{ + SelectParams(ChainType::MAIN); + const auto& seed_peers = Params().OracleSeedPeers(); + + BOOST_REQUIRE(!seed_peers.empty()); + + const std::vector expected_peers{ + "oracle1.digibyte.io:12024", + "digihash.digibyte.io:12024", + "oracleseed.digibyte.link:12024", + }; + + for (const auto& expected_peer : expected_peers) { + BOOST_CHECK_MESSAGE(std::find(seed_peers.begin(), seed_peers.end(), expected_peer) != seed_peers.end(), + "Missing mainnet oracle seed peer " << expected_peer); + } + + for (const auto& peer : seed_peers) { + BOOST_CHECK_MESSAGE(peer.size() >= 6 && peer.rfind(":12024") == peer.size() - 6, + "Mainnet oracle seed peer must use P2P port 12024, got " << peer); + } +} + // ============================================================================ // PART 4: Network-Specific Configuration Tests (BONUS) // ============================================================================ From 0b63a5084a0d986e555a4b4dc9f95670093fc63e Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 19:53:40 -0600 Subject: [PATCH 10/14] chainparams: add JohnnyLawDGB oracle seed peer --- src/kernel/chainparams.cpp | 1 + src/test/oracle_config_tests.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 8375a0a677..a4582b2d8c 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -310,6 +310,7 @@ class CMainParams : public CChainParams { "oracle1.digibyte.io:12024", // DigiByte.io / Jared Tate "digihash.digibyte.io:12024", // DigiHash Mining Pool "oracleseed.digibyte.link:12024", // Bastian Driessen + "digiscope.me:12024", // DigiScope / JohnnyLawDGB }; // Mainnet-specific oracle and activation settings diff --git a/src/test/oracle_config_tests.cpp b/src/test/oracle_config_tests.cpp index cfffd2facd..348609784f 100644 --- a/src/test/oracle_config_tests.cpp +++ b/src/test/oracle_config_tests.cpp @@ -399,6 +399,7 @@ BOOST_AUTO_TEST_CASE(mainnet_oracle_seed_peers_include_launch_bootstrap_nodes) "oracle1.digibyte.io:12024", "digihash.digibyte.io:12024", "oracleseed.digibyte.link:12024", + "digiscope.me:12024", }; for (const auto& expected_peer : expected_peers) { From 89d27aa168d638b236091ca9c1c14c3aeb75434d Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 19:59:42 -0600 Subject: [PATCH 11/14] chainparams: add digibyte-maxi oracle seed peer --- src/kernel/chainparams.cpp | 1 + src/test/oracle_config_tests.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index a4582b2d8c..1b7b81f785 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -311,6 +311,7 @@ class CMainParams : public CChainParams { "digihash.digibyte.io:12024", // DigiHash Mining Pool "oracleseed.digibyte.link:12024", // Bastian Driessen "digiscope.me:12024", // DigiScope / JohnnyLawDGB + "oracle.dgbmaxi.com:12024", // digibyte-maxi / Ycagel }; // Mainnet-specific oracle and activation settings diff --git a/src/test/oracle_config_tests.cpp b/src/test/oracle_config_tests.cpp index 348609784f..e4edd7c039 100644 --- a/src/test/oracle_config_tests.cpp +++ b/src/test/oracle_config_tests.cpp @@ -400,6 +400,7 @@ BOOST_AUTO_TEST_CASE(mainnet_oracle_seed_peers_include_launch_bootstrap_nodes) "digihash.digibyte.io:12024", "oracleseed.digibyte.link:12024", "digiscope.me:12024", + "oracle.dgbmaxi.com:12024", }; for (const auto& expected_peer : expected_peers) { From a6a15fe6695b0d777fbe8e5da884c45e1d37df28 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:01:21 -0600 Subject: [PATCH 12/14] RELEASE_v9.26.2: add Groestl consensus fix, mandatory upgrade, v7.17.3 reindex, miner 7-day window --- RELEASE_v9.26.2.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/RELEASE_v9.26.2.md b/RELEASE_v9.26.2.md index c09028bb6c..39f9a2fa48 100644 --- a/RELEASE_v9.26.2.md +++ b/RELEASE_v9.26.2.md @@ -35,6 +35,102 @@ Mainnet is normal mainnet: Do not use the old pre-mainnet test ports, temporary data directories, or modified activation settings with this release. +## Critical Consensus Fix: Retired-Algorithm Enforcement (Groestl) + +Separately from DigiDollar, v9.26.2 ships an urgent consensus security fix. +**Every node on the DigiByte network must upgrade to v9.26.2.** This is not +optional and is independent of whether you use DigiDollar. + +### What Happened + +DigiByte secures the chain with five mining algorithms (SHA256d, Scrypt, Skein, +Qubit, Odocrypt). A sixth algorithm, Groestl, was retired in 2019 at the Odocrypt +fork. The rule that rejected retired-algorithm blocks existed in the v7.17.3-era +software, but it was accidentally dropped during the v8 Bitcoin Core rebase in +2021/2022. The function that knew Groestl was retired still existed and was used +for difficulty and display, but the single line that enforced it when accepting a +block was gone. Because nobody mined Groestl, its difficulty fell to the lowest +possible setting and the gap stayed dormant for years. + +Starting 2026-06-28 16:40:05 UTC, at block 23,751,096, an actor — using AI to +analyze DigiByte's consensus rules — reactivated Groestl and began mining a sixth +algorithm at floor difficulty. + +No coins were stolen and no confirmed transactions were reversed. There is no +evidence of a successful 51% attack, though the cheap mining is consistent with an +attempt: across every competing branch the network has seen, none ever accumulated +more total work than the honest chain, and the deepest reorganization of the +active chain was 4 blocks. The damage was instability and a network split. Block +times dropped from the 15-second target to roughly 12-13 seconds, and the network +divided, because v8/v9 software accepted the Groestl blocks while old v7.17.3 +software rejected them and forked onto a separate, slower chain. + +### The Fix + +v9.26.2 restores retired-algorithm enforcement, the right way: + +- Blocks using a retired (Groestl) or unknown mining algorithm are rejected. +- The rule is enforced in both header validation and block connection, so that + `-reindex` and `-reindex-chainstate` also enforce it. A node cannot carry a + post-activation retired-algorithm block forward after upgrading. +- Existing Groestl blocks already buried in the chain are grandfathered (kept). + No history is rewritten and no transactions are reversed. + +### Mainnet Activation Parameters (Groestl Deactivation) + +| Field | Value | +| --- | --- | +| Deployment name | `algolock` | +| Versionbit (readiness signal) | `0` | +| Signaling start | June 29, 2026 | +| Activation height (backstop) | `23,808,000` (~7 days after release) | +| Enforcement | reject retired (Groestl) and unknown algorithms | +| Existing Groestl blocks | grandfathered below the activation height | + +The `algolock` versionbit is a readiness signal that lets miners advertise they +have upgraded; you can watch adoption with `getdeploymentinfo`. The rule activates +at block 23,808,000 regardless of signaling, giving the network roughly seven days +to upgrade before retired-algorithm blocks are rejected. + +### All Nodes Must Upgrade + +Every full node, miner, pool, exchange, explorer, wallet, and service must upgrade +to v9.26.2. The majority of mining power is currently on the v8 line. That is fine, +but all of it must move to v9.26.2 so that the upgraded chain is the strongest +chain at activation and the network converges back onto a single chain. + +### v7.17.3 And Older Nodes Must Reindex On Upgrade + +Nodes running the ~7-year-old v7.17.3 line reject every post-Odocrypt Groestl +block, including the grandfathered blocks the healed chain keeps. They therefore +cannot follow the unified chain on their own. Operators on v7.17.3 (or older) must: + +1. Upgrade to v9.26.2. +2. Reindex or resync (start with `-reindex`, or perform a fresh sync) so the node + accepts the existing chain history and reorganizes onto the correct chain. + +Upgrading also provides Taproot, DigiDollar, and every other improvement added +since v7.17.3. + +### Miners And Pools: The 7-Day Window + +- Upgrade to v9.26.2 within the ~7-day window before block 23,808,000. +- Use the block version returned by DigiByte Core and do not strip the `algolock` + readiness signal. +- Once the majority of mining power is upgraded, retired-algorithm blocks are + orphaned, the attack ends, and the network heals into one chain. +- Optional, during the window only: miners who wish to actively push back can point + hash power at Groestl themselves. This captures block rewards that would + otherwise go to the attacker and drives Groestl difficulty up, removing the + cheap-mining advantage. This is secondary to upgrading and keeps block times fast + while it lasts; after activation, Groestl is rejected regardless. + +Forensic detail (as of this release, mining was still ongoing): the Groestl blocks +account for roughly 14-15% of all blocks since onset and are paid to two attacker +payout addresses — `dgb1qy5epvfs535a96tygn945a3a85lauh3ddu9v63y` (coinbase tag +`SORG`) and `D8S5JWaCrpFsryGG1c9AzWKhbS7e7VZ4r8`. Exchanges and custodians should +flag deposits originating from these addresses until the network has healed. + ## Who Should Upgrade All full nodes, miners, mining pools, exchanges, explorers, wallets, service From a3c75a3ad2e4e2b0ee3dd4747756e22f7930e8a6 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:02:07 -0600 Subject: [PATCH 13/14] chainparams: add Mbah Jambon mainnet DNS seed --- src/kernel/chainparams.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 1b7b81f785..333fb0520e 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -225,6 +225,7 @@ class CMainParams : public CChainParams { vSeeds.emplace_back("seed.diginode.tools"); // Olly Stedall @saltedlolly vSeeds.emplace_back("seed.digibyte.link"); // Bastian Driessen @bastiandriessen vSeeds.emplace_back("seed.aroundtheblock.app"); // Mark McNiel @JohnnyLawDGB + vSeeds.emplace_back("seed.tuyul.cc"); // Mbah Jambon @mbah_jambon base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,30); base58Prefixes[SCRIPT_ADDRESS_OLD] = std::vector(1,5); From 38f4e58b6f239583a14b07d98d053ddbd03b72f6 Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:23:02 -0600 Subject: [PATCH 14/14] Address review: drop unused include, clarify bad-algo message includes unknown algos --- src/test/pow_tests.cpp | 2 -- src/validation.cpp | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 32333a6a86..5ff932b9e9 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -13,8 +13,6 @@ #include // For GetVersionForAlgo #include // For IsAlgoActive -#include - BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup) /* Test calculation of next difficulty target with no constraints applying */ diff --git a/src/validation.cpp b/src/validation.cpp index 92da1023dd..bdef1863c0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2863,7 +2863,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, pindex->nHeight >= algo_consensus.nGroestlDeactivationHeight) && (algo == ALGO_UNKNOWN || !IsAlgoActive(pindex->pprev, algo_consensus, algo))) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_ALGO, "bad-algo", - "block uses a deactivated mining algorithm"); + "block uses a deactivated or unknown mining algorithm"); } } @@ -4821,7 +4821,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio nHeight >= consensusParams.nGroestlDeactivationHeight) { if (algo == ALGO_UNKNOWN || !IsAlgoActive(pindexPrev, consensusParams, algo)) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_ALGO, "bad-algo", - "block uses a deactivated mining algorithm"); + "block uses a deactivated or unknown mining algorithm"); } }