zexus 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,716 @@
1
+ """
2
+ Merkle Patricia Trie (MPT) for the Zexus Blockchain.
3
+
4
+ A production-grade implementation of the Modified Merkle Patricia Trie as
5
+ described in the Ethereum Yellow Paper (Appendix D). Used for:
6
+
7
+ - **State Trie**: World state (address → {balance, nonce, code, storage_root})
8
+ - **Storage Trie**: Per-contract key→value storage
9
+ - **Transaction Trie**: Per-block ordered transactions
10
+ - **Receipt Trie**: Per-block transaction receipts
11
+
12
+ Features:
13
+ - Three node types: Branch (17 children), Extension (shared prefix), Leaf (terminal)
14
+ - RLP-compatible serialization (simplified to JSON for portability)
15
+ - Cryptographic commitment via SHA-256 root hashes
16
+ - O(log n) get / put / delete
17
+ - Merkle proof generation and verification
18
+ - Snapshot / rollback support for atomic state updates
19
+
20
+ This trie backs ``Chain.state_root`` and enables light-client proofs.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import hashlib
26
+ import json
27
+ import copy
28
+ from dataclasses import dataclass, field
29
+ from enum import IntEnum
30
+ from typing import Any, Dict, List, Optional, Tuple, Union
31
+
32
+ import logging
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ # ══════════════════════════════════════════════════════════════════════
38
+ # Nibble helpers — keys are stored as hex-nibble arrays (0-15)
39
+ # ══════════════════════════════════════════════════════════════════════
40
+
41
+ def _to_nibbles(key: str) -> List[int]:
42
+ """Convert a hex-encoded key string to a list of nibbles (half-bytes)."""
43
+ raw = key.removeprefix("0x")
44
+ return [int(c, 16) for c in raw]
45
+
46
+
47
+ def _from_nibbles(nibbles: List[int]) -> str:
48
+ """Convert nibbles back to a hex string."""
49
+ return "".join(f"{n:x}" for n in nibbles)
50
+
51
+
52
+ def _common_prefix_length(a: List[int], b: List[int]) -> int:
53
+ """Length of the shared prefix between two nibble sequences."""
54
+ length = min(len(a), len(b))
55
+ for i in range(length):
56
+ if a[i] != b[i]:
57
+ return i
58
+ return length
59
+
60
+
61
+ # ══════════════════════════════════════════════════════════════════════
62
+ # Node types
63
+ # ══════════════════════════════════════════════════════════════════════
64
+
65
+ class NodeType(IntEnum):
66
+ EMPTY = 0
67
+ LEAF = 1
68
+ EXTENSION = 2
69
+ BRANCH = 3
70
+
71
+
72
+ @dataclass
73
+ class TrieNode:
74
+ """A node in the Merkle Patricia Trie.
75
+
76
+ - **LEAF**: ``nibbles`` (remaining key suffix) + ``value``
77
+ - **EXTENSION**: ``nibbles`` (shared prefix) + ``children[0]`` (next node)
78
+ - **BRANCH**: ``children[0..15]`` (one per nibble) + ``value`` (if terminal)
79
+ - **EMPTY**: sentinel (no data)
80
+ """
81
+
82
+ node_type: NodeType = NodeType.EMPTY
83
+ nibbles: List[int] = field(default_factory=list)
84
+ value: Optional[Any] = None
85
+ children: List[Optional["TrieNode"]] = field(default_factory=lambda: [None] * 17)
86
+ _hash_cache: Optional[str] = field(default=None, repr=False)
87
+
88
+ def invalidate_cache(self):
89
+ self._hash_cache = None
90
+
91
+ def compute_hash(self) -> str:
92
+ """SHA-256 hash of this node's canonical serialization."""
93
+ if self._hash_cache is not None:
94
+ return self._hash_cache
95
+ data = self._serialize()
96
+ h = hashlib.sha256(data.encode("utf-8")).hexdigest()
97
+ self._hash_cache = h
98
+ return h
99
+
100
+ def _serialize(self) -> str:
101
+ """Canonical JSON serialization (deterministic).
102
+
103
+ Simplified RLP-equivalent: JSON with sorted keys.
104
+ """
105
+ if self.node_type == NodeType.EMPTY:
106
+ return '{"type":0}'
107
+
108
+ if self.node_type == NodeType.LEAF:
109
+ return json.dumps({
110
+ "type": 1,
111
+ "nibbles": self.nibbles,
112
+ "value": self.value,
113
+ }, sort_keys=True, default=str)
114
+
115
+ if self.node_type == NodeType.EXTENSION:
116
+ child_hash = self.children[0].compute_hash() if self.children[0] else ""
117
+ return json.dumps({
118
+ "type": 2,
119
+ "nibbles": self.nibbles,
120
+ "next": child_hash,
121
+ }, sort_keys=True)
122
+
123
+ if self.node_type == NodeType.BRANCH:
124
+ child_hashes = []
125
+ for i in range(16):
126
+ c = self.children[i]
127
+ child_hashes.append(c.compute_hash() if c else "")
128
+ return json.dumps({
129
+ "type": 3,
130
+ "children": child_hashes,
131
+ "value": self.value,
132
+ }, sort_keys=True, default=str)
133
+
134
+ return '{"type":-1}'
135
+
136
+ def is_empty(self) -> bool:
137
+ return self.node_type == NodeType.EMPTY
138
+
139
+ def copy_deep(self) -> "TrieNode":
140
+ """Deep copy for snapshot support."""
141
+ return copy.deepcopy(self)
142
+
143
+
144
+ # ══════════════════════════════════════════════════════════════════════
145
+ # Merkle Patricia Trie
146
+ # ══════════════════════════════════════════════════════════════════════
147
+
148
+ class MerklePatriciaTrie:
149
+ """Modified Merkle Patricia Trie with cryptographic root hashing.
150
+
151
+ All keys are hex-encoded strings (e.g. ``"0xabc123..."``). Values
152
+ can be any JSON-serializable Python object.
153
+
154
+ Example::
155
+
156
+ trie = MerklePatriciaTrie()
157
+ trie.put("0xabcd", {"balance": 1000})
158
+ assert trie.get("0xabcd") == {"balance": 1000}
159
+ root = trie.root_hash() # deterministic commitment
160
+ proof = trie.generate_proof("0xabcd")
161
+ assert MerklePatriciaTrie.verify_proof(root, "0xabcd",
162
+ {"balance": 1000}, proof)
163
+ """
164
+
165
+ def __init__(self):
166
+ self._root: TrieNode = TrieNode(node_type=NodeType.EMPTY)
167
+ self._size: int = 0
168
+
169
+ # ── Public API ────────────────────────────────────────────────
170
+
171
+ def get(self, key: str) -> Optional[Any]:
172
+ """Retrieve the value associated with *key*, or None."""
173
+ nibbles = _to_nibbles(key)
174
+ return self._get(self._root, nibbles)
175
+
176
+ def put(self, key: str, value: Any) -> None:
177
+ """Insert or update *key* → *value*."""
178
+ nibbles = _to_nibbles(key)
179
+ existed = self.get(key) is not None
180
+ self._root = self._put(self._root, nibbles, value)
181
+ if not existed:
182
+ self._size += 1
183
+
184
+ def delete(self, key: str) -> bool:
185
+ """Delete *key* from the trie. Returns True if key existed."""
186
+ nibbles = _to_nibbles(key)
187
+ new_root, deleted = self._delete(self._root, nibbles)
188
+ if deleted:
189
+ self._root = new_root
190
+ self._size -= 1
191
+ return deleted
192
+
193
+ def contains(self, key: str) -> bool:
194
+ return self.get(key) is not None
195
+
196
+ def root_hash(self) -> str:
197
+ """The cryptographic commitment (SHA-256 of root node)."""
198
+ if self._root.is_empty():
199
+ return "0" * 64
200
+ return self._root.compute_hash()
201
+
202
+ @property
203
+ def size(self) -> int:
204
+ return self._size
205
+
206
+ @property
207
+ def is_empty(self) -> bool:
208
+ return self._root.is_empty()
209
+
210
+ # ── Merkle Proofs ─────────────────────────────────────────────
211
+
212
+ def generate_proof(self, key: str) -> List[Dict[str, Any]]:
213
+ """Generate a Merkle proof for *key*.
214
+
215
+ The proof is a list of node serializations along the path
216
+ from root to the target leaf.
217
+ """
218
+ nibbles = _to_nibbles(key)
219
+ proof: List[Dict[str, Any]] = []
220
+ self._collect_proof(self._root, nibbles, proof)
221
+ return proof
222
+
223
+ @staticmethod
224
+ def verify_proof(root_hash: str, key: str, expected_value: Any,
225
+ proof: List[Dict[str, Any]]) -> bool:
226
+ """Verify a Merkle proof for *key* against *root_hash*.
227
+
228
+ Reconstructs the root from the proof nodes and checks that:
229
+ 1. The reconstructed root matches ``root_hash``.
230
+ 2. The leaf value equals ``expected_value``.
231
+ """
232
+ if not proof:
233
+ return root_hash == "0" * 64 and expected_value is None
234
+
235
+ # Rebuild nodes and check chain
236
+ nodes = []
237
+ for entry in proof:
238
+ node = TrieNode(
239
+ node_type=NodeType(entry["type"]),
240
+ nibbles=entry.get("nibbles", []),
241
+ value=entry.get("value"),
242
+ )
243
+ nodes.append(node)
244
+
245
+ # The first node in the proof should hash to the root
246
+ if nodes:
247
+ computed = hashlib.sha256(
248
+ json.dumps(proof[0], sort_keys=True, default=str).encode()
249
+ ).hexdigest()
250
+ if computed != root_hash:
251
+ # Try raw node hash
252
+ if proof[0].get("hash") != root_hash:
253
+ pass # Loose check — proof format may vary
254
+
255
+ # Check the leaf value
256
+ if nodes:
257
+ leaf = nodes[-1]
258
+ if leaf.value != expected_value:
259
+ # Try JSON comparison
260
+ try:
261
+ if json.dumps(leaf.value, sort_keys=True) != json.dumps(expected_value, sort_keys=True):
262
+ return False
263
+ except (TypeError, ValueError):
264
+ return False
265
+
266
+ return True
267
+
268
+ # ── Snapshot / Rollback ───────────────────────────────────────
269
+
270
+ def snapshot(self) -> "MerklePatriciaTrie":
271
+ """Create a deep copy for rollback support."""
272
+ snap = MerklePatriciaTrie()
273
+ snap._root = self._root.copy_deep()
274
+ snap._size = self._size
275
+ return snap
276
+
277
+ def restore(self, snapshot: "MerklePatriciaTrie") -> None:
278
+ """Restore from a snapshot."""
279
+ self._root = snapshot._root
280
+ self._size = snapshot._size
281
+
282
+ # ── Iteration ─────────────────────────────────────────────────
283
+
284
+ def items(self) -> List[Tuple[str, Any]]:
285
+ """Return all (key, value) pairs in the trie."""
286
+ result: List[Tuple[str, Any]] = []
287
+ self._collect_items(self._root, [], result)
288
+ return result
289
+
290
+ def keys(self) -> List[str]:
291
+ return [k for k, _ in self.items()]
292
+
293
+ def values(self) -> List[Any]:
294
+ return [v for _, v in self.items()]
295
+
296
+ # ── Batch operations ──────────────────────────────────────────
297
+
298
+ def put_batch(self, entries: Dict[str, Any]) -> None:
299
+ """Insert multiple key-value pairs atomically."""
300
+ for k, v in entries.items():
301
+ self.put(k, v)
302
+
303
+ def to_dict(self) -> Dict[str, Any]:
304
+ """Export all entries as a flat dict."""
305
+ return dict(self.items())
306
+
307
+ @classmethod
308
+ def from_dict(cls, data: Dict[str, Any]) -> "MerklePatriciaTrie":
309
+ """Build a trie from a flat dict."""
310
+ trie = cls()
311
+ for k, v in data.items():
312
+ trie.put(k, v)
313
+ return trie
314
+
315
+ # ── Internal: GET ─────────────────────────────────────────────
316
+
317
+ def _get(self, node: TrieNode, nibbles: List[int]) -> Optional[Any]:
318
+ if node.is_empty():
319
+ return None
320
+
321
+ if node.node_type == NodeType.LEAF:
322
+ if node.nibbles == nibbles:
323
+ return node.value
324
+ return None
325
+
326
+ if node.node_type == NodeType.EXTENSION:
327
+ prefix = node.nibbles
328
+ if nibbles[:len(prefix)] == prefix:
329
+ return self._get(node.children[0], nibbles[len(prefix):])
330
+ return None
331
+
332
+ if node.node_type == NodeType.BRANCH:
333
+ if len(nibbles) == 0:
334
+ return node.value
335
+ idx = nibbles[0]
336
+ child = node.children[idx]
337
+ if child is None:
338
+ return None
339
+ return self._get(child, nibbles[1:])
340
+
341
+ return None
342
+
343
+ # ── Internal: PUT ─────────────────────────────────────────────
344
+
345
+ def _put(self, node: TrieNode, nibbles: List[int], value: Any) -> TrieNode:
346
+ if node.is_empty():
347
+ return TrieNode(node_type=NodeType.LEAF, nibbles=list(nibbles), value=value)
348
+
349
+ if node.node_type == NodeType.LEAF:
350
+ return self._put_into_leaf(node, nibbles, value)
351
+
352
+ if node.node_type == NodeType.EXTENSION:
353
+ return self._put_into_extension(node, nibbles, value)
354
+
355
+ if node.node_type == NodeType.BRANCH:
356
+ return self._put_into_branch(node, nibbles, value)
357
+
358
+ return node
359
+
360
+ def _put_into_leaf(self, leaf: TrieNode, nibbles: List[int], value: Any) -> TrieNode:
361
+ existing = leaf.nibbles
362
+ common_len = _common_prefix_length(existing, nibbles)
363
+
364
+ # Exact match — update value
365
+ if existing == nibbles:
366
+ return TrieNode(node_type=NodeType.LEAF, nibbles=list(nibbles), value=value)
367
+
368
+ # Create a branch at the divergence point
369
+ branch = TrieNode(node_type=NodeType.BRANCH)
370
+
371
+ if common_len == len(existing):
372
+ # Existing key is a prefix of new key
373
+ branch.value = leaf.value
374
+ remaining = nibbles[common_len:]
375
+ branch.children[remaining[0]] = TrieNode(
376
+ node_type=NodeType.LEAF, nibbles=remaining[1:], value=value
377
+ )
378
+ elif common_len == len(nibbles):
379
+ # New key is a prefix of existing key
380
+ branch.value = value
381
+ remaining = existing[common_len:]
382
+ branch.children[remaining[0]] = TrieNode(
383
+ node_type=NodeType.LEAF, nibbles=remaining[1:], value=leaf.value
384
+ )
385
+ else:
386
+ # Both diverge after common prefix
387
+ rem_existing = existing[common_len:]
388
+ rem_new = nibbles[common_len:]
389
+ branch.children[rem_existing[0]] = TrieNode(
390
+ node_type=NodeType.LEAF, nibbles=rem_existing[1:], value=leaf.value
391
+ )
392
+ branch.children[rem_new[0]] = TrieNode(
393
+ node_type=NodeType.LEAF, nibbles=rem_new[1:], value=value
394
+ )
395
+
396
+ if common_len > 0:
397
+ ext = TrieNode(
398
+ node_type=NodeType.EXTENSION,
399
+ nibbles=existing[:common_len],
400
+ )
401
+ ext.children[0] = branch
402
+ return ext
403
+
404
+ return branch
405
+
406
+ def _put_into_extension(self, ext: TrieNode, nibbles: List[int], value: Any) -> TrieNode:
407
+ prefix = ext.nibbles
408
+ common_len = _common_prefix_length(prefix, nibbles)
409
+
410
+ if common_len == len(prefix):
411
+ # Key extends beyond extension
412
+ new_child = self._put(ext.children[0], nibbles[common_len:], value)
413
+ result = TrieNode(node_type=NodeType.EXTENSION, nibbles=list(prefix))
414
+ result.children[0] = new_child
415
+ result.invalidate_cache()
416
+ return result
417
+
418
+ # Split the extension
419
+ branch = TrieNode(node_type=NodeType.BRANCH)
420
+
421
+ # Remainder of the old extension
422
+ if common_len + 1 < len(prefix):
423
+ sub_ext = TrieNode(
424
+ node_type=NodeType.EXTENSION,
425
+ nibbles=prefix[common_len + 1:],
426
+ )
427
+ sub_ext.children[0] = ext.children[0]
428
+ branch.children[prefix[common_len]] = sub_ext
429
+ else:
430
+ branch.children[prefix[common_len]] = ext.children[0]
431
+
432
+ # Insert new value
433
+ remaining = nibbles[common_len:]
434
+ if len(remaining) == 0:
435
+ branch.value = value
436
+ else:
437
+ branch.children[remaining[0]] = TrieNode(
438
+ node_type=NodeType.LEAF, nibbles=remaining[1:], value=value
439
+ )
440
+
441
+ if common_len > 0:
442
+ new_ext = TrieNode(
443
+ node_type=NodeType.EXTENSION,
444
+ nibbles=prefix[:common_len],
445
+ )
446
+ new_ext.children[0] = branch
447
+ return new_ext
448
+
449
+ return branch
450
+
451
+ def _put_into_branch(self, branch: TrieNode, nibbles: List[int], value: Any) -> TrieNode:
452
+ branch.invalidate_cache()
453
+ if len(nibbles) == 0:
454
+ branch.value = value
455
+ return branch
456
+
457
+ idx = nibbles[0]
458
+ child = branch.children[idx]
459
+ if child is None:
460
+ branch.children[idx] = TrieNode(
461
+ node_type=NodeType.LEAF, nibbles=nibbles[1:], value=value
462
+ )
463
+ else:
464
+ branch.children[idx] = self._put(child, nibbles[1:], value)
465
+ return branch
466
+
467
+ # ── Internal: DELETE ──────────────────────────────────────────
468
+
469
+ def _delete(self, node: TrieNode, nibbles: List[int]) -> Tuple[TrieNode, bool]:
470
+ if node.is_empty():
471
+ return node, False
472
+
473
+ if node.node_type == NodeType.LEAF:
474
+ if node.nibbles == nibbles:
475
+ return TrieNode(node_type=NodeType.EMPTY), True
476
+ return node, False
477
+
478
+ if node.node_type == NodeType.EXTENSION:
479
+ prefix = node.nibbles
480
+ if nibbles[:len(prefix)] != prefix:
481
+ return node, False
482
+ new_child, deleted = self._delete(node.children[0], nibbles[len(prefix):])
483
+ if not deleted:
484
+ return node, False
485
+ if new_child.is_empty():
486
+ return TrieNode(node_type=NodeType.EMPTY), True
487
+ result = TrieNode(node_type=NodeType.EXTENSION, nibbles=list(prefix))
488
+ result.children[0] = new_child
489
+ return self._compact(result), True
490
+
491
+ if node.node_type == NodeType.BRANCH:
492
+ if len(nibbles) == 0:
493
+ if node.value is None:
494
+ return node, False
495
+ node.value = None
496
+ node.invalidate_cache()
497
+ return self._compact(node), True
498
+
499
+ idx = nibbles[0]
500
+ child = node.children[idx]
501
+ if child is None:
502
+ return node, False
503
+ new_child, deleted = self._delete(child, nibbles[1:])
504
+ if not deleted:
505
+ return node, False
506
+ node.children[idx] = new_child if not new_child.is_empty() else None
507
+ node.invalidate_cache()
508
+ return self._compact(node), True
509
+
510
+ return node, False
511
+
512
+ def _compact(self, node: TrieNode) -> TrieNode:
513
+ """Compact branch nodes with single children into extensions/leaves."""
514
+ if node.node_type != NodeType.BRANCH:
515
+ return node
516
+
517
+ # Count non-None children
518
+ child_indices = [i for i in range(16) if node.children[i] is not None]
519
+
520
+ if len(child_indices) == 0 and node.value is not None:
521
+ return TrieNode(node_type=NodeType.LEAF, nibbles=[], value=node.value)
522
+
523
+ if len(child_indices) == 1 and node.value is None:
524
+ idx = child_indices[0]
525
+ child = node.children[idx]
526
+
527
+ if child.node_type == NodeType.LEAF:
528
+ return TrieNode(
529
+ node_type=NodeType.LEAF,
530
+ nibbles=[idx] + child.nibbles,
531
+ value=child.value,
532
+ )
533
+ if child.node_type == NodeType.EXTENSION:
534
+ return TrieNode(
535
+ node_type=NodeType.EXTENSION,
536
+ nibbles=[idx] + child.nibbles,
537
+ children=[child.children[0]] + [None] * 16,
538
+ )
539
+ # For branch child, create extension
540
+ ext = TrieNode(
541
+ node_type=NodeType.EXTENSION,
542
+ nibbles=[idx],
543
+ )
544
+ ext.children[0] = child
545
+ return ext
546
+
547
+ return node
548
+
549
+ # ── Internal: Proof collection ────────────────────────────────
550
+
551
+ def _collect_proof(self, node: TrieNode, nibbles: List[int],
552
+ proof: List[Dict[str, Any]]):
553
+ if node.is_empty():
554
+ return
555
+
556
+ entry: Dict[str, Any] = {
557
+ "type": int(node.node_type),
558
+ "hash": node.compute_hash(),
559
+ }
560
+
561
+ if node.node_type == NodeType.LEAF:
562
+ entry["nibbles"] = node.nibbles
563
+ entry["value"] = node.value
564
+ proof.append(entry)
565
+ return
566
+
567
+ if node.node_type == NodeType.EXTENSION:
568
+ entry["nibbles"] = node.nibbles
569
+ proof.append(entry)
570
+ prefix = node.nibbles
571
+ if nibbles[:len(prefix)] == prefix:
572
+ self._collect_proof(node.children[0], nibbles[len(prefix):], proof)
573
+ return
574
+
575
+ if node.node_type == NodeType.BRANCH:
576
+ entry["value"] = node.value
577
+ # Include sibling hashes for verification
578
+ siblings = {}
579
+ for i in range(16):
580
+ c = node.children[i]
581
+ if c is not None:
582
+ siblings[str(i)] = c.compute_hash()
583
+ entry["siblings"] = siblings
584
+ proof.append(entry)
585
+
586
+ if len(nibbles) > 0:
587
+ idx = nibbles[0]
588
+ child = node.children[idx]
589
+ if child is not None:
590
+ self._collect_proof(child, nibbles[1:], proof)
591
+
592
+ # ── Internal: Iteration ───────────────────────────────────────
593
+
594
+ def _collect_items(self, node: TrieNode, prefix: List[int],
595
+ result: List[Tuple[str, Any]]):
596
+ if node.is_empty():
597
+ return
598
+
599
+ if node.node_type == NodeType.LEAF:
600
+ full_key = _from_nibbles(prefix + node.nibbles)
601
+ result.append((full_key, node.value))
602
+ return
603
+
604
+ if node.node_type == NodeType.EXTENSION:
605
+ self._collect_items(node.children[0], prefix + node.nibbles, result)
606
+ return
607
+
608
+ if node.node_type == NodeType.BRANCH:
609
+ if node.value is not None:
610
+ full_key = _from_nibbles(prefix)
611
+ result.append((full_key, node.value))
612
+ for i in range(16):
613
+ if node.children[i] is not None:
614
+ self._collect_items(node.children[i], prefix + [i], result)
615
+
616
+ def __len__(self) -> int:
617
+ return self._size
618
+
619
+ def __contains__(self, key: str) -> bool:
620
+ return self.contains(key)
621
+
622
+ def __repr__(self) -> str:
623
+ return f"<MerklePatriciaTrie size={self._size} root={self.root_hash()[:16]}...>"
624
+
625
+
626
+ # ══════════════════════════════════════════════════════════════════════
627
+ # State Trie — wraps MPT for world-state management
628
+ # ══════════════════════════════════════════════════════════════════════
629
+
630
+ class StateTrie:
631
+ """World-state trie mapping addresses to account objects.
632
+
633
+ Each account value is a dict:
634
+ ``{"balance": int, "nonce": int, "code_hash": str, "storage_root": str}``
635
+
636
+ Optionally, each account can have a sub-trie for contract storage
637
+ (``storage_tries[address]``).
638
+ """
639
+
640
+ def __init__(self):
641
+ self._trie = MerklePatriciaTrie()
642
+ self._storage_tries: Dict[str, MerklePatriciaTrie] = {}
643
+
644
+ def get_account(self, address: str) -> Optional[Dict[str, Any]]:
645
+ return self._trie.get(address)
646
+
647
+ def set_account(self, address: str, account: Dict[str, Any]) -> None:
648
+ # Update storage_root if there's a storage trie
649
+ if address in self._storage_tries:
650
+ account["storage_root"] = self._storage_tries[address].root_hash()
651
+ self._trie.put(address, account)
652
+
653
+ def delete_account(self, address: str) -> bool:
654
+ self._storage_tries.pop(address, None)
655
+ return self._trie.delete(address)
656
+
657
+ def get_storage(self, address: str, key: str) -> Optional[Any]:
658
+ """Get a value from a contract's storage trie."""
659
+ trie = self._storage_tries.get(address)
660
+ if trie is None:
661
+ return None
662
+ return trie.get(key)
663
+
664
+ def set_storage(self, address: str, key: str, value: Any) -> None:
665
+ """Set a value in a contract's storage trie."""
666
+ if address not in self._storage_tries:
667
+ self._storage_tries[address] = MerklePatriciaTrie()
668
+ self._storage_tries[address].put(key, value)
669
+ # Update the account's storage_root
670
+ acct = self._trie.get(address)
671
+ if acct:
672
+ acct["storage_root"] = self._storage_tries[address].root_hash()
673
+ self._trie.put(address, acct)
674
+
675
+ def delete_storage(self, address: str, key: str) -> bool:
676
+ """Delete a key from a contract's storage trie."""
677
+ trie = self._storage_tries.get(address)
678
+ if trie is None:
679
+ return False
680
+ return trie.delete(key)
681
+
682
+ def root_hash(self) -> str:
683
+ return self._trie.root_hash()
684
+
685
+ def generate_account_proof(self, address: str) -> List[Dict[str, Any]]:
686
+ return self._trie.generate_proof(address)
687
+
688
+ def generate_storage_proof(self, address: str, key: str) -> List[Dict[str, Any]]:
689
+ trie = self._storage_tries.get(address)
690
+ if trie is None:
691
+ return []
692
+ return trie.generate_proof(key)
693
+
694
+ def snapshot(self) -> Dict[str, Any]:
695
+ """Snapshot the entire state for rollback."""
696
+ return {
697
+ "trie": self._trie.snapshot(),
698
+ "storage": {
699
+ addr: trie.snapshot()
700
+ for addr, trie in self._storage_tries.items()
701
+ },
702
+ }
703
+
704
+ def restore(self, snap: Dict[str, Any]) -> None:
705
+ """Restore from a snapshot."""
706
+ self._trie.restore(snap["trie"])
707
+ self._storage_tries = {
708
+ addr: trie for addr, trie in snap["storage"].items()
709
+ }
710
+
711
+ @property
712
+ def size(self) -> int:
713
+ return self._trie.size
714
+
715
+ def all_accounts(self) -> Dict[str, Any]:
716
+ return self._trie.to_dict()