linera-io/linera-protocol · error · Error

MetaMask is not connected with the requested 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.

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.

Appendix: 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)