linera-io/linera-protocol · error · Error

MetaMask is not connected with the requested owner: ${owner}

Error message

MetaMask is not connected with the requested owner: ${owner}

What it means

revoke_epochs builds an Admin operation that revokes all epochs up to and including revoked_epoch. The guard ensure!(revoked_epoch < current_epoch) rejects revoking the epoch the chain is currently in (or any future epoch): the current epoch's committee is still signing blocks, so revoking it would break consensus. Only strictly older epochs may be revoked.

Source

Thrown at web/@linera/metamask/src/signer.ts:57

  async sign(owner: string, value: Uint8Array): Promise<string> {
    if (!window.ethereum) {
      throw new Error("MetaMask is not available");
    }

    // Explicitly type the result and check for undefined
    const accounts = (await window.ethereum.request({
      method: "eth_requestAccounts",
    })) as string[] | undefined;

    if (!accounts || accounts.length === 0) {
      throw new Error("No MetaMask accounts connected");
    }

    const connected = accounts.find(
      (acc) => acc.toLowerCase() === owner.toLowerCase(),
    );
    if (!connected) {
      throw new Error(
        `MetaMask is not connected with the requested owner: ${owner}`,
      );
    }

    // Encode message as hex string
    const msgHex = `0x${uint8ArrayToHex(value)}`;
    try {
      const signature = (await window.ethereum.request({
        method: "personal_sign",
        params: [msgHex, owner],
      })) as string;

      if (!signature) {
        throw new Error("No signature returned");
      }

      return signature;
    } catch (err: any) {

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Pass an epoch strictly lower than the current one: use revoke_epochs(Epoch(current.0.saturating_sub(1))) at most, and only if current > 0.
  2. If you intend to retire the current committee, first create and migrate to a new epoch, then revoke the old one.
  3. Read the chain's current epoch via chain_info().epoch before constructing the call and validate the bound in your script.
  4. Check for off-by-one in loops: iterate 0..revoked_epoch.0 after validating revoked_epoch < current_epoch.

Example fix

// before: revoking the epoch the chain is still using
client.revoke_epochs(current_epoch).await?; // CannotRevokeCurrentEpoch

// after: revoke only strictly older epochs
let current = client.chain_info().await?.epoch;
if revoked_epoch < current {
    client.revoke_epochs(revoked_epoch).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the epoch bound before revoking
let current = client.chain_info().await?.epoch;
if revoked_epoch >= current {
    return Err(anyhow::anyhow!("can only revoke epochs < current ({current}); got {revoked_epoch}"));
}

Type guard

pub async fn can_revoke_epoch(client: &ChainClient, epoch: Epoch) -> Result<bool, Error> {
    Ok(epoch < client.chain_info().await?.epoch)
}

Try / catch

match client.revoke_epochs(revoked_epoch).await {
    Ok(outcome) => outcome,
    Err(Error::CannotRevokeCurrentEpoch(current)) => {
        // wait for a new epoch, then revoke the old one
        return Err(anyhow::anyhow!("epoch {current} is current; create the next epoch before revoking"));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling revoke_epochs(epoch) where epoch >= chain_info().epoch — typically passing the current epoch (e.g. Epoch(0) on a fresh network where current is also 0). Producers: governance scripts that compute 'revoke up to now' instead of 'up to previous'; off-by-one loops over 0..=current_epoch.

Common situations: Test suites revoking epochs after creating a committee without advancing to a new epoch; operators cleaning old committee data on a young network; version changes in the admin API where the epoch argument semantics shifted.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/fdc2f8a04f0bbf0c. Report an issue: GitHub.