Skip to content

feat: optimize compute unit usage for SIMD-0553 - #1201

Open
bryzettler wants to merge 33 commits into
developfrom
feat/simd-553-cu-optimizations
Open

feat: optimize compute unit usage for SIMD-0553#1201
bryzettler wants to merge 33 commits into
developfrom
feat/simd-553-cu-optimizations

Conversation

@bryzettler

@bryzettler bryzettler commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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

  • withPriorityFees now 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).
  • Fee estimate, blockhash fetch, and CU simulation run concurrently; lookup tables are resolved once per transaction build instead of per instruction.
  • Loaded-accounts data size requested from the simulation's measured usage (×1.1, rounded up to the 32 KiB fee quantum), since SIMD-0553 charges the requested size; flat 16 MiB fallback when simulation isn't possible.
  • Removed hardcoded computeUnitLimit overrides now covered by estimation.

Client: validate fees against the cluster

  • Balance checks now price transactions via 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 the oracle_signer bump/seeds instead of grinding find_program_address on every wrapper call.
  • Claim/distribute paths skip no-op transfer CPIs and reallocs.

Tooling: keep the table honest

  • sample-cu.ts samples real mainnet CU usage per instruction.
  • check-cu-table.ts runs 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

  • Same two-tier model for both resources: simulation measures, static fallback (CU table / 16 MiB) covers RPC failure. Both degrade to overpaying, never to failed transactions.
  • Hardcoded oracle_signer seeds assume the program ID never changes; the bump is verified against the canonical PDA in tests.

@bryzettler
bryzettler force-pushed the feat/simd-553-cu-optimizations branch 4 times, most recently from ff79a3c to b8f28f1 Compare July 8, 2026 19:34
@bryzettler
bryzettler requested a review from madninja July 8, 2026 19:50
@bryzettler
bryzettler force-pushed the feat/simd-553-cu-optimizations branch from ec94ecb to efcb722 Compare July 8, 2026 20:40
bryzettler and others added 19 commits July 21, 2026 12:50
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.
Commit 8e2644b was labeled a whitespace-only revert but also removed
the SIMD-0553 fix from e20346b: estimateComputeBudget simulation with
precomputed-value fallback and the SIMULATE_CONCURRENCY cap. Restore the
functional change without re-introducing the reformat.
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
bryzettler force-pushed the feat/simd-553-cu-optimizations branch from d43748c to 1f3e4bc Compare July 21, 2026 18:11
…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.
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