hanzo-vm 1.1.22

Hanzo Network — EVM with precompiles for PQ verify, Quasar, AI inference, AI embedding (0x0101/0x0102/0x0201/0x0202)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
//! Lux VM engine implementation.
//!
//! [`HanzoVm`] is the primary entry point. It implements [`VmEngine`], which
//! mirrors the Snow VM interface used by Lux subnets:
//!
//! - `initialize` -- load genesis state.
//! - `build_block` -- construct a new block from pending transactions.
//! - `parse_block` / `accept` / `reject` -- consensus lifecycle.
//! - `last_accepted` -- current canonical tip.
//! - `health_check` -- readiness probe.

use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::block::Block;
use crate::evm_backend::{self, EvmExecutor};
use crate::precompiles::PrecompileRegistry;
use crate::state::StateDb;

// ---------------------------------------------------------------------------
// VmEngine trait
// ---------------------------------------------------------------------------

/// Snow-compatible VM interface for a Lux subnet.
///
/// Every method takes `&mut self` where state mutation is required, or
/// `&self` for read-only queries.
pub trait VmEngine {
    /// Load genesis bytes and initialize chain state.
    ///
    /// Called exactly once when the node starts for the first time.
    /// The genesis payload is an opaque blob whose format is defined
    /// by the specific VM implementation.
    fn initialize(&mut self, genesis: &[u8]) -> Result<()>;

    /// Build a new block from the given raw EVM transactions.
    ///
    /// Each element of `txs` is a single RLP-encoded transaction.
    fn build_block(&mut self, txs: Vec<Vec<u8>>) -> Result<Block>;

    /// Deserialize a block received from the network.
    fn parse_block(&self, bytes: &[u8]) -> Result<Block>;

    /// Set the preferred block that this node wants to build on.
    fn set_preference(&mut self, block_id: [u8; 32]) -> Result<()>;

    /// Accept a block into the canonical chain.
    fn accept(&mut self, block_id: [u8; 32]) -> Result<()>;

    /// Reject a block, discarding any state changes.
    fn reject(&mut self, block_id: [u8; 32]) -> Result<()>;

    /// Return the ID of the last accepted (finalized) block.
    fn last_accepted(&self) -> Result<[u8; 32]>;

    /// Return current health status for readiness probes.
    fn health_check(&self) -> Result<HealthStatus>;
}

// ---------------------------------------------------------------------------
// VmConfig
// ---------------------------------------------------------------------------

/// Configuration for the Hanzo VM instance.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmConfig {
    /// EVM chain ID (e.g., 1313161554 for Hanzo mainnet).
    pub chain_id: u64,

    /// Directory for persistent state (SQLite DB, block store).
    pub data_dir: String,

    /// Port for the JSON-RPC endpoint.
    pub rpc_port: u16,

    /// Maximum gas limit per block.
    pub block_gas_limit: u64,

    /// Target block interval in milliseconds.
    pub target_block_time_ms: u64,

    /// Which EVM backend to use. When set to `None`, auto-detection picks
    /// the fastest available backend (cevm > revm).
    #[serde(default)]
    pub evm_backend: Option<evm_backend::EvmBackend>,
}

impl Default for VmConfig {
    fn default() -> Self {
        Self {
            chain_id: 1313161554,
            data_dir: "/tmp/hanzo-vm".into(),
            rpc_port: 9650,
            block_gas_limit: 30_000_000,
            target_block_time_ms: 2_000,
            evm_backend: None,
        }
    }
}

// ---------------------------------------------------------------------------
// HealthStatus
// ---------------------------------------------------------------------------

/// Result of a health-check probe.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStatus {
    /// Whether the node is healthy and ready to serve requests.
    pub healthy: bool,
    /// Human-readable reason when unhealthy.
    pub reason: Option<String>,
    /// Height of the last accepted block.
    pub last_accepted_height: u64,
}

// ---------------------------------------------------------------------------
// HanzoVm
// ---------------------------------------------------------------------------

/// The Hanzo L2 virtual machine.
///
/// Wraps EVM execution behind the Lux Snow consensus interface, using
/// SQLite-backed state for development and custom precompiles for
/// post-quantum crypto and AI operations.
pub struct HanzoVm {
    /// Persistent account/storage state.
    pub state: StateDb,
    /// Custom precompile registry.
    pub precompiles: PrecompileRegistry,
    /// Pluggable EVM execution backend.
    pub executor: Box<dyn EvmExecutor>,
    /// VM configuration.
    pub config: VmConfig,
    /// ID of the currently preferred block.
    preferred: [u8; 32],
    /// ID of the last accepted (finalized) block.
    last_accepted_id: [u8; 32],
    /// Height of the last accepted block.
    last_accepted_height: u64,
    /// Whether genesis has been loaded.
    initialized: bool,
}

impl HanzoVm {
    /// Create a new `HanzoVm` with the given configuration.
    ///
    /// The EVM backend is selected based on `config.evm_backend`:
    /// - `None` -- auto-detect (cevm if available, else revm).
    /// - `Some(Revm)` -- built-in Rust revm.
    /// - `Some(Cevm)` -- C++ EVM via FFI.
    /// - `Some(GoEvm)` -- Go EVM via subprocess.
    pub fn new(config: VmConfig) -> Self {
        let state = StateDb::new(&config.data_dir);
        let precompiles = PrecompileRegistry::default();

        let executor: Box<dyn EvmExecutor> = match config.evm_backend {
            None => evm_backend::auto_detect(),
            Some(evm_backend::EvmBackend::Revm) => Box::new(evm_backend::RevmExecutor),
            Some(evm_backend::EvmBackend::Cevm) => Box::new(evm_backend::CevmExecutor::auto()),
            Some(evm_backend::EvmBackend::GoEvm) => {
                let home = std::env::var("HOME").unwrap_or_default();
                let binary = format!("{home}/work/luxcpp/evm/build/bin/cevm");
                Box::new(evm_backend::GoEvmExecutor::new(binary))
            }
        };

        log::info!(
            "HanzoVm: using EVM backend '{}' (gpu={})",
            executor.name(),
            executor.gpu_capable(),
        );

        Self {
            state,
            precompiles,
            executor,
            config,
            preferred: [0u8; 32],
            last_accepted_id: [0u8; 32],
            last_accepted_height: 0,
            initialized: false,
        }
    }

    /// Create a `HanzoVm` with a specific executor (for testing or embedding).
    pub fn with_executor(config: VmConfig, executor: Box<dyn EvmExecutor>) -> Self {
        let state = StateDb::new(&config.data_dir);
        let precompiles = PrecompileRegistry::default();
        Self {
            state,
            precompiles,
            executor,
            config,
            preferred: [0u8; 32],
            last_accepted_id: [0u8; 32],
            last_accepted_height: 0,
            initialized: false,
        }
    }
}

impl VmEngine for HanzoVm {
    fn initialize(&mut self, genesis: &[u8]) -> Result<()> {
        if self.initialized {
            anyhow::bail!("VM already initialized");
        }

        // Parse genesis JSON.
        let genesis_state: GenesisState = serde_json::from_slice(genesis)
            .map_err(|e| anyhow::anyhow!("invalid genesis payload: {e}"))?;

        // Initialize state DB.
        self.state.init()?;

        // Apply genesis allocations.
        for alloc in &genesis_state.alloc {
            let account = crate::state::Account {
                nonce: alloc.nonce,
                balance: alloc.balance,
                code_hash: [0u8; 32],
                storage_root: [0u8; 32],
            };
            self.state.set_account(&alloc.address, &account)?;
        }

        // Build and accept the genesis block.
        let genesis_block = Block::genesis(genesis_state.chain_id, genesis_state.timestamp);
        let block_id = genesis_block.id();
        self.last_accepted_id = block_id;
        self.last_accepted_height = 0;
        self.preferred = block_id;
        self.config.chain_id = genesis_state.chain_id;
        self.initialized = true;

        log::info!(
            "VM initialized: chain_id={}, genesis_block={}",
            genesis_state.chain_id,
            hex::encode(block_id)
        );
        Ok(())
    }

    fn build_block(&mut self, txs: Vec<Vec<u8>>) -> Result<Block> {
        if !self.initialized {
            anyhow::bail!("VM not initialized");
        }

        let mut transactions: Vec<crate::block::Transaction> = txs
            .into_iter()
            .enumerate()
            .map(|(i, raw)| crate::block::Transaction {
                raw_bytes: raw,
                tx_index: i as u32,
                gas_used: 0,
            })
            .collect();

        // Execute transactions through the pluggable EVM backend.
        let result = self.executor.execute_block(&transactions, &mut self.state)?;

        // Fill in per-transaction gas usage from execution results.
        for (tx, &gas) in transactions.iter_mut().zip(result.gas_used.iter()) {
            tx.gas_used = gas;
        }

        let height = self.last_accepted_height + 1;
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        let block = Block::new(
            self.last_accepted_id,
            height,
            timestamp,
            transactions,
            self.state.root(),
        );

        log::debug!(
            "built block height={height} id={} backend={} gas={} time={:.2}ms",
            hex::encode(block.id()),
            self.executor.name(),
            result.total_gas,
            result.exec_time_ms,
        );
        Ok(block)
    }

    fn parse_block(&self, bytes: &[u8]) -> Result<Block> {
        let block: Block = serde_json::from_slice(bytes)
            .map_err(|e| anyhow::anyhow!("failed to parse block: {e}"))?;
        Ok(block)
    }

    fn set_preference(&mut self, block_id: [u8; 32]) -> Result<()> {
        self.preferred = block_id;
        log::debug!("preference set to {}", hex::encode(block_id));
        Ok(())
    }

    fn accept(&mut self, block_id: [u8; 32]) -> Result<()> {
        if !self.initialized {
            anyhow::bail!("VM not initialized");
        }

        // In a full implementation this would apply block state transitions
        // and persist the accepted block. For now we update bookkeeping.
        self.last_accepted_id = block_id;
        self.last_accepted_height += 1;

        log::info!(
            "accepted block height={} id={}",
            self.last_accepted_height,
            hex::encode(block_id)
        );
        Ok(())
    }

    fn reject(&mut self, block_id: [u8; 32]) -> Result<()> {
        log::info!("rejected block id={}", hex::encode(block_id));
        // Discard any pending state associated with this block.
        // Full implementation would clean up uncommitted state diffs.
        Ok(())
    }

    fn last_accepted(&self) -> Result<[u8; 32]> {
        Ok(self.last_accepted_id)
    }

    fn health_check(&self) -> Result<HealthStatus> {
        if !self.initialized {
            return Ok(HealthStatus {
                healthy: false,
                reason: Some("VM not yet initialized".into()),
                last_accepted_height: 0,
            });
        }

        // Check state DB connectivity.
        if let Err(e) = self.state.ping() {
            return Ok(HealthStatus {
                healthy: false,
                reason: Some(format!("state DB unreachable: {e}")),
                last_accepted_height: self.last_accepted_height,
            });
        }

        Ok(HealthStatus {
            healthy: true,
            reason: None,
            last_accepted_height: self.last_accepted_height,
        })
    }
}

// ---------------------------------------------------------------------------
// Genesis types (internal)
// ---------------------------------------------------------------------------

/// Genesis allocation for a single account.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GenesisAlloc {
    /// Hex-encoded address (0x-prefixed).
    address: String,
    /// Initial balance in wei.
    balance: u128,
    /// Initial nonce.
    #[serde(default)]
    nonce: u64,
}

/// Full genesis state payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GenesisState {
    /// Chain identifier.
    chain_id: u64,
    /// Genesis timestamp (unix seconds).
    timestamp: u64,
    /// Initial account allocations.
    #[serde(default)]
    alloc: Vec<GenesisAlloc>,
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    fn test_genesis() -> Vec<u8> {
        serde_json::to_vec(&serde_json::json!({
            "chain_id": 31337,
            "timestamp": 1700000000,
            "alloc": [
                {
                    "address": "0x0000000000000000000000000000000000000001",
                    "balance": 1_000_000_000_000_000_000u128,
                    "nonce": 0
                }
            ]
        }))
        .unwrap()
    }

    #[test]
    fn initialize_and_health_check() {
        let dir = tempfile::tempdir().unwrap();
        let config = VmConfig {
            data_dir: dir.path().to_string_lossy().into_owned(),
            ..VmConfig::default()
        };
        let mut vm = HanzoVm::new(config);
        vm.initialize(&test_genesis()).unwrap();

        let status = vm.health_check().unwrap();
        assert!(status.healthy);
        assert_eq!(status.last_accepted_height, 0);
    }

    #[test]
    fn double_initialize_fails() {
        let dir = tempfile::tempdir().unwrap();
        let config = VmConfig {
            data_dir: dir.path().to_string_lossy().into_owned(),
            ..VmConfig::default()
        };
        let mut vm = HanzoVm::new(config);
        vm.initialize(&test_genesis()).unwrap();
        assert!(vm.initialize(&test_genesis()).is_err());
    }

    #[test]
    fn build_and_accept_block() {
        let dir = tempfile::tempdir().unwrap();
        let config = VmConfig {
            data_dir: dir.path().to_string_lossy().into_owned(),
            ..VmConfig::default()
        };
        let mut vm = HanzoVm::new(config);
        vm.initialize(&test_genesis()).unwrap();

        let block = vm.build_block(vec![vec![0xde, 0xad]]).unwrap();
        let block_id = block.id();
        assert_eq!(block.header.height, 1);

        vm.accept(block_id).unwrap();
        assert_eq!(vm.last_accepted().unwrap(), block_id);
        assert_eq!(vm.health_check().unwrap().last_accepted_height, 1);
    }

    #[test]
    fn parse_round_trip() {
        let dir = tempfile::tempdir().unwrap();
        let config = VmConfig {
            data_dir: dir.path().to_string_lossy().into_owned(),
            ..VmConfig::default()
        };
        let mut vm = HanzoVm::new(config);
        vm.initialize(&test_genesis()).unwrap();

        let block = vm.build_block(vec![]).unwrap();
        let bytes = serde_json::to_vec(&block).unwrap();
        let parsed = vm.parse_block(&bytes).unwrap();

        assert_eq!(block.id(), parsed.id());
        assert_eq!(block.header.height, parsed.header.height);
    }
}