feat: optimize compute unit usage for SIMD-0553 - #1201
Open
bryzettler wants to merge 33 commits into
Open
Conversation
bryzettler
force-pushed
the
feat/simd-553-cu-optimizations
branch
4 times, most recently
from
July 8, 2026 19:34
ff79a3c to
b8f28f1
Compare
bryzettler
force-pushed
the
feat/simd-553-cu-optimizations
branch
from
July 8, 2026 20:40
ec94ecb to
efcb722
Compare
Right-size compute-unit requests ahead of the SIMD-0553 fee change: simulate each transaction for an accurate CU limit and loaded-accounts data-size, fall back to a mainnet-sampled static table when simulation is unavailable, and refresh that table. Harden the estimator (distinguish sim failure from a zero-unit sim, log transport errors, treat 0 as unset) and the sampler tooling, cap migration-service simulate concurrency, and trim program CU in claim_rewards, rewards-oracle, and lazy-distributor. Restores the staked veHNT log in claim_rewards_v0.
Price balance-validation fees from the validator's own getFeeForMessage RPC (which accounts for SIMD-0553 automatically) with a local base+priority fallback, and thread the connection through every call site so the fee helpers can run async.
Sample at confirmed commitment so short suites' just-committed traffic is visible, and skip the gate for priority-fees (mocked) and sus (remote RPC) which never commit localnet traffic.
Add 12 instructions the check-cu-table gate flagged as missing (price_oracle, voter_stake_registry, lazy_transactions canopy ops, mobile_entity_manager incentive programs, helium_entity_manager.update_maker_v0, hexboosting.close_boost_v1, dc_auto_top.close_auto_top_off_v0, tuktuk_dca.initialize_dca_nested_v0). Bump dc_auto_top.top_off_hnt_v0 105949 -> 222223: localnet p95 is ~2x the stale mainnet reading, past bump-grind slack, so account structures grew and the old value under-requests.
…o sample-cu Sampled mainnet (1000 sigs/program) and found 8 instructions whose fallback ceiling (entry × FALLBACK_CU_MARGIN) no longer covered the observed max, so their fallback txs would under-request CU and fail on-chain. Raised each to the observed mainnet max; added the missing update_lazy_transactions_v0 entry. sample-cu now verifies the committed table against the same sample it takes to regenerate values — printing paste-ready under-provisioned and missing entries and exiting non-zero — instead of a duplicate mainnet pass.
Grinding find_program_address on every call made the instruction's CU cost nondeterministic, intermittently blowing the budget and failing with ProgramFailedToComplete. Use the stored bump like close_ and schedule_task_v0 already do.
estimateTransactionFeeLocally read the compute-unit price with manual little-endian shifts, so a price >= 2^31 microLamports/CU set the sign bit on `<< 24` and parsed as negative, yielding a bogus (negative) fee in the getFeeForMessage RPC-failure fallback. Parse the u64 with Buffer.readBigUInt64LE instead; add a regression test at the boundary. Realistic Helium fees never reach 2^31, so this only hardened the fallback path. Also align sample-cu.ts verifyTable to Math.ceil(entry * margin) to match the runtime request (Math.ceil(total * margin)); floor rounded the ceiling down and over-flagged entries by up to one CU in the safe direction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The localnet CU gate flagged present-but-too-low entries as a warning only, so CI stayed green even when an instruction's fallback ceiling (entry * FALLBACK_CU_MARGIN + bump-grind slack) no longer covered its observed consumption. That is the dangerous direction: the sim-failure fallback then under-requests CU and the tx fails on-chain (fee still burned under skip-preflight). Exit non-zero on drift, same as missing entries. Raise the two entries this now-blocking gate flags, both distribute_v0 paths that grind PDA bumps at runtime, to their localnet p95: fanout.distribute_v0 41055 -> 72441 mini_fanout.distribute_v0 144276 -> 247164 Their mainnet samples sit within margin, so the localnet excess is run-to-run grind noise past the slack; raising each to its localnet worst case keeps the fallback conservative (it is only used when live simulation is unavailable) and stops the gate re-flagging it, since later noise then stays within slack. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This reverts commit 0872d8f. Making the localnet CU gate hard-fail on drift proved too flaky for a per-PR blocker: localnet runs 29-76% hotter than the mainnet-derived table entries for PDA-grinding instructions (fanout/mini_fanout distribute_v0, mini_fanout schedule_task_v0), and which one crosses the ceiling+slack varies run to run. A CI run failed on schedule_task_v0 by 441 CU (0.2% over) - pure sampling noise, not structural growth. Raising entries to localnet p95 is whack-a-mole and inflates the mainnet-p95 table with localnet noise; a slack wide enough to absorb it neuters the gate for smaller instructions. Back to warning-only. The authoritative hard check remains `pnpm run sample-cu` against mainnet, run when an instruction gains mainnet volume, per the table's maintenance rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The static CU table is the sim-failure fallback; its request is entry(p95) x FALLBACK_CU_MARGIN. At 1.2 that ceiling was exceeded by PDA-grinding instructions (distribute_v0, schedule_task_v0 ran up to ~1.8x the raw p95 on localnet), so a fallback could under-request CU and fail the tx on-chain. Raise the margin to 2.0 so the fallback is worst-case and never under-provisions; it only applies when live simulation is unavailable, so the extra CU is a rare, safe overpay. The primary simulation path (x computeScaleUp) is unchanged. Test and comments reference FALLBACK_CU_MARGIN instead of a hardcoded 1.2 so they track the constant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each of the 17 e2e matrix jobs ran a full build-anchor. When the anchor build cache was evicted (repo sits at the 10GB Actions cache cap), every job did a cold anchor build that can't finish inside the 20-minute job timeout, so entire runs died as 'cancelled'. The matrix jobs only need target/types to build @helium/idls; build once in a dedicated job with a 60-minute budget and hand the generated types/IDLs to the matrix via an artifact.
Rebasing over the earlier 'merge origin/develop' commit dropped the conflict-resolution content that adapted getTransactionFee call sites, since rebase skips merge commits. Restore those files from the pre-rebase tip.
develop added tests/hpl-crons.ts to the test-contracts matrix, but the check-cu-table gate's PROGRAMS list didn't include hpl-crons, so the gate would hard-fail sampling its traffic. Add the program and the entries measured from a local run of the suite.
bryzettler
force-pushed
the
feat/simd-553-cu-optimizations
branch
from
July 21, 2026 18:11
d43748c to
1f3e4bc
Compare
…hange Build the changed programs' IDLs at the PR base and head with anchor idl build and require an @helium/idls changeset only when the generated IDL actually differs (ignoring metadata.version). Internal logic changes like the SIMD-553 CU optimizations no longer demand a no-op package bump. Verified locally: all four programs this branch touches produce byte-identical IDLs vs develop.
…tion
The websocket path can reject with the raw confirmation result
({ err: { InstructionError } }) rather than the InstructionError
itself, which #1213's fix didn't cover.
…ptimizations # Conflicts: # Cargo.lock # packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer.ts # programs/helium-sub-daos/Cargo.toml
…ptimizations # Conflicts: # packages/blockchain-api/src/server/api/routers/governance/procedures/voting/vote.ts # packages/blockchain-api/src/server/api/routers/migration/procedures/migrate.ts
…CB prefix by length The sim-instruction cascade in withPriorityFees duplicated the skip-if- caller-set rules that prependComputeBudgetIxs already owns. In batchInstructionsToTxsWithPriorityFee, the prepended ComputeBudget prefix is exactly the length difference between withPriorityFees output and input, which also stops a caller-supplied leading CB ix from being captured and replayed onto subsequent batch txs.
…ia existing helper Fetch all lookup tables in one getMultipleAccountsInfo call instead of one RPC per LUT, estimate the prioritization fee once (block txs differ only by PDA), and replace the manual index-stepping loop with chunks().
…l fee fallback Without an explicit limit ix the runtime grants 200k CU per non-ComputeBudget top-level instruction (capped 1.4M), not a flat 200k per tx. Also document that the local fallback does not model the SIMD-0553 resource fee (getFeeForMessage primary path picks it up at activation) and that BASE_TX_FEE_LAMPORTS call sites need revisiting once a burn gate is live.
…to Helium RPC Explicit computeUnitLimit bypasses simulation in withPriorityFees; removing the overrides lets queue/requeue-hotspot-claims, queue-position-claims, and create-council-fanout simulate like every other Helium-paid path.
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.
Why
SIMD-0553 changes Solana's fee model so transactions pay for the compute units they request, not just what they use. Our transactions currently over-request CUs (hardcoded limits, default 200k budgets) and burn CUs on avoidable work on-chain. Both now translate directly into higher fees for every claim, delegation, and oracle call.
What
Client: request only the CUs we need
withPriorityFeesnow estimates the CU limit via simulation, falling back to a static per-instruction CU table when simulation isn't possible. Table values are p95 from mainnet sampling, worst-case (no accounts pre-initialized).computeUnitLimitoverrides now covered by estimation.Client: validate fees against the cluster
getFeeForMessage— the validator's own fee calc, so it tracks base, priority, and any future fee components without local modeling. Falls back to a local base + priority estimate when the RPC can't answer.On-chain: burn fewer CUs
rewards-oracle: hardcode theoracle_signerbump/seeds instead of grindingfind_program_addresson every wrapper call.Tooling: keep the table honest
sample-cu.tssamples real mainnet CU usage per instruction.check-cu-table.tsruns in CI and fails if any exercised instruction is missing from the table; drift beyond margin is reported as a warning (localnet CU is noisy from PDA bump grinding).Notes for reviewers