datahaven-xyz/datahaven · error · Error

SkippedSyncCommitteePeriod

SkippedSyncCommitteePeriod

Error message

SkippedSyncCommitteePeriod

What it means

When no next sync committee is stored yet, the light client only accepts updates whose signature period equals the store period; a `signature_slot` in any later period would jump past an entire sync committee period, which cannot be verified. `verify_update` raises `SkippedSyncCommitteePeriod` in this branch to prevent the client from skipping an unverified period.

Solutions

  1. Re-bootstrap the light client from a current bootstrap checkpoint (`bootstrap` extrinsic / trusted checkpoint) so store_period is current.
  2. Catch up with intermediate updates covering each sync committee period in order before submitting the latest one.
  3. Ensure the relayer is running continuously so no full period is ever missed.
  4. If halted periods occurred, restart from a new trusted checkpoint rather than replaying old updates.

Example fix

// before
 client.submit(latest_update); // periods ahead of store
// after
 let store_period = period(client.latest_finalized_slot());
 for u in updates_in_periods_from(store_period) { client.submit(u)?; }
Defensive patterns

Strategy: validation

Validate before calling

const storePeriod = period(latestFinalizedSlot);
if (!<NextSyncCommittee exists> && period(update.signatureSlot) !== storePeriod) throw new Error('would skip sync committee period');

Try / catch

try { await submit(update); } catch (e) { if (e.includes('SkippedSyncCommitteePeriod')) { await rebootstrap(); } else throw e; }

Prevention

When it happens

Trigger: Submitting an update while `NextSyncCommittee<T>` does not exist and `compute_period(update.signature_slot) != compute_period(latest_finalized_state.slot)` — i.e. the client missed updates for at least one full sync committee period (~27 hours on mainnet).

Common situations: Light client was bootstrapped long ago and the relayer went down for over a day; the pallet was halted during an incident and updates lapsed; operator submits only the latest update instead of catching up period by period.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13). Data as JSON: /api/errors/e68cac194e4438eb. Report an issue: GitHub.

Appendix: source

Thrown at operator/pallets/ethereum-client/src/lib.rs:337

            // Verify sync committee has sufficient participants.
            let participation =
                decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits);
            Self::sync_committee_participation_is_supermajority(&participation)?;

            // Verify update does not skip a sync committee period.
            ensure!(
                update.signature_slot > update.attested_header.slot
                    && update.attested_header.slot >= update.finalized_header.slot,
                Error::<T>::InvalidUpdateSlot
            );
            // Retrieve latest finalized state.
            let latest_finalized_state =
                FinalizedBeaconState::<T>::get(LatestFinalizedBlockRoot::<T>::get())
                    .ok_or(Error::<T>::NotBootstrapped)?;
            let store_period = compute_period(latest_finalized_state.slot);
            let signature_period = compute_period(update.signature_slot);
            if <NextSyncCommittee<T>>::exists() {
                ensure!(
                    (store_period..=store_period + 1).contains(&signature_period),
                    Error::<T>::SkippedSyncCommitteePeriod
                )
            } else {
                ensure!(
                    signature_period == store_period,
                    Error::<T>::SkippedSyncCommitteePeriod
                )
            }

            // Verify update is relevant.
            let update_attested_period = compute_period(update.attested_header.slot);
            let update_finalized_period = compute_period(update.finalized_header.slot);
            let update_has_next_sync_committee = !<NextSyncCommittee<T>>::exists()
                && (update.next_sync_committee_update.is_some()
                    && update_attested_period == store_period);
            ensure!(
                update.attested_header.slot > latest_finalized_state.slot

View on GitHub (pinned to edcb13dbbc)