Download code/offload/dspark/spark/remote_dspark/wire.py from ProprietaryLegal/dsv41-sampled-drafting: direct link, hf CLI and curl.
- Browser
- Download file 20 kB
-
https://huggingface.co/ProprietaryLegal/dsv41-sampled-drafting/resolve/main/code/offload/dspark/spark/remote_dspark/wire.py
- Command line
-
hf download hf://ProprietaryLegal/dsv41-sampled-drafting/code/offload/dspark/spark/remote_dspark/wire.py
-
curl -L -o wire.py https://huggingface.co/ProprietaryLegal/dsv41-sampled-drafting/resolve/main/code/offload/dspark/spark/remote_dspark/wire.py
20 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Wire codec for the remote DSpark contract (offload/dspark/CONTRACT.md, v1.0). | |
| Pure Python + numpy, no torch and no vLLM: shared by the Spark proxy, the mock worker | |
| and the offline tests. Every layout here mirrors a table in CONTRACT.md section 6; keep | |
| them in step (and bump CONTRACT_VERSION / the change log) on any change. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import struct | |
| from dataclasses import dataclass, field | |
| try: # numpy is needed for STEP bodies; HELLO/PING/STATS framing works without it | |
| import numpy as np # (the probe tool runs on Spark hosts whose python3 has no numpy) | |
| except ImportError: # pragma: no cover | |
| np = None # type: ignore[assignment] | |
| CONTRACT_VERSION = "1.8" | |
| MAGIC = 0x31505344 # bytes "DSP1" little-endian | |
| VERSION = 1 | |
| OP_HELLO = 1 | |
| OP_STEP = 2 | |
| OP_RESET = 3 | |
| OP_STATS = 4 | |
| OP_PING = 5 | |
| OP_NAMES = {OP_HELLO: "HELLO", OP_STEP: "STEP", OP_RESET: "RESET", | |
| OP_STATS: "STATS", OP_PING: "PING"} | |
| # Frame status (CONTRACT 9.1) | |
| ST_OK = 0 | |
| ST_BAD_VERSION = 1 | |
| ST_IDENTITY_MISMATCH = 2 | |
| ST_CONFIG_MISMATCH = 3 | |
| ST_NO_SESSION = 4 | |
| ST_STALE_SEQ = 5 | |
| ST_MALFORMED = 6 | |
| ST_CAPACITY = 7 | |
| ST_INTERNAL = 8 | |
| ST_UNSUPPORTED_OP = 9 | |
| FRAME_STATUS_NAMES = { | |
| ST_OK: "OK", ST_BAD_VERSION: "BAD_VERSION", ST_IDENTITY_MISMATCH: "IDENTITY_MISMATCH", | |
| ST_CONFIG_MISMATCH: "CONFIG_MISMATCH", ST_NO_SESSION: "NO_SESSION", | |
| ST_STALE_SEQ: "STALE_SEQ", ST_MALFORMED: "MALFORMED", ST_CAPACITY: "CAPACITY", | |
| ST_INTERNAL: "INTERNAL", ST_UNSUPPORTED_OP: "UNSUPPORTED_OP", | |
| } | |
| # Frame errors after which the remote must not be retried (configuration errors). | |
| FATAL_FRAME_STATUS = frozenset( | |
| {ST_BAD_VERSION, ST_IDENTITY_MISMATCH, ST_CONFIG_MISMATCH, ST_UNSUPPORTED_OP} | |
| ) | |
| # Per-request status (CONTRACT 9.2) | |
| RS_OK = 0 | |
| RS_PARTIAL_WINDOW = 1 | |
| RS_STALE_GENERATION = 2 | |
| RS_BAD_ENTRY = 3 | |
| RS_INTERNAL = 4 | |
| REQ_STATUS_NAMES = {RS_OK: "OK", RS_PARTIAL_WINDOW: "PARTIAL_WINDOW", | |
| RS_STALE_GENERATION: "STALE_GENERATION", RS_BAD_ENTRY: "BAD_ENTRY", | |
| RS_INTERNAL: "INTERNAL"} | |
| # Per-request statuses whose drafts may be used. | |
| USABLE_REQ_STATUS = frozenset({RS_OK, RS_PARTIAL_WINDOW}) | |
| # ReqEntry.req_flags | |
| RF_NEW_GEN = 0x1 | |
| RF_NEED_DRAFT = 0x2 | |
| RF_RESYNC = 0x4 | |
| # StepHeader.step_flags / ReplyHeader.reply_flags | |
| SF_WANT_MARGIN = 0x1 | |
| RPF_HAS_MARGIN = 0x1 | |
| PAYLOAD_TAPS_BF16 = 1 | |
| PAYLOAD_MAINX_BF16 = 2 | |
| SAMPLE_GREEDY = 1 | |
| SAMPLE_TOP32 = 2 | |
| CAP_TOP32 = 8 | |
| SF_SAMPLED = 2 | |
| RPF_SAMPLED = 2 | |
| CAP_TAPS_BF16 = 0x1 | |
| CAP_MAINX_BF16 = 0x2 | |
| CAP_HAS_MARGIN = 0x4 | |
| APP_HEADER = struct.Struct("<IHHQIIiI") # 32 B | |
| HELLO_REQ = struct.Struct("<20I32s32s48s") # 192 B | |
| HELLO_REP = struct.Struct("<6I32s32s40s") # 128 B | |
| STEP_HEADER = struct.Struct("<QHHIII8x") # 32 B | |
| REQ_ENTRY = struct.Struct("<BBBBIqIiq") # 32 B | |
| REPLY_HEADER = struct.Struct("<QHHIII8x") # 32 B | |
| REQ_STATUS = struct.Struct("<HHI") # 8 B | |
| RESET_BODY = struct.Struct("<I") # 4 B | |
| assert APP_HEADER.size == 32 | |
| assert HELLO_REQ.size == 192 | |
| assert HELLO_REP.size == 128 | |
| assert STEP_HEADER.size == 32 | |
| assert REQ_ENTRY.size == 32 | |
| assert REPLY_HEADER.size == 32 | |
| assert REQ_STATUS.size == 8 | |
| APP_HEADER_SIZE = APP_HEADER.size | |
| U32_MASK = 0xFFFFFFFF | |
| class WireError(ValueError): | |
| """A malformed or inconsistent message.""" | |
| def align64(n: int) -> int: | |
| return (n + 63) & ~63 | |
| def rows_offset_for(n_req: int, sampled: bool = False) -> int: | |
| """Offset of the row block from the start of a STEP body (multiple of 64).""" | |
| return align64(STEP_HEADER.size + n_req * REQ_ENTRY.size + (16 * n_req if sampled else 0)) | |
| def gen_newer(a: int, b: int) -> bool: | |
| """Serial-number arithmetic on u32 generations: is a newer than b?""" | |
| d = (a - b) & U32_MASK | |
| return d != 0 and d < 0x80000000 | |
| # --------------------------------------------------------------------------- headers | |
| class AppHeader: | |
| op: int | |
| seq: int | |
| session: int | |
| body_len: int | |
| status: int = 0 | |
| flags: int = 0 | |
| magic: int = MAGIC | |
| version: int = VERSION | |
| def pack_into(self, buf, off: int = 0) -> None: | |
| APP_HEADER.pack_into(buf, off, self.magic, self.version, self.op, self.seq, | |
| self.session, self.body_len, self.status, self.flags) | |
| def pack(self) -> bytes: | |
| b = bytearray(APP_HEADER_SIZE) | |
| self.pack_into(b) | |
| return bytes(b) | |
| def unpack_from(cls, buf, off: int = 0) -> "AppHeader": | |
| if len(buf) - off < APP_HEADER_SIZE: | |
| raise WireError(f"short frame: {len(buf) - off} < {APP_HEADER_SIZE}") | |
| magic, version, op, seq, session, body_len, status, flags = APP_HEADER.unpack_from( | |
| buf, off) | |
| h = cls(op=op, seq=seq, session=session, body_len=body_len, status=status, | |
| flags=flags, magic=magic, version=version) | |
| return h | |
| def check(self, frame_len: int) -> None: | |
| if self.magic != MAGIC: | |
| raise WireError(f"bad magic 0x{self.magic:08x}") | |
| if self.version != VERSION: | |
| raise WireError(f"unsupported version {self.version}") | |
| if APP_HEADER_SIZE + self.body_len != frame_len: | |
| raise WireError( | |
| f"body_len {self.body_len} does not match frame length {frame_len}") | |
| # --------------------------------------------------------------------------- HELLO | |
| class Hello: | |
| num_spec: int = 5 | |
| window: int = 128 | |
| hidden: int = 5120 | |
| n_taps: int = 3 | |
| tap_layer_ids: tuple[int, ...] = (37, 38, 39) | |
| payload_kind: int = PAYLOAD_TAPS_BF16 | |
| row_bytes: int = 3 * 5120 * 2 | |
| noise_token_id: int = 128799 | |
| vocab_size: int = 129280 | |
| max_model_len: int = 327680 | |
| max_slots: int = 4 | |
| max_rows_per_step: int = 4 * 128 | |
| sample_mode: int = SAMPLE_GREEDY | |
| tp_size: int = 2 | |
| drafter_identity: bytes = bytes(32) | |
| markov_identity: bytes = bytes(32) | |
| client_name: str = "" | |
| proto_min: int = 1 | |
| proto_max: int = 1 | |
| def pack(self) -> bytes: | |
| ids = list(self.tap_layer_ids) + [0] * (4 - len(self.tap_layer_ids)) | |
| if len(ids) != 4: | |
| raise WireError("at most 4 tap layers") | |
| return HELLO_REQ.pack( | |
| self.proto_min, self.proto_max, self.num_spec, self.window, self.hidden, | |
| self.n_taps, *ids, self.payload_kind, self.row_bytes, self.noise_token_id, | |
| self.vocab_size, self.max_model_len, self.max_slots, self.max_rows_per_step, | |
| self.sample_mode, self.tp_size, 0, | |
| _b32(self.drafter_identity), _b32(self.markov_identity), | |
| self.client_name.encode("ascii", "replace")[:48], | |
| ) | |
| def unpack(cls, body) -> "Hello": | |
| if len(body) != HELLO_REQ.size: | |
| raise WireError(f"HELLO body {len(body)} != {HELLO_REQ.size}") | |
| v = HELLO_REQ.unpack(bytes(body)) | |
| n_taps = v[5] | |
| return cls( | |
| proto_min=v[0], proto_max=v[1], num_spec=v[2], window=v[3], hidden=v[4], | |
| n_taps=n_taps, tap_layer_ids=tuple(v[6:6 + n_taps]), payload_kind=v[10], | |
| row_bytes=v[11], noise_token_id=v[12], vocab_size=v[13], max_model_len=v[14], | |
| max_slots=v[15], max_rows_per_step=v[16], sample_mode=v[17], tp_size=v[18], | |
| drafter_identity=v[20], markov_identity=v[21], | |
| client_name=v[22].rstrip(b"\0").decode("ascii", "replace"), | |
| ) | |
| def config_tuple(self) -> tuple: | |
| """Fields that client and worker must agree on (CONFIG_MISMATCH otherwise).""" | |
| return (self.num_spec, self.window, self.hidden, self.n_taps, | |
| tuple(self.tap_layer_ids), self.payload_kind, self.row_bytes, | |
| self.noise_token_id, self.vocab_size, self.sample_mode) | |
| class HelloReply: | |
| proto: int = 1 | |
| max_slots: int = 4 | |
| max_rows_per_step: int = 512 | |
| capabilities: int = CAP_TAPS_BF16 | |
| window: int = 128 | |
| num_spec: int = 5 | |
| drafter_identity: bytes = bytes(32) | |
| markov_identity: bytes = bytes(32) | |
| worker_name: str = "" | |
| def pack(self) -> bytes: | |
| return HELLO_REP.pack(self.proto, self.max_slots, self.max_rows_per_step, | |
| self.capabilities, self.window, self.num_spec, | |
| _b32(self.drafter_identity), _b32(self.markov_identity), | |
| self.worker_name.encode("ascii", "replace")[:40]) | |
| def unpack(cls, body) -> "HelloReply": | |
| if len(body) != HELLO_REP.size: | |
| raise WireError(f"HELLO reply body {len(body)} != {HELLO_REP.size}") | |
| v = HELLO_REP.unpack(bytes(body)) | |
| return cls(proto=v[0], max_slots=v[1], max_rows_per_step=v[2], capabilities=v[3], | |
| window=v[4], num_spec=v[5], drafter_identity=v[6], markov_identity=v[7], | |
| worker_name=v[8].rstrip(b"\0").decode("ascii", "replace")) | |
| def _b32(b: bytes) -> bytes: | |
| if len(b) != 32: | |
| raise WireError(f"identity must be 32 bytes, got {len(b)}") | |
| return b | |
| # --------------------------------------------------------------------------- STEP | |
| class ReqEntry: | |
| slot: int | |
| generation: int | |
| first_pos: int | |
| n_rows: int | |
| anchor_token: int | |
| anchor_pos: int | |
| flags: int = 0 | |
| k_window: int = 0 | |
| n_out: int = 0 # CONTRACT 1.3: draft positions the Spark consumes (0 = all N) | |
| def pack_into(self, buf, off: int) -> None: | |
| REQ_ENTRY.pack_into(buf, off, self.slot, self.flags, self.k_window, self.n_out, | |
| self.generation & U32_MASK, self.first_pos, self.n_rows, | |
| self.anchor_token, self.anchor_pos) | |
| def unpack_from(cls, buf, off: int) -> "ReqEntry": | |
| slot, flags, k_window, n_out, gen, first_pos, n_rows, anchor_token, anchor_pos = ( | |
| REQ_ENTRY.unpack_from(buf, off)) | |
| return cls(slot=slot, generation=gen, first_pos=first_pos, n_rows=n_rows, | |
| anchor_token=anchor_token, anchor_pos=anchor_pos, flags=flags, | |
| k_window=k_window, n_out=n_out) | |
| class StepRequest: | |
| """A decoded STEP body. ``rows`` is a [n_rows, row_bytes] uint8 view (no copy).""" | |
| step_id: int | |
| step_flags: int | |
| row_bytes: int | |
| entries: list[ReqEntry] | |
| rows: np.ndarray | |
| def n_req(self) -> int: | |
| return len(self.entries) | |
| def step_body_len(n_req: int, n_rows: int, row_bytes: int) -> int: | |
| return rows_offset_for(n_req) + n_rows * row_bytes | |
| def pack_step_prefix(buf, off: int, step_id: int, entries: list[ReqEntry], | |
| n_rows: int, row_bytes: int, step_flags: int = 0, sample_meta=None) -> int: | |
| """Write StepHeader + entries + zero padding at ``buf[off:]`` (``off`` = start of the | |
| STEP body). Returns the offset of the row block relative to the body start. The | |
| caller places the rows at ``buf[off + rows_offset:]`` (e.g. a pinned D2H target).""" | |
| n_req = len(entries) | |
| ro = rows_offset_for(n_req, sample_meta is not None) | |
| STEP_HEADER.pack_into(buf, off, step_id, n_req, step_flags, n_rows, row_bytes, ro) | |
| p = off + STEP_HEADER.size | |
| total = 0 | |
| for e in entries: | |
| e.pack_into(buf, p) | |
| p += REQ_ENTRY.size | |
| total += e.n_rows | |
| if total != n_rows: | |
| raise WireError(f"entries carry {total} rows, header says {n_rows}") | |
| pad_end = off + ro | |
| if pad_end > p: | |
| memoryview(buf).cast("B")[p:pad_end] = bytes(pad_end - p) | |
| if sample_meta is not None: | |
| from .sampled_wire import META | |
| if len(sample_meta) != n_req or not step_flags & SF_SAMPLED: | |
| raise WireError("missing sample flag/context") | |
| for i, (seed, mask) in enumerate(sample_meta): | |
| META.pack_into(buf, off + STEP_HEADER.size + n_req * REQ_ENTRY.size + i * META.size, | |
| int(seed), int(mask)) | |
| return ro | |
| def encode_step(step_id: int, entries: list[ReqEntry], rows: np.ndarray | None, | |
| row_bytes: int, step_flags: int = 0) -> bytes: | |
| """Convenience (tests, mock): full STEP body as bytes. ``rows`` is [n, row_bytes] | |
| uint8 (or any array whose bytes are the rows in order).""" | |
| n_rows = sum(e.n_rows for e in entries) | |
| body = bytearray(step_body_len(len(entries), n_rows, row_bytes)) | |
| ro = pack_step_prefix(body, 0, step_id, entries, n_rows, row_bytes, step_flags) | |
| if n_rows: | |
| raw = np.ascontiguousarray(rows).view(np.uint8).reshape(-1) | |
| if raw.size != n_rows * row_bytes: | |
| raise WireError(f"rows hold {raw.size} bytes, expected {n_rows * row_bytes}") | |
| body[ro:ro + raw.size] = raw.tobytes() | |
| return bytes(body) | |
| def decode_step(body, *, max_slots: int, max_rows: int, expected_row_bytes: int, | |
| window: int) -> StepRequest: | |
| mv = memoryview(body) | |
| if len(mv) < STEP_HEADER.size: | |
| raise WireError("STEP body shorter than its header") | |
| step_id, n_req, step_flags, n_rows, row_bytes, ro = STEP_HEADER.unpack_from(mv, 0) | |
| if row_bytes != expected_row_bytes: | |
| raise WireError(f"row_bytes {row_bytes} != {expected_row_bytes}") | |
| if not 1 <= n_req <= max_slots: | |
| raise WireError(f"n_req {n_req} outside 1..{max_slots}") | |
| if n_rows > max_rows: | |
| raise WireError(f"n_rows {n_rows} > max_rows_per_step {max_rows}") | |
| if ro != rows_offset_for(n_req, bool(step_flags & SF_SAMPLED)): | |
| raise WireError(f"rows_offset {ro} != {rows_offset_for(n_req)}") | |
| if len(mv) != ro + n_rows * row_bytes: | |
| raise WireError(f"STEP body {len(mv)} != {ro + n_rows * row_bytes}") | |
| entries = [ReqEntry.unpack_from(mv, STEP_HEADER.size + i * REQ_ENTRY.size) | |
| for i in range(n_req)] | |
| seen = set() | |
| total = 0 | |
| for e in entries: | |
| if e.slot >= max_slots or e.slot in seen: | |
| raise WireError(f"slot {e.slot} invalid or repeated") | |
| seen.add(e.slot) | |
| total += e.n_rows | |
| if total != n_rows: | |
| raise WireError(f"entries carry {total} rows, header says {n_rows}") | |
| rows = np.frombuffer(mv, dtype=np.uint8, count=n_rows * row_bytes, offset=ro) | |
| rows = rows.reshape(n_rows, row_bytes) if n_rows else rows.reshape(0, row_bytes) | |
| return StepRequest(step_id=step_id, step_flags=step_flags, row_bytes=row_bytes, | |
| entries=entries, rows=rows) | |
| class StepReply: | |
| step_id: int | |
| num_spec: int | |
| status: np.ndarray # [n_req] uint16 | |
| window_rows: np.ndarray # [n_req] uint16 | |
| generation: np.ndarray # [n_req] uint32 | |
| draft_ids: np.ndarray # [n_req, N] int32 | |
| confidence: np.ndarray # [n_req, N] float32 | |
| margin: np.ndarray | None = None # [n_req, N] float32 | |
| worker_us: int = 0 | |
| recv_to_send_us: int = 0 | |
| reply_flags: int = 0 | |
| support_ids: np.ndarray | None = None | |
| proposal_q: np.ndarray | None = None | |
| sampled_mask: np.ndarray | None = None | |
| def n_req(self) -> int: | |
| return int(self.status.shape[0]) | |
| def pack(self) -> bytes: | |
| n, N = self.n_req, self.num_spec | |
| flags = self.reply_flags | (RPF_HAS_MARGIN if self.margin is not None else 0) | |
| if self.support_ids is not None: | |
| flags |= RPF_SAMPLED | |
| parts = [REPLY_HEADER.pack(self.step_id, n, N, flags, self.worker_us, | |
| self.recv_to_send_us)] | |
| st = np.zeros(n, dtype=np.dtype([("s", "<u2"), ("w", "<u2"), ("g", "<u4")])) | |
| st["s"], st["w"], st["g"] = self.status, self.window_rows, self.generation | |
| parts.append(st.tobytes()) | |
| parts.append(np.ascontiguousarray(self.draft_ids, dtype="<i4").reshape(n, N).tobytes()) | |
| parts.append(np.ascontiguousarray(self.confidence, dtype="<f4").reshape(n, N).tobytes()) | |
| if self.margin is not None: | |
| parts.append(np.ascontiguousarray(self.margin, dtype="<f4").reshape(n, N).tobytes()) | |
| body = b"".join(parts) | |
| if flags & RPF_SAMPLED: | |
| from .sampled_wire import append_q | |
| if self.support_ids is None or self.proposal_q is None or self.sampled_mask is None: | |
| raise WireError("sampled reply requires support/q/mask") | |
| body = append_q(body, self.sampled_mask, self.support_ids, self.proposal_q) | |
| return body | |
| def unpack(cls, body, *, expect_n_req: int | None = None, | |
| expect_num_spec: int | None = None) -> "StepReply": | |
| mv = memoryview(body) | |
| if len(mv) < REPLY_HEADER.size: | |
| raise WireError("STEP reply shorter than its header") | |
| step_id, n, N, flags, worker_us, r2s = REPLY_HEADER.unpack_from(mv, 0) | |
| if expect_n_req is not None and n != expect_n_req: | |
| raise WireError(f"reply n_req {n} != {expect_n_req}") | |
| if expect_num_spec is not None and N != expect_num_spec: | |
| raise WireError(f"reply num_spec {N} != {expect_num_spec}") | |
| has_margin = bool(flags & RPF_HAS_MARGIN) | |
| need = REPLY_HEADER.size + 8 * n + 8 * n * N + (4 * n * N if has_margin else 0) | |
| support_ids = proposal_q = sampled_mask = None | |
| if flags & RPF_SAMPLED: | |
| from .sampled_wire import unpack_q | |
| try: | |
| sampled_mask, support_ids, proposal_q = unpack_q(mv, need, n, N) | |
| except ValueError as exc: | |
| raise WireError(str(exc)) from exc | |
| elif len(mv) != need: | |
| raise WireError(f"STEP reply {len(mv)} bytes, expected {need}") | |
| o = REPLY_HEADER.size | |
| st = np.frombuffer(mv, dtype=np.dtype([("s", "<u2"), ("w", "<u2"), ("g", "<u4")]), | |
| count=n, offset=o) | |
| o += 8 * n | |
| ids = np.frombuffer(mv, dtype="<i4", count=n * N, offset=o).reshape(n, N) | |
| o += 4 * n * N | |
| conf = np.frombuffer(mv, dtype="<f4", count=n * N, offset=o).reshape(n, N) | |
| o += 4 * n * N | |
| margin = None | |
| if has_margin: | |
| margin = np.frombuffer(mv, dtype="<f4", count=n * N, offset=o).reshape(n, N) | |
| return cls(step_id=step_id, num_spec=N, status=st["s"].copy(), | |
| window_rows=st["w"].copy(), generation=st["g"].copy(), | |
| draft_ids=ids.copy(), confidence=conf.copy(), | |
| margin=None if margin is None else margin.copy(), | |
| worker_us=worker_us, recv_to_send_us=r2s, reply_flags=flags, | |
| support_ids=support_ids, proposal_q=proposal_q, sampled_mask=sampled_mask) | |
| # --------------------------------------------------------------------------- frames | |
| def frame(op: int, seq: int, session: int, body: bytes = b"", status: int = 0) -> bytes: | |
| return AppHeader(op=op, seq=seq, session=session, body_len=len(body), | |
| status=status).pack() + body | |
| def split_frame(buf) -> tuple[AppHeader, memoryview]: | |
| mv = memoryview(buf) | |
| h = AppHeader.unpack_from(mv) | |
| h.check(len(mv)) | |
| return h, mv[APP_HEADER_SIZE:] | |
| # --------------------------------------------------------------------------- identity | |
| def drafter_identity(file_hashes: dict[str, str]) -> bytes: | |
| """CONTRACT 6.7: sha256 over "<basename> <sha256-hex>\\n" sorted by basename.""" | |
| lines = [] | |
| for name in sorted(file_hashes): | |
| h = file_hashes[name].strip().lower() | |
| if len(h) != 64 or any(c not in "0123456789abcdef" for c in h): | |
| raise ValueError(f"{name}: not a sha256 hex digest: {h!r}") | |
| if "/" in name: | |
| raise ValueError(f"{name}: basenames only") | |
| lines.append(f"{name} {h}\n") | |
| return hashlib.sha256("".join(lines).encode()).digest() | |
| def parse_hex32(text: str, what: str) -> bytes: | |
| t = (text or "").strip().lower() | |
| if len(t) != 64: | |
| raise ValueError(f"{what}: expected 64 hex characters, got {len(t)}") | |
| return bytes.fromhex(t) | |
| class Counters: | |
| """Small helper for per-reason counters (shared by client and mock worker).""" | |
| values: dict[str, int] = field(default_factory=dict) | |
| def inc(self, key: str, n: int = 1) -> None: | |
| self.values[key] = self.values.get(key, 0) + n | |