Skip to content

Steward: Fix Rebalance try_accounts stack frame exceeding SBF limit - #316

Draft
aoikurokawa wants to merge 1 commit into
masterfrom
ak/fix_rebalance_stack
Draft

Steward: Fix Rebalance try_accounts stack frame exceeding SBF limit#316
aoikurokawa wants to merge 1 commit into
masterfrom
ak/fix_rebalance_stack

Conversation

@aoikurokawa

Copy link
Copy Markdown
Collaborator

Problem

Rebalance::try_accounts (the anchor-generated account validation for the Rebalance instruction) has a 4,144-byte stack frame — 48 bytes over the SBF per-function limit of 4,096. Every build has been printing this as a non-fatal diagnostic:

Error: Function ...Rebalance...try_accounts... Stack offset of 4144 exceeded max offset of 4096 by 48 bytes ... may cause undefined behavior during execution.

With the current toolsuite (anza v3.0.8) the overflow happens to be harmless — codegen luck. Building the unchanged master source with newer platform-tools (v1.52, via the anza v3.1.9 toolsuite) shifts the stack layout so the same overflow corrupts a pointer, and Rebalance fails deterministically at runtime:

Program failed: Access violation in unknown section at address 0x10 of size 8

All 7 steward::test_cycle::* integration tests that reach Rebalance fail this way. This was discovered while attempting the solana-gossip upgrade (which forces a toolsuite bump for edition-2024 dependencies), but it's independent of that work: any future toolchain upgrade of the program build pipeline produces a broken Rebalance until this is fixed.

Root cause

Three #[account(...)] constraints call deserialize_stake_pool(&stake_pool)?.<field>. The helper returns StakePool by value, so each call materializes the full struct (~600B) in try_accounts' own frame — by-value returns land in the caller's frame, so the helper being a separate function doesn't help.

Fix

The SBF stack limit is per-function-frame, so the fix moves the temporaries into their own frames: three #[inline(never)] accessors in stake_pool_utils.rs that deserialize internally and return only the needed field (u8/Pubkey):

  • bump = deserialize_stake_pool(&stake_pool)?.stake_withdraw_bump_seedbump = stake_pool_withdraw_bump_seed(&stake_pool)?
  • address = ...validator_listaddress = stake_pool_validator_list(&stake_pool)?
  • address = ...reserve_stakeaddress = stake_pool_reserve_stake(&stake_pool)?

Identical checks, identical error propagation, no behavior change. Two files, no IDL change, no Cargo.lock change.

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.

1 participant