Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7d9c3a5
script: DD opcodes are BIP342 OP_SUCCESSx until SCRIPT_VERIFY_DIGIDOL…
JaredTate Apr 29, 2026
424645e
digidollar: canonical P2TR check, remove skipOracleValidation bypass …
JaredTate Apr 29, 2026
1747ed4
validation: move DD mempool check after input caching, re-validate on…
JaredTate Apr 29, 2026
e229ae6
oracle: reject duplicate oracle outputs in coinbase, harden bundle pa…
JaredTate Apr 29, 2026
326a83d
miner: recalculate merkle root after oracle bundle is injected into c…
JaredTate Apr 29, 2026
9e144e6
digidollar/rpc: gate getmockoracleprice behind DigiDollar activation …
JaredTate Apr 29, 2026
dffc1b1
digidollar: return explicit error when transfer fee inputs are underf…
JaredTate Apr 29, 2026
13f6d20
digidollar/health: remove MAX_DIGIDOLLAR cap from supply overflow pro…
JaredTate Apr 29, 2026
569da79
test: update unit tests for script activation, canonical P2TR, and or…
JaredTate Apr 29, 2026
8d1813f
test/functional: add ancestor-reorg transfer test, update activation …
JaredTate Apr 29, 2026
30bab84
docs: update REPO_MAP supply cap description, add Red Hornet continua…
JaredTate Apr 29, 2026
844fba8
docs: add MVP_PLAN.md — DigiDollar mainnet V1 TDD wave execution plan
JaredTate Apr 29, 2026
5cefdcd
release: bump version to v9.26.0-rc34, update wallet branding image
JaredTate Apr 29, 2026
00bfd60
test: update DigiDollar unit expectations
JaredTate Apr 29, 2026
b33606f
test/fuzz: Phase 2A harnesses failed seeded runs — fix init
JaredTate Apr 29, 2026
109c34b
test: coinstatsindex restart lost IPv4 RPC bind — pin bind
JaredTate Apr 29, 2026
713eb5b
Add red oracle P0.5 tests
JaredTate Apr 30, 2026
d845023
Add DigiDollar burn tier address tests
JaredTate Apr 30, 2026
98085b1
Add Wave 1 DigiDollar red tests
JaredTate Apr 30, 2026
d8d0583
fix(rpc/wallet): RC33 battery cleanup — clearer errors + correct resp…
JohnnyLawDGB May 3, 2026
f89b700
fix(rpc/wallet): three deeper RC33 battery defects
JohnnyLawDGB May 5, 2026
7f4d358
test: align unit expectations with RC33 battery cleanup behavior
JohnnyLawDGB May 10, 2026
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
667 changes: 667 additions & 0 deletions MVP_PLAN.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions REPO_MAP_DIGIDOLLAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This is the granular file index for all DigiDollar and Oracle source code. Read
## DigiDollar Core

### src/digidollar/digidollar.h
- `MAX_DIGIDOLLAR` → static constant: 21 billion DGB equivalent in cents (21B × 100), hard cap on total DD supply
- `MAX_DIGIDOLLAR` → static constant: per-output serialization bound (21B DGB equivalent in cents); DigiDollar has no global supply cap total DD in circulation is theoretically unlimited, constrained only by available DGB collateral and the per-block minting rate
- `CDigiDollarOutput` (class) → represents a DigiDollar UTXO with Taproot-based redemption paths
- `CDigiDollarOutput()` → default constructor, zeroes amount/locktime, nulls collateral ID
- `CDigiDollarOutput(nDDAmountIn, collateralIdIn, nLockTimeIn)` → parameterized constructor linking DD to specific collateral
Expand All @@ -49,7 +49,7 @@ This is the granular file index for all DigiDollar and Oracle source code. Read
### src/digidollar/health.h
- `DigiDollar::SystemMetrics` (struct) → aggregates all system-wide DD health data: supply, collateral, per-tier breakdown, DCA/ERR/volatility status, oracle status
- `TierMetrics` (nested struct) → per-tier stats: lockDays, ddMinted, dgbLocked, positions count, healthRatio
- `DigiDollar::AlertThresholds` (struct) → static constexpr thresholds for alerts: MAX_DD_SUPPLY (100M), MIN_HEALTH_RATIO (120%), CRITICAL (110%), MIN_ORACLES (5), MAX_VOLATILITY (30%), STALE_ORACLE_BLOCKS (100)
- `DigiDollar::AlertThresholds` (struct) → static constexpr monitoring alert thresholds: ALERT_DD_SUPPLY (monitoring trigger at 100M, not a supply cap), MIN_HEALTH_RATIO (120%), CRITICAL (110%), MIN_ORACLES (5), MAX_VOLATILITY (30%), STALE_ORACLE_BLOCKS (100)
- `DigiDollar::SystemHealthMonitor` (class) → real-time system health tracking and alerting
- `GetSystemMetrics()` → returns current SystemMetrics after updating tiers/protection/oracle status
- `GetTierBreakdown()` → returns per-tier metrics vector with health ratios per lock period
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ AC_PREREQ([2.69])
define(_CLIENT_VERSION_MAJOR, 9)
define(_CLIENT_VERSION_MINOR, 26)
define(_CLIENT_VERSION_BUILD, 0)
define(_CLIENT_VERSION_RC, 33)
define(_CLIENT_VERSION_RC, 34)
define(_CLIENT_VERSION_IS_RELEASE, false)
define(_COPYRIGHT_YEAR, 2026)
define(_COPYRIGHT_HOLDERS,[The %s developers])
Expand Down
771 changes: 771 additions & 0 deletions reports/red_hornet_ledger.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,22 @@ DIGIBYTE_TESTS =\
test/base64_tests.cpp \
test/digidollar_activation_tests.cpp \
test/digidollar_address_tests.cpp \
test/digidollar_burn_enforcement_tests.cpp \
test/digidollar_consensus_tests.cpp \
test/digidollar_dca_tests.cpp \
test/digidollar_err_attack_tests.cpp \
test/digidollar_rh32_collateral_dca_tests.cpp \
test/digidollar_rh33_mempool_relay_tests.cpp \
test/digidollar_rh34_multiblock_state_tests.cpp \
test/digidollar_err_tests.cpp \
test/digidollar_health_dca_tests.cpp \
test/digidollar_health_tests.cpp \
test/digidollar_t2_05_tests.cpp \
test/digidollar_opcodes_tests.cpp \
test/digidollar_script_attacks_tests.cpp \
test/digidollar_oracle_tests.cpp \
test/digidollar_oracle_musig2_tests.cpp \
test/digidollar_oracle_roster_tests.cpp \
test/digidollar_hot_path_logging_tests.cpp \
test/rh50_oracle_keyset_alignment_tests.cpp \
test/rh51_checkphase3_v1_split_tests.cpp \
Expand Down Expand Up @@ -191,6 +195,7 @@ DIGIBYTE_TESTS =\
test/digidollar_restore_tests.cpp \
test/digidollar_timelock_tests.cpp \
test/digidollar_lock_height_tests.cpp \
test/digidollar_locktier_tests.cpp \
test/bech32_tests.cpp \
test/bip32_tests.cpp \
test/blockchain_tests.cpp \
Expand Down Expand Up @@ -306,7 +311,9 @@ DIGIBYTE_TESTS += \
wallet/test/group_outputs_tests.cpp \
wallet/test/digidollar_persistence_wallet_tests.cpp \
wallet/test/digidollar_wallet_security_tests.cpp \
wallet/test/rh59_coincontrol_dd_lock_bypass_tests.cpp
wallet/test/rh59_coincontrol_dd_lock_bypass_tests.cpp \
test/digidollar_rpc_unit_tests.cpp \
test/digidollar_wallet_hd_tests.cpp

FUZZ_SUITE_LD_COMMON +=\
$(SQLITE_LIBS) \
Expand Down
41 changes: 15 additions & 26 deletions src/digidollar/health.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,24 +446,22 @@ void SystemHealthMonitor::ScanUTXOSet(CCoinsView* view, CCoinsView* validation_v
// Called from ConnectBlock/DisconnectBlock under cs_main.
// ============================================================================

static void AddClampedAmount(CAmount& total, CAmount amount, const char* label)
{
if (amount <= 0) return;
if (total <= std::numeric_limits<CAmount>::max() - amount) {
total += amount;
return;
}
LogPrintf("Health: WARNING - %s would overflow, capping\n", label);
total = std::numeric_limits<CAmount>::max();
}

void SystemHealthMonitor::OnMintConnected(CAmount ddAmount, CAmount dgbCollateral)
{
std::lock_guard<std::mutex> lock(s_metricsMutex); // RH-44: thread safety
// SECURITY [RH-11]: Prevent supply overflow — cap at MAX_DIGIDOLLAR
if (ddAmount > 0 && s_currentMetrics.totalDDSupply <= MAX_DIGIDOLLAR - ddAmount) {
s_currentMetrics.totalDDSupply += ddAmount;
} else if (ddAmount > 0) {
LogPrintf("Health: WARNING - totalDDSupply would exceed MAX_DIGIDOLLAR, capping at %s\n",
FormatMoney(MAX_DIGIDOLLAR));
s_currentMetrics.totalDDSupply = MAX_DIGIDOLLAR;
}
// Cap collateral at MAX_MONEY to prevent int64_t overflow
if (dgbCollateral > 0 && s_currentMetrics.totalCollateral <= std::numeric_limits<CAmount>::max() - dgbCollateral) {
s_currentMetrics.totalCollateral += dgbCollateral;
} else if (dgbCollateral > 0) {
LogPrintf("Health: WARNING - totalCollateral would overflow, capping\n");
s_currentMetrics.totalCollateral = std::numeric_limits<CAmount>::max();
}
AddClampedAmount(s_currentMetrics.totalDDSupply, ddAmount, "totalDDSupply");
AddClampedAmount(s_currentMetrics.totalCollateral, dgbCollateral, "totalCollateral");
LogPrint(BCLog::DIGIDOLLAR, "Health: Mint connected - DD +%s, Collateral +%s (totals: DD=%s, Collateral=%s)\n",
FormatMoney(ddAmount), FormatMoney(dgbCollateral),
FormatMoney(s_currentMetrics.totalDDSupply), FormatMoney(s_currentMetrics.totalCollateral));
Expand Down Expand Up @@ -492,17 +490,8 @@ void SystemHealthMonitor::OnMintDisconnected(CAmount ddAmount, CAmount dgbCollat
void SystemHealthMonitor::OnRedeemDisconnected(CAmount ddAmount, CAmount dgbCollateral)
{
std::lock_guard<std::mutex> lock(s_metricsMutex); // RH-44: thread safety
// SECURITY [RH-11]: Same overflow protection as OnMintConnected
if (ddAmount > 0 && s_currentMetrics.totalDDSupply <= MAX_DIGIDOLLAR - ddAmount) {
s_currentMetrics.totalDDSupply += ddAmount;
} else if (ddAmount > 0) {
s_currentMetrics.totalDDSupply = MAX_DIGIDOLLAR;
}
if (dgbCollateral > 0 && s_currentMetrics.totalCollateral <= std::numeric_limits<CAmount>::max() - dgbCollateral) {
s_currentMetrics.totalCollateral += dgbCollateral;
} else if (dgbCollateral > 0) {
s_currentMetrics.totalCollateral = std::numeric_limits<CAmount>::max();
}
AddClampedAmount(s_currentMetrics.totalDDSupply, ddAmount, "totalDDSupply");
AddClampedAmount(s_currentMetrics.totalCollateral, dgbCollateral, "totalCollateral");
LogPrint(BCLog::DIGIDOLLAR, "Health: Redeem disconnected - DD +%s, Collateral +%s (totals: DD=%s, Collateral=%s)\n",
FormatMoney(ddAmount), FormatMoney(dgbCollateral),
FormatMoney(s_currentMetrics.totalDDSupply), FormatMoney(s_currentMetrics.totalCollateral));
Expand Down
60 changes: 39 additions & 21 deletions src/digidollar/txbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,16 @@ CAmount TransferTxBuilder::GetDDFromUTXO(const COutPoint& outpoint) const {
return 5000; // Default: $50.00 in cents for testing
}

bool TransferTxBuilder::ValidateTransferParams(const TxBuilderTransferParams& params) const {
bool TransferTxBuilder::ValidateTransferParams(const TxBuilderTransferParams& params, std::string* reason) const {
auto fail = [&](const std::string& msg) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - %s\n", msg);
if (reason) *reason = msg;
return false;
};

// Must have recipients
if (params.recipients.empty()) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - No recipients\n");
return false;
return fail("No recipients specified");
}

// Validate all recipient addresses and amounts
Expand All @@ -471,46 +476,45 @@ bool TransferTxBuilder::ValidateTransferParams(const TxBuilderTransferParams& pa
for (const auto& [address, amount] : params.recipients) {
// Validate address format
if (!ValidateDDAddress(address)) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - Invalid address: %s\n", address);
return false;
return fail(strprintf("Invalid DD address: %s", address));
}

// Validate amount ranges
if (amount <= 0) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - Non-positive amount: %d\n", amount);
return false; // No zero or negative amounts
return fail(strprintf("Recipient amount must be positive (got %lld cents)",
static_cast<long long>(amount)));
}

if (amount < minOutput) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - Below dust threshold: %d < %d\n", amount, minOutput);
return false; // Below dust threshold
return fail(strprintf("Recipient amount %lld cents is below DD minimum output (%lld cents / $%.2f)",
static_cast<long long>(amount),
static_cast<long long>(minOutput),
static_cast<double>(minOutput) / 100.0));
}

// Check maximum single transfer limit ($100,000)
if (amount > 10000000) { // $100,000.00 in cents
LogPrintf("DigiDollar: ValidateTransferParams FAILED - Exceeds max transfer: %d > 10000000\n", amount);
return false;
return fail(strprintf("Recipient amount %lld cents exceeds maximum single-transfer limit ($100,000 / 10000000 cents)",
static_cast<long long>(amount)));
}

totalOutput += amount;
}

// Must have DD inputs
if (params.ddUtxos.empty()) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - No DD UTXOs\n");
return false;
return fail("No DD UTXOs provided to fund the transfer");
}

// Validate key
if (!params.spenderKey.IsValid()) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - Invalid spender key\n");
return false;
return fail("Spender key is missing or invalid");
}

// Validate fee rate
if (!ValidateFeeRate(params.feeRate)) {
LogPrintf("DigiDollar: ValidateTransferParams FAILED - Invalid fee rate: %d\n", params.feeRate);
return false;
return fail(strprintf("Invalid DGB fee rate: %lld sat/kB",
static_cast<long long>(params.feeRate)));
}

LogPrintf("DigiDollar: ValidateTransferParams PASSED\n");
Expand Down Expand Up @@ -588,10 +592,13 @@ bool TransferTxBuilder::SelectDDInputs(const std::vector<CTxOut>& available, CAm
TxBuilderResult TransferTxBuilder::BuildTransferTransaction(const TxBuilderTransferParams& params) {
TxBuilderResult result;

// Validate parameters
if (!ValidateTransferParams(params)) {
result.error = "Invalid transfer parameters";
return result;
// Validate parameters; capture specific reason instead of generic catch-all.
{
std::string reason;
if (!ValidateTransferParams(params, &reason)) {
result.error = reason.empty() ? "Invalid transfer parameters" : reason;
return result;
}
}

// Calculate totals and check DD conservation
Expand Down Expand Up @@ -719,6 +726,17 @@ TxBuilderResult TransferTxBuilder::BuildTransferTransaction(const TxBuilderTrans
LogPrintf("DigiDollar: Fee calculation - calculated: %d sats, minimum: %d sats, actual: %d sats\n",
calculatedFee, MIN_DD_FEE, actualFee);

if (totalFeeIn <= 0) {
result.error = "Insufficient DGB fee input: no fee inputs selected";
return result;
}

if (totalFeeIn < actualFee) {
result.error = strprintf("Insufficient DGB fee input: selected=%d sats, required=%d sats",
totalFeeIn, actualFee);
return result;
}

// Add DGB change output if needed (after we know actual fee)
if (totalFeeIn > 0) {
CAmount dgbChange = totalFeeIn - actualFee;
Expand Down
3 changes: 2 additions & 1 deletion src/digidollar/txbuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ class TransferTxBuilder : public TxBuilder {
public:
using TxBuilder::TxBuilder;
TxBuilderResult BuildTransferTransaction(const TxBuilderTransferParams& params);
bool ValidateTransferParams(const TxBuilderTransferParams& params) const;
// If reason is non-null and validation fails, populates with a specific error message.
bool ValidateTransferParams(const TxBuilderTransferParams& params, std::string* reason = nullptr) const;
CAmount CalculateTotalDDInput(const std::vector<CTxOut>& inputs,
const std::vector<CAmount>& amounts) const;
CScript CreateDDTransferScript(const CPubKey& recipient, CAmount amount) const;
Expand Down
Loading