Skip to content

Protocol Upgrades

Kaizen uses a hard-fork model for protocol upgrades, similar to Ethereum. Upgrades are scheduled at specific block heights and are compiled into the node binary.

Overview

Block 499,999 (v1.0.0)


Block 500,000 (upgrade height)

    ├─→ Migrations run (if any)


Block 500,000+ (v1.1.0)

    └─→ New rules active

Key Concepts

Protocol Version

Each block is produced under a specific protocol version (e.g., v1.0.0). The version is:

  • Stored in BlockHeader.protocol_version
  • Included in block hash calculation
  • Used to gate new features
// Check version before enabling new feature
if version.at_least(1, 1, 0) {
    // New feature available
}

Upgrade Schedule

Upgrades are defined in code, NOT in configuration:

// crates/app/src/upgrade.rs
const MAINNET_UPGRADES: &[UpgradeDefinition] = &[
    UpgradeDefinition {
        name: "v1.1-new-features",
        height: 1_000_000,
        version: (1, 1, 0),
    },
];

This ensures all nodes have the same schedule without coordination.

Migrations

Migrations transform existing data when an upgrade activates:

impl Migration for MigrateToV1_1 {
    fn migrate(&self, state: &mut StateManager) -> Result<(), MigrationError> {
        // Transform data here
        // Runs once at upgrade height
    }
}

Adding an Upgrade

1. Define the Upgrade

// crates/app/src/upgrade.rs
const MAINNET_UPGRADES: &[UpgradeDefinition] = &[
    UpgradeDefinition {
        name: "v1.1-my-upgrade",
        height: 500_000,        // Activation height
        version: (1, 1, 0),
    },
];

2. Implement Logic Changes

Version-gate new behavior in the engine:

// crates/engine/src/...
fn execute(&mut self, ...) -> Result<...> {
    let version = self.state.current_protocol_version()?;
 
    if version.at_least(1, 1, 0) {
        // New behavior
    } else {
        // Old behavior
    }
}

3. Add Migration (if needed)

Only required if state schema changes:

// crates/app/src/upgrade.rs
pub mod migrations {
    pub struct MigrateToV1_1;
 
    impl Migration for MigrateToV1_1 {
        fn version(&self) -> ProtocolVersion {
            ProtocolVersion::new(1, 1, 0)
        }
 
        fn name(&self) -> &'static str {
            "v1.1-add-new-field"
        }
 
        fn migrate(&self, state: &mut StateManager) -> Result<(), MigrationError> {
            // Your migration logic
            Ok(())
        }
    }
}

Register in UpgradeRegistry::new():

handler.register_migration(Box::new(migrations::MigrateToV1_1));

4. Bump Version

// crates/types/src/upgrade.rs
impl ProtocolVersion {
    pub const CURRENT: Self = Self { major: 1, minor: 1, patch: 0 };
}

5. Release

  1. Merge changes
  2. Tag release (e.g., v1.1.0)
  3. Announce upgrade height and deadline
  4. Operators upgrade nodes before activation

Network Configuration

Set network in config to use the correct schedule:

[node]
network = "mainnet"  # or "testnet", "local"
NetworkUpgrade Schedule
mainnetProduction upgrades
testnetTest upgrades (more frequent)
localNo scheduled upgrades

Compatibility

Node Version Check

On startup, nodes verify compatibility:

✓ Protocol version check passed
  network: mainnet
  node_version: v1.1.0
  chain_version: v1.0.0

If incompatible:

✗ Node version v1.0.0 is incompatible with chain at height 500000
  Required: v1.1.0

Upgrade Timeline

  1. T-2 weeks: Announce upgrade
  2. T-1 week: Release new node version
  3. T-0: Upgrade activates at scheduled height

File Locations

PurposeFile
Upgrade schedulecrates/app/src/upgrade.rs
Migrationscrates/app/src/upgrade.rs (migrations module)
Version-gated logiccrates/engine/src/*.rs
Version constantscrates/types/src/upgrade.rs
Upgrade handlercrates/engine/src/upgrade.rs

Breaking Changes