Skip to main content

Beta v4 migration guide

Linea Beta v4 is a mandatory hard fork introducing Maru, the new consensus layer (CL).

Maru replaces Clique and aligns Linea with Ethereum's dual-layer design (execution and consensus).

After the fork:

  • You must run both an execution layer (EL) client of your choice and Maru, a CL client. A standalone EL client will not work.
  • Sequencer signatures move from EL extraData to the Maru attestations API.
  • EL clients must be upgraded to Beta v4 compatible versions (any EL client that supports Prague should be compatible).

See the Beta v4 release notes for the scheduled hard fork dates.

We have separate instructions below for:

Overview of changes​

Breaking change: sequencer signatures​

Sequencer signatures will no longer be in EL block extraData after the upgrade.

Before (pre-fork):

// on EL client
let signatures = block.extra_data;

After (post-fork):

// Must switch to Maru API
let signatures = maru_api.get("/eth/v2/beacon/blocks/{block_id}").data.message.body.attestations;

attestations returns a list containing the current block signature/attestation and the previous block signature/attestation.

Key architecture changes​

Current​

  • Single layer: EL client only (Besu with Clique)
  • Sequencer signatures: Stored in EL block extraData
  • Block production: Clique handles execution and consensus

After Beta v4​

  • Dual-layer architecture: Execution Layer (EL) client + Consensus Layer (CL) client (The CL is currently powered by Maru, a consensus client implementing a customized variation of the QBFT algorithm. While Maru is the only supported client today, the architecture is designed to support future client diversity and algorithm evolution).
  • Sequencer signatures: Moved to Maru's SealedBeaconBlock.commitSeals
  • Block production: Maru (QBFT) coordinates consensus, EL executes transactions
  • API: Signatures and consensus data available via Maru APIs

EL client compatibility​

By design, Linea with Maru will support any client compatible with L1; vanilla client compatible with Prague will work.

Supported EL clients include:

  • Besu: 25.8.0 or higher
  • Geth:
    • Pre-Shanghai: v1.13.15
    • Post-Shanghai: v1.16.x
  • Erigon: Latest release
  • Nethermind: Latest release
  • Linea-specific: A new linea-besu-package release is available via Docker: consensys/linea-besu-package:beta-v4.0-rc11-20251004063519-a5c4c31
Geth-specific issues

Geth clients have a slightly more complex compatability situation:

  • Geth v1.15.x → incompatible with Clique (pre-fork)
  • Geth v1.13.x → incompatible with Prague (post-fork)

As a result, for a zero-downtime upgrade path, you must upgrade in a specific order:

  1. Start with v1.13.15 (compatible with London hard fork, i.e. pre-Beta v4)
  2. Upgrade to v1.16.x after Linea has updated to Shanghai (10am CET, 23 October, 2025) and before Linea upgrades to Cancun (time TBC)

Upgrade (run Maru with an existing EL client)​

If you're already running a Linea node using an EL client such as Besu, Nethermind, or Geth, follow these steps. Your main task will be to start running Maru alongside your existing client.

See below for the steps you'll need to follow to upgrade, and then refer to the guide for implementation:

Pre-upgrade​

All clients:

  • Prepare Docker setup (EL + Maru)
  • Upgrade EL client to Prague-compatible version. See the guidance on compatibility above, especially for Geth nodes.

Geth:

  • Keep Geth at v1.13.15, the version compatible with the Clique consensus algorithm that pre-Beta v4 Linea uses.

During upgrade​

Besu, Erigon, Nethermind:

  • Start Maru before the fork timestamp
  • Keep EL client running
  • Monitor logs for sync and consensus:
    • docker logs linea-maru | grep "imported block"
    • curl -s -X POST http://localhost:8545 -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Geth: The Geth migration process depends on when you are upgrading. Based on the [compatibility notes](#el-client-compatibility above, you must:

  • Start Maru before the fork timestamp
  • Keep Geth at v1.13.15 until Linea has updated to the Shanghai EVM
  • Upgrade to v1.16.x after Shanghai but before Linea upgrades to Cancun

Post-upgrade​

  • Verify block import in Maru logs
  • Confirm EL block height with eth_blockNumber

Prerequisites​

Step 1: Obtain files​

The files you need to run Maru are available in the Linea monorepo at the /docs/getting-started directory, in either the linea-mainnet or linea-sepolia directory:

To get the files, choose one of the following methods:

  1. Clone the Linea monorepo, potentially shallow cloning with e.g. git clone --depth 5 to save disk space. Cloning the repo is probably the easiest way to get the files. For a normal clone, run:
git clone https://github.com/Consensys/linea-monorepo.git
  1. Download the Linea monorepo as a ZIP from GitHub, then move the /docker/linea-mainnet (or /linea-sepolia) directory to your preferred location locally.
  2. Create corresponding files locally and copy and paste the contents of their counterparts from the monorepo.

Step 2: Run docker compose​

Now that you have the docker-compose.yml file in docker/linea-mainnet (or docker/linea-sepolia), you can run:

docker compose up maru-node

You must run this to start Maru before the Paris fork block number detailed in the release notes — block 24787631.

Step 3: Verify​

You can check Maru's health:

curl -X GET "http://localhost:8080/eth/v1/node/health"

...and verify P2P connection:

docker logs linea-maru | grep "Currently connected peers"
# Should show: peers=[...]

Start Maru and EL client simultaneously​

You can start Maru and an EL client simultaneously, using one docker compose up command.

If you are running a Geth node, you will need to start Maru and Geth simultaneously during your maintenance window.

Prerequisites​

Step 1: Obtain files​

The files you need to run Maru are available in the Linea monorepo at the /docs/getting-started directory, in either the linea-mainnet or linea-sepolia directory:

To get the files, choose one of the following methods::

  1. Clone the Linea monorepo, potentially shallow cloning with e.g. git clone --depth 5 to save disk space. Cloning the repo is probably the easiest way to get the files.
  2. Download the Linea monorepo as a ZIP from GitHub, then move the /docker/linea-mainnet (or /linea-sepolia) directory to your preferred location locally.
  3. Create corresponding files locally and copy them from the monorepo.

Step 2: Run docker compose​

Now that you have the docker-compose.yml file in docker/linea-mainnet (or docker/linea-sepolia), you can run:

docker compose up maru-node geth-node

Step 3: Verify​

You can check whether Geth is running correctly by running the following command:

curl localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}'
```bash

And you can also check Maru's health:

```bash
curl -X GET "http://localhost:8080/eth/v1/node/health"

JWT Authentication​

For production environments, you should enable JWT authentication:

Generate JWT secret​

openssl rand -hex 32 > ~/linea-node/jwt/jwt.hex

Update Besu config​

# Replace engine-jwt-disabled=true with:
engine-jwt-enabled=true
engine-jwt-file="/opt/besu/jwt.hex"

Update Maru config​

[payload-validator]
engine-api-endpoint = { endpoint = "http://linea-besu:8550", jwt-secret-path = "/jwt.hex" }

Update Docker volumes​

# Add to both Besu and Maru:
volumes:
- ./jwt/jwt.hex:/jwt.hex:ro

Private key management​

Development: Maru auto-generates private keys at startup if none exist. (TBC)

Production: Generate and backup your private key (not mandatory):

# Option 1: Let Maru auto-generate, then backup:
docker cp linea-maru:/data/private-key ./backup/

# Option 2: Generate using Maru's key generation tool:
# https://github.com/Consensys/maru/tree/main/jvm-libs/utils
Important

Maintaining the same private key preserves node identity across restarts.

Troubleshooting​

Debug commands​

# EL client block height
curl -s -X POST http://localhost:8545 -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Maru health
curl http://localhost:8080/eth/v1/node/health

# Check block headers
curl http://localhost:8080/eth/v1/beacon/headers/head