darkfi/runtime/
vm_runtime.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{
20    cell::{Cell, RefCell},
21    sync::Arc,
22};
23
24use darkfi_sdk::{
25    crypto::contract_id::{
26        ContractId, SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME,
27    },
28    tx::TransactionHash,
29    wasm, AsHex,
30};
31use darkfi_serial::serialize;
32use tracing::{debug, error, info};
33use wasmer::{
34    imports, sys::CompilerConfig, wasmparser::Operator, AsStoreMut, AsStoreRef, Function,
35    FunctionEnv, Instance, Memory, MemoryType, MemoryView, Module, Pages, Store, Value,
36    WASM_PAGE_SIZE,
37};
38use wasmer_compiler_singlepass::Singlepass;
39use wasmer_middlewares::{
40    metering::{get_remaining_points, set_remaining_points, MeteringPoints},
41    Metering,
42};
43
44use super::{import, import::db::DbHandle, memory::MemoryManipulation};
45use crate::{blockchain::BlockchainOverlayPtr, Error, Result};
46
47/// Name of the wasm linear memory in our guest module
48const MEMORY: &str = "memory";
49
50/// Gas limit for a single contract call (Single WASM instance)
51pub const GAS_LIMIT: u64 = 400_000_000;
52
53// ANCHOR: contract-section
54#[derive(Clone, Copy, PartialEq)]
55pub enum ContractSection {
56    /// Setup function of a contract
57    Deploy,
58    /// Entrypoint function of a contract
59    Exec,
60    /// Apply function of a contract
61    Update,
62    /// Metadata
63    Metadata,
64    /// Placeholder state before any initialization
65    Null,
66}
67// ANCHOR_END: contract-section
68
69impl ContractSection {
70    pub const fn name(&self) -> &str {
71        match self {
72            Self::Deploy => "__initialize",
73            Self::Exec => "__entrypoint",
74            Self::Update => "__update",
75            Self::Metadata => "__metadata",
76            Self::Null => unreachable!(),
77        }
78    }
79}
80
81/// The WASM VM runtime environment instantiated for every smart contract that runs.
82pub struct Env {
83    /// Blockchain overlay access
84    pub blockchain: BlockchainOverlayPtr,
85    /// Overlay tree handles used with `db_*`
86    pub db_handles: RefCell<Vec<DbHandle>>,
87    /// The contract ID being executed
88    pub contract_id: ContractId,
89    /// The compiled wasm bincode being executed,
90    pub contract_bincode: Vec<u8>,
91    /// The contract section being executed
92    pub contract_section: ContractSection,
93    /// State update produced by a smart contract function call
94    pub contract_return_data: Cell<Option<Vec<u8>>>,
95    /// Logs produced by the contract
96    pub logs: RefCell<Vec<String>>,
97    /// Direct memory access to the VM
98    pub memory: Option<Memory>,
99    /// Object store for transferring memory from the host to VM
100    pub objects: RefCell<Vec<Vec<u8>>>,
101    /// Block height number runtime verifies against.
102    /// For unconfirmed txs, this will be the current max height in the chain.
103    pub verifying_block_height: u32,
104    /// Currently configured block time target, in seconds
105    pub block_target: u32,
106    /// The hash for this transaction the runtime is being run against.
107    pub tx_hash: TransactionHash,
108    /// The index for this call in the transaction
109    pub call_idx: u8,
110    /// Parent `Instance`
111    pub instance: Option<Arc<Instance>>,
112}
113
114impl Env {
115    /// Provide safe access to the memory
116    /// (it must be initialized before it can be used)
117    ///
118    ///     // ctx: FunctionEnvMut<Env>
119    ///     let env = ctx.data();
120    ///     let memory = env.memory_view(&ctx);
121    ///
122    pub fn memory_view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> {
123        self.memory().view(store)
124    }
125
126    /// Get memory, that needs to have been set fist
127    pub fn memory(&self) -> &Memory {
128        self.memory.as_ref().unwrap()
129    }
130
131    /// Subtract given gas cost from remaining gas in the current runtime
132    pub fn subtract_gas(&mut self, ctx: &mut impl AsStoreMut, gas: u64) {
133        match get_remaining_points(ctx, self.instance.as_ref().unwrap()) {
134            MeteringPoints::Remaining(rem) => {
135                if gas > rem {
136                    set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
137                } else {
138                    set_remaining_points(ctx, self.instance.as_ref().unwrap(), rem - gas);
139                }
140            }
141            MeteringPoints::Exhausted => {
142                set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
143            }
144        }
145    }
146}
147
148/// Define a wasm runtime.
149pub struct Runtime {
150    /// A wasm instance
151    pub instance: Arc<Instance>,
152    /// A wasm store (global state)
153    pub store: Store,
154    // Wrapper for [`Env`], defined above.
155    pub ctx: FunctionEnv<Env>,
156}
157
158impl Runtime {
159    /// Create a new wasm runtime instance that contains the given wasm module.
160    pub fn new(
161        wasm_bytes: &[u8],
162        blockchain: BlockchainOverlayPtr,
163        contract_id: ContractId,
164        verifying_block_height: u32,
165        block_target: u32,
166        tx_hash: TransactionHash,
167        call_idx: u8,
168    ) -> Result<Self> {
169        info!(target: "runtime::vm_runtime", "[WASM] Instantiating a new runtime");
170        // This function will be called for each `Operator` encountered during
171        // the wasm module execution. It should return the cost of the operator
172        // that it received as its first argument. For now, every wasm opcode
173        // has a cost of `1`.
174        // https://docs.rs/wasmparser/latest/wasmparser/enum.Operator.html
175        let cost_function = |_operator: &Operator| -> u64 { 1 };
176
177        // `Metering` needs to be configured with a limit and a cost function.
178        // For each `Operator`, the metering middleware will call the cost
179        // function and subtract the cost from the remaining points.
180        let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
181
182        // Define the compiler and middleware, engine, and store
183        let mut compiler_config = Singlepass::new();
184        compiler_config.push_middleware(metering);
185        let mut store = Store::new(compiler_config);
186
187        // Create a larger Memory for the instance
188        let memory_type = MemoryType::new(
189            Pages(256),        // init: 16 MB (256 * 64KB)
190            Some(Pages(4096)), // max: 256 MB
191            false,
192        );
193        let memory = Memory::new(&mut store, memory_type)?;
194
195        debug!(target: "runtime::vm_runtime", "Compiling module");
196        let module = Module::new(&store, wasm_bytes)?;
197
198        // Initialize data
199        let db_handles = RefCell::new(vec![]);
200        let logs = RefCell::new(vec![]);
201
202        debug!(target: "runtime::vm_runtime", "Importing functions");
203
204        let ctx = FunctionEnv::new(
205            &mut store,
206            Env {
207                blockchain,
208                db_handles,
209                contract_id,
210                contract_bincode: wasm_bytes.to_vec(),
211                contract_section: ContractSection::Null,
212                contract_return_data: Cell::new(None),
213                logs,
214                memory: Some(memory.clone()),
215                objects: RefCell::new(vec![]),
216                verifying_block_height,
217                block_target,
218                tx_hash,
219                call_idx,
220                instance: None,
221            },
222        );
223
224        let imports = imports! {
225            "env" => {
226                "memory" => memory,
227
228                "drk_log_" => Function::new_typed_with_env(
229                    &mut store,
230                    &ctx,
231                    import::util::drk_log,
232                ),
233
234                "set_return_data_" => Function::new_typed_with_env(
235                    &mut store,
236                    &ctx,
237                    import::util::set_return_data,
238                ),
239
240                "db_init_" => Function::new_typed_with_env(
241                    &mut store,
242                    &ctx,
243                    import::db::db_init,
244                ),
245
246                "db_lookup_" => Function::new_typed_with_env(
247                    &mut store,
248                    &ctx,
249                    import::db::db_lookup,
250                ),
251
252                "db_get_" => Function::new_typed_with_env(
253                    &mut store,
254                    &ctx,
255                    import::db::db_get,
256                ),
257
258                "db_contains_key_" => Function::new_typed_with_env(
259                    &mut store,
260                    &ctx,
261                    import::db::db_contains_key,
262                ),
263
264                "db_set_" => Function::new_typed_with_env(
265                    &mut store,
266                    &ctx,
267                    import::db::db_set,
268                ),
269
270                "db_del_" => Function::new_typed_with_env(
271                    &mut store,
272                    &ctx,
273                    import::db::db_del,
274                ),
275
276                "zkas_db_set_" => Function::new_typed_with_env(
277                    &mut store,
278                    &ctx,
279                    import::db::zkas_db_set,
280                ),
281
282                "get_object_bytes_" => Function::new_typed_with_env(
283                    &mut store,
284                    &ctx,
285                    import::util::get_object_bytes,
286                ),
287
288                "get_object_size_" => Function::new_typed_with_env(
289                    &mut store,
290                    &ctx,
291                    import::util::get_object_size,
292                ),
293
294                "merkle_add_" => Function::new_typed_with_env(
295                    &mut store,
296                    &ctx,
297                    import::merkle::merkle_add,
298                ),
299
300                "sparse_merkle_insert_batch_" => Function::new_typed_with_env(
301                    &mut store,
302                    &ctx,
303                    import::smt::sparse_merkle_insert_batch,
304                ),
305
306                "get_verifying_block_height_" => Function::new_typed_with_env(
307                    &mut store,
308                    &ctx,
309                    import::util::get_verifying_block_height,
310                ),
311
312                "get_block_target_" => Function::new_typed_with_env(
313                    &mut store,
314                    &ctx,
315                    import::util::get_block_target,
316                ),
317
318                "get_tx_hash_" => Function::new_typed_with_env(
319                    &mut store,
320                    &ctx,
321                    import::util::get_tx_hash,
322                ),
323
324                "get_call_index_" => Function::new_typed_with_env(
325                    &mut store,
326                    &ctx,
327                    import::util::get_call_index,
328                ),
329
330                "get_blockchain_time_" => Function::new_typed_with_env(
331                    &mut store,
332                    &ctx,
333                    import::util::get_blockchain_time,
334                ),
335
336                "get_last_block_height_" => Function::new_typed_with_env(
337                    &mut store,
338                    &ctx,
339                    import::util::get_last_block_height,
340                ),
341
342                "get_tx_" => Function::new_typed_with_env(
343                    &mut store,
344                    &ctx,
345                    import::util::get_tx,
346                ),
347
348                "get_tx_location_" => Function::new_typed_with_env(
349                    &mut store,
350                    &ctx,
351                    import::util::get_tx_location,
352                ),
353            }
354        };
355
356        debug!(target: "runtime::vm_runtime", "Instantiating module");
357        let instance = Arc::new(Instance::new(&mut store, &module, &imports)?);
358
359        let env_mut = ctx.as_mut(&mut store);
360        env_mut.memory = Some(instance.exports.get_with_generics(MEMORY)?);
361        env_mut.instance = Some(Arc::clone(&instance));
362
363        Ok(Self { instance, store, ctx })
364    }
365
366    /// Call a contract method defined by a [`ContractSection`] using a supplied
367    /// payload. Returns a `Vec<u8>` corresponding to the result data of the call.
368    /// For calls that do not return any data, an empty `Vec<u8>` is returned.
369    fn call(&mut self, section: ContractSection, payload: &[u8]) -> Result<Vec<u8>> {
370        debug!(target: "runtime::vm_runtime", "Calling {} method", section.name());
371
372        let env_mut = self.ctx.as_mut(&mut self.store);
373        env_mut.contract_section = section;
374        // Verify contract's return data is empty, or quit.
375        assert!(env_mut.contract_return_data.take().is_none());
376
377        // Clear the logs
378        let _ = env_mut.logs.take();
379
380        // Serialize the payload for the format the wasm runtime is expecting.
381        let payload = Self::serialize_payload(&env_mut.contract_id, payload);
382
383        // Allocate enough memory for the payload and copy it into the memory.
384        let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
385        self.set_memory_page_size(pages_required as u32)?;
386        self.copy_to_memory(&payload)?;
387
388        debug!(target: "runtime::vm_runtime", "Getting {} function", section.name());
389        let entrypoint = self.instance.exports.get_function(section.name())?;
390
391        // Call the entrypoint. On success, `call` returns a WASM [`Value`]. (The
392        // value may be empty.) This value functions similarly to a UNIX exit code.
393        // The following section is intended to unwrap the exit code and handle fatal
394        // errors in the Wasmer runtime. The value itself and the return data of the
395        // contract are processed later.
396        debug!(target: "runtime::vm_runtime", "Executing wasm");
397        let ret = match entrypoint.call(&mut self.store, &[Value::I32(0_i32)]) {
398            Ok(retvals) => {
399                self.print_logs();
400                info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
401                retvals
402            }
403            Err(e) => {
404                self.print_logs();
405                info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
406                // WasmerRuntimeError panics are handled here. Return from run() immediately.
407                error!(target: "runtime::vm_runtime", "[WASM] Wasmer Runtime Error: {e:#?}");
408                return Err(e.into())
409            }
410        };
411
412        debug!(target: "runtime::vm_runtime", "wasm executed successfully");
413
414        // Move the contract's return data into `retdata`.
415        let env_mut = self.ctx.as_mut(&mut self.store);
416        env_mut.contract_section = ContractSection::Null;
417        let retdata = env_mut.contract_return_data.take().unwrap_or_default();
418
419        // Determine the return value of the contract call. If `ret` is empty,
420        // assumed that the contract call was successful.
421        let retval: i64 = match ret.len() {
422            0 => {
423                // Return a success value if there is no return value from
424                // the contract.
425                debug!(target: "runtime::vm_runtime", "Contract has no return value (expected)");
426                wasm::entrypoint::SUCCESS
427            }
428            _ => {
429                match ret[0] {
430                    Value::I64(v) => {
431                        debug!(target: "runtime::vm_runtime", "Contract returned: {:?}", ret[0]);
432                        v
433                    }
434                    // The only supported return type is i64, so panic if another
435                    // value is returned.
436                    _ => unreachable!("Got unexpected result return value: {ret:?}"),
437                }
438            }
439        };
440
441        // Check the integer return value of the call. A value of `entrypoint::SUCCESS` (i.e. zero)
442        // corresponds to a successful contract call; in this case, we return the contract's
443        // result data. Otherwise, map the integer return value to a [`ContractError`].
444        match retval {
445            wasm::entrypoint::SUCCESS => Ok(retdata),
446            _ => {
447                let err = darkfi_sdk::error::ContractError::from(retval);
448                error!(target: "runtime::vm_runtime", "[WASM] Contract returned: {err:?}");
449                Err(Error::ContractError(err))
450            }
451        }
452    }
453
454    /// This function runs when a smart contract is initially deployed, or re-deployed.
455    ///
456    /// The runtime will look for an `__initialize` symbol in the wasm code, and execute
457    /// it if found. Optionally, it is possible to pass in a payload for any kind of special
458    /// instructions the developer wants to manage in the initialize function.
459    ///
460    /// This process is supposed to set up the overlay trees for storing the smart contract
461    /// state, and it can create, delete, modify, read, and write to databases it's allowed to.
462    /// The permissions for this are handled by the `ContractId` in the overlay db API so we
463    /// assume that the contract is only able to do write operations on its own overlay trees.
464    pub fn deploy(&mut self, payload: &[u8]) -> Result<()> {
465        let cid = self.ctx.as_ref(&self.store).contract_id;
466        info!(target: "runtime::vm_runtime", "[WASM] Running deploy() for ContractID: {cid}");
467
468        // Scoped for borrows
469        {
470            let env_mut = self.ctx.as_mut(&mut self.store);
471            // We always want to have the zkas db as index 0 in db handles and batches when
472            // deploying.
473            let contracts = &env_mut.blockchain.lock().unwrap().contracts;
474
475            // Open or create the zkas db tree for this contract
476            let zkas_tree_handle =
477                match contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME) {
478                    Ok(v) => v,
479                    Err(_) => contracts.init(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME)?,
480                };
481
482            // Create the monotree db tree for this contract,
483            // if it doesn't exists.
484            if contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME).is_err() {
485                contracts.init(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME)?;
486            }
487
488            let mut db_handles = env_mut.db_handles.borrow_mut();
489            db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle));
490        }
491
492        //debug!(target: "runtime::vm_runtime", "[WASM] payload: {payload:?}");
493        let _ = self.call(ContractSection::Deploy, payload)?;
494
495        // Update the wasm bincode in the ContractStore wasm tree if the deploy exec passed successfully.
496        let env_mut = self.ctx.as_mut(&mut self.store);
497        env_mut
498            .blockchain
499            .lock()
500            .unwrap()
501            .contracts
502            .insert(env_mut.contract_id, &env_mut.contract_bincode)?;
503
504        info!(target: "runtime::vm_runtime", "[WASM] Successfully deployed ContractID: {cid}");
505        Ok(())
506    }
507
508    /// This function runs first in the entire scheme of executing a smart contract.
509    ///
510    /// The runtime will look for a `__metadata` symbol in the wasm code and execute it.
511    /// It is supposed to correctly extract public inputs for any ZK proofs included
512    /// in the contract calls, and also extract the public keys used to verify the
513    /// call/transaction signatures.
514    pub fn metadata(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
515        let cid = self.ctx.as_ref(&self.store).contract_id;
516        info!(target: "runtime::vm_runtime", "[WASM] Running metadata() for ContractID: {cid}");
517
518        debug!(target: "runtime::vm_runtime", "metadata payload: {}", payload.hex());
519        let ret = self.call(ContractSection::Metadata, payload)?;
520        debug!(target: "runtime::vm_runtime", "metadata returned: {:?}", ret.hex());
521
522        info!(target: "runtime::vm_runtime", "[WASM] Successfully got metadata ContractID: {cid}");
523        Ok(ret)
524    }
525
526    /// This function runs when someone wants to execute a smart contract.
527    ///
528    /// The runtime will look for an `__entrypoint` symbol in the wasm code, and
529    /// execute it if found. A payload is also passed as an instruction that can
530    /// be used inside the vm by the runtime.
531    pub fn exec(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
532        let cid = self.ctx.as_ref(&self.store).contract_id;
533        info!(target: "runtime::vm_runtime", "[WASM] Running exec() for ContractID: {cid}");
534
535        debug!(target: "runtime::vm_runtime", "exec payload: {}", payload.hex());
536        let ret = self.call(ContractSection::Exec, payload)?;
537        debug!(target: "runtime::vm_runtime", "exec returned: {:?}", ret.hex());
538
539        info!(target: "runtime::vm_runtime", "[WASM] Successfully executed ContractID: {cid}");
540        Ok(ret)
541    }
542
543    /// This function runs after successful execution of `exec` and tries to
544    /// apply the state change to the overlay databases.
545    ///
546    /// The runtime will lok for an `__update` symbol in the wasm code, and execute
547    /// it if found. The function does not take an arbitrary payload, but just takes
548    /// a state update from `env` and passes it into the wasm runtime.
549    pub fn apply(&mut self, update: &[u8]) -> Result<()> {
550        let cid = self.ctx.as_ref(&self.store).contract_id;
551        info!(target: "runtime::vm_runtime", "[WASM] Running apply() for ContractID: {cid}");
552
553        debug!(target: "runtime::vm_runtime", "apply payload: {:?}", update.hex());
554        let ret = self.call(ContractSection::Update, update)?;
555        debug!(target: "runtime::vm_runtime", "apply returned: {:?}", ret.hex());
556
557        info!(target: "runtime::vm_runtime", "[WASM] Successfully applied ContractID: {cid}");
558        Ok(())
559    }
560
561    /// Prints the wasm contract logs.
562    fn print_logs(&self) {
563        let logs = self.ctx.as_ref(&self.store).logs.borrow();
564        for msg in logs.iter() {
565            info!(target: "runtime::vm_runtime", "[WASM] Contract log: {msg}");
566        }
567    }
568
569    /// Calculate the remaining gas using wasm's concept
570    /// of metering points.
571    pub fn gas_used(&mut self) -> u64 {
572        let remaining_points = get_remaining_points(&mut self.store, &self.instance);
573
574        match remaining_points {
575            MeteringPoints::Remaining(rem) => {
576                if rem > GAS_LIMIT {
577                    // This should never occur, but catch it explicitly to avoid
578                    // potential underflow issues when calculating `remaining_points`.
579                    unreachable!("Remaining wasm points exceed GAS_LIMIT");
580                }
581                GAS_LIMIT - rem
582            }
583            MeteringPoints::Exhausted => GAS_LIMIT + 1,
584        }
585    }
586
587    // Return a message informing the user whether there is any
588    // gas remaining. Values equal to GAS_LIMIT are not considered
589    // to be exhausted. e.g. Using 100/100 gas should not give a
590    // 'gas exhausted' message.
591    fn gas_info(&mut self) -> String {
592        let gas_used = self.gas_used();
593
594        if gas_used > GAS_LIMIT {
595            format!("Gas fully exhausted: {gas_used}/{GAS_LIMIT}")
596        } else {
597            format!("Gas used: {gas_used}/{GAS_LIMIT}")
598        }
599    }
600
601    /// Set the memory page size. Returns the previous memory size.
602    fn set_memory_page_size(&mut self, pages: u32) -> Result<Pages> {
603        // Grab memory by value
604        let memory = self.take_memory();
605        // Modify the memory
606        let ret = memory.grow(&mut self.store, Pages(pages))?;
607        // Replace the memory back again
608        self.ctx.as_mut(&mut self.store).memory = Some(memory);
609        Ok(ret)
610    }
611
612    /// Take Memory by value. Needed to modify the Memory object
613    /// Will panic if memory isn't set.
614    fn take_memory(&mut self) -> Memory {
615        let env_memory = &mut self.ctx.as_mut(&mut self.store).memory;
616        let memory = env_memory.take();
617        memory.expect("memory should be set")
618    }
619
620    /// Copy payload to the start of the memory
621    fn copy_to_memory(&self, payload: &[u8]) -> Result<()> {
622        // Payload is copied to index 0.
623        // Get the memory view
624        let env = self.ctx.as_ref(&self.store);
625        let memory_view = env.memory_view(&self.store);
626        memory_view.write_slice(payload, 0)
627    }
628
629    /// Serialize contract payload to the format accepted by the runtime functions.
630    /// We keep the same payload as a slice of bytes, and prepend it with a [`ContractId`],
631    /// and then a little-endian u64 to tell the payload's length.
632    fn serialize_payload(cid: &ContractId, payload: &[u8]) -> Vec<u8> {
633        let ser_cid = serialize(cid);
634        let payload_len = payload.len();
635        let mut out = Vec::with_capacity(ser_cid.len() + 8 + payload_len);
636        out.extend_from_slice(&ser_cid);
637        out.extend_from_slice(&(payload_len as u64).to_le_bytes());
638        out.extend_from_slice(payload);
639        out
640    }
641}