Skip to content

feat(kora-deploy): add resume and buffer cleanup support to upgrade() - #775

Open
raushan728 wants to merge 6 commits into
solana-foundation:mainfrom
raushan728:feat/upgrade-resume
Open

raushan728 wants to merge 6 commits into
solana-foundation:mainfrom
raushan728:feat/upgrade-resume

Conversation

@raushan728

Copy link
Copy Markdown
Contributor

Closes #701

Adds --resume and buffer cleanup support to upgrade(), bringing it to parity with what was added for deploy() in #614, and reuses the mockito + Solana RPC mock test harness established in #710.

Previously, if an upgrade failed midway (due to RPC timeouts, network drops, etc.), no local state file was saved. The buffer account ended up orphaned on-chain, keeping the paymaster's rent locked with no way for the user to resume where they left off.

What was done in this:

  • Added UpgradeState (persisted to .kora-upgrade-state.json) and introduced a ChunkWriterState trait so deploy() feat(kora-deploy): add resume capability and buffer cleanup on transient failures #614 and upgrade() share the exact same chunk-writing, invariant check, and buffer cleanup logic without duplicating code.

  • Added preflight checks if the on-chain program already matches the local binary hash, we exit early without creating a buffer. When resuming, if the buffer is already gone, it checks whether the upgrade transaction had already landed on-chain so we don't error out on network drops during finalization.

  • Added StateLockGuard (<state_path>.lock) to prevent concurrent processes from colliding on the same state file.

  • Handled backward compatibility for older DeployState files where wallet_pubkey was missing, and updated hash-mismatch error messages to include the buffer pubkey so users know to close the buffer before deleting the state file.

  • Unlocked --resume, --no-cleanup-on-failure, and --state-path flags on the upgrade CLI, and updated the README with recovery instructions.

  • Expanded the mock orchestration suite introduced in test(kora-deploy): add mockito based orchestration test coverage for deploy() #710 to test all upgrade flows end-to-end.

  • AI tooling was used

  • No AI tooling was used

Tool and extent: OpenCode was used with free-tier models (e.g., GLM-5.3-Flash, DeepSeek V4.1 Flash) for coding tasks. The exact model is not fixed and varies based on availability and successful responses. All changes were reviewed and verified by me before committing.

Adds `UpgradeState`, the persisted state for resuming an upgrade, with
its own state file so it never collides with the deploy state file.
It implements `ChunkWriterState` so `write_chunks` can be shared
between deploy and upgrade.

`DeployState` and `UpgradeState` now share `load_state_file` and
`save_state_file` helpers. This changes deploy-side behavior slightly:
- load returns `None` only on `NotFound`; other `IO` errors are now
propagated instead of being treated as a missing file.
- save removes the temp file if writing, syncing or renaming
fails, so raw keypair bytes are not left behind.
…e engine

Add state tracking and resume
support for program upgrades via `UpgradeState`, matching the deploy workflow.
Refactor chunk writing, state invariant validation, and buffer cleanup into unified helpers shared across deploy and upgrade.
Expose `--resume`, `--no-cleanup-on-failure`, and `--state-path` flags for upgrade commands.
Introduce preflight checks to verify on-chain state and avoid redundant writes if an upgrade already finalized.
Expand the RPC mock harness and add comprehensive tests for upgrade resume, crash recovery, and state invariants.
@greptile-apps

greptile-apps Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect the operator workflow for deploying and upgrading programs through the paymaster.

Fix the buffer-creation recovery gap and satisfy the required error-log sanitizing before merging.

Findings

  1. P1 Creation timeouts lose recovery state ▶
  2. P2 Smoke test skips real upgrades ▶
  3. P2 Recovery instructions leave rent locked ▶
  4. P2 RPC errors bypass log filtering ▶

Summary

Adds saved upgrade progress, resume support, buffer cleanup, and state-file locks.

  • Interrupted upgrades can resume or confirm that the new program is already active.
  • Deploy and upgrade runs can use locked state files at chosen paths.
  • RPC mocks can supply ordered account responses for deployment and upgrade flows.

Reviews (1) · Last reviewed commit: "docs(kora-deploy): document upgrade resu..." · Reviewed by Greptile

Comment on lines +556 to +559
let buffer = Keypair::new();
create_buffer_account(ctx, &kora_pubkey, &buffer, bytes.len()).await?;

let new_state = UpgradeState {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Creation timeouts lose recovery state

If buffer creation lands but its HTTP response or confirmation times out, create_buffer_account(...).await? returns before UpgradeState is saved. No cleanup runs on that error. The next run cannot resume or recover the buffer from a state file, leaving the paymaster's rent locked.

Save the buffer identity before sending the create transaction, and handle an uncertain creation result during resume.

Comment on lines +41 to +44
match sig {
Some(s) => println!(" upgraded program as registered owner (sig {s})"),
None => println!(" program was already upgraded"),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Smoke test skips real upgrades

This test deploys and upgrades the same program_so, so the new hash check returns None before uploading or sending an upgrade transaction. Accepting that result lets the smoke suite pass even when real upgrades are broken.

Use a different valid binary for the upgrade and require Some(signature) so the test still checks an on-chain upgrade.

Comment on lines 66 to +67
Either restore the original `.so` file, or delete the state file
and start a fresh deploy.
and start over.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Recovery instructions leave rent locked

The guide still tells users to delete the state file and start over after a hash mismatch. This PR now preserves the buffer on a mismatch, even with cleanup enabled. Following these instructions discards the recovery file while leaving the paymaster's rent locked.

Tell users to close the buffer before deleting the state file, matching the new error message.

Suggested change
Either restore the original `.so` file, or delete the state file
and start a fresh deploy.
and start over.
Either restore the original `.so` file, or close the buffer account shown
in the error message before deleting the state file and starting over.
Deleting the state file without closing the buffer leaves its rent locked.

Ok(resp) if resp.value => return Ok(()),
Ok(_) => {}
Err(err) => {
log::debug!("transient error confirming {sig}: {err}; retrying...");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 RPC errors bypass log filtering

The new retry logs write external RPC errors directly through {err} here and in wait_for_next_slot(). The repository guide requires errors from external services to be wrapped in sanitize_error! before logging.

Sanitize both errors before logging them. This repository requirement must be satisfied before merging.

Context Used: CLAUDE.md (source)

This branch has not been deployed

No deployments
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.

Add --resume and buffer cleanup support to upgrade()

1 participant