From bf41c3cb1f366615116e3e2663f5fce433fe678f Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:18:49 -0600 Subject: [PATCH 01/71] version: bump to v9.26.6 and update wallet image --- configure.ac | 2 +- src/qt/res/icons/digibyte_wallet.png | Bin 18747 -> 18606 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 2632ff681d3..5aab6de54d8 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 9) define(_CLIENT_VERSION_MINOR, 26) -define(_CLIENT_VERSION_BUILD, 5) +define(_CLIENT_VERSION_BUILD, 6) define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_SUFFIX, []) define(_CLIENT_VERSION_IS_RELEASE, true) diff --git a/src/qt/res/icons/digibyte_wallet.png b/src/qt/res/icons/digibyte_wallet.png index e26e14db464eab5325110451328e13509fb175d4..98947dadb280f485630f8c3a074bb164fcee1ee8 100644 GIT binary patch literal 18606 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 X+9QO(MxNll00000NkvXXu0mjf(dX(L From 938f9a07d35fac50912e4b1c7ead7b631716f81e Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:21:28 -0600 Subject: [PATCH 02/71] DOCS: organize release documentation --- HOUSEKEEPING_TO-DOs.md | 493 ------------------ digidollar/HOUSEKEEPING_TO-DOs.md | 2 +- doc/README.md | 6 + .../release-notes/RELEASE-NOTES-8.26.2.md | 0 .../release-notes/RELEASE_v9.26.2.md | 0 .../release-notes/RELEASE_v9.26.5.md | 0 doc/release-notes/release-notes-9.26.4.md | 2 +- .../Z_DOC_VALIDATION_v9.26.2_PROMPT.md | 464 +++++++++++++++++ .../v9.26.4/V9.26.4_MAINNET_VALIDATION.md | 0 .../v9.26.4/V9.26.4_PRUNING_EXPLAINER.md | 4 +- .../v9.26.4/V9.26.4_PRUNING_PLAN.md | 4 +- .../v9.26.4/V9.26.4_PR_EXPLAINER.md | 8 +- .../v9.26.4/V9.26.4_TESTNET_PRUNE_SUMMARY.md | 4 +- .../v9.26.4/V9.26.4_TESTNET_PRUNE_TESTS.md | 4 +- reports/doc_validation_final_report.md | 83 +++ 15 files changed, 567 insertions(+), 507 deletions(-) delete mode 100644 HOUSEKEEPING_TO-DOs.md rename RELEASE-NOTES-8.26.2.md => doc/release-notes/RELEASE-NOTES-8.26.2.md (100%) rename RELEASE_v9.26.2.md => doc/release-notes/RELEASE_v9.26.2.md (100%) rename RELEASE_v9.26.5.md => doc/release-notes/RELEASE_v9.26.5.md (100%) create mode 100644 doc/v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md rename V9.26.4_MAINNET_VALIDATION.md => doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md (100%) rename V9.26.4_PRUNING_EXPLAINER.md => doc/v9.26.4/V9.26.4_PRUNING_EXPLAINER.md (97%) rename V9.26.4_PRUNING_PLAN.md => doc/v9.26.4/V9.26.4_PRUNING_PLAN.md (99%) rename V9.26.4_PR_EXPLAINER.md => doc/v9.26.4/V9.26.4_PR_EXPLAINER.md (97%) rename V9.26.4_TESTNET_PRUNE_SUMMARY.md => doc/v9.26.4/V9.26.4_TESTNET_PRUNE_SUMMARY.md (97%) rename V9.26.4_TESTNET_PRUNE_TESTS.md => doc/v9.26.4/V9.26.4_TESTNET_PRUNE_TESTS.md (98%) create mode 100644 reports/doc_validation_final_report.md diff --git a/HOUSEKEEPING_TO-DOs.md b/HOUSEKEEPING_TO-DOs.md deleted file mode 100644 index e9986465a9e..00000000000 --- a/HOUSEKEEPING_TO-DOs.md +++ /dev/null @@ -1,493 +0,0 @@ -# HOUSEKEEPING TO-DOs - -Audit date: 2026-06-25 - -Scope: formal v9.26.2 DigiDollar mainnet release readiness. This document now -tracks both the original audit findings and the housekeeping execution status. - -## Confirmed In Code - -- Version source is set to v9.26.2: - `configure.ac` has major `9`, minor `26`, build `2`, release `true`, and no RC - suffix. -- Normal public mainnet parameters are restored: - `src/kernel/chainparams.cpp` uses magic `fa c3 b6 da` and P2P port `12024`; - `src/chainparamsbase.cpp` uses mainnet data directory suffix `""`, RPC port - `14022`, and onion target port `14122`. -- The PRE/mainnet rehearsal network settings are not active in source: - the active source no longer uses `mainnet-pre`, `12046`, `14046`, or `14146` - for mainnet. -- Historical DigiByte fork heights are still present in mainnet chainparams: - DigiShield `67200`, MultiAlgo `145000`, MultiShield `400000`, DigiSpeed - `1430000`, reserve algo bits `8547840`, algo swap `9100000`, Odocrypt - `9112320`, and BIP34/BIP65/BIP66/CSV/SegWit at `4394880`. -- Mainnet DigiDollar activation is BIP9-gated: - bit `23`, threshold `28224`, window `40320`, start time `1780272000`, - timeout `1811808000`, and minimum activation height `23627520`. -- Mainnet DigiDollar, oracle validation, and MuSig2 are aligned: - `nDDActivationHeight`, `nOracleActivationHeight`, and - `nDigiDollarMuSig2Height` are all `23627520`. -- Mainnet oracle quorum is configured as 7-of-35: - `nOraclePubkeyCount = 35`, `nOracleConsensusRequired = 7`, with 35 configured - oracle pubkeys and 35 configured oracle node entries. -- The current oracle bundle validation path is shared by mainnet and testnet: - a DigiDollar price-sensitive mint or redeem must have a valid v0x03 MuSig2 - oracle bundle after activation. -- Lock-tier policy is intentionally not a housekeeping item: - the current build still contains the 1-hour and 10-year lock tiers, and this - audit does not propose changing them. - -Targeted validation already run during this audit: - -- `./src/test/test_digibyte --run_test=musig2_activation_tests` -- `./src/test/test_digibyte --run_test=rh50_oracle_keyset_alignment_tests` -- `./src/test/test_digibyte --run_test=digidollar_activation_wave12_tests` -- `./src/test/test_digibyte --run_test=miner_dd_validation_tests` -- `./src/test/test_digibyte --run_test=rh65_mainnet_testnet_validator_parity_tests` - -All five targeted test groups passed. - -## 2026-06-26 Execution Update - -Completed in this housekeeping pass: - -- Commit `88b7ad81cd` (`release: prune stale mainnet seeders`) - removed the four mainnet DNS seed domains that did not resolve from local and - Cloudflare DNS checks: - `seed.digibyteblockchain.org`, `eu.digibyteseed.com`, - `seed.quakeguy.com`, and `seed.digibyte.services`. -- The retained mainnet DNS seed domains were rechecked and returned reachable - sampled peers on TCP `12024`: - `seed.digibyte.io`, `seed.diginode.tools`, `seed.digibyte.link`, and - `seed.aroundtheblock.app`. -- `contrib/seeds/makeseeds.py` now accepts the v9.26.x user-agent family so a - future fixed-seed refresh does not filter current v9 release nodes out. -- `src/chainparamsseeds.h` was regenerated from the current - `contrib/seeds/nodes_main.txt` and `contrib/seeds/nodes_test.txt`; the - generated output matched the checked-in file, so no fixed-seed byte change was - needed. -- Commit `47b9ea3481` (`release: refresh mainnet chain metadata`) added a fresh - block `23,500,000` mainnet checkpoint and updated release safety metadata. - -Mainnet checkpoint data used from the local synced v9.26.2 Qt node: - -- Height: `23,500,000` -- Block hash: - `ade47d5ccbb92cb1d965b97a187bdbf65bf74be6a3709cb6a01339f8c2856deb` -- Chainwork: - `0000000000000000000000000000000000000000001cae290ed41eb2efd4804c` -- Block time: `1778906996` -- Chain transaction count from `getchaintxstats` at that block: `53,488,713` -- Transaction rate from the 8192-block stats window: - `0.09148870167189133` - -The placeholder mainnet AssumeUTXO entry was removed because it had a zero -serialized UTXO hash and zero transaction count. v9.26.2 now ships no mainnet -AssumeUTXO snapshot until a real snapshot is generated. - -Taproot live state was also checked with the running mainnet node: - -- Taproot status: `active` -- Taproot since height: `21,168,000` -- DigiDollar status during the check: `started`, minimum activation height - `23,627,520` - -Validation completed after the code changes: - -- `make -j$(nproc)` -- `python3 contrib/seeds/generate-seeds.py contrib/seeds` produced no diff - against `src/chainparamsseeds.h` -- Focused unit tests: - `oracle_config_tests`, `rh50_oracle_keyset_alignment_tests`, - `digidollar_oracle_tests/chainparams_oracle_endpoint_uniqueness`, and - `validation_tests` -- Full unit test suite: - `./src/test/test_digibyte --show_progress` passed `3401` test cases -- Full functional suite: - `test/functional/test_runner.py --jobs=8` passed all `378` listed entries - with expected skips; runtime `398s` -- Fuzz smoke with an empty input file passed for: - `dd_txbuilder_validate_mint_params`, `dd_txbuilder_mint`, - `dd_txbuilder_redeem`, `oracle_bundle_v03_roundtrip`, - `oracle_bundle_validation`, `oracle_musig2_bundle`, - `oracle_p2p_wire_messages`, `oracle_validate_block_data`, and - `fuzz_block_algo_routing_phase2a` -- Commit `07a63a9586` (`test: refresh regtest assumeutxo vector`) refreshed the - regtest height-299 AssumeUTXO vector and updated `feature_assumeutxo.py` to - match the deterministic DigiByte regtest snapshot it builds. -- Commit `1fec3a51f9` (`test: restore assumeutxo functional coverage`) restored - `feature_assumeutxo.py` to the default functional runner. -- `test/functional/feature_assumeutxo.py` passed directly. -- `test/functional/test_runner.py feature_assumeutxo.py --jobs=1` passed. -- `feature_assumevalid.py` remains intentionally disabled. The current test - buries the invalid block by 2100 blocks, which is Bitcoin's two-week-work - assumption, but only hours of DigiByte-equivalent work at 15-second spacing. - Porting it correctly is a separate test-maintenance task. - -Oracle peer-seeding decision: - -- The 35 oracle public keys, 35 active oracle slots, and 7-of-35 MuSig2 quorum - must stay intact. -- The release does not need 35 live oracle DNS endpoints for consensus. The - 35-slot endpoint list is roster/status metadata; oracle security comes from - the hardcoded public keys and MuSig2 bundle validation. -- For launch operations, oracle operators should use the two public v9.26.2 - mainnet oracle seed peers we have now: - `oracle1.digibyte.io:12024` and `digihash.digibyte.io:12024`. -- A third, fourth, and fifth stable public seed peer would be useful for - redundancy, but this is operational bootstrap redundancy, not consensus. - -## Release Housekeeping Status - -### 1. Remaining: Verify Mainnet Oracle Seed Peers - -Code currently defines 35 active mainnet oracle slots and endpoint strings, all -using port `12024`. Those endpoint strings are not consensus trust anchors. - -Local DNS verification found only these oracle hostnames resolving: - -- `oracle1.digibyte.io` -- `digihash.digibyte.io` - -Local TCP reachability on port `12024` during the re-check: - -- `digihash.digibyte.io:12024` accepted a TCP connection. -- `oracle1.digibyte.io:12024` did not accept a TCP connection from this host. - -The `oracle2.digidollar.org` through `oracle35.digidollar.org` style hostnames -did not resolve locally during this audit. - -Important: these configured oracle endpoints are not the same thing as mainnet -DNS seeders. They are oracle roster metadata used by RPC/status display and key -alignment checks. Oracle message validity is controlled by the configured oracle -public keys and MuSig2 signature validation, not by trusting DNS hostnames. - -Tasks: - -- Confirm `oracle1.digibyte.io` and `digihash.digibyte.io` resolve from outside - the local network. -- Confirm both public oracle seed peers accept inbound DigiByte P2P traffic on - port `12024`. -- Add one to three more stable public v9.26.2 mainnet peers if community hosts - volunteer them. -- Confirm the running oracle node for each slot uses the pubkey assigned to that - slot. -- Run a mainnet smoke check after DNS is fixed: - `listoracle`, `getoraclepubkey`, `getdigidollardeploymentinfo`, and one - controlled oracle start/stop flow. - -Why this matters: - -The code-side 35-slot roster is internally consistent. Oracle operators still -need a few reliable public peers so their nodes can find each other and relay -oracle traffic cleanly through activation. - -### 2. Completed: DNS Seed Cleanup And Fixed-Seed Check - -Mainnet previously listed eight DNS seeds in `src/kernel/chainparams.cpp`. -Commit `88b7ad81cd` removed the four domains that did not resolve locally. -The formal v9.26.2 mainnet source now retains these four DNS seeds: - -- `seed.digibyte.io` -- `seed.diginode.tools` -- `seed.digibyte.link` -- `seed.aroundtheblock.app` - -Local TCP reachability on port `12024` during the re-check: - -- Accepted `12024` in the sampled returned peers: `seed.digibyte.io`, - `seed.diginode.tools`, `seed.digibyte.link`, `seed.aroundtheblock.app` - -Current removal candidates because they did not resolve locally: - -- `seed.digibyteblockchain.org` -- `eu.digibyteseed.com` -- `seed.quakeguy.com` -- `seed.digibyte.services` - -Completed: - -- The four non-resolving domains were removed from mainnet chainparams. -- The retained seed domains returned reachable sampled mainnet peers on TCP - `12024` during the local audit. -- `contrib/seeds/makeseeds.py` now accepts v9.26.x user agents. -- Regenerating `src/chainparamsseeds.h` from the current seed files produced no - diff, so the fixed-seed byte list did not need a source change. - -Why this matters: - -The release should not depend on stale seed infrastructure. New mainnet users -need reliable initial peers after installing v9.26.2. - -### 3. Completed: Add Fresh Mainnet Checkpoint And Refresh Chain Safety Metadata - -Commit `47b9ea3481` refreshed mainnet chain safety metadata from the synced -local v9.26.2 Qt node at block `23,500,000`. - -Completed: - -- Added checkpoint height `23,500,000`. -- Updated `defaultAssumeValid` to block `23,500,000`. -- Set `nMinimumChainWork` from that block's chainwork. -- Refreshed `chainTxData` from `getchaintxstats`. -- Removed the placeholder mainnet AssumeUTXO entry because it had zero values - and was not a real release snapshot. - -Why this matters: - -This is normal release housekeeping, but it matters more for DigiDollar because -activation and oracle validation depend on nodes being cleanly synced on the -same mainnet chain. - -### 4. Completed: Confirm Taproot Is Active On Live Mainnet - -DigiDollar V1 uses P2TR vault scripts. The code has a separate Taproot BIP9 -deployment and DigiDollar activation assumes Taproot script support is available -before DigiDollar activates. - -Completed: - -- The synced local mainnet v9.26.2 Qt node reported Taproot `active`. -- Taproot was active since height `21,168,000`. - -Why this matters: - -The code sets DigiDollar activation separately from Taproot. The release process -must confirm the live network state before asking wallets, exchanges, pools, and -oracles to prepare for DigiDollar activation. - -### 5. Prepare Formal v9.26.2 Release Notes And User-Facing Docs - -The source version is v9.26.2, but several public docs still read like RC, -testnet, or PRE rehearsal material. - -Tasks: - -- Add formal `digidollar/RELEASE_v9.26.2.md` release notes. -- Update root `README.md` to explain that v9.26.2 is the formal DigiDollar - mainnet activation release. -- Update `doc/release-notes.md`; it still contains stale DigiDollar language - from older phases, including old oracle counts and old activation descriptions. -- Update `DIGIDOLLAR_ARCHITECTURE.md` and `DIGIDOLLAR_EXPLAINER.md` where they - still describe validation as RC/testnet-focused instead of formal mainnet - release-focused. -- Update oracle setup and integration guides so exchanges, wallets, pools, and - oracle operators see v9.26.2 as the formal baseline. -- Clearly mark `digidollar/MAINNET_TEST.md` and PRE release notes as historical - rehearsal documentation, not formal mainnet configuration. - -Why this matters: - -The code may be correct, but stale docs can cause operators to use the wrong -version, wrong network assumptions, wrong oracle count, or wrong activation -expectations. - -### 6. Add A Release Version Regression Test - -The build source says v9.26.2, but the current tests only check that version -output exists. They do not assert the exact formal release version. - -Tasks: - -- Add or update a focused test that verifies the exact v9.26.2 release string. -- Include the GUI splash/version text if the release image is part of the - expected user-facing version surface. - -Why this matters: - -The formal release should not ship with mismatched binary version, GUI version, -release notes, or tag name. - -### 7. Clean Up Or Register Stray Test Sources - -Tracked test files exist that are not wired into the normal test build lists. -These should be intentionally registered or intentionally removed before the -formal release. - -Unit-test files found tracked but not registered in the current test list: - -- `src/test/bip324_tests.cpp` -- `src/test/limitedmap_tests.cpp` -- `src/test/rbf_tests.cpp` -- `src/wallet/test/accounting_tests.cpp` - -Additional cleanup finding: - -- `src/test/crypto_tests_backup.cpp` defines a duplicate `crypto_tests` suite - name and appears to be a backup artifact, not an intended release test. - -Fuzz files found tracked but not wired into the normal fuzz source list: - -- `src/test/fuzz/crypto_chacha20_poly1305_aead.cpp` -- `src/test/fuzz/data_stream.cpp` -- `src/test/fuzz/odocrypt.cpp` - -Tasks: - -- Decide whether each file should be active release coverage or removed as - stale source. -- Register intended tests in the appropriate makefile lists. -- Remove true backup/stale artifacts in a focused cleanup commit. -- Re-run unit, functional, and fuzz build/test gates after cleanup. - -Why this matters: - -Unregistered tests create false confidence. Backup test artifacts can also break -future test discovery or duplicate-suite cleanup. - -### 8. Run Full Final Release Gates After Remaining Release Decisions - -After the remaining oracle endpoint and release-document decisions are fixed, -run one final release validation pass. - -Required gates: - -- Full unit test suite. -- Full functional test suite: - `test/functional/test_runner.py --jobs=8` -- DigiDollar/oracle-focused functional subset if the full suite fails for an - unrelated environment reason. -- Fuzz target build and at least a short smoke run for DigiDollar/oracle/mining - related targets. -- Clean build from a fresh checkout or clean build directory. -- GUI launch smoke check showing the v9.26.2 DigiDollar release branding. -- Mainnet startup smoke check with no PRE network parameters active. - -Why this matters: - -The formal DigiDollar release needs one clean final proof pass after metadata, -seed, docs, and version housekeeping are complete. - -## Should Do Before Or Shortly After Release - -### 9. Review Headerssync Parameters - -`doc/release-process.md` calls out headerssync review as part of release -maintenance. `contrib/devtools/headerssync-params.py` should be checked against -current DigiByte mainnet assumptions before the formal release. - -Tasks: - -- Confirm the script is still used by this release process. -- If used, update parameters from current mainnet data. -- If not used, document that clearly so future release work does not treat stale - values as active release policy. - -### 10. Fix Stale Comments Around DigiDollar Format Gates - -One RPC comment still describes V1 as setting the format gate to zero, while -current mainnet code sets MuSig2/DigiDollar activation at height `23627520`. - -Tasks: - -- Update stale comments in `src/rpc/digidollar.cpp` and nearby docs so they - match the current v0x03 MuSig2-only release model. - -### 11. Review No-libcurl Oracle Build Wording - -The exchange-price code fails closed when libcurl support is unavailable. -Release documentation and build messaging should make that clear for oracle -operators. - -Tasks: - -- Confirm configure/build output describes oracle price fetching accurately. -- Document that production oracle operators need libcurl-enabled builds. -- Make clear that no-libcurl builds are not valid production oracle-signing - builds. - -### 12. Refresh Repo Maps After Final Housekeeping - -The repo maps are useful for future agents and release audits, but they should -match the final v9.26.2 tree after housekeeping changes. - -Tasks: - -- Update `REPO_MAP.md`. -- Update `REPO_MAP_DIGIDOLLAR.md`. -- Remove or clearly mark obsolete PRE/testnet-only references. - -## Not Current To-DOs - -- Do not change the mainnet magic bytes. -- Do not change the normal mainnet ports. -- Do not change the normal mainnet data directory. -- Do not change historical DigiByte fork heights. -- Do not move DigiDollar, oracle validation, and MuSig2 to different mainnet - heights. -- Do not remove the current 1-hour or 10-year lock tiers as part of this - housekeeping pass. -- Do not keep PRE/mainnet rehearsal settings in the formal release. - -## Summary - -The formal v9.26.2 source tree now has the main PRE rehearsal reversion done, -normal mainnet network identity restored, stale DNS seeds pruned, fresh -mainnet chain safety metadata added, Taproot verified active, and DigiDollar, -oracle validation, and MuSig2 activation aligned. - -The highest-risk remaining release item is operational oracle/bootstrap -connectivity: the 35 oracle public keys and 7-of-35 quorum are correct in code, -and the launch plan is to use DigiByte.io and DigiHash as public v9.26.2 -mainnet oracle seed peers while asking for one to three more for redundancy. -Formal public docs are being handled by the separate docs team. - -## Dev Chat Post Draft - -Quick v9.26.2 DigiDollar housekeeping update before we do the formal mainnet -release. - -The main PRE rehearsal changes have been reverted. The current source tree is -back on normal DigiByte mainnet identity: same mainnet magic bytes, same normal -mainnet ports, same normal mainnet data directory, and the historical DigiByte -fork heights are still intact. DigiDollar, oracle validation, and MuSig2 are -all aligned at the same mainnet activation height in chainparams. - -Taproot is already active, and the release/docs team is handling the formal -public release notes and integration docs separately. The core housekeeping -work completed in this pass: - -1. Fresh mainnet checkpoint and chain metadata are updated. - We added block `23,500,000` as the current checkpoint, updated - `defaultAssumeValid`, set real `nMinimumChainWork`, refreshed - `chainTxData`, and removed the placeholder mainnet AssumeUTXO entry. - -2. Mainnet DNS seeds are cleaned up. - The seeds that resolved and returned reachable sampled mainnet peers were: - `seed.digibyte.io`, `seed.diginode.tools`, `seed.digibyte.link`, and - `seed.aroundtheblock.app`. - - The seed domains that did not resolve locally were removed: - `seed.digibyteblockchain.org`, `eu.digibyteseed.com`, - `seed.quakeguy.com`, and `seed.digibyte.services`. - - `src/chainparamsseeds.h` was regenerated from the current seed files and had - no diff. The seed-generation user-agent filter now accepts v9.26.x nodes. - -3. Regtest AssumeUTXO coverage is restored. - The stale regtest AssumeUTXO vector was updated and `feature_assumeutxo.py` - is back in the default functional runner. - -Remaining item: - -1. Verify oracle/bootstrap connectivity. - Oracle hostnames are not consensus trust anchors. The actual oracle security - comes from the hardcoded oracle public keys and 7-of-35 MuSig2 validation. - The hostnames are only bootstrap/status/operator metadata. - - For the formal release we only need stable public bootstrap/connectivity for - DigiHash and DigiByte.io style nodes, with one to three more useful but not - consensus-required. Those seed peers should accept normal mainnet P2P on TCP - `12024` before we put them in operator instructions. - -2. Run final release gates after the remaining release decisions. - Once oracle endpoint/docs decisions are finalized, we need the full unit - suite, full functional suite with `test/functional/test_runner.py --jobs=8`, - relevant fuzz build/smoke coverage, Qt launch/version verification, and a - final mainnet startup smoke check. - -The important part: nothing in this housekeeping changes DigiDollar economics, -mainnet identity, historical fork heights, or the DigiDollar/oracle/MuSig2 -activation alignment. This is the final cleanup pass to make sure the formal -v9.26.2 DigiDollar release starts from clean mainnet settings and reliable -network bootstrap data. diff --git a/digidollar/HOUSEKEEPING_TO-DOs.md b/digidollar/HOUSEKEEPING_TO-DOs.md index e9986465a9e..e3940e09c30 100644 --- a/digidollar/HOUSEKEEPING_TO-DOs.md +++ b/digidollar/HOUSEKEEPING_TO-DOs.md @@ -263,7 +263,7 @@ testnet, or PRE rehearsal material. Tasks: -- Add formal `digidollar/RELEASE_v9.26.2.md` release notes. +- Add formal `doc/release-notes/RELEASE_v9.26.2.md` release notes. - Update root `README.md` to explain that v9.26.2 is the formal DigiDollar mainnet activation release. - Update `doc/release-notes.md`; it still contains stale DigiDollar language diff --git a/doc/README.md b/doc/README.md index c8d7a74c9d7..af07f3a59c7 100644 --- a/doc/README.md +++ b/doc/README.md @@ -67,6 +67,12 @@ The DigiByte repo's [root README](/README.md) contains relevant information on t - [Dnsseed Policy](dnsseed-policy.md) - [Benchmarking](benchmarking.md) +### Release documentation + +- [v9.26.4 pruning documentation](v9.26.4/V9.26.4_PRUNING_EXPLAINER.md) +- [v9.26.2 archived documentation-validation prompt](v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md) +- [Release-note archive](release-notes/), including [v8.26.2](release-notes/RELEASE-NOTES-8.26.2.md), [v9.26.2](release-notes/RELEASE_v9.26.2.md), and [v9.26.5](release-notes/RELEASE_v9.26.5.md) + ### Resources * Discuss on the [DigiByteTalk](https://digibytetalk.org/) forums, in the [Development & Technical Discussion board](https://digibytetalk.org/index.php?board=6.0). * Discuss project-specific development on #digibyte-core-dev on Libera Chat. If you don't have an IRC client, you can use [web.libera.chat](https://web.libera.chat/#digibyte-core-dev). diff --git a/RELEASE-NOTES-8.26.2.md b/doc/release-notes/RELEASE-NOTES-8.26.2.md similarity index 100% rename from RELEASE-NOTES-8.26.2.md rename to doc/release-notes/RELEASE-NOTES-8.26.2.md diff --git a/RELEASE_v9.26.2.md b/doc/release-notes/RELEASE_v9.26.2.md similarity index 100% rename from RELEASE_v9.26.2.md rename to doc/release-notes/RELEASE_v9.26.2.md diff --git a/RELEASE_v9.26.5.md b/doc/release-notes/RELEASE_v9.26.5.md similarity index 100% rename from RELEASE_v9.26.5.md rename to doc/release-notes/RELEASE_v9.26.5.md diff --git a/doc/release-notes/release-notes-9.26.4.md b/doc/release-notes/release-notes-9.26.4.md index c3455cc3831..fe84ded3e06 100644 --- a/doc/release-notes/release-notes-9.26.4.md +++ b/doc/release-notes/release-notes-9.26.4.md @@ -14,7 +14,7 @@ the floor is never treated as vault collateral, whether it appears as the redemp input 0 (rejected as `bad-collateral-release-not-vault`) or as a fee input (see `ValidateCollateralReleaseAmount` in `src/digidollar/validation.cpp`). This rule is what keeps pruned and full nodes in agreement, and the pre-release mainnet scan found -no existing coins it affects (`V9.26.4_MAINNET_VALIDATION.md`). v9.26.2's Groestl +no existing coins it affects (`doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`). v9.26.2's Groestl algolock and the DigiDollar BIP9 deployment are carried forward unchanged, and a pruned v9.26.4 node accepts and rejects exactly the same blocks and transactions as a full node. Upgrading is optional — a node that does not set `-prune` behaves like diff --git a/doc/v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md b/doc/v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md new file mode 100644 index 00000000000..eb01267c832 --- /dev/null +++ b/doc/v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md @@ -0,0 +1,464 @@ +# v9.26.2 — Final Root-Markdown Release Validation Prompt + +**Archive note:** This prompt records the v9.26.2 documentation review. Its root-directory scope, branch, and release settings describe that historical review. File references now point to their current locations. This archived copy is retained in version control. Repository file paths below are relative to the repository root. + +## Goal + +Validate that **every Markdown (`.md`) file in the repository root directory** is +accurate, current, internally consistent, and ready to ship with the formal, +full, final **DigiByte Core v9.26.2 / DigiDollar v1** release. + +"Ready to ship" means each root `.md` file is proven, against live source on the +current `feature/digidollar-v1` tree, to: + +1. **Match the code.** Every factual, technical, numeric, or behavioral claim is + backed by current source (`src/`), build files, chainparams, tests, scripts, + or RPC/Qt registration — not by memory, not by an older release note, not by + a stale sibling doc. +2. **Reflect DigiByte and DGB correctly.** The docs describe DigiByte (DGB), the + DigiByte chain values, DigiByte ports/addresses/magic, and DigiDollar — not + Bitcoin (BTC), Bitcoin defaults, or Bitcoin Core lineage values left over + from the upstream fork. Inherited Bitcoin terminology, BTC units, Bitcoin + block times, Bitcoin ports, Bitcoin RPC examples, or "Bitcoin Core" branding + that should read "DigiByte Core" are defects. +3. **Read clearly for both AI agents and humans.** Each doc is well-structured, + unambiguous, navigable, free of contradictions, free of dead/broken internal + references, and easy for an AI agent or a human reader (developer, exchange, + miner, oracle operator, or end user) to understand and act on. +4. **State the correct version and release status.** Where a doc references a + version, it agrees with the source of truth: `configure.ac` + (`9` / `26` / `2`, `RC=0`, `IS_RELEASE=true`) and `src/clientversion.h`. + +This is a **documentation-validation** prompt, not a code audit. Do not hunt for +code bugs. Do not change `src/`, tests, build files, or scripts. The only files +you may modify are root-directory `.md` files, and only to make them match the +code that already exists. **Code is the source of truth; the docs must conform to +the code, never the reverse.** If a doc and the code disagree, the doc is wrong +unless the discrepancy reveals an actual code defect — in which case you record +it as `CODE_DISCREPANCY_FOR_JARED` and do **not** edit code to fit the doc. + +This rule from `CLAUDE.md` governs the entire run: + +> Avoid widening doc claims beyond what code shows. Treat code as truth. + +## Scope — root `.md` files only + +In scope: **only `.md` files in the repository root** (`/home/jared/Code/digibyte/*.md`). +No subdirectories. No `docs/`, no `src/`, no `doc/`, no `contrib/`. Re-enumerate +the live set at the start of the run; do not trust this list blindly: + +```bash +cd ~/Code/digibyte +ls -1 *.md | sort +git status --short -- '*.md' +``` + +The root `.md` set at the time this prompt was written (re-confirm live — files +may have been added or removed): + +**Tier A — Public / release-facing docs (highest bar; ship with the release):** + +| File | Purpose | Primary code surface to validate against | +|------|---------|------------------------------------------| +| `README.md` | Project front page, links, version badge | `configure.ac`, repo links, `INSTALL.md` | +| `INSTALL.md` | Build/install entry | `doc/build-*`, `configure.ac` | +| `SECURITY.md` | Security policy / disclosure | low code coupling — validate contacts/keys/links/policy are current; "low coupling" is not an exemption from checking | +| `CONTRIBUTING.md` | Contributor workflow | branch model, build/test commands | +| `ARCHITECTURE.md` | Core DigiByte system design | `src/` core: chainparams, consensus, net, pow | +| `DIGIDOLLAR_EXPLAINER.md` | User-facing DigiDollar V1 summary | `src/digidollar/`, `src/consensus/digidollar*` | +| `DIGIDOLLAR_ARCHITECTURE.md` | DD mint/transfer/redeem/collateral/health/state | `src/digidollar/`, `src/consensus/`, `src/script/` | +| `DIGIDOLLAR_ACTIVATION_EXPLAINER.md` | BIP9 gating of DD/oracle surface | `src/consensus/params.h`, `src/kernel/chainparams.cpp`, `src/deploymentinfo*` | +| `DIGIDOLLAR_ORACLE_EXPLAINER.md` | User-facing oracle/MuSig2 summary | `src/oracle/`, `src/primitives/oracle.h` | +| `DIGIDOLLAR_ORACLE_ARCHITECTURE.md` | Exchange aggregation, bundle lifecycle, MuSig2, P2P | `src/oracle/`, `src/net_processing.cpp`, `src/protocol.cpp` | +| `ORACLE_BUNDLE_EXPLAINER.md` | Oracle bundle wire/lifecycle explainer | `src/primitives/oracle.h`, `src/oracle/bundle_manager.cpp`, `src/consensus/` | +| `ORACLE_DISCOVERY_ARCHITECTURE.md` | Oracle endpoint discovery design | `src/oracle/`, discovery/P2P code | +| `DIGIDOLLAR_ORACLE_SETUP.md` | Oracle setup + migration runbook | oracle RPCs, `src/oracle/`, operator flags | +| `DIGIDOLLAR_WALLET_INTEGRATION.md` | Wallet/RPC integration guide | `src/wallet/`, `src/wallet/rpc/wallet.cpp`, `src/rpc/digidollar.cpp` | +| `DIGIDOLLAR_EXCHANGE_INTEGRATION.md` | Exchange/custody integration guide | DD RPCs, address formats, units | +| `DIGIDOLLAR_MINING_INTEGRATION_GUIDE.md` | Miner integration for DD/oracle bundles | `src/node/miner.cpp`, `getblocktemplate`, bundle injection | + +**Tier B — AI-agent / maintainer guides (must be code-accurate; AI relies on them):** + +| File | Purpose | Primary code surface to validate against | +|------|---------|------------------------------------------| +| `CLAUDE.md` | AI agent guide, branch rules, code-surface warnings | the entire DD/oracle surface it summarizes | +| `REPO_MAP.md` | Core DigiByte file index | `src/` core file tree | +| `REPO_MAP_DIGIDOLLAR.md` | DD/oracle file index | `src/digidollar/`, `src/oracle/`, DD RPC/Qt/test tree | +| `REPO_MAP_GUIDE.md` | How to maintain/interpret the repo maps | the two repo maps | + +**Tier C — Internal / non-release (verify they are NOT misleading; lightest bar, +do not ship-block on prose polish):** + +| File | Purpose | +|------|---------| +| `digidollar/HOUSEKEEPING_TO-DOs.md` | Internal task list | +| `Z_RED_HORNET_FINAL_PROMPT.md` | Internal audit prompt (gitignored) | +| `Z_RED_HORNET_v2.md` | Internal audit prompt (gitignored) | +| `doc/v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md` | This archived prompt (tracked) | + +For Tier C: confirm tier classification is correct, confirm gitignored internal +prompts are still gitignored, and confirm none of them are misleading — meaning +no statement that contradicts current code, no outdated procedure that could +confuse a maintainer, and no public-facing falsehood that could leak into a +release. Verify by spot-checking the critical claims in each Tier-C file against +code, not by skimming for obvious lies. Do not polish their prose and do not +ship-block on them. If a Tier-C file is actually stale internal cruft, recommend +(do not perform) deletion and record it under `HOUSEKEEPING`. + +Re-derive each file's tier from its actual content during Wave 0; do not assume +the table is still correct. + +## Operating principles + +- **Code is truth.** Open the cited source and read it with your own eyes. A claim + is validated only when you have read the current source and can cite the exact + `file:line` that backs it. Reading the source is mandatory, not optional. +- **Do not hallucinate.** Never invent or "remember" a constant, RPC name, file + path, port, height, oracle count, quorum, opcode value, address prefix, version + string, or behavior. If you cannot find it in code, the claim is `UNVERIFIED` + until proven, and you must not assert it as fact in a doc. +- **Minimal, surgical edits.** Fix only what is wrong or unclear. Do not rewrite + docs wholesale, do not restructure for taste, do not "improve" passing prose. + Every edit closes a validated drift, ambiguity, or DigiByte/DGB-correctness + defect. No drive-by rewrites. +- **Preserve voice and format.** Match each doc's existing heading style, tone, + table format, and code-fence conventions. A reader should not be able to tell a + validation pass happened, except that the content is now correct. +- **Don't fix code.** If a doc accurately describes code and the code looks wrong, + that is out of scope — log it as `CODE_DISCREPANCY_FOR_JARED` with evidence and + move on. This run never edits non-`.md` files. +- **No proof-by-proxy.** The only acceptable proof for a technical claim is a + current `file:line` from **original source** (`src/`, `configure.ac`, build + files, registered tests). You may **not** cite, as proof: the canonical-facts + ledger, `CLAUDE.md`, a sibling doc, an old report, an RC release note, memory, + or training knowledge. Those are leads; source is proof. A command/test result + may be cited **only as supplementary** evidence, and only with a one-line + explanation of exactly what the output proves and which source it corresponds + to — never as a standalone substitute for reading the code. +- **Stop and ask Jared** before: deleting any doc, merging/splitting docs, + changing a doc's public meaning in a way that implies a protocol/release + decision, or "fixing" a doc to match code where the code itself appears wrong. + +## Canonical facts ledger (build this FIRST, in Wave 0) + +Most doc drift is a number or term that disagrees with code or with a sibling +doc. Before validating any doc, extract the **canonical values from source** into +a single ledger (`reports/doc_validation_canonical_facts.md`). Every later wave +checks each doc against this one ledger so all 20+ docs end up internally +consistent with each other and with code. At minimum, pin from code: + +- **Version / release:** `configure.ac` major/minor/build/RC/suffix/IS_RELEASE; + `src/clientversion.h`. Confirm the release name every doc should use is + **v9.26.2** (final, not RC). +- **Chain constants** (`src/chainparams*`, `src/consensus/`, `src/kernel/chainparams.cpp`, + `src/validation.cpp`, `src/amount`/`policy`): block time, `SUBSIDY`, + `COINBASE_MATURITY` / `COINBASE_MATURITY_2`, `MAX_MONEY`, min relay / default + tx fee in **DGB/kB** (not BTC/vB), P2P ports (mainnet `12024`, testnet26 + `12033`), RPC ports, network magic, bech32 HRPs (`dgb`/`dgbt`/`dgbrt`), DD + address prefixes (`DD`/`TD`/`RD`). + - **Block-time nuance — validate carefully, do not blindly "correct".** DigiByte + is multi-algo. The effective/average block time is **15s** (`BLOCK_TIME_SECONDS` + in `src/validation.h`), while the per-algorithm target spacing is **60s** + (`nTargetSpacing` in `src/kernel/chainparams.cpp`). A doc saying "15 second + blocks" is correct for the effective rate; a doc saying "60s" may be correct + if it explicitly means per-algorithm spacing. Confirm which the doc means + against both source locations before changing anything. +- **DigiDollar consensus** (`src/digidollar/`, `src/consensus/digidollar*`, + `src/script/script.h`): DD tx version marker `0x0770`, type field encoding + (1=MINT/2=TRANSFER/3=REDEEM), mint min/max cents, the 10 lock tiers and their + canonical durations + confirmation-window rule, DD amount unit (1 cent), + `MAX_DIGIDOLLAR`, opcode values `OP_DIGIDOLLAR=0xbb` … `OP_ORACLE=0xbf` and + the `OP_CHECKPRICE` deterministically-disabled status. +- **Activation** (`src/consensus/params.h`, `src/kernel/chainparams.cpp`, + `src/deploymentinfo.cpp`): BIP9 bit (23), `DEPLOYMENT_DIGIDOLLAR` start / + min-activation-height / window / threshold per network (mainnet, testnet26, + regtest), and the `nDDActivationHeight` / `nOracleActivationHeight` / + `nDigiDollarMuSig2Height` values per network. +- **Oracle roster** (`src/kernel/chainparams.cpp` `vOracleNodes` / + `consensus.vOraclePublicKeys` / `nOraclePubkeyCount` / `nOracleConsensusRequired`, + `src/primitives/oracle.h`): per network — total slots, active count, and + consensus/quorum required. **This is the single highest-risk drift area.** + Re-derive the values yourself from `src/kernel/chainparams.cpp` and cite the + lines. As of this writing the code declares **mainnet 35 slots / 7-of-35**, + **testnet26 35 slots / 7-of-35**, and **regtest 7 slots / 4-of-7** (chainparams + `nOraclePubkeyCount` / `nOracleConsensusRequired`, with `vOraclePublicKeys` + holding 35 / 35 / 7 keys respectively) — confirm these are still current, then + treat **any** doc that says otherwise (e.g. stale "21 active" or "9-of-17" + figures from the RC era) as a defect to correct to the code's actual numbers. +- **RPC surface** (`src/rpc/digidollar.cpp` `RegisterDigiDollarRPCCommands`, + `src/wallet/rpc/wallet.cpp` `GetWalletRPCCommands`): the exact list and count + of registered DD/oracle RPCs (base vs wallet-context), and which named RPCs are + intentionally absent/legacy/dead (`sendoracleprice`, `submitoracleprice`, + `src/rpc/digidollar_transactions.cpp` entries). +- **P2P / wire** (`src/protocol.cpp`, `src/net_processing.cpp`): oracle message + wire names and their gating. + +When two docs disagree, the ledger value (from code) wins, and **both** docs are +corrected. Record in the ledger any place where the code itself is ambiguous or +where two source locations disagree — that is a `CODE_DISCREPANCY_FOR_JARED`. + +## Fleet model — waves of up to 5 sub-agents + +Run in **waves**, **up to 5 sub-agents per wave**, at **maximum ultracode effort**. +The main agent is the **orchestrator**: it owns the canonical-facts ledger, wave +dispatch, synthesis, conflict resolution between sub-agents, applying edits, +re-verification, the running ledger, and final signoff. Sub-agents investigate +and propose; the orchestrator decides and edits. If two sub-agents disagree about +a fact, the orchestrator re-derives it from code and is the tiebreaker. + +**Orchestrator must spot-check, not trust.** Before applying a sub-agent's +corrections or marking any doc `VERIFIED`, the orchestrator independently re-reads +at least ~20% of that sub-agent's `file:line` citations (and 100% of any citation +behind a consensus/roster/version/activation/RPC-surface claim) by opening the +cited source itself. If any cited line does not actually say what the sub-agent +claimed, or is too vague to prove the claim, the whole doc is bounced back for +re-validation. Sub-agent assertions are leads until the orchestrator has seen the +code. + +**Default per-agent unit of work: one document, validated end to end.** Each +sub-agent owns one root `.md` file (or a small cluster of tightly related ones), +reads it line by line, and for every claim either (a) cites the current +`file:line` that proves it, (b) proposes the corrected text with the code +citation that justifies the correction, or (c) marks it `UNVERIFIED` and records +the search performed (specific files opened, search terms used, and why the claim +could not be confirmed). Larger docs (e.g. `DIGIDOLLAR_ARCHITECTURE.md`, +`DIGIDOLLAR_ORACLE_ARCHITECTURE.md`, `REPO_MAP*.md`) may warrant a whole agent +each; tiny docs (`SECURITY.md`, `INSTALL.md`) can be clustered — but clustering is +a grouping for efficiency only, never an excuse for less depth: every clustered +doc gets the full D1–D4 treatment with the same rigor as a solo doc. + +A **material technical claim** — the thing that always requires a source citation +— is any statement a reader could act on or that affects correctness: a version, +number, constant, port, address/HRP/prefix, opcode, height, oracle count/quorum, +RPC/CLI/config name, API field, behavior, procedure step, or feature-availability +claim. Expository prose that depends on such a claim must be backed by the same +citation. Pure narrative with no actable content needs no citation but also must +not contradict code. + +Each sub-agent must check **all four dimensions** for its doc(s): + +- **D1 — Code accuracy:** every material technical claim matches current source; + cite `file:line` from **original source**, never from the canonical-facts + ledger or a sibling doc (the ledger is a consistency cross-check, not proof). + Flag every value that disagrees with the canonical-facts ledger. +- **D2 — DigiByte/DGB correctness:** no stray Bitcoin/BTC branding, units, + defaults, ports, block times, or "Bitcoin Core" labels that should be DigiByte; + DGB units and DigiByte-specific values used throughout; address/HRP/prefix + examples are DigiByte, not Bitcoin. +- **D3 — Readability for AI + humans:** clear structure, working internal links + and file references, no contradictions within the doc or across docs, correct + cross-references, terminology consistent with the canonical ledger, no + confusing or stale instructions, examples that actually run/parse. +- **D4 — Release-readiness:** version/status correct (v9.26.2 final), no + "RC"/"draft"/"TODO"/"coming soon"/placeholder language in Tier A/B docs, no + references to removed features or renamed symbols, no dead links to deleted + files. + +Give each sub-agent the context bundle below plus the canonical-facts ledger plus +its explicit file list and the specific code paths to read. Sub-agents **propose +diffs with citations**; they do not edit files. The orchestrator applies and +re-verifies. This keeps edits consistent across all docs and prevents two agents +from making contradictory changes. + +### Sub-agent context bundle (every sub-agent must read first) + +1. `CLAUDE.md` +2. The canonical-facts ledger (`reports/doc_validation_canonical_facts.md`) +3. The specific doc(s) it owns +4. The code paths listed for that doc in the Scope table +5. For DD/oracle docs, also: `DIGIDOLLAR_ARCHITECTURE.md`, + `DIGIDOLLAR_ORACLE_ARCHITECTURE.md`, `REPO_MAP_DIGIDOLLAR.md` as cross-refs + (but remember those are themselves under validation — trust code over them, + and never cite them as proof in your report). + +Sub-agents must not edit any file. Sub-agents must not redesign docs or invent +new sections. If a doc section describes a feature, the sub-agent's job is to +confirm the description matches code, not to reimagine the feature. A sub-agent +may not report a claim as `VERIFIED` unless its report carries the exact +original-source `file:line` it read; "looks right", "consistent with the ledger", +or "matches CLAUDE.md" are not verification. + +## Per-document validation checklist + +For each root `.md` file, the owning sub-agent produces a structured report: + +- **File + tier.** +- **Claim inventory:** the material technical claims, each marked + `VERIFIED (file:line)`, `DRIFT → corrected text (file:line)`, or `UNVERIFIED`. +- **DigiByte/DGB findings:** any Bitcoin/BTC leftovers, wrong units, wrong ports, + wrong branding, with the corrected DigiByte value and its code citation. +- **Readability findings:** contradictions, broken links, dead file references, + ambiguous instructions, cross-doc term mismatches. +- **Release-readiness findings:** version/status/placeholder/removed-feature + issues. +- **Proposed diff:** exact before→after text for each correction, each with a + code citation. Minimal edits only. +- **Cross-doc notes:** any value that must also change in another doc to stay + consistent (hand to orchestrator). +- **CODE_DISCREPANCY_FOR_JARED:** any place the doc looks right but the code looks + wrong, or two code locations disagree. Evidence only; no code edits. + +## Suggested wave plan (adapt to the live file set) + +Re-enumerate first; add or split waves as needed. Do not exceed 5 sub-agents per +wave. The orchestrator applies all corrections from a wave, re-verifies against +code, updates the ledger, and (if authorized) commits before starting the next +wave. + +- **Wave 0 — Ground truth & inventory (orchestrator + up to 5 scouts).** + Re-enumerate root `.md` files. Build the canonical-facts ledger from code. + Resolve the high-risk oracle-roster / quorum / version numbers from source. + Classify each file into Tier A/B/C. Produce the per-doc → code-surface map. + Output: `reports/doc_validation_canonical_facts.md` and the wave plan. + +- **Wave 1 — Core repo docs (Tier A/B).** `README.md`, `INSTALL.md`, + `SECURITY.md`, `CONTRIBUTING.md`, `ARCHITECTURE.md` (+ `CLAUDE.md` if not held + for Wave 5). Validate version badge/links, build/install steps, branch model, + and core architecture against `src/` core, `configure.ac`, and build docs. + +- **Wave 2 — DigiDollar core docs.** `DIGIDOLLAR_EXPLAINER.md`, + `DIGIDOLLAR_ARCHITECTURE.md`, `DIGIDOLLAR_ACTIVATION_EXPLAINER.md`. Validate + mint/transfer/redeem/collateral/health, lock tiers, opcodes, DD tx marker, and + BIP9 activation against `src/digidollar/`, `src/consensus/`, `src/script/`, + `src/kernel/chainparams.cpp`, `src/consensus/params.h`. + +- **Wave 3 — Oracle docs.** `DIGIDOLLAR_ORACLE_EXPLAINER.md`, + `DIGIDOLLAR_ORACLE_ARCHITECTURE.md`, `ORACLE_BUNDLE_EXPLAINER.md`, + `ORACLE_DISCOVERY_ARCHITECTURE.md`, `DIGIDOLLAR_ORACLE_SETUP.md`. Validate + exchange aggregation, bundle v0x03 lifecycle, MuSig2, P2P wire names/gating, + roster/quorum, discovery, and operator setup against `src/oracle/`, + `src/primitives/oracle.h`, `src/net_processing.cpp`, `src/protocol.cpp`, and + the oracle RPCs. + +- **Wave 4 — Integration / operator guides.** `DIGIDOLLAR_WALLET_INTEGRATION.md`, + `DIGIDOLLAR_EXCHANGE_INTEGRATION.md`, `DIGIDOLLAR_MINING_INTEGRATION_GUIDE.md`. + Validate every RPC name/param/return, address format, unit, and miner/bundle + step against `src/wallet/rpc/wallet.cpp`, `src/rpc/digidollar.cpp`, + `src/node/miner.cpp`, and registration tables. + +- **Wave 5 — AI-agent maps, internal docs, and cross-doc consistency.** + `CLAUDE.md`, `REPO_MAP.md`, `REPO_MAP_DIGIDOLLAR.md`, `REPO_MAP_GUIDE.md`, and + Tier C files. Validate that the repo maps match the actual file tree (no listed + file is missing, no major file is unlisted), that `CLAUDE.md`'s surface + warnings match code, and run a **final cross-doc consistency sweep**: every + shared number/term agrees across all docs and with the canonical ledger. + +- **Wave 6+ (add-on) — Reconciliation.** Any doc that failed, any cross-doc + conflict surfaced late, any `CODE_DISCREPANCY_FOR_JARED` needing a second pass. + Add waves until every Tier A/B doc is `VERIFIED` or has an explicit, recorded + open item. + +## Fix discipline (verify → correct → re-verify) + +For every correction: + +1. **Prove the drift** against current code with an original-source `file:line` + citation. No source citation, no edit. +2. **Apply the minimal edit** to the `.md` file — change only the wrong words, + numbers, links, or branding. Preserve surrounding prose and formatting. +3. **Re-verify** the corrected text against the same code citation. Confirm you + did not introduce a new inconsistency with a sibling doc or the ledger. +4. **Propagate** the same fact to every other doc that repeats it (orchestrator + tracks shared facts so all docs stay consistent). +5. **Record** the correction in the running ledger with before/after and citation. + +Do not "fix" by deleting hard-to-verify content. If a claim cannot be verified, +either prove it from code, soften it to exactly what code supports, or escalate +it — do not silently drop or invent. Do not introduce new claims a reader cannot +verify from code. + +## Anti-hallucination rules + +- Cite current original-source `file:line` for every material claim that ends up + in a doc or that you assert in a report. Command/test output may accompany a + citation as supplementary proof, with a note on what it demonstrates, but never + replaces reading the source. +- Never invent filenames, RPC names, test names, config keys, line numbers, + commit hashes, release facts, oracle slots, quorum values, ports, opcodes, + address prefixes, or activation states. +- If unsure, open the file and read it. If still unsure, mark `UNVERIFIED` and + record what evidence is missing (files opened, terms searched) — never guess + into a doc. +- Old reports, RC release notes, the canonical-facts ledger, `CLAUDE.md`, and + sibling docs are **leads, not proof**. Reconfirm against current source. +- Separate observation from inference; label inferences as inferences and cite + the source/observation each inference rests on. An inference that affects code + correctness or release readiness must link to the code it depends on. + +## Commit discipline + +Never push. Before any edit: `git status --short -- '*.md'` — do not overwrite +unrelated local work. Only `.md` files in root may be staged by this run. + +If commits are authorized, commit cleanly: + +- Group by doc or by a single shared fact corrected across docs. +- One logical correction set per commit; no unrelated mega-commits. +- Commit message names the doc(s) and the nature of the fix, e.g. + `docs: correct oracle roster/quorum numbers to match chainparams (v9.26.2)`. +- Commit body cites the code that justifies the change. +- Do not mix Tier-C housekeeping with Tier-A release-doc corrections. + +If commits are not authorized, leave edits uncommitted and record the exact +proposed commit split. + +## Reporting + +Maintain a durable ledger from Wave 0 to signoff: + +- Canonical facts: `reports/doc_validation_canonical_facts.md` +- Running ledger: `reports/doc_validation_ledger.md` +- Final report: `reports/doc_validation_final_report.md` + +Each wave summary records: wave number; docs covered; sub-agent assignments; per +doc D1–D4 findings; corrections applied (before→after + citation); cross-doc +facts propagated; `UNVERIFIED` items; `CODE_DISCREPANCY_FOR_JARED` items; +re-verification result; commit hash/status. + +Final report records: the canonical-facts ledger; per-doc final status +(`VERIFIED` / `VERIFIED WITH OPEN ITEMS` / `BLOCKED`); every correction with its +original-source citation; every `UNVERIFIED` item still open; every +`CODE_DISCREPANCY_FOR_JARED` gathered into one section for Jared; the cross-doc +consistency result; and a single release verdict. A doc may be marked `VERIFIED` +**only** if the report carries, or references in the running ledger, the +`file:line` citations behind its material claims — and the orchestrator has +spot-checked a sample of them against source. "VERIFIED" with no recorded +citations is not allowed. + +## Final signoff criteria for v9.26.2 + +The doc set is release-ready only when **all** are true: + +- Every Tier A and Tier B root `.md` file is `VERIFIED` against current code — + each material claim backed by a recorded original-source `file:line` the + orchestrator spot-checked — or has an explicitly listed, Jared-accepted open + item. No doc is `VERIFIED` on intuition or proxy citations. +- Every shared fact (version, oracle roster/quorum, activation heights, ports, + opcodes, RPC counts, constants, addresses, units) is identical across all docs + and equals the code-derived canonical value. +- No Tier A/B doc contains Bitcoin/BTC branding, units, ports, or defaults that + should be DigiByte/DGB. +- No Tier A/B doc contains RC/draft/TODO/placeholder/"coming soon" language, + references to removed features, or dead links to deleted files. +- Every doc reads clearly for both AI agents and humans (structure, links, no + contradictions). +- All `CODE_DISCREPANCY_FOR_JARED` items are gathered in one section for Jared's + decision; none were silently patched, and no code was edited. +- Tier C internal/gitignored files are confirmed non-misleading and still + correctly classified/ignored. + +## Final output to Jared + +Keep it short enough to read and complete enough to act on: + +- Overall verdict: READY / READY WITH OPEN ITEMS / NOT READY. +- Root `.md` files validated (count) and per-doc status table. +- Corrections applied (count) and commit status. +- Cross-doc consistency result. +- DigiByte/DGB-correctness result. +- Open `UNVERIFIED` items. +- `CODE_DISCREPANCY_FOR_JARED` items needing Jared's decision. +- Exact report paths. + +Do not hide risk. Do not rely on memory. Code is the source of truth; the docs +must conform to it. Validate, do not rewrite. Fix only what is proven wrong. diff --git a/V9.26.4_MAINNET_VALIDATION.md b/doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md similarity index 100% rename from V9.26.4_MAINNET_VALIDATION.md rename to doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md diff --git a/V9.26.4_PRUNING_EXPLAINER.md b/doc/v9.26.4/V9.26.4_PRUNING_EXPLAINER.md similarity index 97% rename from V9.26.4_PRUNING_EXPLAINER.md rename to doc/v9.26.4/V9.26.4_PRUNING_EXPLAINER.md index c4b847963e8..1371e949b80 100644 --- a/V9.26.4_PRUNING_EXPLAINER.md +++ b/doc/v9.26.4/V9.26.4_PRUNING_EXPLAINER.md @@ -58,8 +58,8 @@ release v9.26.4 Qt on **live mainnet** with `prune=2000` and no txindex: normally in ~2 min (the first prune only has a one-time ~8-min step marking 21M old block-index entries). -Full test record: `V9.26.4_MAINNET_VALIDATION.md`. Design + test plan: -`V9.26.4_PRUNING_PLAN.md`. +Full test record: `doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`. Design + test plan: +`doc/v9.26.4/V9.26.4_PRUNING_PLAN.md`. **For pools — upgrade once. How to set up `digibyte.conf`:** diff --git a/V9.26.4_PRUNING_PLAN.md b/doc/v9.26.4/V9.26.4_PRUNING_PLAN.md similarity index 99% rename from V9.26.4_PRUNING_PLAN.md rename to doc/v9.26.4/V9.26.4_PRUNING_PLAN.md index 36ac48dcf3e..dec52975fa2 100644 --- a/V9.26.4_PRUNING_PLAN.md +++ b/doc/v9.26.4/V9.26.4_PRUNING_PLAN.md @@ -281,7 +281,7 @@ functionally a v9.26.3. # Part II — TDD Test Plan -Companion to `V9.26.4_PRUNING_PLAN.md`. This is the test blueprint that must be **100% +Companion to `doc/v9.26.4/V9.26.4_PRUNING_PLAN.md`. This is the test blueprint that must be **100% green before tag**. It follows strict TDD: every test is written **RED first** (fails on today's v9.26.3), then the implementation makes it **GREEN**, and the whole suite is the release gate. The job the tests verify is plumbing — that every reader on a pruned node @@ -320,7 +320,7 @@ matter on mainnet.) ## 1. TDD workflow (per change) -For each of the 6 changes in `V9.26.4_PRUNING_PLAN.md §4`: +For each of the 6 changes in `doc/v9.26.4/V9.26.4_PRUNING_PLAN.md §4`: 1. **RED** — write the unit/functional test for the intended behavior; run it on the current tree; capture the *specific* failure (e.g. InitError `"DigiDollar requires -txindex=1"`). Commit the test (marked expected-fail / skipped diff --git a/V9.26.4_PR_EXPLAINER.md b/doc/v9.26.4/V9.26.4_PR_EXPLAINER.md similarity index 97% rename from V9.26.4_PR_EXPLAINER.md rename to doc/v9.26.4/V9.26.4_PR_EXPLAINER.md index b16c4ee8e18..8d7bc78e958 100644 --- a/V9.26.4_PR_EXPLAINER.md +++ b/doc/v9.26.4/V9.26.4_PR_EXPLAINER.md @@ -143,7 +143,7 @@ remove `prune=`, set `txindex=1`, restart with `-reindex`. - a **truncated/partially-restored DD-era block file** (which passes the index-flag guard) is caught by the fail-closed startup reconstruction and refuses to start — then recovers with `-reindex`. -- **Live mainnet validation** (full record: `V9.26.4_MAINNET_VALIDATION.md`): +- **Live mainnet validation** (full record: `doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`): we ran the release Qt binary against a copy of a synced mainnet datadir with `prune=2000` and no txindex, on the live network: - the prune lock registered at 23,627,520 while DigiDollar is still @@ -288,9 +288,9 @@ a full v9.26.4 node (txindex + stats index) — separate ports, same live networ | Document | Content | |---|---| -| `V9.26.4_PRUNING_PLAN.md` | Design + full TDD test plan | -| `V9.26.4_PRUNING_EXPLAINER.md` | Short shareable community explainer | -| `V9.26.4_MAINNET_VALIDATION.md` | The live mainnet test record | +| `doc/v9.26.4/V9.26.4_PRUNING_PLAN.md` | Design + full TDD test plan | +| `doc/v9.26.4/V9.26.4_PRUNING_EXPLAINER.md` | Short shareable community explainer | +| `doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md` | The live mainnet test record | | `doc/release-notes/release-notes-9.26.4.md` | Operator-facing release notes & limitations | *Fine print: pruned nodes don't serve deep history to new peers (archival nodes diff --git a/V9.26.4_TESTNET_PRUNE_SUMMARY.md b/doc/v9.26.4/V9.26.4_TESTNET_PRUNE_SUMMARY.md similarity index 97% rename from V9.26.4_TESTNET_PRUNE_SUMMARY.md rename to doc/v9.26.4/V9.26.4_TESTNET_PRUNE_SUMMARY.md index efdc609efc7..8a02e178d04 100644 --- a/V9.26.4_TESTNET_PRUNE_SUMMARY.md +++ b/doc/v9.26.4/V9.26.4_TESTNET_PRUNE_SUMMARY.md @@ -54,7 +54,7 @@ testnet proves the **functional** side (a pruned node validates / mints / redeem DigiDollar correctly and stays in lockstep with full nodes), while the **disk-saving** side was proven on **mainnet** separately: **38 GB → 0.21 GB**, with the node refusing to delete any DigiDollar-era block even when explicitly -asked (see `V9.26.4_MAINNET_VALIDATION.md`). +asked (see `doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`). ## Every scenario we ran (all passed) @@ -72,7 +72,7 @@ Nothing is left on the testnet list. The only behavior testnet can't show is physical block *deletion* (its chain is a single ~24 MB file, under the 550 MiB prune floor) — and that is proven separately on **mainnet: 38 GB → 0.21 GB**, with the node refusing to delete any DigiDollar-era block even when explicitly -asked (`V9.26.4_MAINNET_VALIDATION.md`). Our automated regtest suite adds 15 +asked (`doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`). Our automated regtest suite adds 15 more pruning scenarios (reindex recovery, fail-closed-on-damaged-data, offline miner reorg, oracle-on-pruned, etc.). diff --git a/V9.26.4_TESTNET_PRUNE_TESTS.md b/doc/v9.26.4/V9.26.4_TESTNET_PRUNE_TESTS.md similarity index 98% rename from V9.26.4_TESTNET_PRUNE_TESTS.md rename to doc/v9.26.4/V9.26.4_TESTNET_PRUNE_TESTS.md index 98a91f39ef5..276e8d4d833 100644 --- a/V9.26.4_TESTNET_PRUNE_TESTS.md +++ b/doc/v9.26.4/V9.26.4_TESTNET_PRUNE_TESTS.md @@ -2,7 +2,7 @@ *— DigiSwarm, DGB AI dev team · live testnet26, 2026-07-02* -Companion to `V9.26.4_MAINNET_VALIDATION.md`. Where the mainnet record proves the +Companion to `doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`. Where the mainnet record proves the prune lock and disk behavior on the live mainnet chain (DigiDollar still *signaling*), this record exercises the **full operational battery on testnet26, where DigiDollar is already ACTIVE** (activation height 599/600) — so a pruned @@ -64,7 +64,7 @@ deleted once *every* block in it is prunable — this one holds the whole chain tip, so it can never be removed. So testnet cannot demonstrate actual block *deletion*; the prune lock is registered at 600 and would clamp if deletion ever occurred. Real deletion + the clamp are proven on mainnet: **38 GB → 0.21 GB**, `pruneblockchain 23774000` clamped to -23,511,221 (`V9.26.4_MAINNET_VALIDATION.md`). +23,511,221 (`doc/v9.26.4/V9.26.4_MAINNET_VALIDATION.md`). ### T3 — Full-vs-pruned parity (live) Full node (`~/.dgb-tnF`, txindex + stats index) vs pruned node (UTXO-scan fallback), same tip: diff --git a/reports/doc_validation_final_report.md b/reports/doc_validation_final_report.md new file mode 100644 index 00000000000..3ec4eaeb9dc --- /dev/null +++ b/reports/doc_validation_final_report.md @@ -0,0 +1,83 @@ +# DigiByte v9.26.2 — Root-Markdown Release Validation: Final Report + +Run date: 2026-06-25 · Branch: `feature/digidollar-v1` · Driver: `doc/v9.26.2/Z_DOC_VALIDATION_v9.26.2_PROMPT.md` + +Archive note: Findings and work status below describe the June 2026 review. File references have been updated for the documentation archive; this report does not certify the current release. + +## Verdict: **READY WITH 3 OPEN ITEMS FOR JARED** + +All 21 Tier A/B root `.md` docs (incl. `doc/release-notes/RELEASE_v9.26.2.md`) are validated against +current source, cross-doc consistent, and free of stale roster figures, RC labels, +and Bitcoin-branding errors. Three items require Jared's decision (no code-derivable +fix; nothing was invented). Nothing was committed. + +## Method +- **Wave 0:** 5 scouts code-derived the canonical-facts ledger (`reports/doc_validation_canonical_facts.md`). +- **Waves 1–5:** validate→fix→adversarially-verify pipeline over 20 docs (each agent owned one file, applied only minimal source-cited edits; an independent read-only agent on a different model re-checked every edit). +`doc/release-notes/RELEASE_v9.26.2.md` validated separately (created mid-run). +- **Consistency sweep + orchestrator reconciliation:** spot-checked critical numbers against code, fixed the one flagged bad edit, de-RC'd stale labels, bumped version headers, resolved cross-doc drift. +- Totals: 47 sub-agents, ~3.5M tokens, ~2,270 tool calls. + +## Per-doc status +| Doc | Verdict | Notes | +|---|---|---| +| doc/release-notes/RELEASE_v9.26.2.md | VERIFIED (clean) | every claim matches code; ports/roster/activation/RPC confirmed | +| DIGIDOLLAR_WALLET_INTEGRATION.md | VERIFIED | RPC names/params confirmed | +| DIGIDOLLAR_EXCHANGE_INTEGRATION.md | VERIFIED | units/prefixes confirmed | +| DIGIDOLLAR_MINING_INTEGRATION_GUIDE.md | VERIFIED | GBT rule + miner path confirmed | +| ORACLE_DISCOVERY_ARCHITECTURE.md | VERIFIED | P2P getoracles confirmed | +| REPO_MAP_DIGIDOLLAR.md | VERIFIED | RPC count 18 + de-RC applied | +| DIGIDOLLAR_ORACLE_ARCHITECTURE.md | VERIFIED (reconciled) | duplicate heading fixed (5.1/5.4); de-RC; version header | +| DIGIDOLLAR_ARCHITECTURE.md | VERIFIED (reconciled) | version header → v9.26.2 | +| DIGIDOLLAR_ACTIVATION_EXPLAINER.md | VERIFIED | activation values confirmed | +| DIGIDOLLAR_EXPLAINER.md | VERIFIED | last-verified date synced | +| DIGIDOLLAR_ORACLE_EXPLAINER.md | VERIFIED | RC44 label removed from subtitle | +| DIGIDOLLAR_ORACLE_SETUP.md | VERIFIED | de-RC; roster confirmed | +| ORACLE_BUNDLE_EXPLAINER.md | VERIFIED | de-RC; 9-of-17 kept as history | +| ARCHITECTURE.md | VERIFIED | core constants confirmed | +| CLAUDE.md | VERIFIED | surface warnings confirmed; v9.26 lineage refs kept | +| REPO_MAP.md / REPO_MAP_GUIDE.md | VERIFIED | titles → v9.26.2 | +| README.md | VERIFIED w/ open item | see #2 | +| CONTRIBUTING.md | VERIFIED w/ open item | see #3 | +| SECURITY.md | VERIFIED w/ open item | see #1 | +| INSTALL.md | VERIFIED | trailing newline fixed | + +## Corrections applied (orchestrator reconciliation, on top of agent edits) +- Fixed bad edit: duplicate "ConnectBlock() Integration" heading → `5.1 ConnectBlock() Oracle Validation` / `5.4 ConnectBlock() Price-Cache Update` (`DIGIDOLLAR_ORACLE_ARCHITECTURE.md`); both confirmed against `src/validation.cpp:2852`. +- De-RC sweep (current keys are "RC46" in code; final release): removed present-tense `RC44` labels in ORACLE_BUNDLE_EXPLAINER, DIGIDOLLAR_ORACLE_SETUP (×2), DIGIDOLLAR_ORACLE_ARCHITECTURE (×2), REPO_MAP_DIGIDOLLAR (×3), DIGIDOLLAR_ORACLE_EXPLAINER subtitle. Historical `RC38`/`RC41` references kept. +- Version headers/titles → v9.26.2: REPO_MAP, REPO_MAP_DIGIDOLLAR, REPO_MAP_GUIDE, DIGIDOLLAR_ARCHITECTURE, DIGIDOLLAR_ORACLE_ARCHITECTURE. +- Minor: INSTALL.md trailing newline; DIGIDOLLAR_EXPLAINER last-verified date 2026-06-25. + +## Cross-doc consistency: PASS +Shared facts (v9.26.2; 7-of-35 / 4-of-7; bit 23 + heights; opcodes 0xbb–0xbf; +ports 12024/12033; HRPs dgb/dgbt/dgbrt; prefixes DD/TD/RD; 18+17 RPCs) are +consistent across all docs and equal the code value. No `21 active` / `9-of-17` +present-tense figures remain (only correct historical mentions). + +## DigiByte/DGB correctness: PASS +No Bitcoin/BTC branding, unit, or port errors. Remaining "Bitcoin Core" mentions +are accurate lineage/coding-standard references; "40x faster than BTC" and +"DGB/BTC × BTC/USDT" are correct comparisons / real exchange pairs. + +## OPEN ITEMS FOR JARED (not code-derivable — nothing invented) +1. **SECURITY.md PGP table** lists Bitcoin Core devs (Wuille, Ford, Chow), not + DigiByte security key holders. Needs the real DigiByte disclosure keys — + cannot be synthesized from code. +2. **README.md circulating supply** "14,293,304,147 DGB (May 2021)" is ~5 years + stale (pre-existing). Needs a current figure from an external data source. +3. **CONTRIBUTING.md** references a `master` branch (lines 80, 169, 403) and a + `github.com/digibyte/digibyte/.../master/...` URL; repo default is `develop` + and README uses `github.com/DigiByte-Core/digibyte`. Inherited Bitcoin Core + workflow text — confirm DigiByte's actual contributor branch/URL before fixing. + +## CODE_DISCREPANCY_FOR_JARED +None. No doc was found to be correct against incorrect code; no code was edited. + +## Human-check (out of code scope) +- External URL `https://digibyte.io/activation` (RELEASE notes) — confirm live. +- No `v9.26.2` git tag exists yet (latest: `v9.26.0-rc46`, `v9.26.1-pre2`) — + expected for a not-yet-tagged release; tag at release time. + +## Commit status +Uncommitted — left in working tree for review (`git diff -- '*.md'`). Proposed +split: (a) version/title bumps to v9.26.2; (b) oracle de-RC label cleanup; +(c) DIGIDOLLAR_ORACLE_ARCHITECTURE heading fix; (d) minor (INSTALL newline, dates). From bc9e6f4ca9f4c308ec101c745d69b1826c8cd49c Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:24:03 -0600 Subject: [PATCH 03/71] Scripts Cleanup --- build_digibyte_ubuntu.sh => contrib/build_digibyte_ubuntu.sh | 0 deploy_testnet_oracle.sh => contrib/deploy_testnet_oracle.sh | 0 finish_oracle_setup.sh => contrib/finish_oracle_setup.sh | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename build_digibyte_ubuntu.sh => contrib/build_digibyte_ubuntu.sh (100%) rename deploy_testnet_oracle.sh => contrib/deploy_testnet_oracle.sh (100%) rename finish_oracle_setup.sh => contrib/finish_oracle_setup.sh (100%) diff --git a/build_digibyte_ubuntu.sh b/contrib/build_digibyte_ubuntu.sh similarity index 100% rename from build_digibyte_ubuntu.sh rename to contrib/build_digibyte_ubuntu.sh diff --git a/deploy_testnet_oracle.sh b/contrib/deploy_testnet_oracle.sh similarity index 100% rename from deploy_testnet_oracle.sh rename to contrib/deploy_testnet_oracle.sh diff --git a/finish_oracle_setup.sh b/contrib/finish_oracle_setup.sh similarity index 100% rename from finish_oracle_setup.sh rename to contrib/finish_oracle_setup.sh From ca292a0c1acc1d02b264f09503741597e21256cb Mon Sep 17 00:00:00 2001 From: DigiSwarm <13957390+JaredTate@users.noreply.github.com> Date: Fri, 11 Sep 2026 19:07:42 -0600 Subject: [PATCH 04/71] v9.26.6 Wave 1: node crash, node deadlocks, $100k send/redeem cap, log noise What was wrong: live nodes crashed (a null read during block connect on the message-handler thread), live nodes froze (two threads taking the same two locks in opposite order), a decimal-point typo could send or redeem 100 times the intended DigiDollar amount, and three log lines were written for every transaction on every node regardless of settings. What changed, one line per item: - Crash: during a reorg the mempool's index update was run on entries that belonged to the Dandelion stempool, re-linking stempool entries into the mempool's indexes and cross-wiring both pools; the next block connect walked a broken structure. Each pool now only updates its own entries. - Crash hardening: the stempool's self-check was hard-wired off; it now follows -checkmempool (on in tests, off on mainnet by default), and the stempool lock is asserted at block connect and disconnect. - Crash: the wallet's fallback broadcast path (used when no Dandelion peer is available) changed the stempool and ran mempool acceptance with no locks; it now takes cs_main and the stempool lock in the same order as every other stempool writer. - Crash: on UTXO-snapshot activation the stempool stayed on the old chainstate, an inconsistent lock order; it now moves with the mempool. - Deadlock: the once-a-second embargo check held the Dandelion embargo lock while taking the chain lock, the opposite order from the network thread. The embargo lock is now a true leaf lock: the map is copied under it and nothing else, and every pool lookup and the mempool move happen unlocked. - Deadlock: when a peer disconnected with Dandelion transactions still queued, FinalizeNode held that peer's lock while taking global locks in the opposite order from the stem push path; it now copies the list out first. - Deadlock: the inventory handler asked "is this a Dandelion peer" while holding that peer's lock, again in the opposite order, on every connection; it now asks first. - Money at risk: senddigidollar and redeemdigidollar reject any amount above $100,000 before any coin selection or transaction construction, as sendmanydigidollar already did. The explicit amount-unit contract comes in a later wave. - Disk fills up: the three unconditional per-transaction log lines are gone. - Baseline repair: a unit test looked for deploy_testnet_oracle.sh at the repo root after the scripts cleanup moved it to contrib/; it looks there too. Why this fix: each repair is the smallest change that removes the established defect, keeps every existing lock order that other code depends on, and leaves block validity untouched (relay pools, locks, RPC input checks, logging and tests only). The original crash report (GitHub #445) had no stack trace, so it stays classified "investigated, cause unconfirmed"; the repaired reorg defect matches its thread, function, fault address and workload. How it was tested: every fix has a regression test that fails before and passes after (p2p_dandelion_lockorder.py and four dandelion_tests cases; dandelion_stempool_reorg_tests, p2p_dandelion_stempool_reorg.py, p2p_dandelion_stempool_race.py, wallet_dandelion_fallback_lock.py; digidollar_rpc_amount_cap.py, digidollar_isstandardtx_no_log.py). On this tree: full unit suite passes; Qt suite on a real display passes with no skips; extended functional suite 374 of 374; the Dandelion tests pass on a DEBUG_LOCKORDER build, an address-sanitizer build and a thread-sanitizer build with zero sanitizer reports (the race test asserts that embargo expiries ran while blocks were being connected); every fuzz target ran over a generated corpus with no findings; the Qt wallet minted, sent and redeemed DigiDollar on regtest with screenshots of every DigiDollar tab. Claude-Session: https://claude.ai/code/session_01KmXsuD4LyZWpsaytcei9ma --- src/Makefile.test.include | 1 + src/init.cpp | 7 +- src/net_processing.cpp | 289 +++++++++++------ src/node/transaction.cpp | 12 +- src/policy/policy.cpp | 8 - src/rpc/digidollar.cpp | 32 +- src/test/dandelion_stempool_reorg_tests.cpp | 268 ++++++++++++++++ src/test/dandelion_tests.cpp | 295 ++++++++++++++++++ .../digidollar_wave21_dos_resource_tests.cpp | 26 +- .../validation_chainstatemanager_tests.cpp | 3 + src/validation.cpp | 76 ++++- .../digidollar_isstandardtx_no_log.py | 72 +++++ test/functional/digidollar_rpc_amount_cap.py | 166 ++++++++++ test/functional/p2p_dandelion_lockorder.py | 163 ++++++++++ .../functional/p2p_dandelion_stempool_race.py | 254 +++++++++++++++ .../p2p_dandelion_stempool_reorg.py | 157 ++++++++++ test/functional/test_runner.py | 6 + .../wallet_dandelion_fallback_lock.py | 52 +++ 18 files changed, 1757 insertions(+), 130 deletions(-) create mode 100644 src/test/dandelion_stempool_reorg_tests.cpp create mode 100755 test/functional/digidollar_isstandardtx_no_log.py create mode 100755 test/functional/digidollar_rpc_amount_cap.py create mode 100755 test/functional/p2p_dandelion_lockorder.py create mode 100755 test/functional/p2p_dandelion_stempool_race.py create mode 100755 test/functional/p2p_dandelion_stempool_reorg.py create mode 100755 test/functional/wallet_dandelion_fallback_lock.py diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 18e44eb4192..e64799531c8 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -232,6 +232,7 @@ DIGIBYTE_TESTS =\ test/crypto_tests.cpp \ test/cuckoocache_tests.cpp \ test/dbwrapper_tests.cpp \ + test/dandelion_stempool_reorg_tests.cpp \ test/dandelion_tests.cpp \ test/denialofservice_tests.cpp \ test/deployment_burial_tests.cpp \ diff --git a/src/init.cpp b/src/init.cpp index de8b10b505c..549a34343e4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1789,7 +1789,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // Initialize DigiByte stempool for Dandelion++ privacy protocol CTxMemPool::Options stempool_opts{ .estimator = nullptr, - .check_ratio = 0, + // The stempool checks its own bookkeeping as often as the main + // mempool does (-checkmempool; regtest and the tests use 1, so + // every block and transaction is checked). This used to be + // hard-wired to 0, so a corrupted stempool went unnoticed until + // the node crashed. + .check_ratio = mempool_opts.check_ratio, .min_relay_feerate = mempool_opts.min_relay_feerate, // Use same relay fee as mempool .max_datacarrier_bytes = mempool_opts.max_datacarrier_bytes, // Use same datacarrier settings as mempool .is_stempool = true, diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 5d40d8b360b..4074566d7d4 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1639,117 +1639,177 @@ void PeerManagerImpl::RelayDandelionTransaction(const CTransaction& tx, CNode* p void PeerManagerImpl::CheckDandelionEmbargoes() { // ========================================================================= - // Bug #29 fix: ABBA deadlock between m_nodes_mutex and m_dandelion_embargo_mutex + // Lock-order rule for this function: m_dandelion_embargo_mutex is a leaf + // lock. It is NEVER held while acquiring any other lock: not cs_main, not + // m_nodes_mutex, and not the mempool or stempool locks that the pool + // accessors (exists, get, size) take internally. // - // The established lock ordering throughout the Dandelion++ code is: - // m_nodes_mutex FIRST, then m_dandelion_embargo_mutex SECOND + // Two orders are established elsewhere and must not be inverted here: + // cs_main -> m_dandelion_embargo_mutex + // The DANDELIONTX handler in ProcessMessage() holds cs_main while + // CConnman::insertDandelionEmbargo() takes the embargo mutex. + // m_nodes_mutex -> m_dandelion_embargo_mutex + // DandelionShuffle() and CloseDandelionConnections() (dandelion.cpp), + // the latter under DisconnectNodes() in net.cpp. // - // This ordering is used by: - // - DandelionShuffle() (dandelion.cpp:344 → 357) - // - CloseDandelionConnections() (dandelion.cpp:211 → 292) - // - DisconnectNodes() (net.cpp:1952 → CloseDandelionConnections) + // Why this matters: this function has frozen whole nodes twice, both + // times because it held the embargo mutex while calling something that + // takes another lock. First against the shuffle timer, which takes + // m_nodes_mutex. Then against the network thread, which holds cs_main + // when it receives a stem transaction and then takes the embargo mutex. + // When two threads take the same two locks in opposite order and overlap, + // each waits for the other forever: logging stops and every RPC times + // out. So nothing that needs cs_main or m_nodes_mutex may run while the + // embargo mutex is held. // - // Before this fix, CheckDandelionEmbargoes() violated that ordering: - // it held m_dandelion_embargo_mutex, then called usingDandelion() and - // localDandelionDestinationPushInventory(), both of which acquire - // m_nodes_mutex internally. This created a classic ABBA deadlock: + // Structure: nothing that needs cs_main or m_nodes_mutex runs while the + // embargo mutex is held. + // Phase 0 — usingDandelion() (m_nodes_mutex) before any embargo lock. + // Phase 1 — under the embargo mutex only: copy the map (txid, expiry, + // stem-routed flag). No other lock of any kind is taken while + // the embargo mutex is held; it is a leaf lock. + // Phase 2 — without the embargo mutex: classify each copy. Entries + // already in the mempool are done. Expired entries are moved + // from the stempool to the mempool: take cs_main per entry, + // AcceptToMemoryPool, relay on success, then remove it from + // the stempool under m_stempool.cs. Unexpired entries that + // still sit in the stempool are collected for stem routing. + // Phase 3 — re-take the embargo mutex and erase the finished entries, + // but only where the stored expiry still matches what Phase 1 + // saw: a peer or the wallet may have re-inserted the txid with + // a new embargo while the mutex was released. + // Phase 4 — stem routing (m_nodes_mutex) without the embargo mutex, then + // a brief re-lock to mark each routed txid. // - // Thread A (shuffle timer): LOCK(m_nodes_mutex) → LOCK(m_dandelion_embargo_mutex) - // Thread B (embargo timer): LOCK(m_dandelion_embargo_mutex) → LOCK(m_nodes_mutex) - // - // When both fire concurrently, each thread holds the lock the other needs. - // Result: sendtoaddress hangs forever at "Processing Dandelion relay", - // shutdown hangs on threadDandelionShuffle.join(), RPC times out. - // (Reported by DanGB on Windows 11, RC26, reproducible after ~1 week uptime.) - // - // Fix: restructure this function into two phases: - // Phase 1 — under m_dandelion_embargo_mutex: scan the embargo map, handle - // expired/mempool entries, collect txids that need stem routing. - // Phase 2 — after releasing m_dandelion_embargo_mutex: perform the stem - // routing (which needs m_nodes_mutex), then briefly re-acquire - // m_dandelion_embargo_mutex to mark them as routed. - // - // usingDandelion() is also moved before the embargo lock for the same reason. - // The bool may be momentarily stale, but that only means we skip one routing - // cycle (~1 second) — no correctness impact. + // usingDandelion() is read before the embargo lock. The bool may be + // momentarily stale, but that only means we skip one routing cycle + // (~1 second) — no correctness impact. // ========================================================================= // Phase 0: query Dandelion destination availability WITHOUT holding the // embargo lock. usingDandelion() acquires m_nodes_mutex internally. bool hasDandelionDestinations = m_connman.usingDandelion(); - // Transactions collected during Phase 1 that need stem routing in Phase 2. - std::vector txidsNeedingStemRoute; - - // Phase 1: scan embargo map under m_dandelion_embargo_mutex. - // Everything in this block touches only embargo-protected state (plus - // cs_main for AcceptToMemoryPool, which has no ordering conflict here). + // A snapshot of one embargo-map entry, copied out in Phase 1. Every + // decision about it is made later, with no lock held, from this copy: + // the expiry lets Phase 3 notice if someone re-embargoed the same + // transaction while we were unlocked. + struct EmbargoSnapshot { + uint256 txid; + std::chrono::microseconds expiry; + bool already_stem_routed; + }; + std::vector snapshots; + std::chrono::milliseconds current_time; + + // Phase 1: copy the embargo map under m_dandelion_embargo_mutex and do + // nothing else. The mutex is a leaf lock: no mempool or stempool call is + // made while it is held (those take their own pool lock inside), nothing + // is logged (the logger has a lock too), and cs_main and m_nodes_mutex + // are never taken here. All lookups happen in Phase 2 on the copy. { LOCK(m_connman.m_dandelion_embargo_mutex); - auto current_time = GetTime(); - - // Log embargo checks (debug level only — this fires every second) - if (!m_connman.mDandelionEmbargo.empty()) { - LogPrint(BCLog::DANDELION, "CheckDandelionEmbargoes: Checking %d embargoed transactions (stempool=%d, mempool=%d)\n", - m_connman.mDandelionEmbargo.size(), m_stempool.size(), m_mempool.size()); - } - - for (auto iter = m_connman.mDandelionEmbargo.begin(); iter != m_connman.mDandelionEmbargo.end();) { - if (m_mempool.exists(iter->first)) { - LogPrint(BCLog::DANDELION, "Embargoed dandeliontx %s found in mempool; removing from embargo map\n", iter->first.ToString()); - m_connman.m_dandelion_stem_routed.erase(iter->first); - iter = m_connman.mDandelionEmbargo.erase(iter); - } else if (iter->second < current_time) { - LogPrintf("CheckDandelionEmbargoes: dandeliontx %s embargo expired\n", iter->first.ToString()); - CTransactionRef ptx = m_stempool.get(iter->first); - if (ptx) { - LogPrintf("CheckDandelionEmbargoes: Moving transaction %s from stempool to mempool for broadcast\n", iter->first.ToString()); - bool accepted_to_mempool{false}; - { - LOCK(cs_main); - const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, ptx, false); - if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { - accepted_to_mempool = true; - LogPrintf("CheckDandelionEmbargoes: Successfully moved tx %s to mempool\n", iter->first.ToString()); - LogPrint(BCLog::MEMPOOL, "AcceptToMemoryPool: accepted %s (poolsz %u txn, %u kB)\n", - iter->first.ToString(), m_mempool.size(), m_mempool.DynamicMemoryUsage() / 1000); - RelayTransaction(ptx->GetHash(), ptx->GetWitnessHash()); - } else { - LogPrintf("CheckDandelionEmbargoes: Failed to move tx %s to mempool: %s\n", - iter->first.ToString(), result.m_state.ToString()); - } - } - WITH_LOCK(m_stempool.cs, m_stempool.removeRecursive(*ptx, accepted_to_mempool ? MemPoolRemovalReason::REORG : MemPoolRemovalReason::EXPIRY)); - } else { - LogPrintf("CheckDandelionEmbargoes: Transaction %s not found in stempool!\n", iter->first.ToString()); - } - m_connman.m_dandelion_stem_routed.erase(iter->first); - iter = m_connman.mDandelionEmbargo.erase(iter); + current_time = GetTime(); + snapshots.reserve(m_connman.mDandelionEmbargo.size()); + for (const auto& [txid, expiry] : m_connman.mDandelionEmbargo) { + snapshots.push_back({txid, expiry, m_connman.m_dandelion_stem_routed.count(txid) != 0}); + } + } // m_dandelion_embargo_mutex released here. + + if (snapshots.empty()) return; + // Log embargo checks (debug level only — this fires every second) + LogPrint(BCLog::DANDELION, "CheckDandelionEmbargoes: Checking %d embargoed transactions (stempool=%d, mempool=%d)\n", + snapshots.size(), m_stempool.size(), m_mempool.size()); + + // Entries to erase from the embargo map in Phase 3: those already in the + // mempool, and those whose embargo expired and was processed here. + std::vector finished; + // Transactions that still need stem routing in Phase 4. + std::vector txidsNeedingStemRoute; + + // Phase 2: classify each snapshot and move expired transactions from the + // stempool to the mempool, all WITHOUT holding m_dandelion_embargo_mutex. + // cs_main is taken here, per transaction, exactly as AcceptToMemoryPool + // requires. + for (const EmbargoSnapshot& snap : snapshots) { + if (m_mempool.exists(snap.txid)) { + LogPrint(BCLog::DANDELION, "Embargoed dandeliontx %s found in mempool; removing from embargo map\n", snap.txid.ToString()); + finished.push_back(snap); + continue; + } + if (snap.expiry >= current_time) { + // Embargo not yet expired — check if this TX needs stem routing: + // 1. We have Dandelion destinations available + // 2. This TX has NOT already been successfully routed + // (prevents the spam bug where we re-send every second) + // 3. OR the previous Dandelion destination disconnected (destination changed) + // + // We only COLLECT the txid here. The actual push happens in Phase 4, + // because localDandelionDestinationPushInventory() acquires m_nodes_mutex. + if (hasDandelionDestinations && !snap.already_stem_routed && m_stempool.exists(snap.txid)) { + txidsNeedingStemRoute.push_back(snap.txid); + } + // Log remaining time + auto remaining = std::chrono::duration_cast(snap.expiry - current_time).count(); + LogPrint(BCLog::DANDELION, "Transaction %s embargo expires in %d seconds\n", snap.txid.ToString(), remaining); + continue; + } + + LogPrintf("CheckDandelionEmbargoes: dandeliontx %s embargo expired\n", snap.txid.ToString()); + finished.push_back(snap); + // The CTransactionRef keeps the transaction alive across the + // unlocked interval below. + const CTransactionRef ptx = m_stempool.get(snap.txid); + if (!ptx) { + LogPrintf("CheckDandelionEmbargoes: Transaction %s not found in stempool!\n", snap.txid.ToString()); + continue; + } + LogPrintf("CheckDandelionEmbargoes: Moving transaction %s from stempool to mempool for broadcast\n", snap.txid.ToString()); + bool accepted_to_mempool{false}; + { + LOCK(cs_main); + const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, ptx, false); + if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { + accepted_to_mempool = true; + LogPrintf("CheckDandelionEmbargoes: Successfully moved tx %s to mempool\n", snap.txid.ToString()); + LogPrint(BCLog::MEMPOOL, "AcceptToMemoryPool: accepted %s (poolsz %u txn, %u kB)\n", + snap.txid.ToString(), m_mempool.size(), m_mempool.DynamicMemoryUsage() / 1000); + RelayTransaction(ptx->GetHash(), ptx->GetWitnessHash()); } else { - // Embargo not yet expired — check if this TX needs stem routing: - // 1. We have Dandelion destinations available - // 2. This TX has NOT already been successfully routed - // (prevents the spam bug where we re-send every second) - // 3. OR the previous Dandelion destination disconnected (destination changed) - // - // We only COLLECT the txid here. The actual push happens in Phase 2, - // after we release m_dandelion_embargo_mutex, because - // localDandelionDestinationPushInventory() acquires m_nodes_mutex. - if (hasDandelionDestinations && m_connman.m_dandelion_stem_routed.count(iter->first) == 0) { - if (m_stempool.exists(iter->first)) { - txidsNeedingStemRoute.push_back(iter->first); - } - } + LogPrintf("CheckDandelionEmbargoes: Failed to move tx %s to mempool: %s\n", + snap.txid.ToString(), result.m_state.ToString()); + } + } + WITH_LOCK(m_stempool.cs, m_stempool.removeRecursive(*ptx, accepted_to_mempool ? MemPoolRemovalReason::REORG : MemPoolRemovalReason::EXPIRY)); + } - // Log remaining time - auto remaining = std::chrono::duration_cast(iter->second - current_time).count(); - LogPrint(BCLog::DANDELION, "Transaction %s embargo expires in %d seconds\n", iter->first.ToString(), remaining); - iter++; + // Phase 3: erase the finished entries under the embargo mutex. Re-check + // each one first: while the mutex was released the txid may have been + // erased, or re-inserted with a new expiry by insertDandelionEmbargo() + // (a peer re-sent the transaction, or the wallet re-broadcast it). A new + // embargo is left in place so it can run its course. The old stem-routed + // mark is cleared either way because the stem phase it belonged to is over. + // Even the logger takes a lock of its own, so nothing is logged while the + // embargo mutex is held; re-embargoed entries are noted and logged after. + std::vector re_embargoed; + if (!finished.empty()) { + LOCK(m_connman.m_dandelion_embargo_mutex); + for (const EmbargoSnapshot& snap : finished) { + m_connman.m_dandelion_stem_routed.erase(snap.txid); + auto entry = m_connman.mDandelionEmbargo.find(snap.txid); + if (entry == m_connman.mDandelionEmbargo.end()) continue; + if (entry->second != snap.expiry) { + re_embargoed.push_back(snap.txid); + continue; } + m_connman.mDandelionEmbargo.erase(entry); } - } // m_dandelion_embargo_mutex released here — safe to touch m_nodes_mutex now. + } + for (const uint256& txid : re_embargoed) { + LogPrint(BCLog::DANDELION, "CheckDandelionEmbargoes: dandeliontx %s was re-embargoed while unlocked; keeping the new embargo\n", txid.ToString()); + } - // Phase 2: perform stem routing WITHOUT holding m_dandelion_embargo_mutex. + // Phase 4: perform stem routing WITHOUT holding m_dandelion_embargo_mutex. // localDandelionDestinationPushInventory() acquires m_nodes_mutex, which is // now safe because we no longer hold the embargo lock. for (const auto& txid : txidsNeedingStemRoute) { @@ -1889,16 +1949,29 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) // Dandelion: Handle any pending Dandelion transactions auto tx_relay = peer->GetTxRelay(); if (tx_relay) { - LOCK(tx_relay->m_tx_inventory_mutex); - if (!tx_relay->vInventoryDandelionTxToSend.empty()) { - LogPrintf("FinalizeNode: Peer %d disconnecting with %d pending Dandelion transactions\n", - nodeid, tx_relay->vInventoryDandelionTxToSend.size()); - + // Copy the pending hashes out and release this peer's inventory + // lock BEFORE calling getLocalDandelionDestination() (which takes + // m_nodes_mutex) or PushDandelionInventory() (which takes + // m_peer_mutex and then the destination's inventory lock). The + // stem push path takes those same locks in the opposite order, so + // holding this peer's inventory lock across these calls could + // deadlock the node. The copy is a plain vector owned by this + // function; the peer is being finalized, so its own queue is + // never sent again and nothing below needs it after the unlock. + std::vector pending_dandelion_txs; + { + LOCK(tx_relay->m_tx_inventory_mutex); + pending_dandelion_txs = tx_relay->vInventoryDandelionTxToSend; + } + if (!pending_dandelion_txs.empty()) { + LogPrintf("FinalizeNode: Peer %d disconnecting with %d pending Dandelion transactions\n", + nodeid, pending_dandelion_txs.size()); + // Get the current local Dandelion destination CNode* newDestination = m_connman.getLocalDandelionDestination(); if (newDestination && newDestination != &node) { // Re-queue the pending transactions to the new destination - for (const uint256& txhash : tx_relay->vInventoryDandelionTxToSend) { + for (const uint256& txhash : pending_dandelion_txs) { CInv inv(MSG_DANDELION_TX, txhash); if (PushDandelionInventory(newDestination, inv)) { LogPrintf("FinalizeNode: Re-queued Dandelion transaction %s to peer %d\n", @@ -4336,16 +4409,24 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } } else if (inv.IsDandelionMsg()) { if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + // Ask connman whether this is a Dandelion inbound peer + // BEFORE taking the inventory lock. isDandelionInbound() + // takes m_nodes_mutex, and the stem push path holds + // m_nodes_mutex while taking a peer's inventory lock, so + // nesting them the other way round here could deadlock the + // node. Every peer announces the discovery hash right + // after its handshake, so this runs on every connection. + const bool is_dandelion_inbound = m_connman.isDandelionInbound(&pfrom); LOCK(tx_relay->m_tx_inventory_mutex); auto result = tx_relay->setDandelionInventoryKnown.insert(inv.hash); const bool fAlreadyHave = !result.second; LogPrint(BCLog::DANDELION, "ProcessMessage INV: Got dandelion inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); if ((!fAlreadyHave && !m_chainman.IsInitialBlockDownload() && - m_connman.isDandelionInbound(&pfrom)) || (inv.hash == DANDELION_DISCOVERYHASH)) { + is_dandelion_inbound) || (inv.hash == DANDELION_DISCOVERYHASH)) { std::vector vInv{inv}; - LogPrintf("ProcessMessage INV: Requesting dandelion inv %s from peer=%d (is_inbound=%d, is_discovery=%d)\n", - inv.hash.ToString(), pfrom.GetId(), - m_connman.isDandelionInbound(&pfrom), + LogPrintf("ProcessMessage INV: Requesting dandelion inv %s from peer=%d (is_inbound=%d, is_discovery=%d)\n", + inv.hash.ToString(), pfrom.GetId(), + is_dandelion_inbound, inv.hash == DANDELION_DISCOVERYHASH); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); } diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 9dee50db2a1..f7771a31f9a 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -219,8 +219,16 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t // Only move from stempool to mempool if we haven't already done so if (!already_in_mempool) { LogPrintf("BroadcastTransaction: Moving transaction %s from stempool to mempool for regular broadcast\n", txid.ToString()); - // Remove from stempool and add to mempool for regular broadcast - node.stempool->removeRecursive(*tx, MemPoolRemovalReason::REORG); + // Remove from stempool and add to mempool for regular broadcast. + // This fallback used to run both steps with no lock at all. + // removeRecursive needs the stempool lock (block connect and + // embargo expiry change the stempool from other threads) and + // ProcessTransaction needs cs_main. cs_main is taken first and + // the stempool lock inside it, the same order every other + // stempool writer uses (cs_main, then mempool, then stempool). + // Both are released at the end of this block, before the relay. + LOCK(cs_main); + WITH_LOCK(node.stempool->cs, node.stempool->removeRecursive(*tx, MemPoolRemovalReason::REORG)); const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ false); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { return HandleATMPError(result.m_state, err_string); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 54c8da5bf44..c0c2c31a622 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -115,19 +115,11 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat const bool isDigiDollar = hasDigiDollarMarker && DigiDollar::GetDigiDollarTxType(tx) != DigiDollar::DD_TX_NONE; - LogPrintf("IsStandardTx: version=%d, isDigiDollar=%d, TX_MAX_STANDARD_VERSION=%d\n", - tx.nVersion, isDigiDollar, TX_MAX_STANDARD_VERSION); - if (!isDigiDollar && (tx.nVersion > TX_MAX_STANDARD_VERSION || tx.nVersion < 1)) { - LogPrintf("IsStandardTx: REJECTED - version out of range\n"); reason = "version"; return false; } - if (isDigiDollar) { - LogPrintf("IsStandardTx: ALLOWED - DigiDollar transaction detected\n"); - } - // Extremely large transactions with lots of inputs can cost the network // almost as much to process as they cost the sender in fees, because // computing signature hashes is O(ninputs*txsize). Limiting transactions diff --git a/src/rpc/digidollar.cpp b/src/rpc/digidollar.cpp index f9e843340f5..fd33dfcea39 100644 --- a/src/rpc/digidollar.cpp +++ b/src/rpc/digidollar.cpp @@ -502,6 +502,16 @@ namespace { } #ifdef ENABLE_WALLET + // The largest DigiDollar amount a single senddigidollar or + // redeemdigidollar request may name: 10,000,000 cents = $100,000.00. + // Checked before any coin selection or transaction construction. It is + // the same limit sendmanydigidollar + // applies per recipient, the per-output transfer consensus limit, and the + // mainnet maximum mint, so no legitimate send or full vault redemption is + // ever blocked by it. It exists to stop a typo (a decimal point turns + // "10000" cents into $10,000) from moving far more than intended. + constexpr CAmount MAX_DD_RPC_AMOUNT_CENTS{10000000}; + CAmount ParseDigiDollarRpcAmount(const UniValue& amount_param) { if (!amount_param.isStr() && !amount_param.isNum()) { @@ -1734,7 +1744,7 @@ RPCHelpMan senddigidollar() "This is the primary RPC command for Phase 7.7 - DD transfers via API.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "DigiDollar address to send to (DD/TD/RD prefix)"}, - {"amount", RPCArg::Type::NUM, RPCArg::Optional::NO, "Amount to send: integer cents (e.g. 10000 = $100.00) OR decimal dollars (e.g. 100.00 = $100.00). A decimal point means dollars, so 10000.00 = $10,000.00.", RPCArgOptions{.skip_type_check = true}}, + {"amount", RPCArg::Type::NUM, RPCArg::Optional::NO, "Amount to send: integer cents (e.g. 10000 = $100.00) OR decimal dollars (e.g. 100.00 = $100.00). A decimal point means dollars, so 10000.00 = $10,000.00. Maximum 10000000 cents ($100,000.00) per request.", RPCArgOptions{.skip_type_check = true}}, {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Optional comment for the transaction"}, {"fee_rate", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Deprecated compatibility argument; ignored because DigiDollar sends use the fixed DD fee policy", RPCArgOptions{.skip_type_check = true}}, {"selected_inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Optional DigiDollar inputs to spend, matching listdigidollarunspent output", @@ -1830,6 +1840,14 @@ RPCHelpMan senddigidollar() if (amount <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Amount must be positive"); } + // Reject an over-cap amount here, before the balance query and the + // transfer build. The parser reads "10000.00" as $10,000 (not $100), + // so a habitual decimal point asks for 100 times the intended + // amount. Such a request used to reach the wallet first and come + // back as a misleading balance or transfer error. + if (amount > MAX_DD_RPC_AMOUNT_CENTS) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Amount exceeds maximum transfer limit ($100,000)"); + } // Parse and validate DD address std::string address_error; @@ -2105,7 +2123,7 @@ RPCHelpMan redeemdigidollar() "Only positions that have reached maturity can be redeemed.\n", { {"position_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Position ID (transaction hash of mint)"}, - {"dd_amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Amount of DD to redeem (in cents)"}, + {"dd_amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Amount of DD to redeem (in cents); must equal the vault's full minted amount. Maximum 10000000 cents ($100,000.00)."}, {"redemption_address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "DGB address to receive unlocked collateral (default: new address)"}, {"fee_rate", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Deprecated compatibility argument; ignored because DigiDollar redemptions use the fixed DD fee policy", RPCArgOptions{.skip_type_check = true}} }, @@ -2143,6 +2161,16 @@ RPCHelpMan redeemdigidollar() if (ddAmount <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Redemption amount must be positive"); } + // ddAmount is the vault principal the caller wants to redeem, and + // a vault principal can never exceed $100,000 (the consensus + // maximum mint), so a larger request is always a mistake. Reject + // it before the wallet or any position is consulted. Only this + // caller-supplied principal is capped: the emergency burn computed + // below from the position itself may legitimately exceed the + // principal and is left alone. + if (ddAmount > MAX_DD_RPC_AMOUNT_CENTS) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Amount exceeds maximum redemption limit ($100,000)"); + } if (!IsHex(positionIdStr) || positionIdStr.length() != 64) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid position ID format"); diff --git a/src/test/dandelion_stempool_reorg_tests.cpp b/src/test/dandelion_stempool_reorg_tests.cpp new file mode 100644 index 00000000000..1c0f7b56602 --- /dev/null +++ b/src/test/dandelion_stempool_reorg_tests.cpp @@ -0,0 +1,268 @@ +// 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. + +// The Dandelion stempool must survive a reorg with its internal indexes +// intact. This is the regression test for a node crash (a null read inside +// removeForBlock on the message-handler thread during a block connect). +// +// Chainstate::MaybeUpdateMempoolForReorg runs one "is this entry still valid?" +// filter over the regular mempool and then over the stempool. When the filter +// has to recompute an entry's lock points (its highest confirmed input was in a +// block that has just been disconnected) it updates the entry in place through +// boost::multi_index::modify(). That call must go to the container the +// iterator belongs to. Before the fix it always went to the regular mempool, +// even for stempool iterators, which silently moved the entry's index nodes +// from the stempool into the mempool and cross-wired the two red-black trees. +// The next tree rebalance in either pool (typically removeForBlock during the +// next block connect) then walked a broken tree and crashed the node. +// +// The test builds exactly that situation: a parent confirmed in the tip block, +// stem-phase children of it in the stempool, then InvalidateBlock on the tip. + +#include +#include +#include +#include +#include +#include +#include +#include +#include