tursodatabase/turso · error

MVCC logical log header checksum mismatch

Error message

MVCC logical log header checksum mismatch

What it means

The log header stores a CRC32C of itself at bytes 52-56, computed with the CRC field zeroed. validate_mvcc_log_header recomputes crc32c over bytes [0,56) with the CRC span zeroed and compares; a mismatch means the first 56 bytes changed after they were written. The running frame CRC chain is seeded from the header salt (initial_mvcc_log_crc), so an untrustworthy header would silently invalidate every later frame check, hence the hard rejection.

Source

Thrown at cli/sync_server.rs:1013

        return Err(anyhow!(
            "invalid MVCC logical log header length: {header_len}"
        ));
    }
    if log[MVCC_LOG_HEADER_RESERVED_START..MVCC_LOG_HEADER_CRC_START]
        .iter()
        .any(|byte| *byte != 0)
    {
        return Err(anyhow!(
            "MVCC logical log header reserved bytes must be zero"
        ));
    }
    let stored_crc = read_u32_le(log, MVCC_LOG_HEADER_CRC_START)?;
    let mut crc_buf = [0u8; MVCC_LOG_HEADER_SIZE];
    crc_buf.copy_from_slice(&log[..MVCC_LOG_HEADER_SIZE]);
    crc_buf[MVCC_LOG_HEADER_CRC_START..MVCC_LOG_HEADER_SIZE].fill(0);
    let expected_crc = crc32c::crc32c(&crc_buf);
    if stored_crc != expected_crc {
        return Err(anyhow!("MVCC logical log header checksum mismatch"));
    }
    Ok(())
}

fn initial_mvcc_log_crc(log: &[u8]) -> Result<u32> {
    let salt = u64::from_le_bytes(
        log[MVCC_LOG_HEADER_SALT_START..MVCC_LOG_HEADER_SALT_END]
            .try_into()
            .expect("fixed-size salt slice"),
    );
    Ok(crc32c::crc32c(&salt.to_le_bytes()))
}

fn read_mvcc_frame_boundary(
    log: &[u8],
    offset: usize,
    running_crc: u32,
) -> Result<Option<(usize, u32)>> {

View on GitHub (pinned to bad083fafb)

Solutions

  1. Treat the log as corrupt: delete the local MVCC logical log and re-bootstrap with a full pull from the server instead of a delta pull.
  2. Verify transfer integrity by comparing sha256 of the log on producer and consumer.
  3. If mismatches recur on one node, check storage health (smartctl, dmesg I/O errors) — the corruption is local.
  4. Do not retry the same bytes; the CRC is deterministic and retrying cannot fix it.

Example fix

// before
let snapshot = scan_mvcc_log(&log)?; // errors: MVCC logical log header checksum mismatch

// after: a broken header makes all deltas untrustworthy — re-bootstrap
let snapshot = match scan_mvcc_log(&log) {
    Ok(snapshot) => snapshot,
    Err(err) if err.to_string().contains("header checksum mismatch") => {
        std::fs::remove_file(&log_path)?;
        rebootstrap_log_from_server()?
    }
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: try-catch

Try / catch

match scan_mvcc_log(&log) {
    Ok(snapshot) => { /* proceed */ }
    Err(err) if err.to_string().contains("header checksum mismatch") => {
        // header untrustworthy -> discard local log and re-bootstrap fully;
        // do not retry: the CRC is deterministic over the same bytes
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: scan_mvcc_log on a log whose stored CRC at [52,56) does not equal crc32c of the rest of the header: any modification of the first 56 bytes after writing — partial overwrite, byte-level transfer corruption, disk bit rot, or a test producer computing the CRC over the wrong byte range (e.g. not zeroing the CRC field).

Common situations: Failing disks or unreliable network filesystems holding the logical log; crash mid-header write; copying logs between nodes without hash verification; hand-written test fixtures with an incorrect CRC computation.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/c1773bc5122c932f. Report an issue: GitHub.