Get Started
Slashing & Rehabilitation
Lightchain AI workers stake 50,000 LCAI to be eligible for inference jobs. That stake is the lever the protocol pulls when a worker misbehaves: the stake is reduced (slashed), and after enough offenses the worker is temporarily removed from the eligible set (suspended). Suspension is always recoverable — there is no permanent tombstoning in the current design — but the operator must explicitly callreinstate() once the cooldown has passed.
This page covers every offense that can lead to a slash, how the slash propagates through the contracts, the states a worker can end up in, and the exact runbook for getting a suspended worker back online.
Slashing offenses
A worker can be slashed for three reasons. All three are enforced byJobRegistry and result in a call to WorkerRegistry.slash(worker, bps).
The slash amount is computed as:
CodeHTML
maxSlashBps = 5000 (50%), so no future proposal can set a single offense rate higher than half of minWorkerStake.
The current values for timeoutSlashBps, completionTimeoutSlashBps, disputeSlashBps, minWorkerStake, suspensionThreshold, and suspensionCooldown are all defined in AIConfig and listed in Governance & Protocol Parameters.
How a slash propagates
When any of the three offenses fires, the contracts execute the following sequence:JobRegistrycallsWorkerRegistry.slash(worker, bps). The function isonlyJobRegistry— no other contract or EOA can slash directly.- The worker's stake is reduced by
slashAmountand the burned amount is added to a protocol-levelslashedFundsaccounting bucket. WorkerSlashed(address indexed worker, uint256 amount, uint256 newStake)is emitted.- If
newStake < minWorkerStake, the worker is immediately removed from every model's eligible set.WorkerDeactivated(worker, modelId)is emitted once per model. The worker can no longer be assigned new jobs. - The worker's
offenseCountis incremented. - If
offenseCount >= suspensionThreshold(default3), the worker is suspended:isSuspended = true,suspendedUntil = block.timestamp + suspensionCooldown(default 7 days), andWorkerSuspended(worker, until)is emitted. The worker is again removed from every eligible set if not already.
minWorkerStake keeps receiving jobs. Only the suspended state — or the below-minimum-stake state — actually takes the worker offline from the dispatcher's perspective.
Worker states
reinstate() clears both isSuspended and resets offenseCount to 0. There is no permanent removal — a worker can be suspended, reinstated, suspended again, and reinstated again indefinitely. Each suspension consumes another 7 days of downtime and at least 15,000 LCAI of stake (three offenses at the cheapest rate).
How disputes lead to slashing
The most expensive offense (15%) comes from losing a dispute. Two paths trigger it:- Consumer-filed dispute. A consumer who is unhappy with the response can call
JobRegistry.disputeJob(jobId)withindisputeWindow(default 24 h) by posting a bond proportional to the job fee. The Disputer service re-executes the prompt against the same model and compares the worker's response to the re-execution using cosine similarity. If similarity <similarityThreshold(default 60%), the verdict is "worker guilty" and the worker is slashed bydisputeSlashBps. The consumer's bond is returned and the job fee is refunded. - Canary sampling. Independently of consumer behavior, the Disputer canary-samples ~
samplingRateBps(default 5%) of completed jobs and runs the same re-execution check. A guilty verdict slashes the worker identically. - Response mismatch. If a consumer can produce the worker's signed ciphertext and prove
keccak256(ciphertext) != job.responseCiphertextHash, callingJobRegistry.disputeResponseMismatch(jobId, ciphertext, signature)slashes the worker bydisputeSlashBpswithout going through similarity scoring at all.
Rehabilitation runbook
If your worker has been suspended, the recovery flow is straightforward but currently requires raw contract calls — there is nolightchain-worker reinstate subcommand yet. The canonical path is cast send against WorkerRegistry.
The examples below assume you already have these environment variables set as in the standard Run a Worker — Mainnet guide:
CodeBASH
WorkerRegistry address above is the mainnet precompile-style fixed address; see Contract Addresses for the canonical list.)
1. Confirm the worker is suspended and read the cooldown deadline
CodeBASH
isSuspended (bool) and suspendedUntil (uint256, Unix seconds). The cooldown has expired once block.timestamp >= suspendedUntil. Compare against current chain time:
CodeBASH
2. Top up stake if it fell below the minimum
If the slashes that caused suspension also pushed your stake under 50,000 LCAI,reinstate() will clear the suspension flag but will not re-add you to model eligible sets — the contract requires stake >= minWorkerStake for that.
Check current stake (read directly from the worker info struct in getWorker(...)'s return tuple), then top up the difference:
CodeBASH
topUpStake() is not a substitute for reinstate(). Topping up alone does not clear the isSuspended flag — you must still call reinstate() once the cooldown has elapsed. Always treat reinstate() as the canonical path out of suspension.
3. Call reinstate() once the cooldown has passed
CodeBASH
- Reverts with
StillSuspended(worker, suspendedUntil)if the cooldown has not elapsed. - Reverts with
WorkerNotSuspended(worker)if the worker is not currently suspended. - Otherwise: clears
isSuspended, resetsoffenseCount = 0, and re-adds the worker to the eligible set for every supported model that is still whitelisted and enabled (assumingstake >= minWorkerStake). - Emits
WorkerReinstated(address indexed worker).
[!NOTE]reinstate()has no fee — you only pay gas. The dispatcher listens forWorkerReinstatedand marks the worker "stale" until the next heartbeat, so expect a short delay (one heartbeat interval, ~10 s) before the dispatcher resumes assigning jobs.
4. Verify recovery
- Confirm
WorkerReinstatedis present in the transaction receipt:CodeBASH - Re-run the status check from Run a Worker — Mainnet § Check registration status and confirm
is_suspendedisfalse. - Tail the worker container logs and look for fresh heartbeats and
ws_job_receivedlines.
Events emitted
The authoritative event signatures fromIWorkerRegistry.sol:
CodeSOLIDITY
WorkerSlashedtypically pairs with theJobTimedOutevent or with aDisputeResolved(workerGuilty=true)event in the same transaction.WorkerSuspendedis always preceded by the thirdWorkerSlashedthat crossed the threshold.WorkerDeactivatedmay be emitted per model on either a below-minimum-stake slash or a suspension.
Parameter reference
These are the defaults at genesis. All can be changed by governance throughAIConfig.
For the canonical, governance-mutable view see Governance & Protocol Parameters.
Related pages
- Run a Worker — Mainnet — base operator guide for setting up and registering a worker.
- Worker API — REST and GraphQL surface for querying slash and suspension history.
- AIVM EL Architecture — Verification — full description of the Disputer pipeline.
- Governance & Protocol Parameters — current on-chain values for every parameter referenced here.
- Contract Addresses — canonical
WorkerRegistry,JobRegistry, andAIConfigaddresses.