zexus 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,666 @@
1
+ """
2
+ Zexus Blockchain — Node
3
+
4
+ The Node is the top-level integration point that ties together:
5
+ - Chain (block storage + state)
6
+ - Mempool (pending transactions)
7
+ - P2P Network (peer connectivity)
8
+ - Consensus Engine (block production + validation)
9
+
10
+ It provides the public API used by the Zexus evaluator, CLI, and RPC.
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ import hashlib
16
+ import time
17
+ import threading
18
+ import logging
19
+ from typing import Any, Callable, Dict, List, Optional, Set
20
+
21
+ from .chain import Block, BlockHeader, Chain, Mempool, Transaction, TransactionReceipt
22
+ from .network import P2PNetwork, Message, MessageType, PeerConnection, PeerReputationManager
23
+ from .consensus import ConsensusEngine, ProofOfWork, create_consensus
24
+
25
+ # Multichain bridge (optional)
26
+ try:
27
+ from .multichain import ChainRouter, BridgeContract, CrossChainMessage
28
+ _MULTICHAIN_AVAILABLE = True
29
+ except ImportError:
30
+ _MULTICHAIN_AVAILABLE = False
31
+ ChainRouter = None # type: ignore
32
+ BridgeContract = None # type: ignore
33
+
34
+ # RPC server (optional — requires aiohttp)
35
+ try:
36
+ from .rpc import RPCServer
37
+ _RPC_AVAILABLE = True
38
+ except ImportError:
39
+ _RPC_AVAILABLE = False
40
+ RPCServer = None # type: ignore
41
+
42
+ # Contract VM bridge (optional — only if the VM module is present)
43
+ try:
44
+ from .contract_vm import ContractVM, ContractExecutionReceipt
45
+ _CONTRACT_VM_AVAILABLE = True
46
+ except ImportError:
47
+ _CONTRACT_VM_AVAILABLE = False
48
+ ContractVM = None # type: ignore
49
+ ContractExecutionReceipt = None # type: ignore
50
+
51
+ # Event indexing (optional)
52
+ try:
53
+ from .events import EventIndex, LogFilter, BloomFilter, EventLog
54
+ _EVENTS_AVAILABLE = True
55
+ except ImportError:
56
+ _EVENTS_AVAILABLE = False
57
+ EventIndex = None # type: ignore
58
+
59
+ logger = logging.getLogger("zexus.blockchain.node")
60
+
61
+
62
+ class NodeConfig:
63
+ """Configuration for a blockchain node."""
64
+
65
+ def __init__(self, **kwargs):
66
+ self.chain_id: str = kwargs.get("chain_id", "zexus-mainnet")
67
+ self.host: str = kwargs.get("host", "0.0.0.0")
68
+ self.port: int = kwargs.get("port", 30303)
69
+ self.data_dir: Optional[str] = kwargs.get("data_dir", None)
70
+ self.consensus: str = kwargs.get("consensus", "pow")
71
+ self.consensus_params: Dict[str, Any] = kwargs.get("consensus_params", {})
72
+ self.miner_address: str = kwargs.get("miner_address", "")
73
+ self.mining_enabled: bool = kwargs.get("mining_enabled", False)
74
+ self.mining_interval: float = kwargs.get("mining_interval", 5.0)
75
+ self.max_peers: int = kwargs.get("max_peers", 25)
76
+ self.bootstrap_nodes: List[Dict[str, Any]] = kwargs.get("bootstrap_nodes", [])
77
+ self.initial_balances: Dict[str, int] = kwargs.get("initial_balances", {})
78
+ self.rpc_enabled: bool = kwargs.get("rpc_enabled", False)
79
+ self.rpc_port: int = kwargs.get("rpc_port", 8545)
80
+
81
+
82
+ class BlockchainNode:
83
+ """Full blockchain node.
84
+
85
+ Coordinates chain storage, transaction processing, P2P networking,
86
+ consensus, and mining into a single cohesive system.
87
+ """
88
+
89
+ def __init__(self, config: Optional[NodeConfig] = None):
90
+ self.config = config or NodeConfig()
91
+
92
+ # Core components
93
+ self.chain = Chain(
94
+ chain_id=self.config.chain_id,
95
+ data_dir=self.config.data_dir,
96
+ )
97
+ self.mempool = Mempool()
98
+ self.consensus_engine: ConsensusEngine = create_consensus(
99
+ self.config.consensus,
100
+ **self.config.consensus_params,
101
+ )
102
+ self.network = P2PNetwork(
103
+ host=self.config.host,
104
+ port=self.config.port,
105
+ chain_id=self.config.chain_id,
106
+ max_peers=self.config.max_peers,
107
+ )
108
+
109
+ # Mining state
110
+ self._mining = False
111
+ self._mining_task: Optional[asyncio.Task] = None
112
+
113
+ # Contract VM bridge — enables smart-contract execution via
114
+ # the Zexus VM with real chain-backed state.
115
+ self.contract_vm: Optional["ContractVM"] = None
116
+ if _CONTRACT_VM_AVAILABLE:
117
+ self.contract_vm = ContractVM(
118
+ chain=self.chain,
119
+ gas_limit=self.config.consensus_params.get("gas_limit", 10_000_000),
120
+ )
121
+
122
+ # Event listeners
123
+ self._event_handlers: Dict[str, List[Callable]] = {}
124
+
125
+ # RPC server
126
+ self.rpc_server: Optional["RPCServer"] = None
127
+
128
+ # Event indexer
129
+ self.event_index: Optional["EventIndex"] = None
130
+ if _EVENTS_AVAILABLE:
131
+ self.event_index = EventIndex(data_dir=self.config.data_dir)
132
+
133
+ # Node lifecycle
134
+ self._running = False
135
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
136
+ self._bg_thread: Optional[threading.Thread] = None
137
+
138
+ # ── Lifecycle ──────────────────────────────────────────────────────
139
+
140
+ async def start(self):
141
+ """Start the node: initialize chain, start networking, begin mining."""
142
+ if self._running:
143
+ return
144
+
145
+ self._loop = asyncio.get_event_loop()
146
+ self._running = True
147
+
148
+ # Initialize chain with genesis if empty
149
+ if not self.chain.blocks:
150
+ self.chain.create_genesis(
151
+ miner=self.config.miner_address or "0x" + "0" * 40,
152
+ initial_balances=self.config.initial_balances or None,
153
+ )
154
+ logger.info("Genesis block created for chain '%s'", self.config.chain_id)
155
+
156
+ # Set up network handlers
157
+ self._register_network_handlers()
158
+
159
+ # Start P2P network
160
+ for bn in self.config.bootstrap_nodes:
161
+ self.network.add_bootstrap_node(bn.get("host", ""), bn.get("port", 0))
162
+ await self.network.start()
163
+
164
+ # Start mining if enabled
165
+ if self.config.mining_enabled and self.config.miner_address:
166
+ self.start_mining()
167
+
168
+ # Start RPC server if enabled
169
+ if self.config.rpc_enabled and _RPC_AVAILABLE:
170
+ self.rpc_server = RPCServer(
171
+ node=self,
172
+ host=self.config.host,
173
+ port=self.config.rpc_port,
174
+ )
175
+ await self.rpc_server.start()
176
+ logger.info("RPC server started on port %d", self.config.rpc_port)
177
+
178
+ logger.info("Node started: chain=%s height=%d peers=%d",
179
+ self.config.chain_id, self.chain.height, self.network.peer_count)
180
+
181
+ async def stop(self):
182
+ """Stop the node gracefully."""
183
+ self._running = False
184
+ self.stop_mining()
185
+ if self.rpc_server and self.rpc_server.is_running:
186
+ await self.rpc_server.stop()
187
+ await self.network.stop()
188
+ self.chain.close()
189
+ logger.info("Node stopped")
190
+
191
+ def start_sync(self):
192
+ """Start the node in a background thread (for non-async callers)."""
193
+ if self._bg_thread and self._bg_thread.is_alive():
194
+ return
195
+
196
+ def _run():
197
+ loop = asyncio.new_event_loop()
198
+ asyncio.set_event_loop(loop)
199
+ self._loop = loop
200
+ loop.run_until_complete(self.start())
201
+ loop.run_forever()
202
+
203
+ self._bg_thread = threading.Thread(target=_run, daemon=True)
204
+ self._bg_thread.start()
205
+
206
+ def stop_sync(self):
207
+ """Stop the node from the synchronous world."""
208
+ if self._loop and self._running:
209
+ asyncio.run_coroutine_threadsafe(self.stop(), self._loop)
210
+
211
+ # ── Events ─────────────────────────────────────────────────────────
212
+
213
+ def on(self, event: str, handler: Callable):
214
+ """Register an event handler. Events: new_block, new_tx, mined, sync."""
215
+ self._event_handlers.setdefault(event, []).append(handler)
216
+
217
+ def _emit(self, event: str, data: Any = None):
218
+ for handler in self._event_handlers.get(event, []):
219
+ try:
220
+ handler(data)
221
+ except Exception as e:
222
+ logger.error("Event handler error (%s): %s", event, e)
223
+
224
+ # ── Transaction API ────────────────────────────────────────────────
225
+
226
+ def submit_transaction(self, tx: Transaction) -> Dict[str, Any]:
227
+ """Submit a transaction to the node.
228
+
229
+ Validates, adds to mempool, and broadcasts to peers.
230
+
231
+ Returns:
232
+ {"success": bool, "tx_hash": str, "error": str}
233
+ """
234
+ # Ensure hash
235
+ if not tx.tx_hash:
236
+ tx.compute_hash()
237
+
238
+ # Basic validation
239
+ if not tx.sender:
240
+ return {"success": False, "tx_hash": "", "error": "missing sender"}
241
+
242
+ sender_acct = self.chain.get_account(tx.sender)
243
+ cost = tx.value + (tx.gas_limit * tx.gas_price)
244
+ if sender_acct["balance"] < cost:
245
+ return {"success": False, "tx_hash": tx.tx_hash,
246
+ "error": f"insufficient balance: need {cost}, have {sender_acct['balance']}"}
247
+
248
+ if tx.nonce < sender_acct["nonce"]:
249
+ return {"success": False, "tx_hash": tx.tx_hash,
250
+ "error": f"nonce too low: expected >= {sender_acct['nonce']}, got {tx.nonce}"}
251
+
252
+ # Add to mempool
253
+ if not self.mempool.add(tx):
254
+ return {"success": False, "tx_hash": tx.tx_hash, "error": "mempool rejected"}
255
+
256
+ # Broadcast to peers
257
+ if self._loop and self.network.is_running:
258
+ asyncio.run_coroutine_threadsafe(
259
+ self.network.gossip(Message(
260
+ type=MessageType.NEW_TX,
261
+ payload=tx.to_dict(),
262
+ )),
263
+ self._loop,
264
+ )
265
+
266
+ self._emit("new_tx", tx)
267
+ return {"success": True, "tx_hash": tx.tx_hash, "error": ""}
268
+
269
+ def create_transaction(self, sender: str, recipient: str, value: int,
270
+ data: str = "", gas_limit: int = 21_000,
271
+ gas_price: int = 1) -> Transaction:
272
+ """Convenience: create and submit a transaction."""
273
+ acct = self.chain.get_account(sender)
274
+ tx = Transaction(
275
+ sender=sender,
276
+ recipient=recipient,
277
+ value=value,
278
+ data=data,
279
+ nonce=acct["nonce"],
280
+ gas_limit=gas_limit,
281
+ gas_price=gas_price,
282
+ timestamp=time.time(),
283
+ )
284
+ tx.compute_hash()
285
+ return tx
286
+
287
+ # ── Block API ──────────────────────────────────────────────────────
288
+
289
+ def get_block(self, hash_or_height) -> Optional[Dict[str, Any]]:
290
+ """Get a block by hash or height."""
291
+ block = self.chain.get_block(hash_or_height)
292
+ return block.to_dict() if block else None
293
+
294
+ def get_latest_block(self) -> Optional[Dict[str, Any]]:
295
+ """Get the latest block."""
296
+ tip = self.chain.tip
297
+ return tip.to_dict() if tip else None
298
+
299
+ def get_chain_info(self) -> Dict[str, Any]:
300
+ """Get combined chain + network info."""
301
+ info = self.chain.get_chain_info()
302
+ info["network"] = self.network.get_network_info()
303
+ info["mempool_size"] = self.mempool.size
304
+ info["mining"] = self._mining
305
+ info["consensus"] = self.config.consensus
306
+ return info
307
+
308
+ # ── Account API ────────────────────────────────────────────────────
309
+
310
+ def get_balance(self, address: str) -> int:
311
+ return self.chain.get_account(address)["balance"]
312
+
313
+ def get_nonce(self, address: str) -> int:
314
+ return self.chain.get_account(address)["nonce"]
315
+
316
+ def get_account(self, address: str) -> Dict[str, Any]:
317
+ return self.chain.get_account(address)
318
+
319
+ def fund_account(self, address: str, amount: int):
320
+ """Fund an account (for testnets / development)."""
321
+ acct = self.chain.get_account(address)
322
+ acct["balance"] += amount
323
+
324
+ # ── Smart Contract API ─────────────────────────────────────────────
325
+
326
+ def deploy_contract(self, contract, deployer: str,
327
+ gas_limit: int = 10_000_000,
328
+ initial_value: int = 0) -> Dict[str, Any]:
329
+ """Deploy a SmartContract onto the chain via the ContractVM.
330
+
331
+ Returns:
332
+ {"success": bool, "address": str, "error": str}
333
+ """
334
+ if self.contract_vm is None:
335
+ return {"success": False, "address": "", "error": "ContractVM not available"}
336
+
337
+ receipt = self.contract_vm.deploy_contract(
338
+ contract=contract,
339
+ deployer=deployer,
340
+ gas_limit=gas_limit,
341
+ initial_value=initial_value,
342
+ )
343
+ return {
344
+ "success": receipt.success,
345
+ "address": contract.address,
346
+ "error": receipt.error,
347
+ "gas_used": receipt.gas_used,
348
+ }
349
+
350
+ def call_contract(self, contract_address: str, action: str,
351
+ args: Optional[Dict[str, Any]] = None,
352
+ caller: str = "",
353
+ gas_limit: int = 500_000,
354
+ value: int = 0) -> Dict[str, Any]:
355
+ """Execute a contract action (state-mutating).
356
+
357
+ Returns:
358
+ {"success": bool, "return_value": Any, "gas_used": int, ...}
359
+ """
360
+ if self.contract_vm is None:
361
+ return {"success": False, "error": "ContractVM not available"}
362
+
363
+ receipt = self.contract_vm.execute_contract(
364
+ contract_address=contract_address,
365
+ action=action,
366
+ args=args,
367
+ caller=caller,
368
+ gas_limit=gas_limit,
369
+ value=value,
370
+ )
371
+ return receipt.to_dict()
372
+
373
+ def static_call_contract(self, contract_address: str, action: str,
374
+ args: Optional[Dict[str, Any]] = None,
375
+ caller: str = "") -> Dict[str, Any]:
376
+ """Execute a read-only contract call (no state changes committed).
377
+
378
+ Returns:
379
+ {"success": bool, "return_value": Any, "gas_used": int, ...}
380
+ """
381
+ if self.contract_vm is None:
382
+ return {"success": False, "error": "ContractVM not available"}
383
+
384
+ receipt = self.contract_vm.static_call(
385
+ contract_address=contract_address,
386
+ action=action,
387
+ args=args,
388
+ caller=caller,
389
+ )
390
+ return receipt.to_dict()
391
+
392
+ # ── Mining ─────────────────────────────────────────────────────────
393
+
394
+ def start_mining(self):
395
+ """Start the mining loop."""
396
+ if self._mining:
397
+ return
398
+ if not self.config.miner_address:
399
+ raise RuntimeError("Cannot mine without a miner_address")
400
+ self._mining = True
401
+ if self._loop:
402
+ self._mining_task = asyncio.run_coroutine_threadsafe(
403
+ self._mining_loop(), self._loop
404
+ )
405
+ logger.info("Mining started (miner=%s)", self.config.miner_address[:8])
406
+
407
+ def stop_mining(self):
408
+ """Stop the mining loop."""
409
+ self._mining = False
410
+ if self._mining_task:
411
+ self._mining_task.cancel()
412
+ self._mining_task = None
413
+ logger.info("Mining stopped")
414
+
415
+ async def _mining_loop(self):
416
+ """Continuous mining loop."""
417
+ while self._mining and self._running:
418
+ try:
419
+ block = await asyncio.get_event_loop().run_in_executor(
420
+ None, self._mine_one_block
421
+ )
422
+ if block:
423
+ success, err = self.chain.add_block(block)
424
+ if success:
425
+ logger.info("Mined block %d: %s", block.header.height, block.hash[:16])
426
+ self._emit("mined", block)
427
+ # Broadcast new block
428
+ await self.network.gossip(Message(
429
+ type=MessageType.NEW_BLOCK,
430
+ payload=block.to_dict(),
431
+ ))
432
+ else:
433
+ logger.warning("Mined block rejected: %s", err)
434
+ else:
435
+ await asyncio.sleep(self.config.mining_interval)
436
+ except asyncio.CancelledError:
437
+ break
438
+ except Exception as e:
439
+ logger.error("Mining error: %s", e)
440
+ await asyncio.sleep(1)
441
+
442
+ def _mine_one_block(self) -> Optional[Block]:
443
+ """Attempt to mine a single block."""
444
+ if self.mempool.size == 0:
445
+ return None
446
+ return self.consensus_engine.seal(
447
+ self.chain, self.mempool, self.config.miner_address
448
+ )
449
+
450
+ def mine_block_sync(self) -> Optional[Block]:
451
+ """Mine a single block synchronously (for testing/scripting)."""
452
+ block = self.consensus_engine.seal(
453
+ self.chain, self.mempool, self.config.miner_address
454
+ )
455
+ if block:
456
+ success, err = self.chain.add_block(block)
457
+ if success:
458
+ self._emit("mined", block)
459
+ # Index events from the new block
460
+ if self.event_index:
461
+ self.event_index.index_block(block)
462
+ return block
463
+ else:
464
+ logger.warning("Block rejected: %s", err)
465
+ return None
466
+
467
+ # ── Chain Sync (Network) ───────────────────────────────────────────
468
+
469
+ def _register_network_handlers(self):
470
+ """Set up P2P message handlers for blockchain protocol."""
471
+ self.network.on(MessageType.NEW_BLOCK, self._on_new_block)
472
+ self.network.on(MessageType.NEW_TX, self._on_new_tx)
473
+ self.network.on(MessageType.GET_BLOCKS, self._on_get_blocks)
474
+ self.network.on(MessageType.BLOCKS, self._on_blocks)
475
+ self.network.on(MessageType.GET_HEADERS, self._on_get_headers)
476
+ self.network.on(MessageType.HEADERS, self._on_headers)
477
+
478
+ async def _on_new_block(self, msg: Message, conn: PeerConnection):
479
+ """Handle a NEW_BLOCK message from a peer."""
480
+ try:
481
+ block = Block.from_dict(msg.payload)
482
+ except Exception as e:
483
+ logger.debug("Invalid block from %s: %s", msg.sender[:8], e)
484
+ return
485
+
486
+ # Skip if we already have this block
487
+ if block.hash in self.chain.block_index:
488
+ return
489
+
490
+ # Validate via consensus
491
+ if not self.consensus_engine.verify(block, self.chain):
492
+ logger.debug("Block %d from %s failed consensus", block.header.height, msg.sender[:8])
493
+ # Penalize peer for invalid block
494
+ if conn.peer_info.peer_id:
495
+ self.network.reputation.update(
496
+ conn.peer_info, PeerReputationManager.INVALID_BLOCK,
497
+ reason="failed consensus verification")
498
+ return
499
+
500
+ # Try to add to chain
501
+ success, err = self.chain.add_block(block)
502
+ if success:
503
+ logger.info("Accepted block %d from peer %s", block.header.height, msg.sender[:8])
504
+ self._emit("new_block", block)
505
+ # Reward peer for valid block
506
+ if conn.peer_info.peer_id:
507
+ self.network.reputation.update(
508
+ conn.peer_info, PeerReputationManager.VALID_BLOCK,
509
+ reason="valid block accepted")
510
+ # Remove block's txs from our mempool
511
+ for tx in block.transactions:
512
+ self.mempool.remove(tx.tx_hash)
513
+ # Relay to other peers
514
+ await self.network.gossip(msg, exclude={msg.sender})
515
+ else:
516
+ logger.debug("Block %d rejected: %s", block.header.height, err)
517
+ # If block is ahead of us, we might need to sync
518
+ if block.header.height > self.chain.height + 1:
519
+ await self._request_sync(conn)
520
+
521
+ async def _on_new_tx(self, msg: Message, conn: PeerConnection):
522
+ """Handle a NEW_TX message from a peer."""
523
+ try:
524
+ tx = Transaction(**msg.payload)
525
+ except Exception as e:
526
+ logger.debug("Invalid TX from %s: %s", msg.sender[:8], e)
527
+ return
528
+
529
+ if self.mempool.add(tx):
530
+ self._emit("new_tx", tx)
531
+ # Reward peer for valid transaction
532
+ if conn.peer_info.peer_id:
533
+ self.network.reputation.update(
534
+ conn.peer_info, PeerReputationManager.VALID_TX,
535
+ reason="valid transaction")
536
+ # Relay
537
+ await self.network.gossip(msg, exclude={msg.sender})
538
+
539
+ async def _on_get_blocks(self, msg: Message, conn: PeerConnection):
540
+ """Respond to a GET_BLOCKS request."""
541
+ start_height = msg.payload.get("start", 0)
542
+ count = min(msg.payload.get("count", 50), 100) # Max 100 blocks per request
543
+
544
+ blocks_data = []
545
+ for h in range(start_height, min(start_height + count, self.chain.height + 1)):
546
+ block = self.chain.get_block(h)
547
+ if block:
548
+ blocks_data.append(block.to_dict())
549
+
550
+ await conn.send(Message(
551
+ type=MessageType.BLOCKS,
552
+ payload={"blocks": blocks_data},
553
+ ))
554
+
555
+ async def _on_blocks(self, msg: Message, conn: PeerConnection):
556
+ """Handle received blocks (sync response)."""
557
+ blocks_data = msg.payload.get("blocks", [])
558
+ added = 0
559
+ for bd in blocks_data:
560
+ try:
561
+ block = Block.from_dict(bd)
562
+ if block.hash not in self.chain.block_index:
563
+ if self.consensus_engine.verify(block, self.chain):
564
+ success, _ = self.chain.add_block(block)
565
+ if success:
566
+ added += 1
567
+ except Exception as e:
568
+ logger.debug("Error processing sync block: %s", e)
569
+ continue
570
+
571
+ if added > 0:
572
+ logger.info("Synced %d blocks from peer %s (height now %d)",
573
+ added, msg.sender[:8], self.chain.height)
574
+
575
+ async def _on_get_headers(self, msg: Message, conn: PeerConnection):
576
+ """Respond to header requests."""
577
+ start = msg.payload.get("start", 0)
578
+ count = min(msg.payload.get("count", 100), 500)
579
+
580
+ headers = []
581
+ for h in range(start, min(start + count, self.chain.height + 1)):
582
+ block = self.chain.get_block(h)
583
+ if block:
584
+ from dataclasses import asdict
585
+ headers.append(asdict(block.header))
586
+
587
+ await conn.send(Message(
588
+ type=MessageType.HEADERS,
589
+ payload={"headers": headers},
590
+ ))
591
+
592
+ async def _on_headers(self, msg: Message, conn: PeerConnection):
593
+ """Handle received headers — decide if we need full blocks."""
594
+ headers = msg.payload.get("headers", [])
595
+ if headers:
596
+ last = headers[-1]
597
+ remote_height = last.get("height", 0)
598
+ if remote_height > self.chain.height:
599
+ # Request missing blocks
600
+ await conn.send(Message(
601
+ type=MessageType.GET_BLOCKS,
602
+ payload={"start": self.chain.height + 1, "count": 50},
603
+ ))
604
+
605
+ async def _request_sync(self, conn: PeerConnection):
606
+ """Request blocks from a peer to catch up."""
607
+ await conn.send(Message(
608
+ type=MessageType.GET_BLOCKS,
609
+ payload={"start": self.chain.height + 1, "count": 50},
610
+ ))
611
+
612
+ # ── Utility ────────────────────────────────────────────────────────
613
+
614
+ def validate_chain(self) -> Dict[str, Any]:
615
+ """Full chain integrity check."""
616
+ valid, err = self.chain.validate_chain()
617
+ return {"valid": valid, "error": err, "height": self.chain.height}
618
+
619
+ def export_chain(self) -> List[Dict[str, Any]]:
620
+ """Export the full chain as a list of block dicts."""
621
+ return [b.to_dict() for b in self.chain.blocks]
622
+
623
+ def __repr__(self):
624
+ return (f"BlockchainNode(chain_id={self.config.chain_id!r}, "
625
+ f"height={self.chain.height}, peers={self.network.peer_count})")
626
+
627
+ # ── Multichain / Cross-chain Bridge ────────────────────────────────
628
+
629
+ def join_router(self, router: "ChainRouter") -> None:
630
+ """Register this node's chain with an external ChainRouter."""
631
+ if not _MULTICHAIN_AVAILABLE:
632
+ raise RuntimeError("Multichain module not available")
633
+ router.register_chain(self.config.chain_id, self.chain)
634
+ self._router = router
635
+ logger.info("Node %s joined ChainRouter", self.config.chain_id)
636
+
637
+ def bridge_to(
638
+ self,
639
+ other_node: "BlockchainNode",
640
+ router: Optional["ChainRouter"] = None,
641
+ ) -> "BridgeContract":
642
+ """Create a bridge contract between this node and *other_node*.
643
+
644
+ If no *router* is provided, a new one is created.
645
+
646
+ Returns a ``BridgeContract`` for lock-and-mint / burn-and-release.
647
+ """
648
+ if not _MULTICHAIN_AVAILABLE:
649
+ raise RuntimeError("Multichain module not available")
650
+ if router is None:
651
+ router = ChainRouter()
652
+ if self.config.chain_id not in router.chain_ids:
653
+ self.join_router(router)
654
+ if other_node.config.chain_id not in router.chain_ids:
655
+ other_node.join_router(router)
656
+ router.connect(self.config.chain_id, other_node.config.chain_id)
657
+ bridge = BridgeContract(
658
+ router=router,
659
+ source_chain=self.config.chain_id,
660
+ dest_chain=other_node.config.chain_id,
661
+ )
662
+ logger.info(
663
+ "Bridge created: %s <-> %s",
664
+ self.config.chain_id, other_node.config.chain_id,
665
+ )
666
+ return bridge