---
fip: "<to be assigned>" <!--keep the qoutes around the fip number, i.e: `fip: "0001"`-->
title: Add support for upgradable actors
author: Fridrik Asmundsson (@fridrik01), Steven Allen (@stebalien)
discussions-to: https://github.com/filecoin-project/FIPs/discussions/396
status: Draft
type: Technical
category: Core
created: 2023-11-27
---
This FIP introduces support for upgradable actors, enabling deployed actors to update their code while retaining their address, state, and balance. This feature is currently limited to use by built-in actors, and as of now, no built-in actor has been updated to become upgradable.
This FIP proposes the integration of upgradable actors into the Filecoin network through the introduction of a new upgrade_actor
syscall and an optional upgrade
WebAssembly (Wasm) entrypoint.
Upgradable actors provide a framework for seamlessly replacing deployed actor code, significantly enhancing the user experience when updating deployed actor code.
Currently, the code associated with all actors on the Filecoin Network is immutable once deployed. To modify the actor code, such as fixing a security bug, the following steps are required:
- Deploy a new actor with the corrected code.
- Migrate all state from the previous actor to the new one.
- Update all other actors interacting with the old actor to use the new actor.
By adding support for upgradable actors, deployed actors can easily upgrade their code and no longer need to go through the series of steps mentioned above.
This FIP is also motivated by the f4
extensible address class which was introduced in [FIP-0048] and required special "placeholder" actors to support interactions with addresses that do not yet exist on-chain. With upgradable actors we can simplify this address class and remove these placeholder actors completely. We will be able to deploy real actors and upgrade their code on first send.
Furthermore, this FIP paves the way for moving more network upgrade logic on-chain in the future, enabling a more seamless process for implementing critical updates and ensuring the continuous improvement of the Filecoin Network.
Introducing support for actor upgrades involves the following changes to the FVM:
- Adding a new
upgrade
Wasm entrypoint, which actors must implement in order to be a valid upgrade target. - Adding a new
upgrade_actor
syscall, enabling actors to upgrade themselves.
These changes are discussed in detail in the following sections.
We introduce a new optional upgrade
Wasm entrypoint. Deployed actors must implement this entrypoint to be a valid upgrade target. It is defined as follows:
pub fn upgrade(params_id: u32, upgrade_info_id: u32) -> u32
Parameters:
params_id
: An IPLD block handle provided by the caller and sent to the upgrade receiver, or0
for none.upgrade_info_id
: An IPLD block handle for anUpgradeInfo
struct provided by the FVM runtime (defined below).
The single u32
return value is an IPLD block handle, or 0
for none.
The UpgradeInfo
struct is defined as follows:
#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)]
pub struct UpgradeInfo {
// the old code cid we are upgrading from
pub old_code_cid: Cid,
}
When a target actor's upgrade
Wasm entrypoint is called, it can make necessary state tree changes from the calling if needed to its actor code. The UpgradeInfo
struct provided by the FVM runtime can be used to check what code CID its upgrading from. A successful return from the upgrade
entrypoint instructs the FVM that it should proceed with the upgrade. The target actor can reject the upgrade by calling `sdk::vm::exit()`` before returning from the upgrade entrypoint.
We introduce a new upgrade_actor
syscall which calls the upgrade
Wasm entrypoint of the calling actor and then atomically replaces the code CID of the calling actor with the provided code CID, and returns the exit code and block of the return. It is defined as follows:
pub fn upgrade_actor(
new_code_cid_off: *const u8,
params: u32,
) -> Result<Send>;
Parameters:
new_code_cid_off
: The code CID the calling actor should be replaced with.params
: The IPLD block handle passed, or0
for none.
The Send
struct is defined as follows:
pub struct Send {
// exit code returned by the upgrade endpoint
pub exit_code: u32,
// the block id/codec/size returned by the upgrade endpoint, or 0 if no block was returned
pub return_id: BlockId,
pub return_codec: u64,
pub return_size: u32,
}
On successful upgrade, this syscall will not return. Instead, the current invocation will "complete" and the return value will be the block returned by the new code's upgrade
endpoint. If the new code rejects the upgrade (calls sdk::vm::exit()
) or performs an illegal operation, this syscall will return the exit code plus the error returned by the upgrade endpoint.
This syscall will:
- Validate that the pointers passed to the syscall are in-bounds.
- Validate that
new_code_cid_off
is a valid code CID. - Validate that the calling actor is not currently executing in "read-only" mode. If so, the syscall fails with a "ReadOnly" (13) syscall error.
- Checks whether the calling actor is already on the call stack where it has previously been called on its
invoke
entrypoint (note that we allow callingupgrade
recursively). If so, the syscall fails with a "Forbidden" (11) syscall error. For example if an actor A has a call stackA (upgrade -> upgrade -> upgrade)
then that is allowed, while call stackA -> B -> A (upgrade)
would be rejected. - Checks that we have space for storing the return block. If not, the syscall fails with a "LimitExceeded" (3) syscall error.
- Start a new Call Manager transaction:
- Validate that the calling actor has not been deleted. If so, the syscall fails with a "IllegalOperation" (2) syscall error.
- Update the actor in the state tree with the new
new_code_cid
keeping the samestate
,sequence
andbalance
. - Invoke the target actor's
upgrade
entrypoint. - If the target actor does not implement the
upgrade
entrypoint, the syscall fails with aExitCode::SYS_INVALID_RECEIVER
exit code. - If the target actor aborts the
upgrade
entrypoint by callingsdk::vm::exit()
, the syscall fails with the provided exit code.
- Apply transaction, committing changes.
- Abort the calling actor and return the IPLD block from the
upgrade
entrypoint.
We considered adding a new get_old_code_cid
syscall to get the calling actors code CID. That has the benefit of keeping the upgrade
entrypoint signature consistent with the invoke
signature. We however rejected that as we felt the benefit didn't outweigh the overhead of adding a new syscall. Furthermore, it did not provide the flexibility of passing in an IPLD handle for a UpgradeInfo
struct where we can easily add more fields if required.
Full backwards compatibility is expected.
Detailed test cases are provided with the implementation.
Upgradable actors pose potential security risks, as users can replace deployed actors' code. However, measures are in place to minimize these risks:
- Upgradable actors are opt-in by default, ensuring no impact on currently deployed actors.
- Actors can only upgrade themselves, preventing one actor from upgrading another actor to a new version.
- We reject re-entrant
upgrade_actor
syscalls, i.e., if some actorA
is already on the call stack, no "deeper" instance ofA
should be able to call the upgrade syscall.
Detailed tests cover these security considerations and edge cases.
This FIP does not materially impact incentives in any way.
This FIP makes it possible to upgrade deployed actors, for example in cases where a bug or security concern was identified in the deployed code, allowing a simple safe way to address such issues which significantly improves the user experience from how it is today.
N/A
Copyright and related rights waived via CC0.