Orderbook Checksum
Verify local orderbook state with CRC32 checksums
Problem
When maintaining a local orderbook via WebSocket, state drift can occur due to:
- Missed messages (network issues)
- Out-of-order message processing
- Bugs in diff application logic
Without detection, the client trades on stale/incorrect data.
Solution
The server includes a CRC32 checksum in every book message. Clients verify their local orderbook state matches the server's expected state.
Message Format
{
"channel": "book",
"product": "BTC-VND",
"type": "update",
"data": {
"bids": [["2275689000", "2.249857"]],
"asks": [],
"timestamp": "1764643130040025000",
"gsn": 2309207234
},
"checksum": 2734336218,
"timestamp": "1764643130040025000",
"gsn": 2309207234
}| Field | Description |
|---|---|
type | "snapshot" = full book (replace local state), "update" = diff (apply changes) |
data.bids/asks | Price levels. For updates: size=0 means remove level |
checksum | CRC32 of full book state (not the diff) |
timestamp | Nanosecond unix timestamp |
gsn | Global sequence number |
Checksum Algorithm
CRC32-IEEE on price:size pairs, alternating bids/asks:
Format: "bid_price:bid_size:ask_price:ask_size:bid_price:bid_size:..."Rules
- Bids sorted high→low, Asks sorted low→high
- Interleave: bid[0], ask[0], bid[1], ask[1], ...
- Use raw decimal strings (e.g., "2226787000", not scientific notation)
- No trailing colon
Example
Bids: [["9", "2"]]
Asks: [["10", "1"]]
Preimage: "9:2:10:1"
Checksum: CRC32("9:2:10:1") = 1226559413Client Implementation (Go)
import (
"hash/crc32"
"strings"
)
type Book struct {
Bids [][2]string // [price, size], sorted high→low
Asks [][2]string // [price, size], sorted low→high
}
func (b Book) Checksum() uint32 {
var sb strings.Builder
maxLen := max(len(b.Bids), len(b.Asks))
for i := 0; i < maxLen; i++ {
if i < len(b.Bids) {
sb.WriteString(b.Bids[i][0] + ":" + b.Bids[i][1] + ":")
}
if i < len(b.Asks) {
sb.WriteString(b.Asks[i][0] + ":" + b.Asks[i][1] + ":")
}
}
s := sb.String()
if len(s) > 0 {
s = s[:len(s)-1] // trim trailing colon
}
return crc32.ChecksumIEEE([]byte(s))
}Client Implementation (TypeScript)
import { crc32 } from 'crc';
interface Book {
bids: [string, string][]; // [price, size], sorted high→low
asks: [string, string][]; // [price, size], sorted low→high
}
function checksum(book: Book): number {
const parts: string[] = [];
const maxLen = Math.max(book.bids.length, book.asks.length);
for (let i = 0; i < maxLen; i++) {
if (i < book.bids.length) {
parts.push(`${book.bids[i][0]}:${book.bids[i][1]}`);
}
if (i < book.asks.length) {
parts.push(`${book.asks[i][0]}:${book.asks[i][1]}`);
}
}
return crc32(parts.join(':')) >>> 0; // unsigned 32-bit
}Handling Mismatch
On checksum mismatch:
- Resubscribe - Unsubscribe and resubscribe to get fresh snapshot
- Log/Alert - Track mismatch frequency for debugging
- Don't trade - Pause trading until state is verified
// Example: resubscribe on mismatch
manager.SetOnChecksumMismatch(func() {
log.Error("checksum mismatch - resubscribing")
ws.Unsubscribe("book", productID)
ws.Subscribe("book", productID)
})Never trade on unverified orderbook data. Always validate checksums before using local state for order decisions.