fix: use CRC-32C (Castagnoli) instead of IEEE for WAL (C1)
Per design §3.2 line 359, all WAL CRC computations must use crc32c
(Castagnoli polynomial 0x82F63B78). The previous code used crc32.IEEE
(Ethernet/PNG polynomial 0xEDB88320) in 5 places. While the system was
self-consistent (encode + decode both used IEEE), it diverged from the
design spec and lost SSE4.2 hardware acceleration (the native CRC32
instruction only supports Castagnoli).
Changes:
- wal/crc32c.go (new): package-level crc32cTable = crc32.MakeTable(
crc32.Castagnoli). Central definition prevents future drift.
- wal/header.go: 2 ChecksumIEEE calls replaced with crc32.Checksum(
data, crc32cTable). Comment updated to reference design §3.2 line 341, 359.
- wal/record.go: 2 ChecksumIEEE calls replaced.
- wal/block_writer_test.go: 1 ChecksumIEEE call in test helper replaced.
- wal/crc32c_test.go (new): 4 regression guards:
- TestCRC32CStandardVector: RFC 3720 fixed vector (crc32c("123456789")
= 0xE3069283).
- TestCRC32CEdistinctFromIEEE: confirms IEEE produces different value.
- TestHeaderCRCUsesCastagnoli: direct assertion on stored header CRC
(catches paired encode/decode reversion that round-trip tests miss).
- TestPhysicalRecordCRCUsesCastagnoli: same for physical record CRC.
BREAKING CHANGE: WAL files written before this fix (with IEEE CRC)
cannot be read after this fix (expects crc32c). Phase 1 has not been
released, so no real data migration is needed. Production users
post-release would need to drain + re-create the database.
Developers pulling this change should delete any local Phase-1 WAL
directories (`rm -rf <db-dir>/segment-*.wal`) before running the code;
old IEEE-encoded WALs will fail recovery on local dev machines.
Verified: all existing round-trip tests pass (encode + decode both use
crc32c, still self-consistent). Full suite green including
go test -race ./... .
Audit context: docs/audit-3.2.md C1.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package wal
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Regression guard for C1: verify all WAL CRC uses Castagnoli polynomial
|
||||
// (crc32c), not IEEE. Per design §3.2 line 359.
|
||||
//
|
||||
// The standard CRC-32C test vector from RFC 3720 Appendix B is the 9-byte
|
||||
// ASCII string "123456789":
|
||||
// - crc32c: 0xE3069283
|
||||
// - crc32 IEEE: 0xCBF43926
|
||||
//
|
||||
// If anyone changes crc32cTable back to IEEE, this test fails immediately.
|
||||
func TestCRC32CStandardVector(t *testing.T) {
|
||||
got := crc32.Checksum([]byte("123456789"), crc32cTable)
|
||||
const want = uint32(0xE3069283)
|
||||
if got != want {
|
||||
t.Errorf("crc32c('123456789') = 0x%X, want 0x%X (Castagnoli)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Negative regression: confirm IEEE would produce a DIFFERENT value. This
|
||||
// catches the case where someone "fixes" crc32cTable to use IEEE by mistake.
|
||||
func TestCRC32CEdistinctFromIEEE(t *testing.T) {
|
||||
data := []byte("123456789")
|
||||
ieee := crc32.ChecksumIEEE(data)
|
||||
castagnoli := crc32.Checksum(data, crc32cTable)
|
||||
if ieee == castagnoli {
|
||||
t.Errorf("IEEE and Castagnoli produced the same CRC (impossible unless table is wrong); both = 0x%X", ieee)
|
||||
}
|
||||
}
|
||||
|
||||
// Regression guard for C1: header CRC must be computed with crc32c, not IEEE.
|
||||
// TestHeaderRoundtrip is self-consistent (encode + decode use same polynomial),
|
||||
// so a paired reversion to IEEE would pass it. This test directly asserts
|
||||
// the stored CRC matches Castagnoli.
|
||||
func TestHeaderCRCUsesCastagnoli(t *testing.T) {
|
||||
hdr := &WalFileHeader{
|
||||
BlockSize: 32 * 1024,
|
||||
SegmentID: 42,
|
||||
StartSequence: 100,
|
||||
}
|
||||
encoded := EncodeWalHeader(hdr)
|
||||
want := crc32.Checksum(encoded[0:28], crc32cTable)
|
||||
got := binary.LittleEndian.Uint32(encoded[28:32])
|
||||
if got != want {
|
||||
t.Errorf("stored headerCRC = 0x%X, want crc32c value 0x%X", got, want)
|
||||
}
|
||||
// Sanity: confirm IEEE would produce a different value (catches paired reversion).
|
||||
ieeeValue := crc32.ChecksumIEEE(encoded[0:28])
|
||||
if got == ieeeValue {
|
||||
t.Errorf("stored headerCRC = 0x%X matches IEEE value (C1 regression)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Regression guard for C1: physical record CRC must be Castagnoli.
|
||||
// Same rationale as TestHeaderCRCUsesCastagnoli — direct value assertion
|
||||
// catches paired encode/decode reversion.
|
||||
func TestPhysicalRecordCRCUsesCastagnoli(t *testing.T) {
|
||||
payload := []byte("test-payload")
|
||||
encoded := EncodePhysicalRecord(RecFull, payload)
|
||||
// Record format: [crc u32][length u16][type u8][payload]
|
||||
want := crc32.Checksum(encoded[4:7+len(payload)], crc32cTable)
|
||||
got := binary.LittleEndian.Uint32(encoded[0:4])
|
||||
if got != want {
|
||||
t.Errorf("stored record CRC = 0x%X, want crc32c value 0x%X", got, want)
|
||||
}
|
||||
ieeeValue := crc32.ChecksumIEEE(encoded[4 : 7+len(payload)])
|
||||
if got == ieeeValue {
|
||||
t.Errorf("stored record CRC = 0x%X matches IEEE value (C1 regression)", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user