diff --git a/proposals/0167-loader-v4.md b/proposals/0167-loader-v4.md new file mode 100644 index 000000000..ae12d754d --- /dev/null +++ b/proposals/0167-loader-v4.md @@ -0,0 +1,369 @@ +--- +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 +- disable new deployments on loader-v3 +`BPFLoaderUpgradeab1e11111111111111111111111` +- enable the loader-v3 migration instruction +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 + +#### Copy + +- Instruction accounts: + - `[writable]` The program account to copy to. + - `[signer]` The authority of the program. + - `[]` The program account to copy from, which can be owned by any loader. +- Instruction data: + - Enum variant `1u32` + - `u32` Offset at which to write. + - `u32` Offset at which to read. + - `u32` Length of the chunk to copy in bytes. +- Behavior: + - Check there are at least three 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 between the program accounts at the offsets, each shifted by + the header size of their loader respectively + +#### 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 `2u32` + - `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 `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 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 `4u32` +- 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 `5u32` +- 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 `6u32` +- 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 + +### Loader-v3 Migration Instruction + +- Instruction accounts: + - `[writable]` The program data account. + - `[writable]` The program account. + - `[signer]` The global migration authority. +- Instruction data: + - Enum variant `8u32` +- Behavior: + - Check that the provided authority is the global migration authority + (pubkey is TBD), + otherwise throw `IncorrectAuthority` + - Check that the program account is a program and not a buffer account, + otherwise throw `InvalidArgument` + - Transfer all funds from the program data account to the program account + - Clear the program account (setting its size to zero) + - Sets the owner of the program account to loader-v4 + - CPI loader-v4 `Truncate` the program account to the size of the program + data account and copy the program authority from the program data account + - If the program data account was not closed (size was greater 0): + - CPI loader-v4 `Copy` the program data account into the program account + - CPI loader-v4 `Deploy` the program account + - If the program data account was finalized: + - CPI loader-v4 `Finalize` without a next version forwarding + - The program data account still owned by loader-v3 is under-funded and will + be automatically deleted by rent collection at the end of the transaction + +## 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 loader-v4 is enabled and new programs can not be deployed on loader-v3 +anymore, the list of all loader-v3 programs becomes fixed and can be extracted +from a snapshot. Using the added loader-v3 migration instruction and the global +migration authority, the core protocol developers will then migrate all +loader-v3 programs to loader-v4 programs, which once completed: + +- 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 program data account size towards the total +transaction account loading limit. +- allows dApp devs to resuscitate closed loader-v3 programs if they still +control the program authority. This allows redeployment at the same address +or completely closing the program account in order to retrieve the locked +funds. + +## Security Considerations + +None. + +## Backwards Compatibility + +None.