Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
96 changes: 96 additions & 0 deletions RELEASE_v9.26.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down Expand Up @@ -110,6 +111,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<int>::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;
Expand Down
4 changes: 4 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 23808000; // v9.26.2 activation (~7 days for miner upgrade): reject reactivated Groestl / unknown algos
consensus.ReserveAlgoBitsHeight = 8547840; // d2c03966aeef35f739b222c8332b68df2676204d49c390b3a2544b967c46163f

// DigiByte-specific difficulty adjustment parameters
Expand Down Expand Up @@ -178,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");
Expand Down Expand Up @@ -217,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<unsigned char>(1,30);
base58Prefixes[SCRIPT_ADDRESS_OLD] = std::vector<unsigned char>(1,5);
Expand Down Expand Up @@ -294,6 +303,18 @@ 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
"digiscope.me:12024", // DigiScope / JohnnyLawDGB
"oracle.dgbmaxi.com:12024", // digibyte-maxi / Ycagel
};

// 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)
Expand Down Expand Up @@ -511,6 +532,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
Expand Down Expand Up @@ -963,6 +988,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{};
Expand Down Expand Up @@ -1024,6 +1053,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
Comment thread
gto90 marked this conversation as resolved.
consensus.MinBIP9WarningHeight = 0;
consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
// Set initial targets for all algorithms (easy difficulty for regtest)
Expand Down Expand Up @@ -1093,6 +1123,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{};
Expand Down
3 changes: 3 additions & 0 deletions src/kernel/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& DNSSeeds() const { return vSeeds; }
/** Return public mainnet peers operators can use to bootstrap DigiDollar oracle P2P traffic */
const std::vector<std::string>& OracleSeedPeers() const { return vOracleSeedPeers; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP() const { return bech32_hrp; }
const std::vector<uint8_t>& FixedSeeds() const { return vFixedSeeds; }
Expand Down Expand Up @@ -200,6 +202,7 @@ class CChainParams
uint64_t m_assumed_blockchain_size;
uint64_t m_assumed_chain_state_size;
std::vector<std::string> vSeeds;
std::vector<std::string> vOracleSeedPeers;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp;
ChainType m_chain_type;
Expand Down
1 change: 1 addition & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/rpc/digidollar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)"},
Expand Down Expand Up @@ -1203,6 +1208,12 @@ static RPCHelpMan getdigidollardeploymentinfo()
result.pushKV("oracle_consensus_required", consensusParams.nOracleConsensusRequired);
result.pushKV("oracle_total_slots", static_cast<int>(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
Expand Down
28 changes: 28 additions & 0 deletions src/test/oracle_config_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <test/util/setup_common.h>
#include <util/chaintype.h>

#include <algorithm>

BOOST_FIXTURE_TEST_SUITE(oracle_config_tests, BasicTestingSetup)

/**
Expand Down Expand Up @@ -386,6 +388,32 @@ 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<std::string> expected_peers{
"oracle1.digibyte.io:12024",
"digihash.digibyte.io:12024",
"oracleseed.digibyte.link:12024",
"digiscope.me:12024",
"oracle.dgbmaxi.com: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)
// ============================================================================
Expand Down
Loading
Loading