darkfi/runtime/
vm_runtime.rs

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