fix(wallet): make managed change starvation-resistant - #457
Merged
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
ty-everett
marked this pull request as ready for review
August 11, 2026 02:30
ty-everett
requested review from
BraydenLangley,
sirdeggen and
tonesnotes
as code owners
August 11, 2026 02:30
…uidity-policy # Conflicts: # docs/reference/package-api-migrations.md # governance/package-release-notes.json # packages/wallet/wallet-toolbox/CHANGELOG.md # scripts/patch-coverage.mjs # scripts/patch-coverage.test.mjs
|
BraydenLangley
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Program and scope
main); official wallet-infra configuration; progressive migration of pre-fee-change wallets.sendingparents; eager one-shot consolidation; changing custom operator basket values; publishing packages or deploying images from this PR.7390ce94ae00ccb796743c9f83cfe256ebdb9795This PR implements the root-cause correction as one coherent policy. It preserves every historical funding avenue as a compatibility fallback, but biases future authorized activity toward a useful, parallel-ready managed-change pool. Existing wallets migrate progressively as they naturally spend old outputs; no background sweep, consumer migration, or new authorization is required.
Behavioral invariants
sendingparents remain available. The final compatibility attempt uses an economic floor of one satoshi for its first remainder, which is at least as permissive as the historical 32-satoshi/8-output planner.completed, thenunproven, thensendingchange. Pending parents are a last-resort funding source for normal plans. If an all-settled plan has already crossed the configurable 16-input comparison threshold, the planner may also measure later tiers and choose them only when the exact transaction-plus-BEEF serialization is smaller.-1for unlimited operation.The final adversarial review identified and this head closes two pre-merge blockers. First, actions already funded by explicit/fixed inputs now materialize managed change from that existing surplus before the compatibility guard. A 7,000-satoshi input funding a 1,000-satoshi output creates a 5,999-satoshi remainder without calling the wallet allocator; larger surplus can fan out within the eight-output cap, and optional fragment retirement remains independently bounded by the four-input migration budget. If the remainder cannot pay both the marginal output fee and the economic dust floor, it remains as a bounded fee rather than causing a retry that gathers another input solely to manufacture change. Second, the SQL policy migration now writes UTC ISO timestamps on SQLite, preserving lexical compatibility with incremental-sync cursors, while MySQL retains its native millisecond timestamp expression.
Planner and BEEF behavior
The funding planner builds progressively broader candidate sets:
Each tier gets a new-policy attempt followed by a same-tier compatibility attempt. This ordering matters: a fragmented settled wallet gets every chance to proceed before pending ancestry is considered, while a wallet that can only proceed through a pending parent still proceeds exactly as before.
For a settled plan over
pendingComparisonInputs(default 16), the planner compares the serialized transaction size plus the exact serialized input BEEF bytes for the later candidate. The smallest measured plan wins. A comparison-only proof lookup failure gives that alternative infinite comparison cost and leaves the already viable baseline untouched.-1disables the optional comparison without disabling pending fallback.This directly reduces the class of 100+ input permission and application transactions while avoiding a blanket “never use sending” rule that would turn temporary confirmation delay into an outage. It also reduces the blast radius of a failed ancestor broadcast: pending ancestry is used deliberately, after settled liquidity or based on a measured total-payload improvement, rather than because database ordering happened to return it first.
Progressive migration for existing wallets
The historical untouched default is recognized narrowly as:
default;Only that exact default advances to 5,000 satoshis. Custom baskets and any operator-modified values are left untouched.
The transition is applied consistently through:
The migration changes policy metadata only. It does not create a transaction or reserve funds. Subsequent authorized
createActioncalls consume old fragments within the configured migration budget and create useful change from real surplus. This makes the transition gradual, interruptible, and compatible with wallets that are only intermittently active.SQLite migration timestamps use the same UTC ISO representation as its incremental-sync query values, so a newly migrated basket is visible in the first eligible sync chunk. MySQL continues to use its native timestamp representation. Regression coverage proves both the exact-match migration and SQLite text comparison from a cursor captured immediately before migration.
Action batches and multiple workspaces
The #448 isolation model remains authoritative: batch membership is determined by explicit transaction-graph references rather than a global “batch mode.” This PR aligns reservation planning with the same ancestry and managed-change rules:
createActionwork and independent action-batch workspaces are not claimed merely because another workspace exists.All additions to storage candidates and batch capability/results are optional/additive. A custom or older provider that lacks the status batch API continues through the base fallback, and no Wallet Wire, Storage Server RPC, persisted action, or BRC-100 request/response contract is narrowed.
Monitor and operator visibility
TaskReviewUtxosnow reports managed-pool condition without mutating it: total managed outputs, useful/undersized counts and satoshis, ancestry-status counts, and current policy values. The authenticated storage admin endpoint/UI exposes the same report. No identity keys, scripts, outpoints, transaction IDs, or other wallet-sensitive values are added to telemetry.The official
wallet-infraimage accepts and validates:WALLET_STORAGE_MANAGED_CHANGE_MAX_OUTPUTS_PER_ACTION(default8)WALLET_STORAGE_MANAGED_CHANGE_MIGRATION_INPUTS_PER_ACTION(default4)WALLET_STORAGE_MANAGED_CHANGE_PENDING_COMPARISON_INPUTS(default16)Every limit accepts
-1with the documented meaning. The values are present in the example environment, Docker Compose wiring, Kubernetes ConfigMap sample, service-operation governance inventory, and operator documentation. Invalid values fail startup instead of producing an ambiguous partial policy.Economics behind the defaults
At the Wallet Toolbox default fee model of 100 satoshis/kB, a roughly 148-byte managed input contributes about 15 satoshis of fee, and a minimal one-input/one-output spend is about 20 satoshis. A 5,000-satoshi unit is therefore about 250 minimal-spend fees at that rate. Even at 1,000 satoshis/kB it remains about 26 minimal-spend fees. A fully aligned 144-output pool represents approximately 720,000 satoshis, while wallets below that balance simply converge on fewer useful units rather than being forced to create dust.
These are operational defaults, not consensus constants. The 144 target provides enough independent outputs for parallel planning; the eight-output fanout avoids a large one-time transaction; and the four-input migration budget prevents cleanup from recreating the very 178-input pattern this work is intended to eliminate.
Impact
Affected packages/services and intended patch versions (publication occurs only through the release workflow after approval):
@bsv/wallet-toolbox: 2.6.5 published -> pending 2.7.0@bsv/wallet-toolbox-client: 2.6.5 published -> pending lockstep 2.7.0@bsv/wallet-toolbox-mobile: 2.6.5 published -> pending lockstep 2.7.0wallet-infraimage: consumes the released 2.7.0 package after the protected version-sync/release cascadeThe new public surface consists of additive policy configuration/types, optional candidate ancestry status, optional action-batch policy information, and read-only admin reporting. Existing calls remain source- and wire-compatible.
Verification
pnpm health:check— 138 repository/governance policy tests passed.pnpm lint— passed.pnpm format:check— passed.pnpm typecheck— all 37 TypeScript projects passed after the governed build.pnpm audit:security— passed; two existing high advisories are covered by the repository's governed exceptions, with no new dependency graph.pnpm docs:examples— 8 examples passed against 21 exact package tarballs.npm ci --ignore-scripts, zero vulnerabilities), build, lint, and changed-file formatting — passed.7390ce94ae00ccb796743c9f83cfe256ebdb9795is terminal green: 42 checks passed and six scope-based jobs skipped as expected. The repository-owned merge gate, zero-new-findings Sonar gate, CodeQL, Codecov patch gate, Socket checks, Conformance, container runtime contracts, Wallet Toolbox coverage shards, wallet browser/mobile consumers, docs, dependency review, and wallet-infra image/security contract all passed. Evidence: CI, CodeQL, Conformance, and container runtime contracts.-1modes, status-tier fallback, sending-only liquidity, exact BEEF comparison, comparison-proof failure, custom-provider status enrichment and unresolved-status failure, atomic status validation, legacy custom-policy preservation, action-batch compatibility, and progressive migrations. The current exact head covers 582/630 changed line/branch points (92.38%).Security and dependencies
Quality Gate passedverdict alone is not merge evidenceCandidate ancestry is treated as authorization-adjacent state: it is advisory during planning but authoritative at the atomic claim. Status changes cannot cause a planner to claim an input outside the selected ancestry set. Comparison/proof retrieval is fail-safe toward the existing viable baseline. Operator admin reporting remains behind the existing authenticated/allowlisted boundary and exposes aggregates only.
Dependency evidence
For human-authored dependency changes, add the useful evidence available for the review. Missing or incomplete fields produce an advisory CI warning, not a merge block. Automated dependency pull requests are exempt; their generated release, compatibility, and security metadata remains the review starting point.
mainwith the complete managed-liquidity behavior, rather than publishing fix(wallet-toolbox): isolate and resume action batches #448 and this correction as incompatible partial policies.Release and operations
mainadvances the combined lockstep candidate to 2.7.0 for TTN; npm still reports 2.6.5 as publishedAfter merge, the protected release workflow publishes the three 2.7.0 wallet packages in lockstep. Its version-sync step updates the standalone wallet-infra dependency/lock before the official image is built; this PR deliberately does not make CI depend on an unpublished npm version. Operators can roll out with defaults, observe the read-only liquidity report and transaction/BEEF telemetry, and then tune limits if their workload warrants it.
Until that sequence completes, the source candidate's standalone wallet-infra lock still resolves Wallet Toolbox 2.6.5. The new environment variables are parsed and validated there but are operationally inert because 2.6.5 does not yet consume the policy object. Deployments must verify that official image provenance contains Wallet Toolbox 2.7.0 or newer before relying on those settings.
Rollback is application/image rollback plus policy restoration. The SQL policy migration is intentionally one-way because later authorized actions may already have reshaped funds; rolling back code does not and must not attempt to reverse blockchain transactions. Existing 5,000-satoshi outputs remain ordinary valid managed change under older code.
Documentation delivered
-1mode;Completion evidence
The remaining unchecked items depend on tracker disposition and maintainer review; they are not represented as complete while those gates remain open.