{"record":{"id":"60c4e997bacf3e30","repo":"linera-io/linera-protocol","slug":"the-faucet-is-empty","errorCode":null,"errorMessage":"The faucet is empty.","messagePattern":"The faucet is empty\\.","errorType":"validation","errorClass":"async_graphql::Error","httpStatus":null,"severity":"error","filePath":"linera-faucet/server/src/lib.rs","lineNumber":901,"sourceCode":"        let full_duration = end_timestamp.delta_since(start_timestamp).as_micros();\n        let remaining_duration = end_timestamp.delta_since(local_time).as_micros();\n        let balance = self.client.local_balance().await?;\n\n        #[cfg(with_metrics)]\n        metrics::FAUCET_BALANCE\n            .with_label_values(&[])\n            .set(f64::from(balance));\n\n        let total_amount = requests\n            .iter()\n            .fold(Amount::ZERO, |acc, r| acc.saturating_add(r.amount));\n        let Ok(remaining_balance) = balance.try_sub(total_amount) else {\n            // Not enough balance - reject all requests\n            #[cfg(with_metrics)]\n            metrics::INSUFFICIENT_BALANCE_REJECTIONS\n                .with_label_values(&[])\n                .inc();\n            return Err(Error::new(\"The faucet is empty.\"));\n        };\n\n        // Rate limit: Locked token balance decreases lineraly with time, i.e.:\n        // remaining_balance / remaining_duration >= start_balance / full_duration\n        if multiply(u128::from(self.config.start_balance), remaining_duration)\n            > multiply(u128::from(remaining_balance), full_duration)\n        {\n            #[cfg(with_metrics)]\n            metrics::RATE_LIMIT_REJECTIONS.with_label_values(&[]).inc();\n            return Err(Error::new(\"Not enough unlocked balance; try again later.\"));\n        }\n        Ok(())\n    }\n\n    /// Sends an error response to all requestors.\n    fn send_err(requests: Vec<PendingRequest>, err: impl Into<async_graphql::Error>) {\n        let err = err.into();\n        for request in requests {","sourceCodeStart":883,"sourceCodeEnd":919,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-faucet/server/src/lib.rs#L883-L919","documentation":"Raised in the faucet's `check_rate_limiting` step before a batch executes: the sum of the batch's claim amounts exceeds the faucet chain's current local balance (`balance.try_sub(total_amount)` fails), so every request in the batch is rejected with this message (metric `INSUFFICIENT_BALANCE_REJECTIONS`). It indicates the faucet is drained or underfunded relative to the queued batch — a server-side funding problem, not a client mistake.","triggerScenarios":"Faucet chain balance drops below the combined amounts of the pending batch (initial plus daily claims); faucet configured with an initial claim amount larger than its remaining funds; a burst of claims draining the faucet between operator refills.","commonSituations":"Public or devnet faucet exhausted by heavy usage; faucet funded once at genesis and never topped up; misconfigured claim amount versus actual faucet balance.","solutions":["Retry later — faucet operators monitor the `FAUCET_BALANCE` metric and refill the faucet chain.","If you operate the faucet, transfer more tokens to the faucet chain or lower the configured claim amounts.","Check the faucet metrics endpoint to distinguish 'empty' (balance too low) from rate limiting.","Reduce concurrent claim bursts so batches are smaller than the remaining balance."],"exampleFix":"// before\nwhile (true) { try { await faucet.claim(owner); break; } catch (e) { /* tight retry loop hammers empty faucet */ } }\n\n// after\nfor (let delay = 60_000; ; delay = Math.min(delay * 2, 3_600_000)) {\n  try { await faucet.claim(owner); break; }\n  catch (e) { if (!/faucet is empty/i.test(String(e.message))) throw e; await sleep(delay); }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (const delay of backoff()) { try { return await faucet.claim(owner); } catch (e) { if (!/faucet is empty/i.test(e.message)) throw e; await sleep(delay); } }","preventionTips":["Watch the faucet's FAUCET_BALANCE metric if you operate it; refill before it hits zero.","Use exponential backoff starting at minutes, not seconds.","Distinguish 'The faucet is empty.' (funding) from 'Not enough unlocked balance' (schedule) before choosing a retry interval."],"tags":["faucet","insufficient-balance","funding","batch"],"backgroundTag":"insufficient-balance","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}