Skip to content

Fix/paymaster cli hardening - #451

Closed
Shen wants to merge 60 commits into
DigiByte-Core:developfrom
Shen:fix/paymaster-cli-hardening
Closed

Shen wants to merge 60 commits into
DigiByte-Core:developfrom
Shen:fix/paymaster-cli-hardening

Conversation

@Shen

@Shen Shen commented Sep 23, 2026

Copy link
Copy Markdown

No description provided.

JaredTate and others added 30 commits July 23, 2026 15:18
Remove the legacy account-entry test source after the wallet account APIs it exercises were retired. Keeping the stale source in the MSVC test project prevents test_digibyte from compiling even though the production code no longer exposes those APIs.
Persist bounded liquidity maintenance, provider finance accounting, and backup reminder state. Reconcile successor outputs and historical provider activity idempotently, expose the related RPC status and controls, and tighten readiness and authorization handling for automatic provider operation.
Present provider readiness, offers, limits, liquidity, finances, operations, and wallet-backup guidance as task-oriented views. Add explicit save and refill workflows, clearer state-dependent actions, and exact Paymaster authorization handling while retaining the existing backend safety checks.
Exercise finance and backup persistence, bounded liquidity replenishment, successor recovery, exact client authorization, and the redesigned Qt workflows. Extend functional and fuzz coverage for restart, replay, fee deduction, pool-target, and automatic provider-service edge cases.
Document provider finance accounting, liquidity maintenance, full-wallet backup requirements, fee deduction, automatic operation, and the corresponding Qt and RPC surfaces. Refresh the DigiDollar repository map for the expanded implementation and tests.
Record finalized Paymaster transfers as idempotent outgoing DigiDollar history entries, reconcile older successful sessions at startup, and make history reconstruction less disruptive to wallet locking. Add functional coverage for exact-gross wallet sweeps.
Introduce shared DigiDollar status presentation and theme-consistent controls, refresh only visible pages, seed transaction views from persisted history, and coalesce authoritative wallet scans off the UI thread. Preserve user sorting with timestamp-aware date items and expand targeted Qt coverage.
Treat every direct-session payload as sensitive regardless of connection routing, and keep dedicated inbound and outbound Paymaster sockets isolated from ordinary relay, address, and block traffic. Require a negotiated V2 channel before processing private session messages.

Reject active announcements that reuse admission UTXOs across provider identities so one reserve cannot manufacture apparent capacity or fill the bounded directory. Add network, expiry, overlap, and concurrency regression coverage.
Harden persisted Paymaster record handling so corrupted or unsupported records fail closed before mutation. Redact private proxy context, randomize new identity signatures while preserving durable retries, and expand the corresponding wallet, network, and protocol tests.

Enable wallet-aware security analysis, sanitizer and fuzz workflows, dependency review, and SBOM generation. Document the audit findings, verification evidence, operational privacy boundary, and remaining release work.
Move PaymasterStore and wallet RPC implementations into domain-specific translation units while preserving their public interfaces and method bodies. Wire the new sources into the generated builds, security workflow, architecture map, release notes, and audit.
Shen and others added 29 commits August 10, 2026 00:37
Make client payment and recovery completion fail closed against authoritative Core session state, expose persisted session recovery to Qt, and bind visible actions to Core-provided capabilities.

Harden Paymaster unlock lifetimes, provider setup and reentrancy, snapshot decoding, privacy redaction, finance pagination, pool plan confirmation, and provider runtime serialization. Add Qt, RPC, functional, and manager regression coverage.
Provide a developer entry point, current design specification, implementation
plan and hardening plan for the Paymaster branch. Explain V5 discovery and
signing, explicit authorization, durable recovery, source boundaries and the
review order, with links from the main README and architecture documentation.

Reconcile the operator guide with the current two-step payment authorization
and distinguish dated verification evidence from outstanding release acceptance.
Include the previously local planning documents so all review links resolve
in a checkout of this branch.

Validation: 134 local links and anchors, all 28 acceptance identifiers,
documentation Git visibility, source/payload checks and git diff --check.
This documentation-only change does not renew executable test evidence.
…g 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 DigiByte-Core#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
… fields and tests

What was wrong: the release needs every consensus change (the mint-only
volatility rule, open-vault health accounting, canonical vault identity) to
switch on together at one agreed block height, but the code had no such
height, no shared way to ask "do the Thaw Day rules apply to this block", no
way to schedule it on regtest for boundary tests, and no way to see its
status.

What changed, one line per item:
- Consensus::Params gains nDDThawDayHeight; the maximum int value means "not
  scheduled". Mainnet, testnet26, signet and regtest all set it explicitly
  to "not scheduled" with a comment saying who sets the real value and when
  (mainnet: the release owner at tagging; testnet: when the September 21
  rehearsal candidate is built).
- Two pure functions, DigiDollar::IsThawDayScheduled and
  DigiDollar::IsThawDayActive(params, candidate_height): active only when
  scheduled, when the DigiDollar deployment is enabled and active at that
  height, and when the height is at or past the Thaw Day height. Callers pass
  the height of the block being checked, the intended mining height, or the
  next block height; the sentinel is only compared, never added to.
- Regtest-only startup option -ddthawdayheight=<n>, strictly parsed, a clean
  startup error on any public network.
- getdigidollardeploymentinfo gains a thaw_day object (scheduled, height when
  scheduled, tip_height, next_block_height, active_at_tip,
  active_next_block), computed only through the shared predicate.
- Tests: predicate edge cases; a 40-row activation table over every
  combination of scheduling and DigiDollar activation; the "which height each
  caller uses" contract; the network-configuration matrix; a four-node
  functional test of H-1/H/H+1 status, invalidateblock/reconsiderblock, a
  competing branch, restarts and knob rejection; and an extension of the
  deployment RPC test. Deliberate mutations of the predicate and of the RPC
  were shown to fail these tests.

Why this fix: one field and one pure predicate keyed on the candidate block's
own height mean a block's rule set never depends on where the node's tip is,
so reindex, reorg and replay agree, and every later rule change keys off the
same height. No production rule calls the predicate yet; block validity is
unchanged in this wave.

How it was tested: full unit suite, Qt suite on a real display, extended
functional suite and every fuzz target on the merged tree (see
v9.26.6/reviews/wave-2/); per-agent evidence including before/after logs and
mutation runs in v9.26.6/reviews/wave-2/agent-{a,b,c}/.

Claude-Session: https://claude.ai/code/session_01KmXsuD4LyZWpsaytcei9ma
… at Thaw Day

What was wrong: DigiDollar validation used local volatility state and health
accounting that could differ after restart. A separate oracle aggregation
path discarded the parity of supplied compressed public keys.

What changed:
- C1: use one mint-only rule at the shared Thaw Day height. Compare the
  block's committed quote with the lower median of the nearest 15 eligible
  ancestor prices at depths 240 through 1440. Keep legacy checks below that
  height. Use the same candidate state for mining, wallets and RPC status.
- C2: keep open-vault principal, collateral and vault count in chainstate,
  with the UTXO database writes and undo. Use that state for health after
  Thaw Day. Keep circulating token supply separate and report it as
  unavailable when historical amounts cannot be recovered reliably.
- C2: reconstruct and check required state during startup, recover interrupted
  writes, support cancellation, and revalidate affected history on late
  upgrades. Preserve pre-activation validation and rollback behavior.
- C2: use verified creating outputs for vault accounting and keep block
  transaction order. Apply the resulting emergency requirements to wallet
  selection, signed fees, mining and Qt redemption status.
- C3: aggregate the supplied full compressed keys through one shared path.
  Preserve active production aggregation and network separation.
- Tests: add activation, accounting, restart, migration, database interruption,
  reorg, lock-order, mining, oracle and Qt coverage. Correct generic pruning
  fixtures while keeping their original assertions and add retention coverage.

Why this fix: all changed block-validity rules use the same candidate height.
The post-activation health calculation comes from the accepted chainstate and
survives restart. Public activation heights remain disabled until scheduled.

How it was tested: clean make -j8 build; 3,517 unit cases including three
warning cases; 127 Qt cases without failures or skips. The broad functional
run passed 356 entries and skipped 15; four startup port failures passed in
isolated reruns on unchanged source, giving 360 distinct passing entries.
An actual Qt wallet minted, sent, completed an emergency redemption, and
restored the same accounting and wallet balance after restart.

Additional evidence: 37 selected lock-order cases and ten sanitizer input
collections passed on an earlier build with unchanged relevant tracked
compilation inputs. The previous-binary compatibility check also passed;
only a Qt test changed afterward. Independent source and verification reviews
cover the recorded candidate. Failed attempts remain in private evidence.

Limits: every node reindex is deferred by the owner, excluding 21 registered
variants. Fifteen functional entries lacked required host, tracing, signet or
historical-release prerequisites. External unit script assets were unavailable;
two partial mint setups produced the other warnings. Public activation and
the seven-day testnet run are pending. This commit records local code work,
not release or network approval.
…e sent, and amounts say what they mean

What was wrong: people could not get their money out. Redeeming a vault failed
with "owner key not found" when the wallet's note of the key had gone missing,
even though the key was still in the wallet. A mint sent its transaction first
and saved its record afterwards, so a node that stopped in between left coins
locked in a vault with nothing in the wallet to redeem it, and the writes that
saved it could not even report failure. A mint that could no longer be mined
kept its coins reserved for ever and showed as pending. A redemption guessed
its own size, picked its fee coins once, and gave up if they fell short, which
is what happens to a wallet whose coins are in many small pieces. And the
commands read "10000" as ten thousand cents but "10000.00" as ten thousand
dollars, so a habitual decimal point asked for a hundred times the intended
amount and the node did it without a word.

What changed, one line per issue:
- Issue 8: a mint writes its owner key and its record to the wallet before it
  sends the transaction, from the console and from the wallet window, and every
  write reports whether it worked. If a write fails, nothing is sent.
- Issues 1 and 2: redeeming a vault whose key record is missing looks for that
  key among the wallet's own keys, including the unused ones it keeps ready,
  and accepts only a key that reproduces the vault's own output on the chain.
  It never invents a key. Five distinct messages replace one, so a locked
  wallet, a watch-only wallet and a genuinely missing key can be told apart.
- Issue 3: a mint the chain can no longer accept releases the coins it was
  holding and is reported as an expired mint, while keeping the key and the
  record so a reorg or a late block brings the vault back.
- Issue 6: a redemption measures the transaction its candidate coins would
  produce and asks the wallet for more until they cover it, instead of guessing
  a fixed size once. Leftover coins after the fee can no longer follow an
  address the caller typed in, such as an exchange deposit address.
- Issue 11: every command that takes a DigiDollar amount now takes the unit
  with it, cents or dollars. A whole number with no unit still means cents, so
  nothing that worked before stops working. A decimal with no unit is refused
  with a message naming the fix.
- Issue 30: which fee settings DigiDollar actually uses is now written down.
  No behaviour changed.
- Issues 5 and 7: on a locked wallet the Redeem button stays enabled and asks
  for the passphrase, instead of being greyed out so the prompt never appeared.
  A wallet with no private keys is still refused, and says why.
- Issue 4: investigated, cause unconfirmed. The repaired fee path explains the
  report well, but two other faults fixed here look identical from outside. The
  five things the reporter would need to send are recorded.
- Two faults found during Wave 1 are closed here: the DigiDollar wallet locks
  are now visible to the lock checker, and the wallet no longer asks the chain
  a question while holding a wallet lock, which is how a node ends up stuck.
  Coin selection can no longer add past the largest number a total can hold.

Why this fix: every one of these was a way for a person to lose access to money
they still owned, or to move more of it than they meant to. The order of a save
and a send is the difference between a recoverable vault and a lost one.

How it was tested: eight new node tests and a dozen new unit and wallet-window
tests, each shown failing on the code before the fix with the exact error from
the user reports. The whole unit binary passes with no errors, the wallet-window
suite passes after a clean rebuild, and the node suite runs in full. The lock
work was proved on a build with lock checking turned on: before the fix four
separate routes stopped a node and named the exact lock and line, and after it
none does. Evidence in the private review folder for this release.

Claude-Session: https://claude.ai/code/session_01KmXsuD4LyZWpsaytcei9ma
…ge can never go to a lost address

What was wrong: the wallet showed a mint as money sent away and then received
back, because the code that turns a transaction into rows handled redemptions
and transfers but not mints. The same code folded the fee into the first row,
which on a mint is the collateral, so the wallet claimed more DigiByte was
locked than really was. One column held DigiByte figures on some rows and
dollar figures on others, so it could not be sorted, added up, or exported into
a spreadsheet. The details window had no DigiDollar knowledge at all and
printed lines reading zero. The DigiDollars a redemption hands back were called
"Redemption Change", which says the opposite of what happens, and nothing
explained them. And when a builder had nowhere to send leftover DigiByte it
made up an address: on a mint it created a key, paid the change to it and threw
the key away, which is money gone for good.

What changed, one line per issue:
- Issue 13: a mint has its own row showing the DigiDollars it created, with the
  collateral and the fee on their own rows and the fee shown once. The
  collateral row carries the collateral alone, which is the figure you need when
  you decide whether to redeem.
- Issue 14: DigiByte and DigiDollar amounts have their own columns, each sorting
  on its own number, each exported under its own heading. The DigiByte column
  still follows the display unit you picked, and the minimum-amount box no
  longer hides every DigiDollar row.
- Issue 15: the details window reads the DigiDollar facts out of the transaction
  and shows them: the amount, the collateral locked or returned, the lock
  period, the block the collateral unlocks at, and the vault. Where a number is
  genuinely not in the transaction it says so rather than printing a zero,
  because a zero reads as a real amount.
- Issue 16: returned change is called "DigiDollar change returned" and carries
  one sentence saying where it came from: the wallet spends whole DigiDollar
  inputs, and if they add up to more than the redemption burns the extra comes
  back. A vault is always closed in full, never in part.
- Issue 17: a mint, a send or a redemption with nowhere safe to send leftover
  DigiByte now stops with a plain error and produces no transaction, instead of
  paying it to an address nobody keeps the key for. A mint also refuses a
  taproot change address, because the network allows a mint only one taproot
  output holding DigiByte, the locked collateral.

Why this fix: a wallet that reports the wrong number teaches people to distrust
the right one, and a builder that picks an address for your leftover money is
guessing with funds that are not its own. Refusing to build can always be
retried; paying to a key nobody keeps cannot be undone.

How it was tested: three new wallet-window test classes and ten new unit tests,
each shown failing on the code before the fix. The whole unit binary passes with
no errors and the wallet-window suite passes 148 with none failed after a clean
rebuild of the window code. A wallet was driven by hand on a private chain
through mint, send and redeem, with the transaction list, the details windows
and an exported file captured at every step. Evidence in the private review
folder for this release.

Claude-Session: https://claude.ai/code/session_01KmXsuD4LyZWpsaytcei9ma
…ine logs

What was wrong: routine DigiDollar logs were noisy, oracle names were unclear,
and unsupported wallets could fail late in minting. Integration instructions
also described amount handling that Wave 4 has replaced.

What changed: keep oracle names as local display metadata; check mint wallet
capabilities before confirmation; gate routine logs while retaining errors;
and update the operator, wallet, exchange and oracle guides. Apply these changes
after Waves 4 and 5 without replacing their persistence, amount or change logic.
The exchange guide now requires explicit dollar units for decimal amounts.
The two new mint-save success messages use the DigiDollar debug category.

Why this fix: users get clearer errors and operators keep useful diagnostics.
Names do not change signing or serialization. No activation height, validity
rule or wallet ownership decision changes in this wave.

How it was tested: the original isolated Wave 6 source passed its recorded
unit and functional checks and independent source review. The rebase was
compared against both original waves and the completed Waves 4 and 5.
Combined clean build, tests and independent review are pending on this private
candidate. Original results are not a pass for the combined source.

Limits: node reindex remains deferred by the owner. Public activation, network
soak, mobile-client checks and final release verification remain pending.
This is a local implementation commit, not release approval. Nothing is pushed.
…tory

What was wrong: startup repeated oracle work, long reconstruction lacked
useful progress, interrupted scans could publish partial cached metrics,
and pruning could remove required early files when no height was eligible.

What changed: reuse parsed oracle bundles and parameter-bound verification
resources; publish completed scans; report progress, cancellation and source
errors; retain independent durable accounting checks and recovery. Preserve
fixed DigiDollar retention floors during disconnect and an empty pruning range.
Count recovery blocks from the height where both required deployments are
active. Add cancellation, storage and progress regression coverage. Refresh
operator guidance and document the explicit RPC amount-unit compatibility change.

Why this fix: startup and recovery report their actual work and preserve the
source data needed for validation. Successful legacy rules remain unchanged.
No activation height, validity rule, accounting format, oracle key, checkpoint,
assumevalid or peer policy changes. No earlier legacy readiness barrier is added.

How it was tested: original isolated Wave 7 verification and source review are
recorded separately. The rebased combined source passed a fresh configure and
make -j8 build. The new progress regression first failed on the old count,
reporting six blocks where only three require recovery. The correction and
combined full unit, Qt and extended functional checks are pending on this
private candidate; earlier results do not certify the combined source.

Limits: all node reindex remains deferred by the owner. Known wallet and
concurrency follow-ups, real-history verification, public activation, network
soak, mobile-client checks and final release review remain open. This local
commit is not release approval. Nothing is pushed.
…ulty is unchanged

What was wrong: every block header the node held in memory carried an array of
eight pointers, one per mining algorithm, recording the last block that used it.
It existed so the difficulty rules could find that block without walking back
through the chain. It cost 64 bytes for every block header, on a chain with 24
million of them, and two of its eight slots were for algorithms that were never
switched on.

What changed: the array is gone, along with the lookup that read it. The two
places that used it now use the walk that the difficulty rules have always used
for everything else. Nothing else changed. The walk itself is byte for byte the
code that was already there.

Why this fix: on the owner's own synced mainnet node this frees 1.56 GB, which
is 14 per cent of everything the node was holding. Startup got 3 to 4 per cent
faster rather than slower. Connecting a block costs about two millionths of a
second more, which is half a per cent, because the walk goes back an average of
3.8 blocks on mainnet and the difficulty rule that calls it already steps back
fifty blocks on every call before it does anything else.

How it was tested: the node reports exactly the same difficulty for all five
algorithms on both mainnet and the test network, and reaches exactly the same
chain tip. The whole public test network chain was replayed from the first block
with every signature checked, on this build and on the build without it, and
both reached the same recorded block. Before the array was deleted the two
lookups were run side by side over every algorithm, every difficulty rule, every
era, and the special case for minimum-difficulty blocks, and their answers were
written down as plain numbers that the tests still check. Nothing on disk
changed: the stored block record never held these pointers. The whole unit
binary passes with 3,587 cases, the wallet window tests pass 157 of 157 on a
real screen, and the node suite passes 377 of 403 with every failure shown to
happen the same way without this change.

The memory figure was measured two independent ways that agree within one per
cent: reading the record size out of each compiled binary, 208 bytes against
144, and watching what two real nodes actually held while replaying a real
chain.

Limits: one measurement is not yet explained. On the test network the phase that
loads a stored block index takes 22 per cent longer with this build, while the
same phase on mainnet got faster. It is real processor work, measured five times
with the order reversed. The likely reason is that it sits inside a loop mainnet
skips, because off mainnet the node recomputes a hash for every block index
entry, but that is not yet proved and it is being investigated. The full mainnet
replay from genesis belongs to the final build and has not been run.

Claude-Session: https://claude.ai/code/session_01KmXsuD4LyZWpsaytcei9ma
Add agreement checks for running, restarted, newly synced, pruned, and
reindexed nodes across Thaw Day. Update the operator documentation and
release notes to describe the completed behavior and remaining release gates.

Final verification covers the complete candidate, including the consolidated
corrections and memory changes. Historical reindex and public testnet checks
remain separate release requirements.
…lifetimes

Cached coin scans now use the database's serialized key order. Accounting
recognizes historical vaults consistently, bounds arithmetic, and applies
the planned sync-state rules at the shared Thaw Day height.

Wallet keys and mint records survive failed writes and rejected transactions.
Redemptions require a real destination. Wallet operations wait for current
chain notifications, and the GUI reports amounts and failures accurately.

Oracle startup and shutdown preserve object lifetimes. Signing retries send
stored public messages, retain unsent messages, and report reached peers.

This combines the eight correction and repair commits around Waves 8 and 9.
The resulting tracked files match the reviewed release source at 6ab9d865ed.
Independent reviews covered accounting, wallet writes, and oracle startup
and shutdown. Full unit, functional, and fuzz validation covers the complete
candidate with Feather.
Keep frequently used block fields in smaller pooled records. Read saved
Merkle roots and nonces from the existing block database through a bounded
cache. Keep new header data in memory until a successful database sync.
Full 256-bit chainwork, stored formats, and validation rules are preserved.

Use buffered database reads with explicit file-handle budgets. Remove the
extra block-counting pass, build chain candidates after coins recovery, and
skip warning scans that cannot produce a warning.

Cover storage errors, cache lifetimes, flush ordering, historical algorithms,
chain selection, and descriptor limits. Document the design and measured
memory use in RAM_IMPROVE.md. The complete candidate passed 3,718 C++ unit
cases, the Qt suite, 391 functional invocations with 17 documented skips,
120 focused sanitizer cases, and all 255 public fuzz targets plus 28 longer
fuzz runs. Repeat full verification on the consolidated release commit.
Verify saved token supply against chain data without changing the debt used for health. Preserve accepted legacy metadata, repair matching saved totals, and keep reorg recovery consistent. Configure oracles before callbacks and synchronize shared settings and proposal epochs. Correct mint collateral details and the activation context of exported script tests.

Add signed price, extra-burn, restart, reorg, interrupted-write, signing retry, and concurrency regressions. Capture expected storage errors in their tests. Focused RED and GREEN evidence is retained locally; complete candidate verification is recorded separately.

Keep the supported fee-file format separate from the application version.
Verify learned estimates survive restart and invalid files preserve existing history.

Keep the automatic high-fee test independent of restored fee estimates.
Set testnet activation to block 432,100 and mainnet to 24,490,000.
Each network uses its one existing height for all Thaw Day rule changes.
Keep historical deployment heights and public override refusal unchanged.

Pin the exact boundaries in unit tests and check the published schedules
through isolated mainnet and testnet RPC nodes. Keep the existing regtest
restart and reorg checks. Update operator instructions with estimated dates,
upgrade windows, and coordinated postponement steps.

The focused tests passed before integration. Complete verification of this
combined candidate is recorded separately; public activation is still pending.
…let's script table

What was wrong: block and mempool validation below Thaw Day read DigiDollar
amounts from the in-memory script metadata registry before reading the chain.
The wallet and the transaction builder fill that table, and it is keyed by
output script alone, so an owner address that is used again keeps only the
last amount. Change from a send returns to the address that held the spent
token, so any wallet that has sent DigiDollar has such an address.

A mainnet rc1 node holding a wallet that had minted $100 and later held $98 of
change at the same address rejected block 23,869,549, the first DigiDollar
mint block, with bad-dd-mint-amount while reindexing, marked every later block
invalid and stopped following the chain. Nodes without that wallet accepted
the block. Block validity depended on local wallet state.

What changed:
- ExtractDDAmount(script, amount) reads only the script's own data. The
  registry form takes an explicit allow_registry flag and is test-only.
- Mint validation classifies outputs by structure at every height and never
  registers the reconstructed vault script.
- Transfer and redemption input resolution, redemption change accounting,
  collateral release and vault-spend detection drop their registry fallbacks.
  Unresolvable data is rejected, which is what every node without a wallet
  entry already did since v9.26.4.
- The remaining registry readers are marked test-only.
- Tests: a new unit suite writes wrong amounts into the registry and checks
  that mint validation, vault detection and the amount reader answer from
  chain data, including the exact mainnet case. A new functional test mints,
  pays the mint address again, then reorgs and reindexes with the wallet
  loaded. Both fail before this change. Existing tests that fed amounts
  through the registry now serve the creating mint through the transaction
  lookup, and a redemption whose change amount was never serialized is
  expected to be rejected below Thaw Day on every node.

Why this fix: consensus must be a function of the chain and the block alone.
The Thaw Day heights and rules are unchanged. The existing chain is what
nodes without the wallet table already accepted, so nothing on it changes.

How it was tested: the new unit suite and functional test fail on the
unchanged code and pass with it. The DigiDollar unit suites and Qt tests
pass. A mainnet node holding the affected wallet accepted block 23,869,549
with this change after reconsiderblock.
…ug category

The message was written once for every connected block while the chainstate
was not ready, which produced an 8 GB log during one mainnet reindex.
rc2 replaces rc1, which could reject a valid DigiDollar mint block during a
reindex on a node holding a DigiDollar wallet. The notes explain the defect,
the repair, and how to recover a stuck rc1 node with reconsiderblock, and
record the rc2 test results. The wallet image now reads v9.26.6rc2.
The private Thaw Day rehearsal (thawDay.sh, contrib/thawday/, and the guide)
runs the same DigiDollar features before and after Thaw Day on an isolated
local network with eight wallet nodes and real MuSig2 oracle signing, then
reindexes a wallet-holding node and a fresh empty node. Commit it to the
release branch with the rest of the v9.26.6rc2 work.

Result. Run rc2-rehearsal-09 completed the whole harness with no failed check:
final height 5,658, tip 0000073bc7...856c, 143 recorded checks, 0 failures.
It crossed Thaw Day at lab height 5,000 and confirmed, before and after it,
all ten lock-tier mints, transfers with exact balances, the early, partial and
second-spend redemption rejections, whole-vault redemption, the extra burn,
block H rule selection, the boundary reorg, the price-edge mint pauses, wallet
backup and restore, Bob's reindex with its wallet loaded, and a fresh empty
node syncing from scratch to the same block. The guide's execution table records
this pass. It is the lab exercise only and does not replace the audit, public
testnet activation and observation, the older-binary comparison, or the full
mainnet history reindex.

Harness fixes made while diagnosing the run (all harness, no product change):
- After the mining node restarts, wait for a signed price mined after the
  restart before treating the candidate quote as ready.
- Confirm the re-signed redemption through the ordinary miner; the
  getblocktemplate diagnostic passes the "digidollar-oracle" rule so it reflects
  what the miner builds.
- Send the extra-burn control payment from Alice and keep it out of the
  DigiDollar ledger.
- Below Thaw Day, assert exact circulating supply only on a node with the stats
  index; the chainstate makes it exact on every node at and after Thaw Day.
- After a reindex, re-establish the spoke connections to the hub so block relay
  resumes.
- Skip a full redemption that carries no DD change (no metadata, no token
  output) in the ledger decode.
- Do not cap mining during the after-Thaw price edges.

Docs. Record the lab pass in the release notes and say reindex, not replay.
Integrate DigiByte release commit 998140e
while preserving upstream consensus rules and RPC argument positions.
Move Paymaster options after amount_unit and update callers and coverage.

Extract the provider console into its own Qt component, restore unrelated
widgets and wallet helpers to upstream, and retain the Paymaster reservation,
persistence and privacy safeguards. Reconcile Windows build inputs and the
wallet database failure-injection hooks with the new release.

Validation: targeted MSVC syntax checks on 19 translation units; Python
syntax checks on 64 changed files; idempotent MSVC project generation;
no unresolved conflicts or whitespace errors relative to the release.
Full builds and runtime regression tests remain pending operator execution.
See doc/digidollar-paymaster-v9.26.6rc2-integration.md for the migration and
remaining verification gates.
Move WalletBatch Paymaster codecs and wallet load tests into dedicated
translation units. Extract PaymasterSendWidget and its Qt tests, sharing
one descriptor-wallet fixture. Keep editable form state in the original
send widget and isolate senddigidollar orchestration in free functions.

Update Autotools, generated MSVC projects, MOC registration and developer
maps. Preserve RPC argument positions, database transactions and existing
signing/confirmation boundaries without adding parallel implementations.

Validation recorded in the integration notes: focused MSVC syntax checks,
Qt MOC generation, generator idempotency and moved-code/test equivalence.
Linked runtime acceptance remains a separate documented release gate.
Describe persisted privacy and authorization fields in send/quote/session
results, share the session schema, and retain the existing nullable wire
values. Keep input validation and signing boundaries unchanged.

Give concurrent RPC sessions disjoint DD inputs and align provider pool
and post-signature fallback assertions with the existing safety rules.
Assert nullable/non-null result types and unchanged rejected-session state.

Validation: focused RPC, lifecycle, failover and provider functional tests
passed with the rebuilt daemon. Record the Windows Qt results and the
extended-suite failure analysis without declaring unresolved integration,
performance or release gates passed.
Exercise HTTP authentication, RPC method whitelists and mixed batches,
wrong-wallet session access, abrupt provider termination, and an older
authorized-client SQLite backup that must reject spent Capacity inputs
without changing balances, reservations or durable accounting.

Add bounded malformed announcements over real V2 peers and reconnects
while checking ping and block relay. Select all paymaster_* unit suites
in security CI, expand integration/build triggers, and upload the actual
sanitizer log instead of maintaining a duplicate suite inventory.

Validation: RPC 40 s, lifecycle 35 s and P2P 14 s passed locally; wallet
load passed 7 cases/172 assertions in the existing unit binary. Python
syntax, workflow YAML, suite registration and diff whitespace checked.

Document stale-backup limits and pending current-revision sanitizer,
fuzz, Qt, deployment, storage-fault and artifact-inventory evidence.
No production protocol, permissions, database schema or consensus change.
Persist finite pool setup approvals and resume confirmed funding steps within approved fee limits. Preserve signed transactions across restart and reorg, reconcile retirement state and finance, and keep internal recovery metadata out of wallet RPC responses.

Harden AUTO storage-error handling and Qt approval workflows. Add regression coverage for setup, persistence, recovery and RPC compatibility; document the implementation and remaining release gates.

Validation: operator WSL Paymaster unit, Qt and nine functional tests passed with exit codes 0/0/0. Source and test files match the tested checkout; git diff --check passed. Full DigiByte and outstanding security release gates remain outside this validation.
Validate capacity replays against stored responses without bypassing readiness or reservation checks. Require authenticated randomized SOCKS credentials for high-privacy connections. Fix wallet-tool linking, RPC presentation and functional test races.

Validation: core package 29/29 and Tor package 10/10 (including framework) passed on the frozen repair build. Sanitizer and fuzz campaigns remain pending; this is not a security audit or release approval.
@Shen Shen closed this Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants