Skip to main content

Upgrade Modes - Details

This page explains how the two upgrade modes work under the hood. If you just want to know which mode to pick and what to do, see Upgrade Modes. This page is for operators who want to understand the mechanism before trusting it with their nodes.

The Big Picture

During the Mesa hard fork, the network transitions from the current chain (referred to internally as "berkeley") to the new Mesa chain. Both upgrade modes use the same stop-slot mechanism to halt the old chain at a predetermined slot. The difference is what happens next:

  • Automode: the node automatically switches to the Mesa binary and resumes. No operator action needed.
  • Manual mode: the operator stops the old node, installs the Mesa release, and starts it themselves.

Both modes reach the same end state — a node running on the Mesa network, producing blocks after the Mesa genesis timestamp.

Stop-Slot Mechanism

Both modes rely on two critical slot numbers baked into the stop-slot release (3.x.x):

SlotWhat happens
stop-transaction-slotThe network stops accepting transactions. Blocks produced after this slot are empty — no user commands, no coinbase rewards, no fee transfers. This begins the State Finalization period.
stop-network-slotThe network stops producing and accepting blocks entirely. The chain halts. This is the point where the fork happens.

The gap between these two slots (approximately 100 slots) is the State Finalization period. It ensures all nodes converge on the same final state before the fork.

For block producers

You will not earn block rewards during State Finalization (no coinbase), but you must keep your node running to maintain network stability and block density. If you are in the Delegation Program, uptime tracking continues during this phase.

Automode — How It Works Internally

Dual-Binary Architecture

The automode release ships with two complete sets of Mina binaries in a single package:

ComponentPathPurpose
Pre-fork binary (mina-{network}-prefork-mesa){RUNTIMES_BASE_PATH}/berkeley/minaRuns the current chain up to the stop-network-slot
Post-fork binary (mina-{network}-postfork-mesa){RUNTIMES_BASE_PATH}/mesa/minaRuns the Mesa chain after the fork
Dispatcher (mina-dispatch)/usr/local/bin/mina-dispatchRoutes your commands to the correct binary

When you install the automode package (or use the automode Docker image), all three components are installed together.

The Dispatcher

The dispatcher (mina-dispatch) is a shell script that acts as a transparent wrapper around the real mina binary. When you run a mina command, the dispatcher decides which binary to execute:

  • daemon subcommand — routes based on the activation state file:
    Does the activation state file exist?
    → NO: route to the pre-fork (berkeley) binary
    → YES: route to the post-fork (mesa) binary
  • client subcommand — always routes to the post-fork (mesa) binary, regardless of activation state. This works because the GraphQL schema did not change between Berkeley and Mesa.
  • --version — passed through without processing.

The activation state file is located at {MINA_HARDFORK_STATE_DIR}/auto-fork-mesa-{network_id}/activated (e.g., ~/.mina-config/auto-fork-mesa-mainnet/activated on the host, or /root/.mina-config/auto-fork-mesa-mainnet/activated in Docker). This file is created by the daemon itself when it detects that the network has reached the stop-network-slot.

Dispatcher Limitations

Current implementation — may change in future releases

This limitation exists because, for most subcommands, the dispatcher does not receive the config directory location as an argument. Without access to the config directory, it cannot check for the activated state file and therefore cannot determine whether the node is running Berkeley or Mesa.

The dispatcher supports the following subcommands:

SubcommandRouting behavior
daemonRoutes to pre-fork or post-fork binary based on activation state
clientAlways routes to the post-fork (mesa) binary
--versionPassed through without processing

Any other subcommand (e.g., accounts list, ledger export) will fail with an error:

mina-dispatch ERROR: unsupported subcommand 'accounts' for automatic hardfork handling

For unsupported subcommands, invoke the correct version-specific binary directly:

BinaryWhen to usePath
mina-berkeleyBefore the fork (or to query pre-fork state)/usr/lib/mina/berkeley/mina
mina-mesaAfter the fork/usr/lib/mina/mesa/mina
# These work at any time — they bypass the dispatcher
mina-berkeley client status
mina-mesa accounts list
mina-mesa ledger export

# Full paths also work for any binary in either runtime
/usr/lib/mina/mesa/mina client status
note
About client routing

The client subcommand (e.g., mina client status) is always routed to the mesa binary because it communicates with the running daemon over GraphQL, and the GraphQL schema did not change between Berkeley and Mesa. This means mina client status works through the dispatcher at any point — before or after the fork — as long as a daemon is running.

What the Dispatcher Does for daemon Commands

When the dispatcher routes a daemon command to the Mesa binary, it automatically adjusts your command-line arguments:

  1. Config files: Your existing -config-file arguments are kept, and the Mesa-specific configuration is appended as the last -config-file entry. This ensures Mesa settings take precedence.
  2. Genesis ledger directory: Any --genesis-ledger-dir argument is rewritten to point to the Mesa ledger directory.
  3. Hardfork handling flag: The --hardfork-handling argument is removed (it is not supported on the Mesa chain).

Automode Timeline

Here is what happens from a block producer's perspective when using automode:

Automode upgrade flow diagram showing four phases: pre-upgrade, state finalization, automatic upgrade with process restart, and post-upgrade

Restart and Filesystem Requirements

The automode transition involves a process restart. When the daemon reaches the stop-network-slot, it:

  1. Generates the Mesa configuration (daemon.json) and genesis ledger tarballs in the config directory
  2. Writes an activated sentinel file to mark the fork as complete
  3. Exits with code 0 (clean shutdown)

The daemon does not restart itself. Your process manager must detect the exit and restart the process. On restart, the dispatcher sees the activated file and launches the Mesa binary with the auto-generated config.

This means two things are critical:

Persistent config directory — The config directory (typically ~/.mina-config or /root/.mina-config in Docker) must survive across restarts. It contains the activated file and the generated Mesa configuration. If this directory is ephemeral or gets wiped on restart, the node will restart into the Berkeley binary and fail to join the Mesa network.

Automatic restart on clean exit — Your process manager must be configured to restart the daemon after exit code 0:

PlatformConfigurationNotes
systemdRestart=alwaysDefault in the Mina systemd unit (mina.service). Restarts after 30 seconds.
Docker--restart=always or --restart=unless-stoppedSet when creating the container. The default (no) will not restart.
KubernetesrestartPolicy: AlwaysThe k8s default for pods. Ensure your liveness probes and Helm chart configuration do not treat exit code 0 as a failure that triggers a volume wipe or full pod replacement. The config directory volume must be a PersistentVolumeClaim, not emptyDir.
For Kubernetes / Helm users

If your Helm chart or pod spec uses emptyDir for the config directory, the activated file and generated Mesa config will be lost when the pod restarts. Use a PersistentVolumeClaim instead. Also verify that your restart logic does not re-initialize the config directory from scratch — the auto-generated Mesa config must be preserved.

Automode Docker Image

If you run your node with Docker, use the automode image (mina-daemon-auto-hardfork):

minaprotocol/mina-daemon-auto-hardfork:{version}-{codename}-{network}

This image:

  • Installs both the pre-fork and post-fork Debian packages
  • Sets MINA_APP=/usr/local/bin/mina-dispatch so the entrypoint uses the dispatcher
  • Sets MINA_HARDFORK_STATE_DIR=/root/.mina-config for the activation state file

You start it with the same flags you normally use. The dispatcher handles the rest.

Automode Debian Packages

If you run your node directly on a server (not Docker), install both automode Debian packages:

sudo apt-get install mina-{network}-prefork-mesa mina-{network}-postfork-mesa

Both packages are required for automode to work:

  • mina-{network}-prefork-mesa — the pre-fork binary that runs the current chain up to the stop-network-slot
  • mina-{network}-postfork-mesa — the post-fork binary that runs the Mesa chain after the fork

The postfork-mesa package also installs the dispatcher at /usr/local/bin/mina-dispatch and the required configuration in /etc/default/mina-dispatch.

caution

The dispatcher reads its configuration from /etc/default/mina-dispatch. This file must exist and be owned by root. Do not modify it unless you know what you are doing.

Manual Mode — How It Works

Manual mode is the traditional upgrade approach, similar to the Berkeley upgrade. You are in full control of every step.

Manual Mode Timeline

Manual mode upgrade flow diagram showing four phases: pre-upgrade, state finalization, manual upgrade with 5 operator steps, and post-upgrade

Manual Mode — What Gets Installed

When you install the Mesa release manually, the package includes:

  • The Mesa mina binary
  • A new runtime configuration JSON for the Mesa network
  • New genesis and epoch ledger tarballs

These replace the pre-fork components. There is no dispatcher involved — you are running the Mesa binary directly.

Manual Mode — Updating Your Flags

When switching from the pre-fork to the Mesa binary, you need to update your startup flags:

  1. Remove --hardfork-handling if you were using it
  2. Update your --genesis-ledger-dir to point to the Mesa ledger directory (included in the package)
  3. Update your -config-file to use the Mesa configuration
  4. Keep your existing --block-producer-key, --libp2p-keypair, and other operator-specific flags

See Post-Upgrade Flags for exact flag values.

Docker (Manual Mode)

For manual mode with Docker, use the hardfork image (not auto-hardfork):

minaprotocol/mina-daemon-hardfork:{version}-{codename}-{network}

This image includes both pre-fork and post-fork packages but uses a dedicated hardfork entrypoint that requires manual intervention to complete the transition.

Which Mode Should I Pick?

Choose Automode if:

  • You want zero intervention during the fork window
  • You are comfortable with the dispatcher managing binary routing
  • You run a standard block producer or SNARK coordinator setup
  • You want to minimize the risk of missing the Mesa genesis timestamp

Choose Manual mode if:

  • You have custom deployment automation that controls which binary runs
  • You want to inspect the Mesa release before starting it
  • You run a non-standard setup where automatic argument rewriting could cause issues
  • You need to coordinate the upgrade with other infrastructure changes

Key Differences at a Glance

AutomodeManual
Packages installedPre-fork + post-fork + dispatcherPre-fork only (Mesa installed later)
Binary switchingAutomatic via dispatcherYou stop old, install new, start new
Flag changes at forkHandled by dispatcherYou update flags yourself
Downtime at forkSeconds (automatic transition)Minutes to hours (depends on you)
Docker imagemina-daemon-auto-hardforkmina-daemon-hardfork
Risk of missing genesisVery lowDepends on how fast you act
For block producers worried about missed blocks

With automode, your node transitions automatically at the stop-network-slot. As long as your node was running during State Finalization, it will be ready to produce blocks at the Mesa genesis timestamp without any action from you.

With manual mode, the time between the stop-network-slot and when you restart your node on Mesa is downtime. If the Mesa genesis timestamp arrives before you finish the upgrade, you will miss blocks until your node catches up.

Troubleshooting Automode

How do I know which binary my node is using?

Check whether the activation state file exists. The file is located at:

{config-directory}/auto-fork-mesa-{network_id}/activated

Where:

  • {config-directory} is the path passed via --config-directory (defaults to ~/.mina-config on the host, or /root/.mina-config in Docker)
  • {network_id} is the network name (e.g., mainnet or devnet)

For example, on a typical mainnet setup:

ls ~/.mina-config/auto-fork-mesa-mainnet/activated
  • File does not exist: your node is using the pre-fork (Berkeley) binary
  • File exists: your node has transitioned to the Mesa binary

Can I run non-daemon commands or use a specific binary version?

As described in the Dispatcher Limitations section, the dispatcher supports daemon, client, and --version. For any other command — including accounts list, ledger export, and other CLI operations — you must use the version-specific binary directly:

BinaryDescriptionFull path
mina-berkeleyPre-fork binary (current chain)/usr/lib/mina/berkeley/mina
mina-mesaPost-fork binary (Mesa chain)/usr/lib/mina/mesa/mina
# Use mina-mesa for all non-daemon commands after the fork
mina-mesa client status
mina-mesa accounts list
mina-mesa ledger export

# Use mina-berkeley for pre-fork queries
mina-berkeley client status

These symlinks (mina-berkeley, mina-mesa) are installed globally by the automode packages and are always available. They bypass the dispatcher entirely and run the binary directly, so they work regardless of the activation state.

Do not manipulate the activation state file

While it is technically possible to force the dispatcher to use a specific runtime by creating or removing the activated file, this is strongly discouraged. Manually manipulating the state file while the network is live can put your node on the wrong chain. If you need to run a specific version, use mina-berkeley or mina-mesa directly instead.

Debug mode

Set MINA_DISPATCHER_DEBUG=1 in your environment to see which binary and arguments the dispatcher is using:

MINA_DISPATCHER_DEBUG=1 mina daemon ...

For a dry run (print the command without executing):

MINA_DISPATCHER_DRYRUN=1 mina daemon ...