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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3411 -573
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zexus Blockchain — Pluggable Storage Backends
|
|
3
|
+
|
|
4
|
+
Provides an abstract StorageBackend interface and concrete implementations
|
|
5
|
+
for SQLite, LevelDB, and RocksDB. This replaces the previous hard-coded
|
|
6
|
+
SQLite persistence in ``chain.py`` and allows operators to choose the
|
|
7
|
+
backend that best fits their deployment — SQLite for development and
|
|
8
|
+
lightweight nodes, LevelDB/RocksDB for high-throughput production chains.
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
# Development / quick start — zero config (default)
|
|
13
|
+
backend = get_storage_backend("sqlite", db_path="/data/chain.db")
|
|
14
|
+
|
|
15
|
+
# Production — LevelDB
|
|
16
|
+
backend = get_storage_backend("leveldb", db_path="/data/chaindb")
|
|
17
|
+
|
|
18
|
+
# Production — RocksDB (best write-throughput)
|
|
19
|
+
backend = get_storage_backend("rocksdb", db_path="/data/chaindb")
|
|
20
|
+
|
|
21
|
+
Pass the backend to ``Chain(storage=backend)``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
import sqlite3
|
|
30
|
+
from abc import ABC, abstractmethod
|
|
31
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("zexus.blockchain.storage")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Abstract interface ─────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
class StorageBackend(ABC):
|
|
39
|
+
"""Minimal key-value style interface for blockchain persistence.
|
|
40
|
+
|
|
41
|
+
The blockchain uses three *namespaces* (logical tables):
|
|
42
|
+
|
|
43
|
+
* ``blocks`` — height ↦ serialised block JSON
|
|
44
|
+
* ``state`` — address ↦ account JSON
|
|
45
|
+
* ``contract_state`` — address ↦ contract state JSON
|
|
46
|
+
|
|
47
|
+
Implementations MUST support these three namespaces and provide
|
|
48
|
+
atomic writes (``commit``).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
NAMESPACES = ("blocks", "state", "contract_state")
|
|
52
|
+
|
|
53
|
+
# -- core operations -------------------------------------------------
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def get(self, namespace: str, key: str) -> Optional[str]:
|
|
57
|
+
"""Retrieve a value by key from *namespace*, or ``None``."""
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def put(self, namespace: str, key: str, value: str) -> None:
|
|
61
|
+
"""Write *value* under *key* in *namespace*.
|
|
62
|
+
|
|
63
|
+
Implementations may buffer the write until ``commit()`` is called.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def delete(self, namespace: str, key: str) -> None:
|
|
68
|
+
"""Remove *key* from *namespace*."""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def has(self, namespace: str, key: str) -> bool:
|
|
72
|
+
"""Return ``True`` if *key* exists in *namespace*."""
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
76
|
+
"""Yield all ``(key, value)`` pairs in *namespace* in
|
|
77
|
+
insertion / key order."""
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
81
|
+
"""Yield all ``(key, value)`` pairs sorted numerically if
|
|
82
|
+
applicable (e.g. block heights)."""
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def commit(self) -> None:
|
|
86
|
+
"""Flush any buffered writes to durable storage."""
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def close(self) -> None:
|
|
90
|
+
"""Release resources held by the backend."""
|
|
91
|
+
|
|
92
|
+
# -- convenience helpers (overridable) --------------------------------
|
|
93
|
+
|
|
94
|
+
def get_json(self, namespace: str, key: str) -> Optional[Any]:
|
|
95
|
+
raw = self.get(namespace, key)
|
|
96
|
+
if raw is None:
|
|
97
|
+
return None
|
|
98
|
+
return json.loads(raw)
|
|
99
|
+
|
|
100
|
+
def put_json(self, namespace: str, key: str, obj: Any) -> None:
|
|
101
|
+
self.put(namespace, key, json.dumps(obj, default=str))
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def name(self) -> str:
|
|
105
|
+
return self.__class__.__name__
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ── SQLite backend (default) ───────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
class SQLiteBackend(StorageBackend):
|
|
111
|
+
"""SQLite-based storage — the battle-tested default.
|
|
112
|
+
|
|
113
|
+
Best for: development, testnets, single-node setups, light nodes.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, db_path: str):
|
|
117
|
+
self._db_path = db_path
|
|
118
|
+
os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True)
|
|
119
|
+
self._conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
120
|
+
self._conn.execute("PRAGMA journal_mode=WAL") # better concurrent reads
|
|
121
|
+
self._conn.execute("PRAGMA synchronous=NORMAL")
|
|
122
|
+
self._init_tables()
|
|
123
|
+
logger.info("SQLite storage backend opened: %s", db_path)
|
|
124
|
+
|
|
125
|
+
def _init_tables(self):
|
|
126
|
+
for ns in self.NAMESPACES:
|
|
127
|
+
self._conn.execute(f"""
|
|
128
|
+
CREATE TABLE IF NOT EXISTS [{ns}] (
|
|
129
|
+
key TEXT PRIMARY KEY,
|
|
130
|
+
value TEXT NOT NULL
|
|
131
|
+
)
|
|
132
|
+
""")
|
|
133
|
+
self._conn.commit()
|
|
134
|
+
|
|
135
|
+
# -- interface -------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def get(self, namespace: str, key: str) -> Optional[str]:
|
|
138
|
+
row = self._conn.execute(
|
|
139
|
+
f"SELECT value FROM [{namespace}] WHERE key = ?", (key,)
|
|
140
|
+
).fetchone()
|
|
141
|
+
return row[0] if row else None
|
|
142
|
+
|
|
143
|
+
def put(self, namespace: str, key: str, value: str) -> None:
|
|
144
|
+
self._conn.execute(
|
|
145
|
+
f"INSERT OR REPLACE INTO [{namespace}] (key, value) VALUES (?, ?)",
|
|
146
|
+
(key, value),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def delete(self, namespace: str, key: str) -> None:
|
|
150
|
+
self._conn.execute(
|
|
151
|
+
f"DELETE FROM [{namespace}] WHERE key = ?", (key,)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def has(self, namespace: str, key: str) -> bool:
|
|
155
|
+
row = self._conn.execute(
|
|
156
|
+
f"SELECT 1 FROM [{namespace}] WHERE key = ? LIMIT 1", (key,)
|
|
157
|
+
).fetchone()
|
|
158
|
+
return row is not None
|
|
159
|
+
|
|
160
|
+
def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
161
|
+
cursor = self._conn.execute(f"SELECT key, value FROM [{namespace}]")
|
|
162
|
+
yield from cursor
|
|
163
|
+
|
|
164
|
+
def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
165
|
+
# For blocks, key is height (numeric string) — CAST allows proper sort
|
|
166
|
+
cursor = self._conn.execute(
|
|
167
|
+
f"SELECT key, value FROM [{namespace}] ORDER BY CAST(key AS INTEGER) ASC"
|
|
168
|
+
)
|
|
169
|
+
yield from cursor
|
|
170
|
+
|
|
171
|
+
def commit(self) -> None:
|
|
172
|
+
self._conn.commit()
|
|
173
|
+
|
|
174
|
+
def close(self) -> None:
|
|
175
|
+
if self._conn:
|
|
176
|
+
self._conn.close()
|
|
177
|
+
self._conn = None # type: ignore[assignment]
|
|
178
|
+
logger.info("SQLite storage backend closed")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ── LevelDB backend ───────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
class LevelDBBackend(StorageBackend):
|
|
184
|
+
"""LevelDB-based storage — optimised for read-heavy workloads.
|
|
185
|
+
|
|
186
|
+
Best for: production validator nodes, archive nodes (read-heavy).
|
|
187
|
+
|
|
188
|
+
Requires the ``plyvel`` package::
|
|
189
|
+
|
|
190
|
+
pip install plyvel
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(self, db_path: str):
|
|
194
|
+
try:
|
|
195
|
+
import plyvel
|
|
196
|
+
except ImportError:
|
|
197
|
+
raise ImportError(
|
|
198
|
+
"LevelDB backend requires the 'plyvel' package.\n\n"
|
|
199
|
+
" Install with: pip install plyvel\n\n"
|
|
200
|
+
" plyvel requires the LevelDB C library. "
|
|
201
|
+
"On Ubuntu/Debian:\n"
|
|
202
|
+
" sudo apt-get install libleveldb-dev\n"
|
|
203
|
+
" On macOS:\n"
|
|
204
|
+
" brew install leveldb\n\n"
|
|
205
|
+
" Alternatively, use SQLiteBackend (no native deps) "
|
|
206
|
+
"or MemoryBackend for development."
|
|
207
|
+
)
|
|
208
|
+
self._db_path = db_path
|
|
209
|
+
os.makedirs(db_path, exist_ok=True)
|
|
210
|
+
self._dbs: Dict[str, Any] = {}
|
|
211
|
+
for ns in self.NAMESPACES:
|
|
212
|
+
self._dbs[ns] = plyvel.DB(
|
|
213
|
+
os.path.join(db_path, ns), create_if_missing=True
|
|
214
|
+
)
|
|
215
|
+
logger.info("LevelDB storage backend opened: %s", db_path)
|
|
216
|
+
|
|
217
|
+
def _encode(self, s: str) -> bytes:
|
|
218
|
+
return s.encode("utf-8")
|
|
219
|
+
|
|
220
|
+
def _decode(self, b: bytes) -> str:
|
|
221
|
+
return b.decode("utf-8")
|
|
222
|
+
|
|
223
|
+
def get(self, namespace: str, key: str) -> Optional[str]:
|
|
224
|
+
val = self._dbs[namespace].get(self._encode(key))
|
|
225
|
+
return self._decode(val) if val is not None else None
|
|
226
|
+
|
|
227
|
+
def put(self, namespace: str, key: str, value: str) -> None:
|
|
228
|
+
self._dbs[namespace].put(self._encode(key), self._encode(value))
|
|
229
|
+
|
|
230
|
+
def delete(self, namespace: str, key: str) -> None:
|
|
231
|
+
self._dbs[namespace].delete(self._encode(key))
|
|
232
|
+
|
|
233
|
+
def has(self, namespace: str, key: str) -> bool:
|
|
234
|
+
return self._dbs[namespace].get(self._encode(key)) is not None
|
|
235
|
+
|
|
236
|
+
def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
237
|
+
for k, v in self._dbs[namespace]:
|
|
238
|
+
yield self._decode(k), self._decode(v)
|
|
239
|
+
|
|
240
|
+
def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
241
|
+
# LevelDB iterates in lexicographic order by default.
|
|
242
|
+
# For numeric keys (block heights) we zero-pad on write, or
|
|
243
|
+
# fall back to in-memory sort for safety.
|
|
244
|
+
pairs = list(self.iterate(namespace))
|
|
245
|
+
try:
|
|
246
|
+
pairs.sort(key=lambda kv: int(kv[0]))
|
|
247
|
+
except ValueError:
|
|
248
|
+
pairs.sort()
|
|
249
|
+
yield from pairs
|
|
250
|
+
|
|
251
|
+
def commit(self) -> None:
|
|
252
|
+
pass # LevelDB writes are durable by default
|
|
253
|
+
|
|
254
|
+
def close(self) -> None:
|
|
255
|
+
for db in self._dbs.values():
|
|
256
|
+
db.close()
|
|
257
|
+
self._dbs.clear()
|
|
258
|
+
logger.info("LevelDB storage backend closed")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ── RocksDB backend ───────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
class RocksDBBackend(StorageBackend):
|
|
264
|
+
"""RocksDB-based storage — high write-throughput for busy chains.
|
|
265
|
+
|
|
266
|
+
Best for: production mining/validator nodes with high TPS.
|
|
267
|
+
|
|
268
|
+
Requires the ``python-rocksdb`` (or ``rocksdb``) package::
|
|
269
|
+
|
|
270
|
+
pip install python-rocksdb
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
def __init__(self, db_path: str):
|
|
274
|
+
try:
|
|
275
|
+
import rocksdb # type: ignore[import-untyped]
|
|
276
|
+
except ImportError:
|
|
277
|
+
raise ImportError(
|
|
278
|
+
"RocksDB backend requires the 'python-rocksdb' package.\n\n"
|
|
279
|
+
" Install with: pip install python-rocksdb\n\n"
|
|
280
|
+
" python-rocksdb requires the RocksDB C++ library. "
|
|
281
|
+
"On Ubuntu/Debian:\n"
|
|
282
|
+
" sudo apt-get install librocksdb-dev\n"
|
|
283
|
+
" On macOS:\n"
|
|
284
|
+
" brew install rocksdb\n\n"
|
|
285
|
+
" Alternatively, use SQLiteBackend (no native deps) "
|
|
286
|
+
"or MemoryBackend for development."
|
|
287
|
+
)
|
|
288
|
+
self._db_path = db_path
|
|
289
|
+
os.makedirs(db_path, exist_ok=True)
|
|
290
|
+
self._rocksdb = rocksdb
|
|
291
|
+
opts = rocksdb.Options()
|
|
292
|
+
opts.create_if_missing = True
|
|
293
|
+
opts.max_open_files = 512
|
|
294
|
+
opts.write_buffer_size = 64 * 1024 * 1024 # 64 MB
|
|
295
|
+
opts.max_write_buffer_number = 3
|
|
296
|
+
opts.target_file_size_base = 64 * 1024 * 1024
|
|
297
|
+
|
|
298
|
+
self._dbs: Dict[str, Any] = {}
|
|
299
|
+
for ns in self.NAMESPACES:
|
|
300
|
+
ns_path = os.path.join(db_path, ns)
|
|
301
|
+
os.makedirs(ns_path, exist_ok=True)
|
|
302
|
+
self._dbs[ns] = rocksdb.DB(ns_path, opts)
|
|
303
|
+
logger.info("RocksDB storage backend opened: %s", db_path)
|
|
304
|
+
|
|
305
|
+
def _encode(self, s: str) -> bytes:
|
|
306
|
+
return s.encode("utf-8")
|
|
307
|
+
|
|
308
|
+
def _decode(self, b: bytes) -> str:
|
|
309
|
+
return b.decode("utf-8")
|
|
310
|
+
|
|
311
|
+
def get(self, namespace: str, key: str) -> Optional[str]:
|
|
312
|
+
val = self._dbs[namespace].get(self._encode(key))
|
|
313
|
+
return self._decode(val) if val is not None else None
|
|
314
|
+
|
|
315
|
+
def put(self, namespace: str, key: str, value: str) -> None:
|
|
316
|
+
self._dbs[namespace].put(self._encode(key), self._encode(value))
|
|
317
|
+
|
|
318
|
+
def delete(self, namespace: str, key: str) -> None:
|
|
319
|
+
self._dbs[namespace].delete(self._encode(key))
|
|
320
|
+
|
|
321
|
+
def has(self, namespace: str, key: str) -> bool:
|
|
322
|
+
return self._dbs[namespace].get(self._encode(key)) is not None
|
|
323
|
+
|
|
324
|
+
def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
325
|
+
it = self._dbs[namespace].iteritems()
|
|
326
|
+
it.seek_to_first()
|
|
327
|
+
for k, v in it:
|
|
328
|
+
yield self._decode(k), self._decode(v)
|
|
329
|
+
|
|
330
|
+
def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
331
|
+
pairs = list(self.iterate(namespace))
|
|
332
|
+
try:
|
|
333
|
+
pairs.sort(key=lambda kv: int(kv[0]))
|
|
334
|
+
except ValueError:
|
|
335
|
+
pairs.sort()
|
|
336
|
+
yield from pairs
|
|
337
|
+
|
|
338
|
+
def commit(self) -> None:
|
|
339
|
+
pass # RocksDB writes are durable by default
|
|
340
|
+
|
|
341
|
+
def close(self) -> None:
|
|
342
|
+
# python-rocksdb DBs are closed via garbage collection
|
|
343
|
+
self._dbs.clear()
|
|
344
|
+
logger.info("RocksDB storage backend closed")
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ── In-memory backend (testing) ────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
class MemoryBackend(StorageBackend):
|
|
350
|
+
"""Ephemeral in-memory backend — useful for tests and benchmarks."""
|
|
351
|
+
|
|
352
|
+
def __init__(self):
|
|
353
|
+
self._data: Dict[str, Dict[str, str]] = {ns: {} for ns in self.NAMESPACES}
|
|
354
|
+
|
|
355
|
+
def get(self, namespace: str, key: str) -> Optional[str]:
|
|
356
|
+
return self._data[namespace].get(key)
|
|
357
|
+
|
|
358
|
+
def put(self, namespace: str, key: str, value: str) -> None:
|
|
359
|
+
self._data[namespace][key] = value
|
|
360
|
+
|
|
361
|
+
def delete(self, namespace: str, key: str) -> None:
|
|
362
|
+
self._data[namespace].pop(key, None)
|
|
363
|
+
|
|
364
|
+
def has(self, namespace: str, key: str) -> bool:
|
|
365
|
+
return key in self._data[namespace]
|
|
366
|
+
|
|
367
|
+
def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
368
|
+
yield from self._data[namespace].items()
|
|
369
|
+
|
|
370
|
+
def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
|
|
371
|
+
pairs = list(self._data[namespace].items())
|
|
372
|
+
try:
|
|
373
|
+
pairs.sort(key=lambda kv: int(kv[0]))
|
|
374
|
+
except ValueError:
|
|
375
|
+
pairs.sort()
|
|
376
|
+
yield from pairs
|
|
377
|
+
|
|
378
|
+
def commit(self) -> None:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
def close(self) -> None:
|
|
382
|
+
self._data.clear()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# ── Backend factory ────────────────────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
_BACKENDS = {
|
|
388
|
+
"sqlite": SQLiteBackend,
|
|
389
|
+
"leveldb": LevelDBBackend,
|
|
390
|
+
"rocksdb": RocksDBBackend,
|
|
391
|
+
"memory": MemoryBackend,
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_storage_backend(name: str, **kwargs) -> StorageBackend:
|
|
396
|
+
"""Instantiate a storage backend by name.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
name : str
|
|
401
|
+
One of ``"sqlite"``, ``"leveldb"``, ``"rocksdb"``, ``"memory"``.
|
|
402
|
+
**kwargs
|
|
403
|
+
Forwarded to the backend constructor (e.g. ``db_path``).
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
StorageBackend
|
|
408
|
+
"""
|
|
409
|
+
name = name.lower().strip()
|
|
410
|
+
cls = _BACKENDS.get(name)
|
|
411
|
+
if cls is None:
|
|
412
|
+
raise ValueError(
|
|
413
|
+
f"Unknown storage backend '{name}'. "
|
|
414
|
+
f"Available: {', '.join(sorted(_BACKENDS))}"
|
|
415
|
+
)
|
|
416
|
+
return cls(**kwargs)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def register_backend(name: str, cls: type) -> None:
|
|
420
|
+
"""Register a custom storage backend class."""
|
|
421
|
+
if not issubclass(cls, StorageBackend):
|
|
422
|
+
raise TypeError(f"{cls} is not a StorageBackend subclass")
|
|
423
|
+
_BACKENDS[name.lower()] = cls
|