tursodatabase/turso · error

MVCC logical log header reserved bytes must be zero

Error message

MVCC logical log header reserved bytes must be zero

What it means

The MVCC logical log header reserves a byte range between MVCC_LOG_HEADER_RESERVED_START and MVCC_LOG_HEADER_CRC_START that must be all zeros. validate_mvcc_log_header rejects any nonzero byte there, catching headers written from uninitialized memory or files damaged after the fact.

Solutions

  1. Restore the MVCC log from a clean backup
  2. Regenerate the log (delete it; clients fall back to replace-base sync)
  3. If writing log headers yourself, zero the whole 56-byte buffer before filling fields

Example fix

// before (writer): header built from uninitialized memory
let mut header = [0xABu8; MVCC_LOG_HEADER_SIZE]; // reserved bytes stay dirty

// after: zeroed buffer, reserved range untouched
let mut header = [0u8; MVCC_LOG_HEADER_SIZE];
header[..4].copy_from_slice(&MVCC_LOG_MAGIC.to_le_bytes());
header[4] = MVCC_LOG_VERSION;
// bytes 5..CRC start remain zero by construction
Defensive patterns

Strategy: validation

Validate before calling

fn mvcc_reserved_bytes_zero(log: &[u8]) -> bool {
    log.len() >= MVCC_LOG_HEADER_CRC_START
        && log[MVCC_LOG_HEADER_RESERVED_START..MVCC_LOG_HEADER_CRC_START]
            .iter()
            .all(|b| *b == 0)
}

Try / catch

// Any header-region error (reserved bytes, flags, length, CRC) is unrecoverable:
// quarantine the file and resync clients from the replace-base fallback.

Prevention

When it happens

Trigger: Any byte in the reserved slice log[MVCC_LOG_HEADER_RESERVED_START..MVCC_LOG_HEADER_CRC_START] is nonzero when a /pull-updates logical pull calls scan_mvcc_log.

Common situations: A writer bug that appends header bytes without zeroing the reserved region; disk corruption; a file reconstructed by hand or by a partial-copy tool that filled gaps with junk.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/92fe48dec41795ef. Report an issue: GitHub.

Appendix: source

Thrown at cli/sync_server.rs:1119

    }
    if log[4] != MVCC_LOG_VERSION {
        return Err(anyhow!("unsupported MVCC logical log version {}", log[4]));
    }
    if log[5] & 0b1111_1110 != 0 {
        return Err(anyhow!("invalid MVCC logical log header flags"));
    }
    let header_len = u16::from_le_bytes([log[6], log[7]]) as usize;
    if header_len != MVCC_LOG_HEADER_SIZE {
        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"),

View on GitHub (pinned to 492c4a71cd)