zexus 1.7.2 → 1.8.1
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 +57 -6
- package/package.json +2 -1
- package/rust_core/Cargo.lock +603 -0
- package/rust_core/Cargo.toml +26 -0
- package/rust_core/README.md +15 -0
- package/rust_core/pyproject.toml +25 -0
- package/rust_core/src/binary_bytecode.rs +543 -0
- package/rust_core/src/contract_vm.rs +643 -0
- package/rust_core/src/executor.rs +847 -0
- package/rust_core/src/hasher.rs +90 -0
- package/rust_core/src/lib.rs +71 -0
- package/rust_core/src/merkle.rs +128 -0
- package/rust_core/src/rust_vm.rs +2313 -0
- package/rust_core/src/signature.rs +79 -0
- package/rust_core/src/state_adapter.rs +281 -0
- package/rust_core/src/validator.rs +116 -0
- package/scripts/postinstall.js +34 -2
- 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/evaluator/bytecode_compiler.py +150 -52
- package/src/zexus/evaluator/core.py +151 -809
- package/src/zexus/evaluator/expressions.py +27 -22
- package/src/zexus/evaluator/functions.py +171 -126
- package/src/zexus/evaluator/statements.py +55 -112
- package/src/zexus/module_cache.py +20 -9
- package/src/zexus/object.py +330 -38
- package/src/zexus/parser/parser.py +69 -14
- package/src/zexus/parser/strategy_context.py +228 -5
- package/src/zexus/parser/strategy_structural.py +2 -2
- package/src/zexus/persistence.py +46 -17
- package/src/zexus/security.py +140 -234
- package/src/zexus/type_checker.py +44 -5
- package/src/zexus/vm/binary_bytecode.py +7 -3
- package/src/zexus/vm/bytecode.py +6 -0
- package/src/zexus/vm/cache.py +24 -46
- package/src/zexus/vm/compiler.py +80 -20
- package/src/zexus/vm/fastops.c +1093 -2975
- package/src/zexus/vm/gas_metering.py +2 -2
- package/src/zexus/vm/memory_pool.py +21 -9
- package/src/zexus/vm/vm.py +527 -67
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +79 -12
- package/src/zexus.egg-info/SOURCES.txt +23 -1
- package/src/zexus.egg-info/requires.txt +26 -0
- package/src/zexus.egg-info/entry_points.txt +0 -4
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
2
|
+
// ECDSA-secp256k1 Signature Verification (batch-parallel)
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
use k256::ecdsa::{
|
|
6
|
+
signature::Verifier, Signature, VerifyingKey,
|
|
7
|
+
};
|
|
8
|
+
use pyo3::prelude::*;
|
|
9
|
+
use rayon::prelude::*;
|
|
10
|
+
use sha2::{Digest, Sha256};
|
|
11
|
+
|
|
12
|
+
#[pyclass]
|
|
13
|
+
pub struct RustSignature;
|
|
14
|
+
|
|
15
|
+
#[pymethods]
|
|
16
|
+
impl RustSignature {
|
|
17
|
+
#[new]
|
|
18
|
+
fn new() -> Self {
|
|
19
|
+
RustSignature
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Verify a single ECDSA-secp256k1 signature.
|
|
23
|
+
///
|
|
24
|
+
/// * `message` — the raw message bytes that were signed
|
|
25
|
+
/// * `signature` — 64-byte DER or compact signature
|
|
26
|
+
/// * `public_key`— 33-byte (compressed) or 65-byte (uncompressed) public key
|
|
27
|
+
///
|
|
28
|
+
/// Returns `true` if the signature is valid.
|
|
29
|
+
#[staticmethod]
|
|
30
|
+
fn verify(message: Vec<u8>, signature: Vec<u8>, public_key: Vec<u8>) -> bool {
|
|
31
|
+
Self::_verify_inner(&message, &signature, &public_key)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Batch-verify multiple signatures in parallel.
|
|
35
|
+
///
|
|
36
|
+
/// Each item is a tuple `(message_bytes, signature_bytes, pubkey_bytes)`.
|
|
37
|
+
/// Returns a list of booleans (one per signature).
|
|
38
|
+
#[staticmethod]
|
|
39
|
+
fn verify_batch(
|
|
40
|
+
py: Python<'_>,
|
|
41
|
+
items: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>,
|
|
42
|
+
) -> Vec<bool> {
|
|
43
|
+
py.allow_threads(|| {
|
|
44
|
+
items
|
|
45
|
+
.par_iter()
|
|
46
|
+
.map(|(msg, sig, pk)| Self::_verify_inner(msg, sig, pk))
|
|
47
|
+
.collect()
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Hash a message with SHA-256 then verify the signature against the
|
|
52
|
+
/// hash. This matches the common blockchain pattern of sign(sha256(msg)).
|
|
53
|
+
#[staticmethod]
|
|
54
|
+
fn verify_hashed(message: Vec<u8>, signature: Vec<u8>, public_key: Vec<u8>) -> bool {
|
|
55
|
+
let hash = Sha256::digest(&message);
|
|
56
|
+
Self::_verify_inner(&hash, &signature, &public_key)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl RustSignature {
|
|
61
|
+
fn _verify_inner(message: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
|
|
62
|
+
verify_single(message, signature, public_key)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Public helper so other modules (validator) can call it directly.
|
|
67
|
+
pub fn verify_single(message: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
|
|
68
|
+
let vk = match VerifyingKey::from_sec1_bytes(public_key) {
|
|
69
|
+
Ok(vk) => vk,
|
|
70
|
+
Err(_) => return false,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let sig = match Signature::from_slice(signature) {
|
|
74
|
+
Ok(s) => s,
|
|
75
|
+
Err(_) => return false,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
vk.verify(message, &sig).is_ok()
|
|
79
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Zexus Blockchain — Rust State Adapter (Phase 3)
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// In-memory state cache that batches reads/writes in Rust to minimise
|
|
6
|
+
// GIL crossings when the Rust VM operates on blockchain state.
|
|
7
|
+
//
|
|
8
|
+
// Architecture:
|
|
9
|
+
//
|
|
10
|
+
// 1. Pre-load state from Python dict → Rust HashMap ("warm cache")
|
|
11
|
+
// 2. STATE_READ/STATE_WRITE hit the Rust cache — zero Python calls
|
|
12
|
+
// 3. After execution, flush dirty keys back to Python in one batch
|
|
13
|
+
// 4. TX_BEGIN/TX_COMMIT/TX_REVERT use Rust-side snapshots
|
|
14
|
+
//
|
|
15
|
+
// This avoids per-opcode GIL acquisition for state operations,
|
|
16
|
+
// delivering ~50x speedup on state-heavy contracts.
|
|
17
|
+
|
|
18
|
+
use pyo3::prelude::*;
|
|
19
|
+
use pyo3::types::{PyDict, PyList};
|
|
20
|
+
use pyo3::ToPyObject;
|
|
21
|
+
use std::collections::{HashMap, HashSet};
|
|
22
|
+
|
|
23
|
+
/// A single value in the state cache.
|
|
24
|
+
#[derive(Debug, Clone)]
|
|
25
|
+
pub enum StateValue {
|
|
26
|
+
Null,
|
|
27
|
+
Bool(bool),
|
|
28
|
+
Int(i64),
|
|
29
|
+
Float(f64),
|
|
30
|
+
Str(String),
|
|
31
|
+
Bytes(Vec<u8>),
|
|
32
|
+
List(Vec<StateValue>),
|
|
33
|
+
Map(Vec<(String, StateValue)>),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl StateValue {
|
|
37
|
+
/// Convert a Python object to a StateValue.
|
|
38
|
+
pub fn from_py(py: Python<'_>, obj: &PyObject) -> Self {
|
|
39
|
+
let bound = obj.bind(py);
|
|
40
|
+
if bound.is_none() {
|
|
41
|
+
return StateValue::Null;
|
|
42
|
+
}
|
|
43
|
+
if let Ok(b) = bound.extract::<bool>() {
|
|
44
|
+
return StateValue::Bool(b);
|
|
45
|
+
}
|
|
46
|
+
if let Ok(i) = bound.extract::<i64>() {
|
|
47
|
+
return StateValue::Int(i);
|
|
48
|
+
}
|
|
49
|
+
if let Ok(f) = bound.extract::<f64>() {
|
|
50
|
+
return StateValue::Float(f);
|
|
51
|
+
}
|
|
52
|
+
if let Ok(s) = bound.extract::<String>() {
|
|
53
|
+
return StateValue::Str(s);
|
|
54
|
+
}
|
|
55
|
+
if let Ok(list) = bound.downcast::<PyList>() {
|
|
56
|
+
let items: Vec<StateValue> = list
|
|
57
|
+
.iter()
|
|
58
|
+
.map(|item| StateValue::from_py(py, &item.to_object(py)))
|
|
59
|
+
.collect();
|
|
60
|
+
return StateValue::List(items);
|
|
61
|
+
}
|
|
62
|
+
if let Ok(dict) = bound.downcast::<PyDict>() {
|
|
63
|
+
let items: Vec<(String, StateValue)> = dict
|
|
64
|
+
.iter()
|
|
65
|
+
.filter_map(|(k, v)| {
|
|
66
|
+
k.extract::<String>()
|
|
67
|
+
.ok()
|
|
68
|
+
.map(|key| (key, StateValue::from_py(py, &v.to_object(py))))
|
|
69
|
+
})
|
|
70
|
+
.collect();
|
|
71
|
+
return StateValue::Map(items);
|
|
72
|
+
}
|
|
73
|
+
// Fallback: try string representation
|
|
74
|
+
if let Ok(s) = bound.str().and_then(|s| s.extract::<String>()) {
|
|
75
|
+
return StateValue::Str(s);
|
|
76
|
+
}
|
|
77
|
+
StateValue::Null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Convert to a Python object.
|
|
81
|
+
pub fn to_py(&self, py: Python<'_>) -> PyObject {
|
|
82
|
+
match self {
|
|
83
|
+
StateValue::Null => py.None(),
|
|
84
|
+
StateValue::Bool(b) => b.to_object(py),
|
|
85
|
+
StateValue::Int(i) => i.to_object(py),
|
|
86
|
+
StateValue::Float(f) => f.to_object(py),
|
|
87
|
+
StateValue::Str(s) => s.to_object(py),
|
|
88
|
+
StateValue::Bytes(b) => {
|
|
89
|
+
pyo3::types::PyBytes::new_bound(py, b).to_object(py)
|
|
90
|
+
}
|
|
91
|
+
StateValue::List(items) => {
|
|
92
|
+
let py_list = PyList::empty_bound(py);
|
|
93
|
+
for item in items {
|
|
94
|
+
let _ = py_list.append(item.to_py(py));
|
|
95
|
+
}
|
|
96
|
+
py_list.to_object(py)
|
|
97
|
+
}
|
|
98
|
+
StateValue::Map(items) => {
|
|
99
|
+
let py_dict = PyDict::new_bound(py);
|
|
100
|
+
for (k, v) in items {
|
|
101
|
+
let _ = py_dict.set_item(k, v.to_py(py));
|
|
102
|
+
}
|
|
103
|
+
py_dict.to_object(py)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Transaction snapshot for nested transaction support.
|
|
110
|
+
struct TxSnapshot {
|
|
111
|
+
cache: HashMap<String, StateValue>,
|
|
112
|
+
dirty: HashSet<String>,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Rust-side state cache with batch flush capability.
|
|
116
|
+
///
|
|
117
|
+
/// Usage from Python:
|
|
118
|
+
/// ```python
|
|
119
|
+
/// from zexus_core import RustStateAdapter
|
|
120
|
+
/// adapter = RustStateAdapter()
|
|
121
|
+
/// adapter.load_from_dict({"key": "value", "counter": 42})
|
|
122
|
+
/// adapter.set("counter", 43)
|
|
123
|
+
/// val = adapter.get("counter")
|
|
124
|
+
/// dirty = adapter.flush_dirty() # returns dict of changed keys
|
|
125
|
+
/// ```
|
|
126
|
+
#[pyclass]
|
|
127
|
+
pub struct RustStateAdapter {
|
|
128
|
+
/// In-memory state cache (key → value).
|
|
129
|
+
cache: HashMap<String, StateValue>,
|
|
130
|
+
|
|
131
|
+
/// Set of keys that have been written (dirty) since last flush.
|
|
132
|
+
dirty: HashSet<String>,
|
|
133
|
+
|
|
134
|
+
/// Transaction snapshot stack for TX_BEGIN/TX_COMMIT/TX_REVERT.
|
|
135
|
+
tx_stack: Vec<TxSnapshot>,
|
|
136
|
+
|
|
137
|
+
/// Total reads served from cache (no Python call).
|
|
138
|
+
cache_hits: u64,
|
|
139
|
+
|
|
140
|
+
/// Total writes batched (no Python call until flush).
|
|
141
|
+
cache_writes: u64,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[pymethods]
|
|
145
|
+
impl RustStateAdapter {
|
|
146
|
+
#[new]
|
|
147
|
+
fn new() -> Self {
|
|
148
|
+
RustStateAdapter {
|
|
149
|
+
cache: HashMap::new(),
|
|
150
|
+
dirty: HashSet::new(),
|
|
151
|
+
tx_stack: Vec::new(),
|
|
152
|
+
cache_hits: 0,
|
|
153
|
+
cache_writes: 0,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Bulk-load state from a Python dict into the Rust cache.
|
|
158
|
+
/// This is the "warm-up" step — called once before Rust VM execution.
|
|
159
|
+
fn load_from_dict(&mut self, py: Python<'_>, data: &Bound<'_, PyDict>) -> PyResult<u64> {
|
|
160
|
+
let mut count: u64 = 0;
|
|
161
|
+
for (k, v) in data.iter() {
|
|
162
|
+
let key = k.extract::<String>()?;
|
|
163
|
+
let val = StateValue::from_py(py, &v.to_object(py));
|
|
164
|
+
self.cache.insert(key, val);
|
|
165
|
+
count += 1;
|
|
166
|
+
}
|
|
167
|
+
Ok(count)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Read a value from the cache.
|
|
171
|
+
fn get(&mut self, py: Python<'_>, key: &str) -> PyObject {
|
|
172
|
+
self.cache_hits += 1;
|
|
173
|
+
match self.cache.get(key) {
|
|
174
|
+
Some(val) => val.to_py(py),
|
|
175
|
+
None => py.None(),
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Write a value to the cache and mark it dirty.
|
|
180
|
+
fn set(&mut self, py: Python<'_>, key: String, value: PyObject) {
|
|
181
|
+
let sv = StateValue::from_py(py, &value);
|
|
182
|
+
self.cache.insert(key.clone(), sv);
|
|
183
|
+
self.dirty.insert(key);
|
|
184
|
+
self.cache_writes += 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Check if a key exists in the cache.
|
|
188
|
+
fn contains(&self, key: &str) -> bool {
|
|
189
|
+
self.cache.contains_key(key)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Delete a key from the cache and mark it dirty (sets to Null).
|
|
193
|
+
fn delete(&mut self, key: &str) {
|
|
194
|
+
self.cache.insert(key.to_string(), StateValue::Null);
|
|
195
|
+
self.dirty.insert(key.to_string());
|
|
196
|
+
self.cache_writes += 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Return all dirty (modified) keys as a Python dict.
|
|
200
|
+
/// Clears the dirty set after flushing.
|
|
201
|
+
fn flush_dirty(&mut self, py: Python<'_>) -> PyResult<PyObject> {
|
|
202
|
+
let result = PyDict::new_bound(py);
|
|
203
|
+
for key in self.dirty.drain() {
|
|
204
|
+
if let Some(val) = self.cache.get(&key) {
|
|
205
|
+
result.set_item(&key, val.to_py(py))?;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
Ok(result.to_object(py))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Return the full cache as a Python dict.
|
|
212
|
+
fn to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
|
|
213
|
+
let result = PyDict::new_bound(py);
|
|
214
|
+
for (k, v) in &self.cache {
|
|
215
|
+
result.set_item(k, v.to_py(py))?;
|
|
216
|
+
}
|
|
217
|
+
Ok(result.to_object(py))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Get the number of keys in the cache.
|
|
221
|
+
fn len(&self) -> usize {
|
|
222
|
+
self.cache.len()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Get the number of dirty keys pending flush.
|
|
226
|
+
fn dirty_count(&self) -> usize {
|
|
227
|
+
self.dirty.len()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Begin a transaction — snapshot current state.
|
|
231
|
+
fn tx_begin(&mut self) {
|
|
232
|
+
let snapshot = TxSnapshot {
|
|
233
|
+
cache: self.cache.clone(),
|
|
234
|
+
dirty: self.dirty.clone(),
|
|
235
|
+
};
|
|
236
|
+
self.tx_stack.push(snapshot);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Commit a transaction — discard the snapshot, keep changes.
|
|
240
|
+
fn tx_commit(&mut self) -> bool {
|
|
241
|
+
if self.tx_stack.is_empty() {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
// Pop snapshot but keep current state — changes are committed
|
|
245
|
+
let _snapshot = self.tx_stack.pop().unwrap();
|
|
246
|
+
// Merge dirty keys from this transaction into the parent
|
|
247
|
+
// (dirty set already contains all writes — nothing extra needed)
|
|
248
|
+
true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Revert a transaction — restore the snapshot.
|
|
252
|
+
fn tx_revert(&mut self) -> bool {
|
|
253
|
+
if let Some(snapshot) = self.tx_stack.pop() {
|
|
254
|
+
self.cache = snapshot.cache;
|
|
255
|
+
self.dirty = snapshot.dirty;
|
|
256
|
+
true
|
|
257
|
+
} else {
|
|
258
|
+
false
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Return cache statistics.
|
|
263
|
+
fn stats(&self, py: Python<'_>) -> PyResult<PyObject> {
|
|
264
|
+
let result = PyDict::new_bound(py);
|
|
265
|
+
result.set_item("cache_size", self.cache.len())?;
|
|
266
|
+
result.set_item("dirty_count", self.dirty.len())?;
|
|
267
|
+
result.set_item("cache_hits", self.cache_hits)?;
|
|
268
|
+
result.set_item("cache_writes", self.cache_writes)?;
|
|
269
|
+
result.set_item("tx_depth", self.tx_stack.len())?;
|
|
270
|
+
Ok(result.to_object(py))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Reset the adapter.
|
|
274
|
+
fn clear(&mut self) {
|
|
275
|
+
self.cache.clear();
|
|
276
|
+
self.dirty.clear();
|
|
277
|
+
self.tx_stack.clear();
|
|
278
|
+
self.cache_hits = 0;
|
|
279
|
+
self.cache_writes = 0;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Block Validation — fast parallel checks
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
use pyo3::prelude::*;
|
|
6
|
+
use rayon::prelude::*;
|
|
7
|
+
use sha2::{Digest, Sha256};
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
|
|
10
|
+
/// Validates block header integrity at native speed.
|
|
11
|
+
#[pyclass]
|
|
12
|
+
pub struct RustBlockValidator;
|
|
13
|
+
|
|
14
|
+
#[pymethods]
|
|
15
|
+
impl RustBlockValidator {
|
|
16
|
+
#[new]
|
|
17
|
+
fn new() -> Self {
|
|
18
|
+
RustBlockValidator
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Verify a block hash matches its header contents.
|
|
22
|
+
///
|
|
23
|
+
/// `header_json` — JSON-encoded block header
|
|
24
|
+
/// `expected_hash` — the hash claimed by the block
|
|
25
|
+
///
|
|
26
|
+
/// Returns `true` if SHA-256(header_json) == expected_hash.
|
|
27
|
+
#[staticmethod]
|
|
28
|
+
fn verify_block_hash(header_json: &str, expected_hash: &str) -> bool {
|
|
29
|
+
let hash = Sha256::digest(header_json.as_bytes());
|
|
30
|
+
hex::encode(hash) == expected_hash
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Validate a chain of block hashes in parallel.
|
|
34
|
+
///
|
|
35
|
+
/// `blocks` — list of (header_json, claimed_hash, prev_hash) tuples.
|
|
36
|
+
/// Returns a list of booleans — one per block.
|
|
37
|
+
///
|
|
38
|
+
/// Checks:
|
|
39
|
+
/// 1. SHA-256(header) == claimed_hash
|
|
40
|
+
/// 2. prev_hash links correctly (sequential check)
|
|
41
|
+
#[staticmethod]
|
|
42
|
+
fn validate_chain(
|
|
43
|
+
py: Python<'_>,
|
|
44
|
+
blocks: Vec<(String, String, String)>,
|
|
45
|
+
) -> Vec<bool> {
|
|
46
|
+
if blocks.is_empty() {
|
|
47
|
+
return vec![];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Step 1: verify hashes in parallel
|
|
51
|
+
let hash_checks: Vec<bool> = py.allow_threads(|| {
|
|
52
|
+
blocks
|
|
53
|
+
.par_iter()
|
|
54
|
+
.map(|(header, claimed, _)| {
|
|
55
|
+
let hash = Sha256::digest(header.as_bytes());
|
|
56
|
+
hex::encode(hash) == *claimed
|
|
57
|
+
})
|
|
58
|
+
.collect()
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Step 2: verify chain linkage (sequential — inherently ordered)
|
|
62
|
+
let mut results = hash_checks;
|
|
63
|
+
for i in 1..blocks.len() {
|
|
64
|
+
let expected_prev = &blocks[i - 1].1; // previous block's hash
|
|
65
|
+
let actual_prev = &blocks[i].2; // this block's prev_hash
|
|
66
|
+
if expected_prev != actual_prev {
|
|
67
|
+
results[i] = false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
results
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Check Proof-of-Work difficulty: does the block hash have the
|
|
75
|
+
/// required number of leading zero bits?
|
|
76
|
+
#[staticmethod]
|
|
77
|
+
fn check_pow_difficulty(block_hash: &str, difficulty: u32) -> bool {
|
|
78
|
+
let bytes = match hex::decode(block_hash) {
|
|
79
|
+
Ok(b) => b,
|
|
80
|
+
Err(_) => return false,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let mut leading_zeros = 0u32;
|
|
84
|
+
for byte in &bytes {
|
|
85
|
+
if *byte == 0 {
|
|
86
|
+
leading_zeros += 8;
|
|
87
|
+
} else {
|
|
88
|
+
leading_zeros += byte.leading_zeros();
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
leading_zeros >= difficulty
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Batch-validate transaction signatures within a block.
|
|
97
|
+
///
|
|
98
|
+
/// `tx_data` — list of (message_bytes, signature_bytes, pubkey_bytes).
|
|
99
|
+
/// Returns the number of valid signatures.
|
|
100
|
+
#[staticmethod]
|
|
101
|
+
fn validate_tx_signatures(
|
|
102
|
+
py: Python<'_>,
|
|
103
|
+
tx_data: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>,
|
|
104
|
+
) -> usize {
|
|
105
|
+
let results: Vec<bool> = py.allow_threads(|| {
|
|
106
|
+
use rayon::prelude::*;
|
|
107
|
+
tx_data
|
|
108
|
+
.par_iter()
|
|
109
|
+
.map(|(msg, sig, pk)| {
|
|
110
|
+
crate::signature::verify_single(msg, sig, pk)
|
|
111
|
+
})
|
|
112
|
+
.collect()
|
|
113
|
+
});
|
|
114
|
+
results.iter().filter(|&&v| v).count()
|
|
115
|
+
}
|
|
116
|
+
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -28,14 +28,46 @@ try {
|
|
|
28
28
|
// Install Zexus Python package
|
|
29
29
|
console.log('\n📦 Installing Zexus Python package...');
|
|
30
30
|
try {
|
|
31
|
-
|
|
31
|
+
// Install with "full" extras so blockchain/network/security features work out of the box.
|
|
32
|
+
// Use --user to avoid permission issues on global Python installs.
|
|
33
|
+
execSync('pip3 install --user "zexus[full]"', { stdio: 'inherit' });
|
|
32
34
|
console.log('\n✓ Zexus Python package installed successfully');
|
|
33
35
|
} catch (error) {
|
|
34
36
|
console.error('\n❌ Failed to install Zexus Python package.');
|
|
35
|
-
console.error('Please run manually: pip3 install zexus');
|
|
37
|
+
console.error('Please run manually: pip3 install --user "zexus[full]"');
|
|
36
38
|
process.exit(1);
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// Best-effort: build/install Rust VM extension from bundled sources when available.
|
|
42
|
+
// This requires a Rust toolchain and may fail on systems without build tooling.
|
|
43
|
+
function hasCommand(cmd) {
|
|
44
|
+
try {
|
|
45
|
+
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
53
|
+
const cargoToml = path.join(pkgRoot, 'rust_core', 'Cargo.toml');
|
|
54
|
+
|
|
55
|
+
if (fs.existsSync(cargoToml) && hasCommand('cargo')) {
|
|
56
|
+
console.log('\n🦀 Rust toolchain detected — attempting to build zexus_core...');
|
|
57
|
+
try {
|
|
58
|
+
execSync('python3 -m pip install --user --upgrade maturin', { stdio: 'inherit' });
|
|
59
|
+
execSync(`python3 -m maturin develop -m "${cargoToml}" --release`, { stdio: 'inherit' });
|
|
60
|
+
execSync('python3 -c "import zexus_core; print(\"zexus_core OK\", zexus_core.version())"', { stdio: 'inherit' });
|
|
61
|
+
console.log('✓ Rust VM extension built and installed');
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn('\n⚠️ Rust VM build failed; continuing with pure-Python VM.');
|
|
64
|
+
console.warn(' To retry manually (from source repo):');
|
|
65
|
+
console.warn(' pip install maturin && maturin develop -m rust_core/Cargo.toml --release');
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.log('\nℹ️ Skipping Rust VM build (cargo not found or rust_core not bundled).');
|
|
69
|
+
}
|
|
70
|
+
|
|
39
71
|
console.log('\n✅ Zexus installed successfully!\n');
|
|
40
72
|
console.log('Get started:');
|
|
41
73
|
console.log(' zexus --help # Show help');
|
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:
|