zexus 1.6.8 → 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 +12 -5
- 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/capability_system.py +184 -9
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +383 -34
- 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/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +16 -5
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- 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 +112 -9
- 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 +457 -37
- package/src/zexus/evaluator/core.py +644 -50
- package/src/zexus/evaluator/expressions.py +358 -62
- package/src/zexus/evaluator/functions.py +458 -20
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +774 -122
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -458
- 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 +239 -9
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +76 -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 +1349 -408
- package/src/zexus/parser/strategy_context.py +755 -58
- package/src/zexus/parser/strategy_structural.py +121 -21
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +61 -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/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__init__.py +10 -2
- 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/runtime/load_manager.py +368 -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 +80 -6
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +59 -11
- 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 +561 -17
- package/src/zexus/vm/compiler.py +818 -51
- 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 +364 -20
- 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 +140 -45
- 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 +3581 -531
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +137 -11
- package/src/zexus/zexus_token.py +16 -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 +16 -6
- package/src/zexus.egg-info/SOURCES.txt +129 -17
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zexus Blockchain — Multiprocessing Batch Executor
|
|
3
|
+
===================================================
|
|
4
|
+
|
|
5
|
+
Dispatches contract-group transaction batches to **separate OS processes**
|
|
6
|
+
via ``multiprocessing.Pool``, giving each group its own Python GIL for
|
|
7
|
+
true CPU-level parallelism.
|
|
8
|
+
|
|
9
|
+
Architecture::
|
|
10
|
+
|
|
11
|
+
Main process (orchestrator)
|
|
12
|
+
│
|
|
13
|
+
├── Worker-0 (own GIL) ← contract group A
|
|
14
|
+
├── Worker-1 (own GIL) ← contract group B
|
|
15
|
+
├── Worker-2 (own GIL) ← contract group C
|
|
16
|
+
└── ...
|
|
17
|
+
|
|
18
|
+
Each worker process re-instantiates the ``ContractVM`` (or a lightweight
|
|
19
|
+
mock) so that contract state is process-local. Results are aggregated
|
|
20
|
+
in the main process.
|
|
21
|
+
|
|
22
|
+
When combined with the Rust batched-GIL executor (Option 2) inside each
|
|
23
|
+
worker, the two optimisations stack: Rayon parallelises within a process
|
|
24
|
+
while multiprocessing parallelises across processes.
|
|
25
|
+
|
|
26
|
+
Usage::
|
|
27
|
+
|
|
28
|
+
from zexus.blockchain.multiprocess_executor import MultiProcessBatchExecutor
|
|
29
|
+
|
|
30
|
+
executor = MultiProcessBatchExecutor(
|
|
31
|
+
vm_factory=lambda: MyContractVM(),
|
|
32
|
+
workers=4,
|
|
33
|
+
)
|
|
34
|
+
result = executor.execute_batch(transactions)
|
|
35
|
+
print(result.throughput) # >> 10,000+ tx/s
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import logging
|
|
42
|
+
import multiprocessing
|
|
43
|
+
import os
|
|
44
|
+
import time
|
|
45
|
+
from collections import defaultdict
|
|
46
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
47
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
48
|
+
|
|
49
|
+
logger = logging.getLogger("zexus.blockchain.multiprocess_executor")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ── Results ────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
class MPBatchResult:
|
|
55
|
+
"""Aggregated result from a multiprocess batch execution."""
|
|
56
|
+
|
|
57
|
+
__slots__ = (
|
|
58
|
+
"total", "succeeded", "failed", "gas_used",
|
|
59
|
+
"elapsed", "receipts",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
total: int = 0,
|
|
65
|
+
succeeded: int = 0,
|
|
66
|
+
failed: int = 0,
|
|
67
|
+
gas_used: int = 0,
|
|
68
|
+
elapsed: float = 0.0,
|
|
69
|
+
receipts: Optional[List[Dict[str, Any]]] = None,
|
|
70
|
+
):
|
|
71
|
+
self.total = total
|
|
72
|
+
self.succeeded = succeeded
|
|
73
|
+
self.failed = failed
|
|
74
|
+
self.gas_used = gas_used
|
|
75
|
+
self.elapsed = elapsed
|
|
76
|
+
self.receipts = receipts or []
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def throughput(self) -> float:
|
|
80
|
+
return self.total / self.elapsed if self.elapsed > 0 else 0.0
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return (
|
|
84
|
+
f"MPBatchResult(total={self.total}, ok={self.succeeded}, "
|
|
85
|
+
f"fail={self.failed}, gas={self.gas_used}, "
|
|
86
|
+
f"{self.throughput:.1f} tx/s)"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── Worker function (runs in child process) ───────────────────────────
|
|
91
|
+
|
|
92
|
+
# Module-level VM holder for the child process.
|
|
93
|
+
_worker_vm: Any = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _init_worker(vm_factory_pickle: bytes) -> None:
|
|
97
|
+
"""Process initialiser — creates a VM instance per worker."""
|
|
98
|
+
import pickle
|
|
99
|
+
global _worker_vm
|
|
100
|
+
vm_factory = pickle.loads(vm_factory_pickle)
|
|
101
|
+
_worker_vm = vm_factory()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _execute_group(
|
|
105
|
+
contract_addr: str,
|
|
106
|
+
txs_json: str,
|
|
107
|
+
) -> str:
|
|
108
|
+
"""Execute a group of transactions in the worker's own GIL.
|
|
109
|
+
|
|
110
|
+
Receives and returns JSON to avoid pickle overhead on complex objects.
|
|
111
|
+
"""
|
|
112
|
+
global _worker_vm
|
|
113
|
+
import json as _json
|
|
114
|
+
import hashlib as _hashlib
|
|
115
|
+
|
|
116
|
+
txs = _json.loads(txs_json)
|
|
117
|
+
results: List[Dict[str, Any]] = []
|
|
118
|
+
succeeded = 0
|
|
119
|
+
failed = 0
|
|
120
|
+
gas_used = 0
|
|
121
|
+
|
|
122
|
+
for tx in txs:
|
|
123
|
+
try:
|
|
124
|
+
if _worker_vm is not None and hasattr(_worker_vm, "execute_action"):
|
|
125
|
+
receipt = _worker_vm.execute_action(
|
|
126
|
+
contract=tx.get("contract", contract_addr),
|
|
127
|
+
action=tx.get("action", ""),
|
|
128
|
+
args=tx.get("args", {}),
|
|
129
|
+
caller=tx.get("caller", ""),
|
|
130
|
+
gas_limit=tx.get("gas_limit", 100_000),
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
# Minimal fallback — hash-based mock
|
|
134
|
+
data = (
|
|
135
|
+
f"{contract_addr}:{tx.get('action', '')}:"
|
|
136
|
+
f"{tx.get('caller', '')}:{_json.dumps(tx.get('args', {}), sort_keys=True)}"
|
|
137
|
+
)
|
|
138
|
+
receipt = {
|
|
139
|
+
"success": True,
|
|
140
|
+
"gas_used": max(21_000, len(data) * 8),
|
|
141
|
+
"result": _hashlib.sha256(data.encode()).hexdigest()[:16],
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if isinstance(receipt, dict) and receipt.get("success"):
|
|
145
|
+
succeeded += 1
|
|
146
|
+
gas_used += receipt.get("gas_used", 0)
|
|
147
|
+
else:
|
|
148
|
+
failed += 1
|
|
149
|
+
results.append(receipt if isinstance(receipt, dict) else {"success": False})
|
|
150
|
+
except Exception as e:
|
|
151
|
+
failed += 1
|
|
152
|
+
results.append({"success": False, "error": str(e)})
|
|
153
|
+
|
|
154
|
+
return _json.dumps({
|
|
155
|
+
"contract": contract_addr,
|
|
156
|
+
"succeeded": succeeded,
|
|
157
|
+
"failed": failed,
|
|
158
|
+
"gas_used": gas_used,
|
|
159
|
+
"receipts": results,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ── Try Rust-accelerated group execution ──────────────────────────────
|
|
164
|
+
|
|
165
|
+
def _execute_group_rust(
|
|
166
|
+
contract_addr: str,
|
|
167
|
+
txs_json: str,
|
|
168
|
+
) -> str:
|
|
169
|
+
"""Execute a group using the Rust batched-GIL executor within the worker."""
|
|
170
|
+
global _worker_vm
|
|
171
|
+
import json as _json
|
|
172
|
+
|
|
173
|
+
txs = _json.loads(txs_json)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
import zexus_core # type: ignore[import-untyped]
|
|
177
|
+
|
|
178
|
+
# Build the vm_callback for Rust
|
|
179
|
+
def _callback(contract, action, args_json_str, caller, gas_limit):
|
|
180
|
+
if _worker_vm is not None and hasattr(_worker_vm, "execute_action"):
|
|
181
|
+
import json as _j
|
|
182
|
+
args = _j.loads(args_json_str) if isinstance(args_json_str, str) else args_json_str
|
|
183
|
+
gas = int(gas_limit) if isinstance(gas_limit, str) else gas_limit
|
|
184
|
+
return _worker_vm.execute_action(
|
|
185
|
+
contract=contract, action=action,
|
|
186
|
+
args=args, caller=caller, gas_limit=gas,
|
|
187
|
+
)
|
|
188
|
+
# Mock fallback
|
|
189
|
+
import hashlib as _h
|
|
190
|
+
data = f"{contract}:{action}:{caller}:{args_json_str}"
|
|
191
|
+
return {
|
|
192
|
+
"success": True,
|
|
193
|
+
"gas_used": max(21_000, len(data) * 8),
|
|
194
|
+
"result": _h.sha256(data.encode()).hexdigest()[:16],
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Serialise txs for Rust
|
|
198
|
+
serialized = []
|
|
199
|
+
for tx in txs:
|
|
200
|
+
serialized.append({
|
|
201
|
+
"contract": str(tx.get("contract", contract_addr)),
|
|
202
|
+
"action": str(tx.get("action", "")),
|
|
203
|
+
"args": _json.dumps(tx.get("args", {})) if not isinstance(tx.get("args"), str) else tx["args"],
|
|
204
|
+
"caller": str(tx.get("caller", "")),
|
|
205
|
+
"gas_limit": str(tx.get("gas_limit", "0")),
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
executor = zexus_core.RustBatchExecutor(max_workers=1)
|
|
209
|
+
result = executor.execute_batch(serialized, _callback)
|
|
210
|
+
|
|
211
|
+
receipts = []
|
|
212
|
+
for r_json in result.receipts:
|
|
213
|
+
try:
|
|
214
|
+
receipts.append(_json.loads(r_json))
|
|
215
|
+
except _json.JSONDecodeError:
|
|
216
|
+
receipts.append({"success": False, "error": r_json})
|
|
217
|
+
|
|
218
|
+
return _json.dumps({
|
|
219
|
+
"contract": contract_addr,
|
|
220
|
+
"succeeded": result.succeeded,
|
|
221
|
+
"failed": result.failed,
|
|
222
|
+
"gas_used": result.gas_used,
|
|
223
|
+
"receipts": receipts,
|
|
224
|
+
})
|
|
225
|
+
except ImportError:
|
|
226
|
+
# Rust not available in worker — fall back to pure Python
|
|
227
|
+
return _execute_group(contract_addr, txs_json)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _default_vm_factory():
|
|
231
|
+
"""Default VM factory that creates no VM (uses mock fallback)."""
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ── Main executor class ───────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
class MultiProcessBatchExecutor:
|
|
238
|
+
"""Multi-process batch executor — true GIL-free parallelism.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
vm_factory : callable
|
|
243
|
+
A zero-argument callable that returns a ContractVM instance.
|
|
244
|
+
Must be picklable (e.g. a module-level function or lambda
|
|
245
|
+
with no closures over unpicklable objects).
|
|
246
|
+
workers : int
|
|
247
|
+
Number of worker processes (default: CPU count).
|
|
248
|
+
use_rust_in_workers : bool
|
|
249
|
+
If True, each worker uses the Rust batched-GIL executor for
|
|
250
|
+
its group — stacking Rayon parallelism (within-process) with
|
|
251
|
+
multiprocessing parallelism (across processes).
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
vm_factory: Optional[Callable[[], Any]] = None,
|
|
257
|
+
workers: int = 0,
|
|
258
|
+
use_rust_in_workers: bool = True,
|
|
259
|
+
):
|
|
260
|
+
self._vm_factory = vm_factory
|
|
261
|
+
self._workers = workers or min(multiprocessing.cpu_count(), 16)
|
|
262
|
+
self._use_rust = use_rust_in_workers
|
|
263
|
+
self._pool: Optional[ProcessPoolExecutor] = None
|
|
264
|
+
self._pool_mp: Optional[multiprocessing.pool.Pool] = None
|
|
265
|
+
|
|
266
|
+
def _ensure_pool(self) -> None:
|
|
267
|
+
"""Lazily create the process pool."""
|
|
268
|
+
if self._pool_mp is not None:
|
|
269
|
+
return
|
|
270
|
+
import pickle
|
|
271
|
+
factory = self._vm_factory if self._vm_factory else _default_vm_factory
|
|
272
|
+
factory_bytes = pickle.dumps(factory)
|
|
273
|
+
self._pool_mp = multiprocessing.Pool(
|
|
274
|
+
processes=self._workers,
|
|
275
|
+
initializer=_init_worker,
|
|
276
|
+
initargs=(factory_bytes,),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def execute_batch(
|
|
280
|
+
self,
|
|
281
|
+
transactions: List[Dict[str, Any]],
|
|
282
|
+
) -> MPBatchResult:
|
|
283
|
+
"""Execute a batch of transactions across multiple processes.
|
|
284
|
+
|
|
285
|
+
Transactions are grouped by contract address. Each group is
|
|
286
|
+
dispatched to a worker process that has its own Python GIL.
|
|
287
|
+
"""
|
|
288
|
+
start = time.perf_counter()
|
|
289
|
+
self._ensure_pool()
|
|
290
|
+
|
|
291
|
+
# Group by contract
|
|
292
|
+
groups: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
293
|
+
for tx in transactions:
|
|
294
|
+
groups[tx.get("contract", "unknown")].append(tx)
|
|
295
|
+
|
|
296
|
+
# Choose worker function
|
|
297
|
+
worker_fn = _execute_group_rust if self._use_rust else _execute_group
|
|
298
|
+
|
|
299
|
+
# Dispatch groups to workers
|
|
300
|
+
async_results = []
|
|
301
|
+
for contract_addr, txs in groups.items():
|
|
302
|
+
txs_json = json.dumps(txs)
|
|
303
|
+
async_results.append(
|
|
304
|
+
self._pool_mp.apply_async(worker_fn, (contract_addr, txs_json)) # type: ignore
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Collect results
|
|
308
|
+
result = MPBatchResult(total=len(transactions))
|
|
309
|
+
for ar in async_results:
|
|
310
|
+
try:
|
|
311
|
+
group_json = ar.get(timeout=30)
|
|
312
|
+
group_data = json.loads(group_json)
|
|
313
|
+
result.succeeded += group_data.get("succeeded", 0)
|
|
314
|
+
result.failed += group_data.get("failed", 0)
|
|
315
|
+
result.gas_used += group_data.get("gas_used", 0)
|
|
316
|
+
result.receipts.extend(group_data.get("receipts", []))
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logger.error("Worker group failed: %s", e)
|
|
319
|
+
result.failed += 1
|
|
320
|
+
|
|
321
|
+
result.elapsed = time.perf_counter() - start
|
|
322
|
+
return result
|
|
323
|
+
|
|
324
|
+
def shutdown(self) -> None:
|
|
325
|
+
"""Terminate the process pool."""
|
|
326
|
+
if self._pool_mp:
|
|
327
|
+
self._pool_mp.terminate()
|
|
328
|
+
self._pool_mp.join()
|
|
329
|
+
self._pool_mp = None
|
|
330
|
+
|
|
331
|
+
def __del__(self) -> None:
|
|
332
|
+
self.shutdown()
|
|
333
|
+
|
|
334
|
+
def __repr__(self) -> str:
|
|
335
|
+
return (
|
|
336
|
+
f"MultiProcessBatchExecutor(workers={self._workers}, "
|
|
337
|
+
f"rust_in_workers={self._use_rust})"
|
|
338
|
+
)
|