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

Documentation for upgrades and use upgrade version for enabling upgrade instead of current sequencer version #1694

Merged
merged 9 commits into from
Jul 11, 2024
72 changes: 72 additions & 0 deletions doc/upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

# Upgrades

Hotshot protocol supports upgrades through an Upgrade proposal mechanism. The Upgrade proposal is broadcast separately from the `QuorumProposal`, typically several views in advance of its attachment. The goal is to ensure ample time for nodes to receive and prepare for the upgrade process.

After enough votes have been collected on the `UpgradeProposal`, an `UpgradeCertificate` is formed. This is attached to the next `QuorumProposal`, and any node that receives an `UpgradeCertificate` in this way re-attaches it to its own `QuorumProposal` until the network has upgraded, or (in rare cases) we failed to reach consensus on the `UpgradeCertificate`.

## Enabling an Upgrade

To enable an upgrade in Hotshot protocol, it is essential to define the base version, the upgrade version, and a upgrade hash:

- **Base Version:** Represents the current version of the protocol (`0.1` in this example).
- **Upgrade Version:** Specifies the version to which the protocol will upgrade once the process is successful (`0.2` in this example).
- **Upgrade Hash:** Acts as a unique identifier for the specific upgrade nodes are voting on. It distinguishes between different proposals of the same version upgrade, ensuring nodes vote and execute the correct one. It consists of a sequence of 32 bytes.

These are defined in [NodeType implementation](../types/src/v0/mod.rs) for the Types (`SeqTypes` in our case).
```rust
impl NodeType for SeqTypes {
type Base = StaticVersion<0, 1>;
type Upgrade = StaticVersion<0, 2>;
const UPGRADE_HASH: [u8; 32] = [
1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
],
..
}
```

These parameters are fetched from the genesis TOML file and set in Hotshot config:

- **start_voting_view:** view at which voting for the upgrade proposal starts. In our implementation, this is set to 1 so that voting is enabled as soon as the node is started.
- **stop_voting_view:** view at which voting for the upgrade proposal stops. To disable an upgrade, set this parameter to 0 or ensure `stop_voting_view` is less than `start_voting_view`.
- **start_proposing_view:** the earliest view in which the node can propose an upgrade. This should be set to when an upgrade is intended. If the current view > `start_proposing_view`, the node will send out an `UpgradeProposal`.
- **stop_proposing_view:** view after which the node stops proposing an upgrade. If the upgrade proposal fails and the current view > stop_proposing_view then the upgrade is never proposed again.

The window between `start_proposing_view` and `stop_proposing_view` should provide sufficient time for nodes to continue proposing the upgrade until successful.

Ensure that the `ESPRESSO_SEQUENCER_GENESIS_FILE` environment variable is defined to point to the path of the genesis TOML file. For an example with upgrades enabled, refer to [`data/genesis/demo.toml`](../data/genesis/demo.toml).

Note: We currently support only chain config upgrade.
### Example TOML Configuration

```toml
[[upgrade]]
version = "0.2"
view = 5
propose_window = 10

[upgrade.chain_config]
chain_id = 999999999
base_fee = '2 wei'
max_block_size = '1mb'
fee_recipient = '0x0000000000000000000000000000000000000000'
fee_contract = '0xa15bb66138824a1c7167f5e85b957d04dd34e468'
```
In the TOML configuration example above, the `upgrade` section defines an array of tables, each specifying upgrade parameters:

- **Version:** the new version after an upgrade is successful.
- **View:** Represents the `start_proposing_view` value at which the upgrade is proposed.
- **Propose Window:** Refers to the view window between `start_proposing_view` and `stop_proposing_view`.

The `upgrade.chain_config` table contains the complete set of chain config parameters, which can be used, for example, to enable protocol fees or modify other parameters.


## Fee upgrade

A successful Hotshot upgrade results in a new version, which allows us to update the `ChainConfig` and execute the upgrade if there exists any. `Chainconfig` includes the fee parameters. The sequencer node has two states: `NodeState` and `ValidatedState`. `NodeState` is an immutable state that contains `ResolvableChainConfig` (Enum of `ChainConfig`'s commitment and full `ChainConfig`), whereas `ValidatedState` is a mutable state. To make updates to the chain config post-upgrade possible, `ResolvableChainConfig` is also added to `ValidatedState`.

`NodeState` also includes two additional fields: `upgrades` and `current_version`. Functions like `Header::new()` and `ValidatedState::apply_header()` include a version parameter, which is used to apply upgrades by checking if this version is greater than `current_version` in NodeState and fetching the upgrade, if available, from the upgrades BTreeMap in NodeState.

In scenarios where nodes join the network or restart, missing the upgrade window may result in their ValidatedState having only a chain config commitment. In such cases, nodes need to catch up from their peers to get the full chain config for this chain config commitment.

Note: For the fee upgrade to work, the builder must have sufficient funds to cover the fees. The Espresso bridge can be used to fund the builder.
8 changes: 4 additions & 4 deletions sequencer/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1447,12 +1447,12 @@ mod test {
..Default::default()
};
let mut map = std::collections::BTreeMap::new();
let view = 5;
let start_proposing_view = 5;
let propose_window = 10;
map.insert(
Version { major: 0, minor: 2 },
Upgrade {
view,
start_proposing_view,
propose_window,
upgrade_type: UpgradeType::ChainConfig {
chain_config: chain_config_upgrade,
Expand All @@ -1463,8 +1463,8 @@ mod test {
let stop_voting_view = 100;
let upgrades = TestNetworkUpgrades {
upgrades: map,
start_proposing_view: view,
stop_proposing_view: view + propose_window,
start_proposing_view,
stop_proposing_view: start_proposing_view + propose_window,
start_voting_view: 1,
stop_voting_view,
};
Expand Down
4 changes: 2 additions & 2 deletions sequencer/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ mod upgrade_serialization {
for (version, upgrade) in map {
seq.serialize_element(&(
version.to_string(),
upgrade.view,
upgrade.start_proposing_view,
upgrade.propose_window,
upgrade.upgrade_type.clone(),
))?;
Expand Down Expand Up @@ -115,7 +115,7 @@ mod upgrade_serialization {
map.insert(
version,
Upgrade {
view: fields.view,
start_proposing_view: fields.view,
propose_window: fields.propose_window,
upgrade_type: fields.upgrade_type,
},
Expand Down
8 changes: 5 additions & 3 deletions sequencer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,11 @@ pub async fn init_node<P: PersistenceOptions, Ver: StaticVersionType + 'static>(
}
};

let version = Ver::version();
if let Some(upgrade) = genesis.upgrades.get(&version) {
let view = upgrade.view;
if let Some(upgrade) = genesis
.upgrades
.get(&<SeqTypes as NodeType>::Upgrade::VERSION)
{
let view = upgrade.start_proposing_view;
config.config.start_proposing_view = view;
config.config.stop_proposing_view = view + upgrade.propose_window;
config.config.start_voting_view = 1;
Expand Down
37 changes: 36 additions & 1 deletion types/src/v0/v0_1/instance_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use vbs::version::Version;

use super::l1::L1Client;

/// Represents the specific type of upgrade.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[serde(rename_all = "snake_case")]
Expand All @@ -19,14 +20,33 @@ pub enum UpgradeType {
ChainConfig { chain_config: ChainConfig },
}

/// Represents the upgrade config including the type of upgrade and upgrade parameters for hotshot config.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Upgrade {
pub view: u64,
/// The view at which the upgrade is proposed.
///
/// Note: Voting for the proposal begins before the upgrade is formally proposed.
/// In our implementation, `start_proposing_view` is set to `1`` for all upgrades,
/// so if an upgrade is planned then the voting starts as soon as node is started.
#[serde(rename = "view")]
pub start_proposing_view: u64,

/// The time window during which the upgrade can be proposed.
///
/// This parameter is used for setting the `stop_propose_window_view`.
/// `stop_proposing_view` is calculated as `start_proposing_view + propose_window`.
pub propose_window: u64,

/// The specific type of upgrade configuration.
///
/// Currently, we only support chain configuration upgrades (`upgrade.chain_config` in genesis toml file).
#[serde(flatten)]
pub upgrade_type: UpgradeType,
}

/// Represents the immutable state of a node.
///
/// For mutable state, use `ValidatedState`.
#[derive(Debug, Clone)]
pub struct NodeState {
pub node_id: u64,
Expand All @@ -36,6 +56,21 @@ pub struct NodeState {
pub genesis_header: GenesisHeader,
pub genesis_state: ValidatedState,
pub l1_genesis: Option<L1BlockInfo>,

/// Map containing all planned and executed upgrades.
///
/// Currently, only one upgrade can be executed at a time.
/// For multiple upgrades, the node needs to be restarted after each upgrade.
///
/// This field serves as a record for planned and past upgrades,
/// listed in the genesis TOML file. It will be very useful if multiple upgrades
/// are supported in the future.
pub upgrades: BTreeMap<Version, Upgrade>,
/// Current version of the sequencer.
///
/// This version is checked to determine if an upgrade is planned,
/// and which version variant for versioned types
/// to use in functions such as genesis.
/// (example: genesis returns V2 Header if version is 0.2)
pub current_version: Version,
}
Loading