Skip to content

Commit 352b558

Browse files
authored
framework: cache decoded SecretKey instead of decode/encode bytes for every sign (leanEthereum#777)
1 parent b948f9a commit 352b558

1 file changed

Lines changed: 12 additions & 13 deletions

File tree

  • packages/testing/src/consensus_testing

packages/testing/src/consensus_testing/keys.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class XmssKeyManager:
180180
181181
- Raw JSON (lightweight hex strings, ~2.7 KB per validator)
182182
- Deserialized public keys only (avoids the heavy secret key objects)
183-
- Advanced secret key state as compact SSZ bytes
183+
- Advanced secret key state as live Python objects
184184
"""
185185

186186
__slots__ = (
@@ -267,10 +267,8 @@ def __init__(
267267
# Populated lazily on first directory scan.
268268
self._available_indices: set[ValidatorIndex] | None = None
269269

270-
# Advanced secret key state cached as raw SSZ bytes.
271-
# Raw bytes (~2.7 KB each) instead of deserialized objects (~370 MB each)
272-
# to avoid holding massive Pydantic model trees in memory.
273-
self._secret_state: dict[tuple[ValidatorIndex, KeyRole], bytes] = {}
270+
# Advanced secret-key state held as live Python objects.
271+
self._secret_state: dict[tuple[ValidatorIndex, KeyRole], SecretKey] = {}
274272

275273
def _scan_indices(self) -> set[ValidatorIndex]:
276274
"""
@@ -413,9 +411,9 @@ def _sign_with_secret(
413411
414412
Memory strategy:
415413
416-
1. Deserialize the secret key from cached bytes or disk
417-
2. Advance and sign (only one full key object in memory)
418-
3. Re-serialize to compact bytes (~2.7 KB) for caching
414+
1. On cache miss, deserialize the key once from disk
415+
2. Advance and sign
416+
3. Keep the advanced object for the next sign
419417
420418
Args:
421419
validator_id: Which validator's key to use.
@@ -428,9 +426,11 @@ def _sign_with_secret(
428426
"""
429427
cache_key = (validator_id, role)
430428

431-
# Deserialize the secret key from either the byte cache or disk.
429+
# Reuse the cached object directly when present, else decode from disk.
430+
# Holding the object avoids the bytes-to-object round-trip on every sign.
431+
# That round-trip dominated prod-scheme runtime under the compact-bytes cache.
432432
if cache_key in self._secret_state:
433-
sk = SecretKey.decode_bytes(self._secret_state[cache_key])
433+
sk = self._secret_state[cache_key]
434434
else:
435435
sk = self._get_secret_key(validator_id, role)
436436

@@ -452,9 +452,8 @@ def _sign_with_secret(
452452
# Produce the signature for the target slot.
453453
signature = self.scheme.sign(sk, slot, message)
454454

455-
# Re-serialize the advanced key state to compact bytes for caching.
456-
# This drops the full Python object tree from memory immediately.
457-
self._secret_state[cache_key] = sk.encode_bytes()
455+
# Park the advanced object back in the cache for the next sign.
456+
self._secret_state[cache_key] = sk
458457

459458
return signature
460459

0 commit comments

Comments
 (0)