The goal of this post is to describe the mechanics of Livepeer protocol smart contract upgrades. In this early alpha stage of the network, upgradeability is especially important to enable fixes to inevitable bugs (such as this one) common to all early software and iteration on protocol mechanics based on experimentation and community feedback. Deploying the new ServiceRegistry contract introduced in the upcoming networking change and rolling out future LIPs will involve the use of one of the methods described in this post.
The Livepeer protocol smart contract system consists of a Controller contract and a set of contracts that are registered with a Controller (for a more detailed description of the specific functionality offered by the contracts check out the specification). The Controller maintains a registry mapping contract IDs to on-chain contract addresses. A contract ID is defined as the keccak256 hash of the contract name. For example, the contract ID for a contract with the name “BondingManager” would be
keccak256("BondingManager"). If a contract is registered with the Controller, there is an entry in the Controller’s registry mapping the contract’s ID to its on-chain address.
The benefit of maintaining mappings from contract IDs to on-chain contract addresses is that contracts can be referenced by their ID with the contract address changing over time. A contract that needs to interact with another contract in the system can lookup the contract’s address from the Controller using the relevant contract ID. Clients that interact with the Livepeer protocol contracts only need to know the address of the Controller - they can lookup the rest of the contract addresses using the Controller.
Only the owner of the Controller contract is able to update the contract registry. At the moment, the owner of the Controller contract is a governance multisig.
Types of Upgrades
1. Proxy Contract Upgrades
A few of the Livepeer protocol contracts are implemented using a proxy upgrade pattern meaning that a proxy contract persists storage while relying on a target implementation contract for business logic. If you are interested in learning more about this pattern check out this great ZeppelinOS labs post.
At the moment, the 3 contracts using the proxy upgrade pattern are the BondingManager (handles transcoder registration, LPT bonding and fees/reward payouts), the JobsManager (handles transcode job submission and verification), and the RoundsManager (handles round progression and initialization).
The contract ID of a proxy contract is defined as the keccak256 hash of the contract name while the contract ID of an associated target implementation contract is defined as the keccak256 hash of the concatenation of the contract name and the word “Target”. For example, the contract ID of the proxy contract for the BondingManager is
keccak256("BondingManager") and the contract ID of the target implementation contract for the BondingManager is
keccak256("BondingManagerTarget"). Whenever the proxy contract receives a function call, it will look up the address for its target implementation contract using the Controller and then forward the function call to the target implementation contract.
With all this in mind, the steps for executing a proxy contract upgrade are the following:
- Make relevant updates to the target implementation contract (adhering to any storage layout restrictions)
- Deploy the new target implementation contract (this can be done using any account)
- Register the new target implementation contract with the Controller (this is currently done using the governance multisig)
After step 3, the next function call that the proxy contract receives will be forwarded to the new target implementation contract since the registry in the Controller has been updated. As a result, users of the contract will be able to immediately use the functionality of the new target implementation contract with all previous storage maintained by the proxy contract preserved.
Clients will only need to be updated if a contract ABI is changed such that additional functions that need to be used by the client are introduced. Otherwise, clients will be able to continue running without interruption.
2. Adding New Contracts
Adding a new contract to the Livepeer protocol contract system can be much more straightforward than upgrading existing contracts.
- Deploy the new contract (this can be done using any account)
- Register the new contract with the Controller (this is currently done using the governance multisig)
The process becomes more complicated if existing contracts need to be able to interact with the new contract - each of those existing contracts would need to be upgraded in order to implement logic to interact with the new contract. The process remains simple if existing contracts do not need to be able to interact with the new contract - an example of this is the new ServiceRegistry contract to be included in the upcoming networking change.