Розробка кастомної віртуальної машини блокчейну
EVM — це не єдина можлива архітектура. Вона була спроектована у 2014 році для конкретних компромісів того часу: 256-бітна стекова машина з дорогим storage, без паралелізму, з синхронним виконанням. За десять років з'явилися задачи, для яких EVM — субоптимальний вибір: ігрові рушії з тисячами станів, ZK-friendly обчислення, спеціалізовані фінансові примітиви. Кастомна VM — це не переизобретення колеса, а інженерне рішення для конкретного performance/expressivity вимірювання.
Зачем створювати VM з нуля
Перед тим як проектувати кастомну VM, потрібно чесно відповісти: не розв'язується ви задача через існуючі варіанти?
Коли існуючого достатньо:
- EVM + Solidity + Foundry закривають 95% DeFi задач
- Solana SBF (sBPF) — високопродуктивний DeFi, gaming
- CosmWasm — кастомна L1 з Cosmos SDK без написання VM
- Move (Aptos/Sui) — resource-oriented programming для asset safety
Коли потрібна кастомна VM:
- ZK-proof generation: існуючі VM (EVM, WASM) не оптимізовані під арифметику кінцевих полів. ZK-friendly VM будується навколо Plonky2, Halo2 або STARK-сумісних операцій.
- Domain-specific language потрібен на рівні виконання: наприклад, chess engine VM, де кожен opcode — операція з шахматною позицією, а state transitions верифікуються on-chain.
- Паралельне виконання: EVM послідовний. Solana Sealevel виконує транзакції паралельно, знаючи заранее які account'ы вони затрагують. Аналогічна архітектура для EVM-несумісної L1.
- Minified execution environment: бортові системи, IoT — мінімальний bytecode інтерпретатор для вбудовуваних пристроїв з верифікацією on-chain.
Архітектурні рішення при проектуванні VM
Тип VM: стекова vs реєстрова
Stack-based VM (EVM, WASM, JVM): операнди беруться зі стека, результат кладеться на стек. Простіший компілятор, компактний bytecode, але складна оптимізація — немає явних реєстрів.
Register-based VM (Lua VM, Dalvik, SBF): операнди — іменовані реєстри. Швидше при інтерпретації (менше push/pop операцій), простіша JIT-компіляція, bytecode трохи об'ємніше.
Для блокчейну зазвичай вибирають стекову VM: вона простіша формально верифікувати (важливо для аудиту), bytecode компактніше (менше calldata), і більшість компіляторів для неї добре вивчені.
Arithmetic: 256-bit vs field arithmetic
EVM використовує 256-bit integers — зручно для Ethereum адрес та хешів, але розточильно для ZK-proof систем. ZK-friendly VM будується навколо arithmetic у prime field F_p, де p — prime modulus конкретної proof system:
- Plonky2: F_{2^64 - 2^32 + 1} (Goldilocks field)
- Groth16 / Plonk на BN254: p ≈ 2^254
- STARKs: F_{2^{64}-2^{32}+1} або F_{2^{31}-1}
Операції в цих полях нативно дешевше для proof generation. Якщо кінцева ціль — верифікувати виконання VM через ZK proof, arithmetic field повинен совпадати з proof system.
Instruction Set Architecture (ISA)
Проектуючи ISA, потрібно мінімізувати кількість opcodes, зберігаючи Turing-completeness. Мінімальний жизнеспособний набір:
Arithmetic: ADD, SUB, MUL, DIV, MOD
Bitwise: AND, OR, XOR, NOT, SHL, SHR
Comparison: EQ, LT, GT
Memory: LOAD, STORE (або PUSH/POP для stack-based)
Control flow: JUMP, JUMPI, HALT
Stack ops: PUSH_N, POP, DUP, SWAP
Crypto: HASH (keccak256 або poseidon для ZK)
Спеціалізовані opcodes для domain: наприклад, для фінансової VM — MULPERCENT (множення з процентами без overflow), SQRT (для AMM формул). Кожен новий opcode — додаткова складність у реалізації інтерпретатора, JIT-компілятора та формальної верифікації.
Реалізація інтерпретатора
Dispatch loop
Основа будь-якої VM — dispatch loop. Три підходи за продуктивністю:
Switch-case (найпростіший):
loop {
let opcode = bytecode[pc];
pc += 1;
match opcode {
0x01 => { // ADD
let a = stack.pop()?;
let b = stack.pop()?;
stack.push(a.wrapping_add(b));
}
0x60 => { // PUSH1
let val = bytecode[pc];
pc += 1;
stack.push(val as u64);
}
0x00 => break, // HALT
_ => return Err(InvalidOpcode(opcode)),
}
}
Computed goto / threaded dispatch (C/C++, GCC extension): замість switch кожен opcode handler закінчується jump до наступного handler'а напрямки. ~20-30% швидше switch у гарячому циклі.
JIT-компіляція: компіляція bytecode в native machine code перед виконанням. Cranelift (Rust, використовується в Wasmtime) або LLVM backend. Дає 5–10x прискорення для compute-intensive контрактів. Додає значну складність: потрібен JIT-компілятор, security sandbox для generated code.
Для блокчейн VM JIT використовується рідко — execution environment повинен бути детерменіванним та sandboxed. WebAssembly (Wasmtime/Wasmer) береб це готовим рішенням.
Memory model
EVM має три типи пам'яті: stack (1024 елементів), memory (лінійна, растет по запиту, оплачується), storage (persistent, дорого). Кастомна VM може спростити це до двох:
Execution memory — локальна память функції, не персистентна. Flat array з bounds checking:
struct Memory {
data: Vec<u8>,
gas_used: u64,
}
impl Memory {
fn load(&mut self, offset: u32, size: u32) -> Result<&[u8]> {
self.expand_to(offset + size)?;
Ok(&self.data[offset as usize..(offset + size) as usize])
}
fn expand_to(&mut self, size: u32) -> Result<()> {
if size as usize > self.data.len() {
let gas = memory_expansion_cost(self.data.len(), size as usize);
self.gas_used += gas;
self.data.resize(size as usize, 0);
}
Ok(())
}
}
State storage — persistent key-value. За блокчейном — зазвичай Merkle Patricia Trie (Ethereum) або Jellyfish Merkle Tree (Aptos/Diem) для state commitment. Реалізація поверх RocksDB або LevelDB.
Gas metering та determinism
Детерменізм — обов'язкова вимога для блокчейн VM. Одинаковий bytecode + state + input повинні давати одинаковий результат на будь-якому вузлі. Це значить:
- Ніякого floating point arithmetic (IEEE 754 може давати різні результати)
- Детерменіваний порядок обходу hash map (у Rust використовуйте
BTreeMap, неHashMap) - Версіоновані opcodes: нові opcodes вводяться лише через hard fork
- Bounds для всіх операцій: максимальний stack size, максимальна memory, максимальний gas
Gas metering — механізм запобігання DoS через нескінченні цикли. Кожен opcode має фіксовану gas стоимість. Перед виконанням перевіряємо та списуємо:
fn execute_opcode(&mut self, opcode: u8) -> Result<()> {
let cost = GAS_TABLE[opcode as usize];
self.gas_remaining = self.gas_remaining
.checked_sub(cost)
.ok_or(OutOfGas)?;
// ... execute opcode
}
Gas table для кастомної VM проектується на основі benchmarking реальних opcode execution times. Дорогі операції (HASH, STORE) коштують пропорційно більше.
ZK-сумісна VM: специфіка
Якщо ціль — доводити виконання VM через ZK proof (zkEVM або кастомний zkVM), архітектура значно ускладнюється.
Execution trace: потрібно генерувати trace виконання програми — таблиця всіх state transitions для кожного кроку. Proof system верифікує, що trace коректен відносно ISA.
Step | PC | Opcode | Stack[0] | Stack[1] | Memory | ...
-----|-----|--------|----------|----------|--------|----
0 | 0 | PUSH 5 | — | — | [] | ...
1 | 2 | PUSH 3 | 5 | — | [] | ...
2 | 4 | ADD | 5 | 3 | [] | ...
3 | 5 | HALT | 8 | — | [] | ...
Constraint system: для кожного opcode пишуться поліноміальні constraints, які доводять коректність переходу. Наприклад, для ADD: stack_next[0] = stack[0] + stack[1]. Все це — algebraic circuit для proof system.
Reference проекти: RISC Zero (zkVM на RISC-V ISA), Valida (спеціально для ZK-proof friendly ISA), Polygon Miden (STARK-based zkVM). Вивчення їх открытых вихідних кодів — обов'язковий крок перед проектуванням власного zkVM.
Етапи розробки
| Фаза | Вміст | Тривалість |
|---|---|---|
| ISA design | Специфікація opcodes, gas table, memory model | 2–4 тиж |
| Interpreter | Базовий інтерпретатор, тести на коректність | 4–6 тиж |
| Gas metering & limits | Повна реалізація gas системи, DoS захист | 2–3 тиж |
| State storage | Merkle tree, persistence, state root | 3–5 тиж |
| Compiler/toolchain | Компілятор з high-level мови в bytecode | 4–8 тиж |
| Integration | Інтеграція у consensus layer блокчейну | 3–6 тиж |
| ZK circuit (опціонально) | Constraint system, proof generation | 8–16 тиж |
| Formal verification | Математична верифікація ключових властивостей | 4–8 тиж |
Розробка production-ready кастомної VM — це 12–24 місяці роботи для команди з 3–5 інженерів. Більшість проектів, які заявляють про "кастомну VM за квартал", реалізують тонку обгортку над WASM або EVM, а не підлинно кастомну архітектуру.







