zexus 1.7.2 β 1.8.0
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 +26 -3
- package/package.json +1 -1
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/blockchain/accelerator.py +27 -0
- package/src/zexus/blockchain/contract_vm.py +409 -3
- package/src/zexus/blockchain/rust_bridge.py +64 -0
- package/src/zexus/cli/main.py +1 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/vm/fastops.c +1093 -2975
- package/src/zexus/vm/gas_metering.py +2 -2
- package/src/zexus/vm/vm.py +163 -0
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +27 -4
- package/src/zexus.egg-info/SOURCES.txt +17 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://python.org)
|
|
8
8
|
[](https://github.com/Zaidux/zexus-interpreter)
|
|
@@ -67,9 +67,9 @@ Zexus is a next-generation, general-purpose programming language designed for se
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
## π What's New in v1.
|
|
70
|
+
## π What's New in v1.8.0
|
|
71
71
|
|
|
72
|
-
### Latest Features (v1.
|
|
72
|
+
### Latest Features (v1.8.0)
|
|
73
73
|
|
|
74
74
|
β
**FIND Keyword** - Declarative project search that resolves exact module paths with scope filtering and smart suggestions
|
|
75
75
|
β
**LOAD Keyword & Manager** - Provider-aware configuration loader with built-in ENV, JSON, and YAML support plus caching
|
|
@@ -2592,6 +2592,29 @@ See [Ecosystem Strategy](docs/ECOSYSTEM_STRATEGY.md) for detailed roadmap.
|
|
|
2592
2592
|
- Consider using `native` keyword for C/C++ FFI
|
|
2593
2593
|
- Profile with `memory_stats()` to check for leaks
|
|
2594
2594
|
|
|
2595
|
+
#### PyPI upload fails on Termux with: "unsupported platform tag 'linux_aarch64'"
|
|
2596
|
+
- PyPI rejects wheels with generic `linux_*` platform tags (common when building on Termux).
|
|
2597
|
+
- **Fix**: upload an sdist (source distribution) and/or a pure-Python wheel:
|
|
2598
|
+
- `python -m pip install --upgrade build twine`
|
|
2599
|
+
- `python -m build` # creates `dist/*.tar.gz` and (by default) a `py3-none-any.whl`
|
|
2600
|
+
- `python -m twine upload dist/*.tar.gz dist/*-py3-none-any.whl`
|
|
2601
|
+
- `ZEXUS_BUILD_EXTENSIONS=1 python -m build` means: **build the optional native extensions** (Cython/C/C++) *in addition* to the Python code (it does not skip Python). This produces a **platform-specific** wheel.
|
|
2602
|
+
- If you want βeverything compiledβ locally, use:
|
|
2603
|
+
- `ZEXUS_BUILD_EXTENSIONS=1 python -m build`
|
|
2604
|
+
- or `ZEXUS_BUILD_EXTENSIONS=1 python -m pip install .`
|
|
2605
|
+
- For publishing native wheels to PyPI, build them in a manylinux environment (e.g. GitHub Actions + cibuildwheel). Wheels built on Termux are usually **not** PyPI-uploadable.
|
|
2606
|
+
- This repo includes a workflow you can run: `.github/workflows/wheels.yml` (builds manylinux `aarch64` + `x86_64`, plus macOS/Windows wheels).
|
|
2607
|
+
- To publish from GitHub Actions without API tokens, use **PyPI Trusted Publishing**:
|
|
2608
|
+
- PyPI β your project β **Settings** β **Publishing** β **Trusted publishers** β **Add a new trusted publisher**
|
|
2609
|
+
- Provider: **GitHub Actions**
|
|
2610
|
+
- Owner: `Zaidux` (or your GitHub org/user)
|
|
2611
|
+
- Repository: `zexus-interpreter`
|
|
2612
|
+
- Workflow: `.github/workflows/wheels.yml`
|
|
2613
|
+
- Environment: `pypi` (and optionally also add another trusted publisher entry for `testpypi`)
|
|
2614
|
+
- Then either:
|
|
2615
|
+
- Run **Actions β βBuild wheels (cibuildwheel)β β Run workflow** and choose `testpypi` first, or
|
|
2616
|
+
- Push a release tag like `v1.7.3` to auto-build + publish
|
|
2617
|
+
|
|
2595
2618
|
#### Blockchain/Contract issues
|
|
2596
2619
|
- Remember `TX` is a global context object (uppercase)
|
|
2597
2620
|
- Use `persistent storage` for contract state
|
package/package.json
CHANGED
package/src/zexus/__init__.py
CHANGED
|
@@ -1008,6 +1008,7 @@ class ExecutionAccelerator:
|
|
|
1008
1008
|
"""Execute a batch of transactions with acceleration.
|
|
1009
1009
|
|
|
1010
1010
|
Execution priority:
|
|
1011
|
+
0. **GIL-free native** β pure-Rust Rayon parallel (Phase 5, requires .zxc bytecode)
|
|
1011
1012
|
1. **Multiprocess** β separate OS processes (true GIL-free parallelism)
|
|
1012
1013
|
2. **Rust batched-GIL** β Rayon parallel groups, one GIL per group
|
|
1013
1014
|
3. **Python ThreadPool** β fallback when neither is available
|
|
@@ -1015,6 +1016,32 @@ class ExecutionAccelerator:
|
|
|
1015
1016
|
Sustains 1,800+ TPS with Rust alone, 10,000+ TPS with
|
|
1016
1017
|
multiprocess + Rust stacked.
|
|
1017
1018
|
"""
|
|
1019
|
+
# ββ Priority 0: GIL-free native Rust execution (Phase 5) ββ
|
|
1020
|
+
# If transactions carry pre-compiled .zxc bytecode, execute
|
|
1021
|
+
# entirely in Rust with zero GIL acquisitions.
|
|
1022
|
+
if self.rust_bridge and self.rust_bridge.is_native:
|
|
1023
|
+
native_txs = [tx for tx in transactions if "bytecode" in tx]
|
|
1024
|
+
if native_txs:
|
|
1025
|
+
try:
|
|
1026
|
+
raw = self.rust_bridge.execute_batch_native(native_txs)
|
|
1027
|
+
if raw is not None:
|
|
1028
|
+
result = TxBatchResult(total=raw["total"])
|
|
1029
|
+
result.succeeded = raw["succeeded"]
|
|
1030
|
+
result.failed = raw["failed"]
|
|
1031
|
+
result.gas_used = raw["gas_used"]
|
|
1032
|
+
result.elapsed = raw["elapsed_secs"]
|
|
1033
|
+
import json as _json
|
|
1034
|
+
result.receipts = [
|
|
1035
|
+
_json.loads(r) if isinstance(r, str) else r
|
|
1036
|
+
for r in raw.get("receipts", [])
|
|
1037
|
+
]
|
|
1038
|
+
self._total_calls += raw["total"]
|
|
1039
|
+
self._accelerated_calls += raw["total"]
|
|
1040
|
+
self._total_time += raw["elapsed_secs"]
|
|
1041
|
+
return result
|
|
1042
|
+
except Exception as exc:
|
|
1043
|
+
logger.warning("GIL-free native batch failed, falling back: %s", exc)
|
|
1044
|
+
|
|
1018
1045
|
# ββ Priority 1: Multiprocess executor βββββββββββββββββββββ
|
|
1019
1046
|
if self.mp_executor:
|
|
1020
1047
|
try:
|
|
@@ -58,6 +58,22 @@ except ImportError:
|
|
|
58
58
|
GasCost = None # type: ignore
|
|
59
59
|
OutOfGasError = None # type: ignore
|
|
60
60
|
|
|
61
|
+
# Rust VM (Phase 3 β adaptive contract execution)
|
|
62
|
+
try:
|
|
63
|
+
from zexus_core import RustVMExecutor as _RustVMExecutor
|
|
64
|
+
_RUST_VM_AVAILABLE = True
|
|
65
|
+
except Exception:
|
|
66
|
+
_RUST_VM_AVAILABLE = False
|
|
67
|
+
_RustVMExecutor = None # type: ignore
|
|
68
|
+
|
|
69
|
+
# Rust ContractVM orchestrator (Phase 4)
|
|
70
|
+
try:
|
|
71
|
+
from zexus_core import RustContractVM as _RustContractVM
|
|
72
|
+
_RUST_CONTRACT_VM_AVAILABLE = True
|
|
73
|
+
except Exception:
|
|
74
|
+
_RUST_CONTRACT_VM_AVAILABLE = False
|
|
75
|
+
_RustContractVM = None # type: ignore
|
|
76
|
+
|
|
61
77
|
# SmartContract from security module
|
|
62
78
|
try:
|
|
63
79
|
from ..security import SmartContract
|
|
@@ -196,8 +212,31 @@ class ContractVM:
|
|
|
196
212
|
"bytecode_executions": 0,
|
|
197
213
|
"bytecode_fallbacks": 0,
|
|
198
214
|
"treewalk_executions": 0,
|
|
215
|
+
"rust_executions": 0,
|
|
216
|
+
"rust_fallbacks": 0,
|
|
199
217
|
}
|
|
200
218
|
|
|
219
|
+
# Rust VM executor (Phase 3 + Phase 6 β Rust-first execution)
|
|
220
|
+
self._rust_vm_executor = _RustVMExecutor() if _RUST_VM_AVAILABLE else None
|
|
221
|
+
self._rust_vm_threshold = 0 # Phase 6: route ALL contracts through Rust by default
|
|
222
|
+
try:
|
|
223
|
+
_env_thresh = os.environ.get("ZEXUS_RUST_VM_THRESHOLD")
|
|
224
|
+
if _env_thresh is not None:
|
|
225
|
+
self._rust_vm_threshold = int(_env_thresh)
|
|
226
|
+
except (ValueError, TypeError):
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
# Rust ContractVM orchestrator (Phase 4)
|
|
230
|
+
self._rust_contract_vm = (
|
|
231
|
+
_RustContractVM(
|
|
232
|
+
gas_discount=0.6,
|
|
233
|
+
default_gas_limit=gas_limit,
|
|
234
|
+
max_call_depth=self._max_call_depth,
|
|
235
|
+
)
|
|
236
|
+
if _RUST_CONTRACT_VM_AVAILABLE
|
|
237
|
+
else None
|
|
238
|
+
)
|
|
239
|
+
|
|
201
240
|
# ------------------------------------------------------------------
|
|
202
241
|
# Contract lifecycle
|
|
203
242
|
# ------------------------------------------------------------------
|
|
@@ -334,7 +373,20 @@ class ContractVM:
|
|
|
334
373
|
env = self._build_env(state_adapter, tx_ctx, contract, args or {})
|
|
335
374
|
builtins = self._build_builtins(tx_ctx, contract_address, logs)
|
|
336
375
|
|
|
337
|
-
# 4
|
|
376
|
+
# ββ Phase 4: Rust ContractVM orchestration ββ
|
|
377
|
+
# Try to handle the entire execution lifecycle in Rust.
|
|
378
|
+
# If Rust signals needs_fallback we fall through to Python.
|
|
379
|
+
if (self._rust_contract_vm is not None
|
|
380
|
+
and self._use_bytecode_vm
|
|
381
|
+
and hasattr(action_obj, 'body')):
|
|
382
|
+
rust_receipt = self._try_rust_contract_vm(
|
|
383
|
+
contract_address, action_obj, state_adapter,
|
|
384
|
+
snapshot, env, args or {}, gas_limit, caller, logs,
|
|
385
|
+
)
|
|
386
|
+
if rust_receipt is not None:
|
|
387
|
+
return rust_receipt
|
|
388
|
+
|
|
389
|
+
# 4. Execute (Python path β Phase 3/0/tree-walk)
|
|
338
390
|
try:
|
|
339
391
|
vm = ZexusVM(
|
|
340
392
|
env=env,
|
|
@@ -717,6 +769,16 @@ class ContractVM:
|
|
|
717
769
|
except Exception:
|
|
718
770
|
pass
|
|
719
771
|
|
|
772
|
+
# --- Phase 3: Rust VM execution for large contracts ---
|
|
773
|
+
if (self._rust_vm_executor is not None
|
|
774
|
+
and self._use_bytecode_vm
|
|
775
|
+
and hasattr(action_obj, 'body')):
|
|
776
|
+
rust_result = self._try_rust_vm_execution(
|
|
777
|
+
action_obj, env, args, vm, cached_bc
|
|
778
|
+
)
|
|
779
|
+
if rust_result is not None:
|
|
780
|
+
used_bytecode, result = rust_result
|
|
781
|
+
|
|
720
782
|
# --- Phase 0: bytecoded execution with fallback ---
|
|
721
783
|
if self._use_bytecode_vm and hasattr(action_obj, 'body'):
|
|
722
784
|
try:
|
|
@@ -811,9 +873,342 @@ class ContractVM:
|
|
|
811
873
|
|
|
812
874
|
return result
|
|
813
875
|
|
|
876
|
+
def _try_rust_vm_execution(
|
|
877
|
+
self,
|
|
878
|
+
action_obj: Any,
|
|
879
|
+
env: Dict[str, Any],
|
|
880
|
+
args: Dict[str, Any],
|
|
881
|
+
vm: "ZexusVM",
|
|
882
|
+
cached_bc: Any,
|
|
883
|
+
) -> Optional[Tuple[bool, Any]]:
|
|
884
|
+
"""Attempt to run an action through the Rust VM.
|
|
885
|
+
|
|
886
|
+
Returns ``(True, result)`` on success, ``None`` if the Rust VM
|
|
887
|
+
is unavailable or signals a fallback. The caller should fall
|
|
888
|
+
through to Phase 0 / tree-walk when ``None`` is returned.
|
|
889
|
+
"""
|
|
890
|
+
try:
|
|
891
|
+
from ..vm.binary_bytecode import serialize as _serialize_zxc
|
|
892
|
+
|
|
893
|
+
# We need serializable bytecode β either from the .zxc cache
|
|
894
|
+
# or by compiling now.
|
|
895
|
+
zxc_data = None
|
|
896
|
+
bc = cached_bc
|
|
897
|
+
if bc is None:
|
|
898
|
+
# Try to compile to bytecode so we can check instruction count
|
|
899
|
+
from ..evaluator.core import Evaluator
|
|
900
|
+
evaluator = Evaluator(use_vm=True)
|
|
901
|
+
try:
|
|
902
|
+
bc = evaluator.compile_to_bytecode(action_obj.body)
|
|
903
|
+
except Exception:
|
|
904
|
+
return None
|
|
905
|
+
|
|
906
|
+
# Check threshold
|
|
907
|
+
instr_count = len(getattr(bc, "instructions", []))
|
|
908
|
+
if instr_count < self._rust_vm_threshold:
|
|
909
|
+
return None # Too small β let Python handle it
|
|
910
|
+
|
|
911
|
+
# Serialize
|
|
912
|
+
zxc_data = _serialize_zxc(bc, include_checksum=True)
|
|
913
|
+
|
|
914
|
+
# Build state dict from ContractStateAdapter
|
|
915
|
+
state_adapter = env.get("_blockchain_state")
|
|
916
|
+
rust_state = {}
|
|
917
|
+
if state_adapter and isinstance(state_adapter, dict):
|
|
918
|
+
for k, v in state_adapter.items():
|
|
919
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
920
|
+
rust_state[k] = v
|
|
921
|
+
|
|
922
|
+
# Build env dict (simple values only)
|
|
923
|
+
from ..object import (
|
|
924
|
+
Integer as ZInteger, Float as ZFloat,
|
|
925
|
+
Boolean as ZBoolean, String as ZString, Null as ZNull,
|
|
926
|
+
)
|
|
927
|
+
rust_env = {}
|
|
928
|
+
for k, v in env.items():
|
|
929
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
930
|
+
rust_env[k] = v
|
|
931
|
+
elif isinstance(v, ZInteger):
|
|
932
|
+
rust_env[k] = v.value
|
|
933
|
+
elif isinstance(v, ZFloat):
|
|
934
|
+
rust_env[k] = v.value
|
|
935
|
+
elif isinstance(v, ZString):
|
|
936
|
+
rust_env[k] = v.value
|
|
937
|
+
elif isinstance(v, ZBoolean):
|
|
938
|
+
rust_env[k] = v.value
|
|
939
|
+
elif isinstance(v, ZNull):
|
|
940
|
+
rust_env[k] = None
|
|
941
|
+
|
|
942
|
+
# Add action parameters
|
|
943
|
+
if hasattr(action_obj, 'parameters') and action_obj.parameters:
|
|
944
|
+
for param in action_obj.parameters:
|
|
945
|
+
param_name = param.value if hasattr(param, 'value') else str(param)
|
|
946
|
+
if param_name in args:
|
|
947
|
+
v = args[param_name]
|
|
948
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
949
|
+
rust_env[param_name] = v
|
|
950
|
+
|
|
951
|
+
# Gas limit
|
|
952
|
+
gas_limit = 0
|
|
953
|
+
if vm.gas_metering:
|
|
954
|
+
remaining_fn = getattr(vm.gas_metering, "remaining", None)
|
|
955
|
+
if callable(remaining_fn):
|
|
956
|
+
try:
|
|
957
|
+
rem = remaining_fn()
|
|
958
|
+
if isinstance(rem, (int, float)) and rem > 0:
|
|
959
|
+
gas_limit = int(rem)
|
|
960
|
+
except Exception:
|
|
961
|
+
pass
|
|
962
|
+
if gas_limit == 0:
|
|
963
|
+
gl = getattr(vm.gas_metering, "gas_limit", 0) or 0
|
|
964
|
+
gu = getattr(vm.gas_metering, "gas_used", 0) or 0
|
|
965
|
+
if isinstance(gl, (int, float)) and isinstance(gu, (int, float)):
|
|
966
|
+
if gl > gu:
|
|
967
|
+
gas_limit = int(gl - gu)
|
|
968
|
+
|
|
969
|
+
# Execute
|
|
970
|
+
result_dict = self._rust_vm_executor.execute(
|
|
971
|
+
zxc_data,
|
|
972
|
+
env=rust_env or None,
|
|
973
|
+
state=rust_state or None,
|
|
974
|
+
gas_limit=gas_limit,
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
# Fallback?
|
|
978
|
+
if result_dict.get("needs_fallback", False):
|
|
979
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
980
|
+
return None
|
|
981
|
+
|
|
982
|
+
# Error?
|
|
983
|
+
error = result_dict.get("error")
|
|
984
|
+
if error:
|
|
985
|
+
if "OutOfGas" in str(error):
|
|
986
|
+
raise RuntimeError(str(error))
|
|
987
|
+
if "RequireFailed" in str(error):
|
|
988
|
+
raise RuntimeError(str(error))
|
|
989
|
+
# Other errors β fall back
|
|
990
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
991
|
+
return None
|
|
992
|
+
|
|
993
|
+
# Success β bridge gas back
|
|
994
|
+
if vm.gas_metering:
|
|
995
|
+
rust_gas = result_dict.get("gas_used", 0)
|
|
996
|
+
if rust_gas > 0:
|
|
997
|
+
current_used = getattr(vm.gas_metering, "gas_used", None)
|
|
998
|
+
if current_used is not None:
|
|
999
|
+
vm.gas_metering.gas_used = current_used + rust_gas
|
|
1000
|
+
add_fn = getattr(vm.gas_metering, "add_gas", None)
|
|
1001
|
+
if add_fn:
|
|
1002
|
+
add_fn(rust_gas)
|
|
1003
|
+
|
|
1004
|
+
# Merge state back into ContractStateAdapter
|
|
1005
|
+
rust_state_out = result_dict.get("state", {})
|
|
1006
|
+
if rust_state_out and state_adapter is not None:
|
|
1007
|
+
for k, v in rust_state_out.items():
|
|
1008
|
+
state_adapter[k] = v
|
|
1009
|
+
|
|
1010
|
+
self._vm_stats["rust_executions"] += 1
|
|
1011
|
+
if self._debug:
|
|
1012
|
+
logger.debug(
|
|
1013
|
+
"Rust VM execution: ops=%d gas=%d",
|
|
1014
|
+
result_dict.get("instructions_executed", 0),
|
|
1015
|
+
result_dict.get("gas_used", 0),
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
return (True, result_dict.get("result"))
|
|
1019
|
+
|
|
1020
|
+
except Exception as e:
|
|
1021
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
1022
|
+
logger.debug("Rust VM execution failed, falling back: %s", e)
|
|
1023
|
+
return None
|
|
1024
|
+
|
|
1025
|
+
def _try_rust_contract_vm(
|
|
1026
|
+
self,
|
|
1027
|
+
contract_address: str,
|
|
1028
|
+
action_obj: Any,
|
|
1029
|
+
state_adapter: ContractStateAdapter,
|
|
1030
|
+
snapshot: Dict[str, Any],
|
|
1031
|
+
env: Dict[str, Any],
|
|
1032
|
+
args: Dict[str, Any],
|
|
1033
|
+
gas_limit: int,
|
|
1034
|
+
caller: str,
|
|
1035
|
+
logs: List[Dict[str, Any]],
|
|
1036
|
+
) -> Optional[ContractExecutionReceipt]:
|
|
1037
|
+
"""Phase 4: Attempt full contract execution via Rust ContractVM.
|
|
1038
|
+
|
|
1039
|
+
Returns a ``ContractExecutionReceipt`` on success, or ``None``
|
|
1040
|
+
if Rust can't handle it (falls back to Python).
|
|
1041
|
+
"""
|
|
1042
|
+
try:
|
|
1043
|
+
from ..vm.binary_bytecode import serialize as _serialize_zxc
|
|
1044
|
+
|
|
1045
|
+
# Compile to bytecode
|
|
1046
|
+
bc = getattr(action_obj, '_cached_bytecode', None)
|
|
1047
|
+
if bc is None:
|
|
1048
|
+
from ..evaluator.core import Evaluator
|
|
1049
|
+
evaluator = Evaluator(use_vm=True)
|
|
1050
|
+
try:
|
|
1051
|
+
bc = evaluator.compile_to_bytecode(action_obj.body)
|
|
1052
|
+
except Exception:
|
|
1053
|
+
return None # Can't compile β fall back
|
|
1054
|
+
|
|
1055
|
+
# Serialize to .zxc
|
|
1056
|
+
zxc_data = _serialize_zxc(bc, include_checksum=True)
|
|
1057
|
+
|
|
1058
|
+
# Build state dict (simple values)
|
|
1059
|
+
rust_state = {}
|
|
1060
|
+
for k, v in state_adapter.items():
|
|
1061
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
1062
|
+
rust_state[k] = v
|
|
1063
|
+
|
|
1064
|
+
# Build env dict (simple values)
|
|
1065
|
+
from ..object import (
|
|
1066
|
+
Integer as ZInteger, Float as ZFloat,
|
|
1067
|
+
Boolean as ZBoolean, String as ZString, Null as ZNull,
|
|
1068
|
+
)
|
|
1069
|
+
rust_env = {}
|
|
1070
|
+
for k, v in env.items():
|
|
1071
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
1072
|
+
rust_env[k] = v
|
|
1073
|
+
elif isinstance(v, ZInteger):
|
|
1074
|
+
rust_env[k] = v.value
|
|
1075
|
+
elif isinstance(v, ZFloat):
|
|
1076
|
+
rust_env[k] = v.value
|
|
1077
|
+
elif isinstance(v, ZString):
|
|
1078
|
+
rust_env[k] = v.value
|
|
1079
|
+
elif isinstance(v, ZBoolean):
|
|
1080
|
+
rust_env[k] = v.value
|
|
1081
|
+
elif isinstance(v, ZNull):
|
|
1082
|
+
rust_env[k] = None
|
|
1083
|
+
|
|
1084
|
+
# Phase 6: Inject chain info for Rust builtins
|
|
1085
|
+
if "_block_number" not in rust_env:
|
|
1086
|
+
try:
|
|
1087
|
+
rust_env["_block_number"] = self._chain.height
|
|
1088
|
+
except Exception:
|
|
1089
|
+
rust_env["_block_number"] = 0
|
|
1090
|
+
if "_block_timestamp" not in rust_env:
|
|
1091
|
+
try:
|
|
1092
|
+
tip = self._chain.tip
|
|
1093
|
+
rust_env["_block_timestamp"] = (
|
|
1094
|
+
tip.header.timestamp if tip else 0.0
|
|
1095
|
+
)
|
|
1096
|
+
except Exception:
|
|
1097
|
+
rust_env["_block_timestamp"] = 0.0
|
|
1098
|
+
|
|
1099
|
+
# Build args dict (simple values)
|
|
1100
|
+
rust_args = {}
|
|
1101
|
+
for k, v in args.items():
|
|
1102
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
1103
|
+
rust_args[k] = v
|
|
1104
|
+
|
|
1105
|
+
# Execute via Rust ContractVM
|
|
1106
|
+
result_dict = self._rust_contract_vm.execute_contract(
|
|
1107
|
+
contract_address=contract_address,
|
|
1108
|
+
action_bytecode=zxc_data,
|
|
1109
|
+
state=rust_state or None,
|
|
1110
|
+
env=rust_env or None,
|
|
1111
|
+
args=rust_args or None,
|
|
1112
|
+
gas_limit=gas_limit,
|
|
1113
|
+
caller=caller,
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
# Check for fallback
|
|
1117
|
+
if result_dict.get("needs_fallback", False):
|
|
1118
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
1119
|
+
if self._debug:
|
|
1120
|
+
logger.debug("Rust ContractVM needs fallback: %s",
|
|
1121
|
+
result_dict.get("error", ""))
|
|
1122
|
+
return None
|
|
1123
|
+
|
|
1124
|
+
# Build receipt
|
|
1125
|
+
success = result_dict.get("success", False)
|
|
1126
|
+
gas_used = result_dict.get("gas_used", 0)
|
|
1127
|
+
|
|
1128
|
+
if success:
|
|
1129
|
+
# Merge new state back to ContractStateAdapter
|
|
1130
|
+
new_state = result_dict.get("new_state", {})
|
|
1131
|
+
if new_state:
|
|
1132
|
+
state_adapter.clear()
|
|
1133
|
+
state_adapter.update(new_state)
|
|
1134
|
+
state_adapter.commit()
|
|
1135
|
+
|
|
1136
|
+
# Phase 6: Collect events emitted by Rust builtins
|
|
1137
|
+
rust_events = result_dict.get("events", [])
|
|
1138
|
+
import time as _time
|
|
1139
|
+
for ev in rust_events:
|
|
1140
|
+
ev_name = ev.get("event", "") if isinstance(ev, dict) else str(ev)
|
|
1141
|
+
ev_data = ev.get("data", None) if isinstance(ev, dict) else None
|
|
1142
|
+
logs.append({
|
|
1143
|
+
"event": ev_name,
|
|
1144
|
+
"data": ev_data,
|
|
1145
|
+
"timestamp": _time.time(),
|
|
1146
|
+
"contract": contract_address,
|
|
1147
|
+
})
|
|
1148
|
+
|
|
1149
|
+
self._vm_stats["rust_executions"] += 1
|
|
1150
|
+
|
|
1151
|
+
return ContractExecutionReceipt(
|
|
1152
|
+
success=True,
|
|
1153
|
+
return_value=result_dict.get("result"),
|
|
1154
|
+
gas_used=gas_used,
|
|
1155
|
+
gas_limit=gas_limit,
|
|
1156
|
+
logs=list(logs),
|
|
1157
|
+
state_changes=result_dict.get("state_changes", {}),
|
|
1158
|
+
)
|
|
1159
|
+
else:
|
|
1160
|
+
# Error β rollback
|
|
1161
|
+
state_adapter.rollback(snapshot)
|
|
1162
|
+
error = result_dict.get("error", "UnknownError")
|
|
1163
|
+
|
|
1164
|
+
if error == "OutOfGas":
|
|
1165
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
1166
|
+
return ContractExecutionReceipt(
|
|
1167
|
+
success=False,
|
|
1168
|
+
gas_used=gas_limit,
|
|
1169
|
+
gas_limit=gas_limit,
|
|
1170
|
+
error="OutOfGas",
|
|
1171
|
+
revert_reason=result_dict.get("revert_reason", ""),
|
|
1172
|
+
)
|
|
1173
|
+
elif error == "ReentrancyGuard":
|
|
1174
|
+
return ContractExecutionReceipt(
|
|
1175
|
+
success=False,
|
|
1176
|
+
error="ReentrancyGuard",
|
|
1177
|
+
revert_reason=result_dict.get("revert_reason", ""),
|
|
1178
|
+
gas_limit=gas_limit,
|
|
1179
|
+
)
|
|
1180
|
+
else:
|
|
1181
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
1182
|
+
return ContractExecutionReceipt(
|
|
1183
|
+
success=False,
|
|
1184
|
+
gas_used=gas_used,
|
|
1185
|
+
gas_limit=gas_limit,
|
|
1186
|
+
error=error,
|
|
1187
|
+
revert_reason=result_dict.get("revert_reason", ""),
|
|
1188
|
+
logs=list(logs),
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
except Exception as e:
|
|
1192
|
+
self._vm_stats["rust_fallbacks"] += 1
|
|
1193
|
+
logger.debug("Rust ContractVM failed, falling back: %s", e)
|
|
1194
|
+
return None
|
|
1195
|
+
|
|
814
1196
|
def get_vm_execution_stats(self) -> Dict[str, Any]:
|
|
815
|
-
"""Return Phase 0 execution statistics."""
|
|
816
|
-
total =
|
|
1197
|
+
"""Return Phase 0-3 execution statistics."""
|
|
1198
|
+
total = (
|
|
1199
|
+
self._vm_stats["bytecode_executions"]
|
|
1200
|
+
+ self._vm_stats["bytecode_fallbacks"]
|
|
1201
|
+
+ self._vm_stats["treewalk_executions"]
|
|
1202
|
+
+ self._vm_stats["rust_executions"]
|
|
1203
|
+
)
|
|
1204
|
+
# Phase 4 stats from Rust ContractVM
|
|
1205
|
+
rust_cvm_stats = {}
|
|
1206
|
+
if self._rust_contract_vm is not None:
|
|
1207
|
+
try:
|
|
1208
|
+
rust_cvm_stats = self._rust_contract_vm.get_stats()
|
|
1209
|
+
except Exception:
|
|
1210
|
+
pass
|
|
1211
|
+
|
|
817
1212
|
return {
|
|
818
1213
|
**self._vm_stats,
|
|
819
1214
|
"total_executions": total,
|
|
@@ -821,7 +1216,15 @@ class ContractVM:
|
|
|
821
1216
|
self._vm_stats["bytecode_executions"] / total * 100
|
|
822
1217
|
if total > 0 else 0.0
|
|
823
1218
|
),
|
|
1219
|
+
"rust_rate": (
|
|
1220
|
+
self._vm_stats["rust_executions"] / total * 100
|
|
1221
|
+
if total > 0 else 0.0
|
|
1222
|
+
),
|
|
824
1223
|
"use_bytecode_vm": self._use_bytecode_vm,
|
|
1224
|
+
"rust_vm_available": self._rust_vm_executor is not None,
|
|
1225
|
+
"rust_vm_threshold": self._rust_vm_threshold,
|
|
1226
|
+
"rust_contract_vm_available": self._rust_contract_vm is not None,
|
|
1227
|
+
"rust_contract_vm_stats": rust_cvm_stats,
|
|
825
1228
|
}
|
|
826
1229
|
|
|
827
1230
|
# ------------------------------------------------------------------
|
|
@@ -940,6 +1343,9 @@ class ContractVM:
|
|
|
940
1343
|
gas_limit=gas_limit,
|
|
941
1344
|
debug=self._debug,
|
|
942
1345
|
)
|
|
1346
|
+
# Static calls use light gas metering (flat 1/op) since
|
|
1347
|
+
# they don't consume chain resources β read-only.
|
|
1348
|
+
vm.enable_gas_light = True
|
|
943
1349
|
result = self._execute_action(vm, action_obj, env, args or {})
|
|
944
1350
|
gas_used = vm.gas_metering.gas_used if vm.gas_metering else 0
|
|
945
1351
|
|
|
@@ -327,6 +327,48 @@ def _execute_batch_py(
|
|
|
327
327
|
return result
|
|
328
328
|
|
|
329
329
|
|
|
330
|
+
# ββ Phase 5: GIL-free Native Batch Execution ββββββββββββββββββββββββββ
|
|
331
|
+
|
|
332
|
+
def execute_batch_native(
|
|
333
|
+
transactions: List[Dict[str, Any]],
|
|
334
|
+
max_workers: int = 0,
|
|
335
|
+
gas_discount: float = 0.6,
|
|
336
|
+
default_gas_limit: int = 10_000_000,
|
|
337
|
+
) -> Optional[Dict[str, Any]]:
|
|
338
|
+
"""Execute a batch of pre-compiled .zxc transactions entirely in Rust.
|
|
339
|
+
|
|
340
|
+
**Zero GIL acquisitions during execution.** Requires Rust core.
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
transactions : list of dict
|
|
345
|
+
Each dict must contain ``bytecode`` (bytes), ``contract_address`` (str),
|
|
346
|
+
``caller`` (str). Optional: ``gas_limit``, ``state``, ``gas_discount``.
|
|
347
|
+
max_workers : int
|
|
348
|
+
Rayon thread count (0 = auto-detect CPU cores).
|
|
349
|
+
gas_discount : float
|
|
350
|
+
Default gas discount for Rust execution (0.6 = 40% cheaper).
|
|
351
|
+
default_gas_limit : int
|
|
352
|
+
Default gas limit if not specified per-transaction.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
dict or None
|
|
357
|
+
Dict with keys: total, succeeded, failed, gas_used, gas_saved,
|
|
358
|
+
throughput, receipts, state_changes, mode='native_gil_free'.
|
|
359
|
+
Returns None if Rust core is not available.
|
|
360
|
+
"""
|
|
361
|
+
if not _RUST_AVAILABLE:
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
executor = _zexus_core.RustBatchExecutor(
|
|
365
|
+
max_workers=max_workers,
|
|
366
|
+
gas_discount=gas_discount,
|
|
367
|
+
default_gas_limit=default_gas_limit,
|
|
368
|
+
)
|
|
369
|
+
return executor.execute_batch_native(transactions)
|
|
370
|
+
|
|
371
|
+
|
|
330
372
|
# ββ Block Validation βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
331
373
|
|
|
332
374
|
def validate_chain_headers(
|
|
@@ -416,6 +458,28 @@ class RustCoreBridge:
|
|
|
416
458
|
transactions, vm_callback, max_workers=self._max_workers
|
|
417
459
|
)
|
|
418
460
|
|
|
461
|
+
def execute_batch_native(
|
|
462
|
+
self,
|
|
463
|
+
transactions: List[Dict[str, Any]],
|
|
464
|
+
) -> Optional[Dict[str, Any]]:
|
|
465
|
+
"""Execute a batch of pre-compiled .zxc transactions β zero GIL.
|
|
466
|
+
|
|
467
|
+
Each transaction dict must contain:
|
|
468
|
+
- ``bytecode``: bytes β .zxc serialized bytecode
|
|
469
|
+
- ``contract_address``: str
|
|
470
|
+
- ``caller``: str
|
|
471
|
+
- ``gas_limit``: int (optional)
|
|
472
|
+
- ``state``: dict (optional)
|
|
473
|
+
- ``gas_discount``: float (optional)
|
|
474
|
+
|
|
475
|
+
Returns a dict with keys: total, succeeded, failed, gas_used,
|
|
476
|
+
gas_saved, throughput, receipts, state_changes, mode='native_gil_free'.
|
|
477
|
+
Returns None if Rust is not available.
|
|
478
|
+
"""
|
|
479
|
+
return execute_batch_native(
|
|
480
|
+
transactions, max_workers=self._max_workers
|
|
481
|
+
)
|
|
482
|
+
|
|
419
483
|
# Validation
|
|
420
484
|
validate_chain = staticmethod(validate_chain_headers)
|
|
421
485
|
check_pow = staticmethod(check_pow_difficulty)
|
package/src/zexus/cli/main.py
CHANGED
|
@@ -156,7 +156,7 @@ def show_all_commands():
|
|
|
156
156
|
console.print("\n[bold green]π‘ Tip:[/bold green] Use 'zx <command> --help' for detailed command options\n")
|
|
157
157
|
|
|
158
158
|
@click.group(invoke_without_command=True)
|
|
159
|
-
@click.version_option(version="1.
|
|
159
|
+
@click.version_option(version="1.8.0", prog_name="Zexus")
|
|
160
160
|
@click.option('--syntax-style', type=click.Choice(['universal', 'tolerable', 'auto']),
|
|
161
161
|
default='auto', help='Syntax style to use (universal=strict, tolerable=flexible)')
|
|
162
162
|
@click.option('--advanced-parsing', is_flag=True, default=True,
|