linera-io/linera-protocol · warning · async_graphql::Error

This user already has a chain

Error message

This user already has a chain

What it means

Returned by the faucet's batch validation when an initial `claim` request reaches execution although the owner already has a chain in faucet storage. The front-door check in `do_claim` normally makes `claim` idempotent — it returns the existing ChainDescription — so this message (`DUPLICATE_CHAIN_MSG`) only appears when a duplicate races past the front door (two concurrent claims for the same owner) and is refused during `validate_request`. The requester side classifies this message as 'duplicate' in metrics.

Source

Thrown at linera-faucet/server/src/lib.rs:866

            let now = self.client.storage_client().clock().current_time();
            let period = current_daily_period(initial_claim.timestamp.micros(), now.micros());
            let last_period = self
                .faucet_storage
                .last_daily_claim_period(&request.owner)
                .await?
                .unwrap_or(0);

            if period <= last_period {
                return Err(Error::new(DAILY_LIMIT_MSG));
            }
        } else {
            match self.faucet_storage.get_chain_id(&request.owner).await {
                Ok(None) => {}
                Ok(Some(_)) => {
                    // Not counted here: the requester side classifies this
                    // refusal by message and counts it once as "duplicate"
                    // (counting at both sites double-counted the request).
                    return Err(Error::new(DUPLICATE_CHAIN_MSG));
                }
                Err(err) => {
                    tracing::error!("Database error: {err}");
                    return Err(Error::new(err.to_string()));
                }
            }
        }
        Ok(())
    }

    /// Checks if the given requests can currently be fulfilled, based on the balance
    /// and rate limiting settings. Returns an error if not.
    async fn check_rate_limiting(&self, requests: &[PendingRequest]) -> async_graphql::Result<()> {
        let end_timestamp = self.config.end_timestamp;
        let start_timestamp = self.config.start_timestamp;
        let local_time = self.client.storage_client().clock().current_time();
        let full_duration = end_timestamp.delta_since(start_timestamp).as_micros();
        let remaining_duration = end_timestamp.delta_since(local_time).as_micros();

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Retry the `claim` mutation: with the chain now recorded, the front door returns the existing ChainDescription instead of erroring.
  2. De-duplicate claim requests client-side — keep at most one in-flight claim per owner.
  3. Treat this message as 'already claimed' and re-fetch the existing chain, not as a hard failure.

Example fix

// before
const d1 = claim(owner); const d2 = claim(owner); // second may hit 'This user already has a chain'

// after
const inFlight = new Map();
function claimOnce(owner) {
  if (!inFlight.has(owner)) inFlight.set(owner, faucet.claim(owner).finally(() => inFlight.delete(owner)));
  return inFlight.get(owner);
}
Defensive patterns

Strategy: fallback

Validate before calling

const existing = await faucet.queryChainForOwner(owner); // if exposed
if (existing) return existing; // skip claim entirely

Try / catch

try { return await faucet.claim(owner); } catch (e) { if (/already has a chain/i.test(e.message)) return await faucet.claim(owner); /* front door now returns the existing description */ throw e; }

Prevention

When it happens

Trigger: Two concurrent `claim` mutations for the same AccountOwner submitted before the first one is recorded in the faucet database; retrying a claim whose response was lost after the chain was already created and stored.

Common situations: Double-submitted forms; client retry logic firing while the first request is still queued in the faucet batch processor; load tests reusing one owner key with parallel requests.

Related errors


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