Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIMD-0167: Loader-v4 #167

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 324 additions & 0 deletions proposals/0167-loader-v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
---
simd: '0167'
title: Loader-v4
authors:
- Alexander Meißner
category: Standard
type: Core
status: Review
created: 2024-08-15
feature: TBD
---

## Summary

A new upgradeable loader which only requires a single account per program.

## Motivation

Loader-v3, which is currently the only deployable loader, requires two accounts
per program. This was a workaround to circumvent the finality of the
`is_executable` flag, which is removed in SIMD-0162. Consequentially, this
setup of the program account being a proxy account, containing the address of
the actual programdata account, is no longer necessary and should be removed.

Another issue with loader-v3 is that the executable file stored in the
programdata account is misaligned relative to the beginning of the account.

Additionally, there currently is no complete specification of the loaders
program management instructions. This proposal would thus fill that gap once
loader-v4 goes into production.

See impact for further motivation.

## Alternatives Considered

A delay-visibility-free redeployment could be achieved by keeping the swap
program around until the end of the slot. This would however mean that two
accounts per program must be loaded until the dapp developer reclaims the
second one. That would defy the purpose of this proposal which is to get rid
of the proxy account.

## New Terminology

None.

## Detailed Design

The associated feature gate must:

- add loader-v4 to the write lock demotion exceptions
- enable loader-v4 `LoaderV411111111111111111111111111111111111` program
management and execution
- simultaneously disable new deployments on loader-v3
(`BPFLoaderUpgradeab1e11111111111111111111111`),
throwing `InvalidIstructionData` if `DeployWithMaxDataLen` is called.

### Owned Program Accounts

Accounts of programs owned by loader-v4 must have the following layout:

- Header (which is 48 bytes long):
- `u64` Slot in which the program was last deployed, retracted or
initialized.
- `[u8; 32]` Authority address which can send program management
instructions. Or if the status is finalized, then the address of the next
version of the program.
- `u64` status enum:
- Enum variant `0u64`: Retracted, program is in maintenance
- Enum variant `1u64`: Deployed, program is ready to be executed
- Enum variant `2u64`: Finalized, same as `Deployed`, but can not be
modified anymore
- Body:
- `[u8]` The programs executable file

Verification the program account checks in the following order that:

- the owner of the program account is loader-v4,
otherwise throw `InvalidAccountOwner`
- the program account is at least as long enough for the header,
otherwise throw `AccountDataTooSmall`
- the program account is writable, otherwise throw `InvalidArgument`
- the provided authority (instruction account at index 1) signed,
otherwise throw `MissingRequiredSignature`
- the authority stored in the program account is the one provided,
otherwise throw `IncorrectAuthority`
- the status stored in the program account is not finalized,
otherwise throw `Immutable`

### Execution / Invocation

Invoking programs owned by loader-v4 checks in the following order that:

- the owner of the program account is loader-v4
- the program account is at least as long enough for the header
- the status stored in the program account is not retracted
- the program account was not deployed within the current slot (delay
visibility)
- the executable file stored in the program account passes executable
verification

failing any of the above checks must throw `UnsupportedProgramId`.

### Program Management Instructions

All program management instructions must cost 2000 CUs.

#### Write

- Instruction accounts:
- `[writable]` The program account to write to.
- `[signer]` The authority of the program.
- Instruction data:
- Enum variant `0u32`
- `u32` Offset at which to write the given bytes
- `[u8]` Chunk of the programs executable file
- Behavior:
- Check there are at least two instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- Verify the program account
- Check the status stored in the program account is retracted,
otherwise throw `InvalidArgument`
- Check that the end offset (sum of offset and length of the chunk) does
not exceed the maximum (program account length minus the header size),
otherwise throw `AccountDataTooSmall`
- Copy the chunk into the program account at the offset shifted by the
header size

#### Truncate

- Instruction accounts:
- `[(signer), writable]` The program account to change the size of.
- `[signer]` The authority of the program.
- `[writable]` Optional, the recipient account.
- Instruction data:
- Enum variant `1u32`
- `u32` The new size after the operation.
- Behavior:
- Check there are at least two instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- If this is an initialization (program account length is too short to
contain the header and the requested new size is greater 0):
- the owner of the program account is loader-v4,
otherwise throw `InvalidAccountOwner`
- the program account is writable, otherwise throw `InvalidArgument`
- the program account (instruction account at index 0) signed,
otherwise throw `MissingRequiredSignature`
- the provided authority (instruction account at index 1) signed,
otherwise throw `MissingRequiredSignature`
- If this is not an initialization:
- Verify the program account
- Check that the status stored in the program account is retracted,
otherwise throw `InvalidArgument`
- Check that there are enough funds in the program account for rent
exemption, otherwise throw `InsufficientFunds`
- If there are more than enough funds:
- Check there are at least three instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- Check that the recipient account (instruction account at index 2) is
writable, otherwise throw `InvalidArgument`
- Transfer the surplus from the program account to the recipient account
- If the requested new size is zero:
- Delete the entire program account, including the header
- If the requested new size is greater than zero:
- Set the length of the program account to the requested new size plus
the header size
- In case that this is an initialization, also initialize the header:
- Set the `is_executable` flag to `true`
- Set the slot to zero, **not** the current slot
- Set the authority address (from the instruction account at index 1)
- Set the status to retracted

#### Deploy

- Instruction accounts:
- `[writable]` The program account to deploy.
- `[signer]` The authority of the program.
- `[writable]` Optional, an undeployed source program account to take data
and lamports from.
- Instruction data:
- Enum variant `2u32`
- Behavior:
- Check there are at least two instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- Verify the program account
- Check that the slot stored in the program account is not the current
(deployment cooldown), otherwise throw `InvalidArgument`
- Check that the status stored in the program account is retracted
otherwise throw `InvalidArgument`
- In case a source program was provided (instruction account at index 2):
- Verify the source program account
- Check that the status stored in the source program account is retracted,
otherwise throw `InvalidArgument`
- Check that the executable file stored in the source program account
passes executable verification
- The feature set that the executable file is verified against is not
necessarily the current one, but the one of the epoch of the next slot
- Also, during deployment certain deprecated syscalls are disabled,
this stays the same as in the older loaders
- Copy the entire source program account into the program account
- Set the length of the source program account to zero
- Transfer all funds of the source program account to the program
account
- In case no source program was provided:
- Check that the executable file stored in the program account passes
executable verification
- Change the slot in the program account to the current slot
- Change the status stored in the program account to deployed

#### Retract

- Instruction accounts:
- `[writable]` The program account to retract.
- `[signer]` The authority of the program.
- Instruction data:
- Enum variant `3u32`
- Behavior:
- Check there are at least two instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- Verify the program account
- Check that the slot stored in the program account is not the current
(deployment cooldown), otherwise throw `InvalidArgument`
- Check that the status stored in the program account is deployed,
otherwise throw `InvalidArgument`
- Note: The slot is **not** set to the current slot to allow a
retract-modify-redeploy-sequence within the same slot
- Change the status stored in the program account to retracted

#### TransferAuthority

- Instruction accounts:
- `[writable]` The program account to change the authority of.
- `[signer]` The current authority of the program.
- `[signer]` The new authority of the program.
- Instruction data:
- Enum variant `4u32`
- Behavior:
- Check there are at least three instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- Verify the program account
- Check that the new authority (instruction account at index 2)
signed as well, otherwise throw `MissingRequiredSignature`
- Check that the authority stored in the program account is different
from the one provided, otherwise throw `InvalidArgument`
- Copy the new authority address into the program account

#### Finalize

- Instruction accounts:
- `[writable]` The program account to change the authority of.
- `[signer]` The current authority of the program.
- `[]` Optional, the reserved address for the next version of the program.
- Instruction data:
- Enum variant `5u32`
- Behavior:
- Check there are at least three instruction accounts,
otherwise throw `NotEnoughAccountKeys`
- Verify the program account
- Check that the status stored in the program account is deployed,
otherwise throw `InvalidArgument`
- for the program account of the next version
(instruction account at index 2) check that:
- the owner of the program account is loader-v4,
otherwise throw `InvalidAccountOwner`
- the program account is at least as long enough for the header,
otherwise throw `AccountDataTooSmall`
- the authority stored in the program account is the one provided,
otherwise throw `IncorrectAuthority`
- the status stored in the program account is not finalized,
otherwise throw `Immutable`
- Copy the address of the next version into the next version field stored in
the previous versions program account
- Change the status stored in the program account to finalized

## Impact

This proposal:

- covers all the use cases loader-v3 had but in a cleaner way and comes with
a specification.
- allows finalized programs to mark which other program supersedes them which
can then be offered as an option in forntends. This provides a more secure
alternative to redeployment / upgrading of programs at the same address.
- makes deployment slightly cheaper for dapp developers as they would no longer
have to burn funds for the rent exception of the proxy account.
- provides an alternative redeployment path which does not require a big
deposit of funds for rent exception during the upload.
- enables dapp developers to withdrawl the surplus of funds required for rent
exception when shortening the length of program accounts or closing them.
- shortens the workflow of temporarily closing a program to a single
instruction, instead of having to build and redeploy an empty program.
- properly alignes the executable file relative to the beginning of the
account. In loader-v3 it is misaligned.
- once all loader-v3 programs are migrated:
- allows transaction account loading to be simplifed, because every program
would load exactly one account, no need to load the proxy account to get to
the actual program data (which is not listed in the transaction accounts).
- allows the removal of the write lock demotion exception if loader-v3 is
present in a transaction.
- corrects the miscounting of the proxy account size towards the total
transaction account loading limit.

## Security Considerations

None.

## Backwards Compatibility

This proposal does not break any existing programs. However, dapp developers
might want to profit from the new program mangement instructions without
influencing their users work flows. To do so they would need a way to turn the
program accounts of loader-v3 to program accounts of loader-v4, changing the
account owner but keeping the program address. A potential issue is that the
programdata header of loader-v3 is only 45 bytes long while loader-v4 takes 48
bytes. An automatic mechanism in the program runtime (triggered by feature
activation) could then perform the following steps per program:

- loader-v3 clears the program proxy account (setting its size to zero)
- loader-v3 transfers all funds from the programdata to the proxy account
- loader-v3 gifts the program proxy account to loader-v4

Choose a reason for hiding this comment

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

How would the gifting work? Do we need to add a new instruction to the v3 loader? Will this upgrade mechanism be specified in a future SIMD?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see three options here:

  1. A migrate instruction for loader-v3 which the dApp devs trigger themselves
  2. A migrate instruction for loader-v3 which we the protocol engineers trigger
  3. A feature gate which scans the accounts DB and migrates all loader-v3 programs

I would prefer the second option as that allows us to keep the gathering of the loader-v3 program list and then slowly working through it off-chain and still makes sure that all loader-v3 programs get migrated.

- loader-v4 initializes it via `Truncate`
- loader-v4 copies the data from the programdata account via `Write`
- loader-v4 deploys it via `Deploy`
- Optinally, loader-v4 finalizes it without a next version forwarding
- loader-v3 closes the programdata account (setting its size to zero)
Loading