darkfi/runtime/import/db/
db_lookup.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 darkfi_sdk::crypto::contract_id::{
20    ContractId, SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME,
21};
22use darkfi_serial::Decodable;
23use tracing::error;
24use wasmer::{FunctionEnvMut, WasmPtr};
25
26use crate::runtime::{
27    import::{acl::acl_allow, util::wasm_mem_read},
28    vm_runtime::{ContractSection, Env},
29};
30
31use super::DbHandle;
32
33/// Lookup an on-chain database handle from its name.
34/// If it exists, push it to the list of db_handles.
35///
36/// Returns the index of the DbHandle in the db_handles Vector on success.
37/// Otherwise, returns an error value.
38///
39/// This function can be called from any [`ContractSection`].
40///
41/// ## Permissions
42/// * `ContractSection::Deploy`
43/// * `ContractSection::Metadata`
44/// * `ContractSection::Exec`
45/// * `ContractSection::Update`
46pub(crate) fn db_lookup(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
47    db_lookup_internal(ctx, ptr, ptr_len, false)
48}
49
50/// Lookup a tx-local database handle from its name.
51/// Unlike the on-chain version, this will also initialize the database
52/// in-memory if it does not exist and the caller is allowed to write.
53/// Then it will push it to the list of transaction-local db_handles.
54///
55/// Returns the index of the DbHandle in the local_db_handles Vector on success.
56/// Otherwise, returns an error value.
57///
58/// This function can be called from any [`ContractSection`].
59///
60/// ## Permissions
61/// * `ContractSection::Deploy`
62/// * `ContractSection::Metadata`
63/// * `ContractSection::Exec`
64/// * `ContractSection::Update`
65pub(crate) fn db_lookup_local(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
66    db_lookup_internal(ctx, ptr, ptr_len, true)
67}
68
69/// Internal `db_lookup` function which branches to either on-chain or
70/// tx-local.
71///
72///
73/// ## Permissions
74/// * `ContractSection::Deploy`
75/// * `ContractSection::Metadata`
76/// * `ContractSection::Exec`
77/// * `ContractSection::Update`
78fn db_lookup_internal(
79    mut ctx: FunctionEnvMut<Env>,
80    ptr: WasmPtr<u8>,
81    ptr_len: u32,
82    local: bool,
83) -> i64 {
84    let lt = if local { "db_lookup_local" } else { "db_lookup" };
85    let (env, mut store) = ctx.data_and_store_mut();
86    let cid = env.contract_id;
87
88    // Enforce function ACL
89    if let Err(e) = acl_allow(
90        env,
91        &[
92            ContractSection::Deploy,
93            ContractSection::Metadata,
94            ContractSection::Exec,
95            ContractSection::Update,
96        ],
97    ) {
98        error!(
99            target: "runtime::db::{lt}",
100            "[WASM] [{cid}] {lt}() called in unauthorized section: {e}",
101        );
102        return darkfi_sdk::error::CALLER_ACCESS_DENIED
103    }
104
105    // Subtract used gas.
106    // Opening an existing db should be free (i.e. 1 gas unit).
107    env.subtract_gas(&mut store, 1);
108
109    // Get the wasm memory reader
110    let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
111        Ok(v) => v,
112        Err(e) => {
113            error!(
114                target: "runtime::db::{lt}",
115                "[WASM] [{cid}] {lt}(): Failed to read WASM memory: {e}",
116            );
117            return darkfi_sdk::error::DB_LOOKUP_FAILED
118        }
119    };
120
121    // Decode ContractId from memory
122    let read_cid: ContractId = match Decodable::decode(&mut buf_reader) {
123        Ok(v) => v,
124        Err(e) => {
125            error!(
126                target: "runtime::db::{lt}",
127                "[WASM] [{cid}] {lt}(): Failed to decode ContractId: {e}",
128            );
129            return darkfi_sdk::error::DB_LOOKUP_FAILED
130        }
131    };
132
133    // Decode DB name from memory
134    let db_name: String = match Decodable::decode(&mut buf_reader) {
135        Ok(v) => v,
136        Err(e) => {
137            error!(
138                target: "runtime::db::{lt}",
139                "[WASM] [{cid}] {lt}(): Failed to decode db_name: {e}",
140            );
141            return darkfi_sdk::error::DB_LOOKUP_FAILED
142        }
143    };
144
145    // Make sure we've read the entire buffer
146    if buf_reader.position() != ptr_len as u64 {
147        error!(
148            target: "runtime::db::{lt}",
149            "[WASM] [{cid}] {lt}(), Trailing bytes in argument stream",
150        );
151        return darkfi_sdk::error::DB_LOOKUP_FAILED
152    }
153
154    // We won't allow reading from the special zkas db or monotree db
155    if [SMART_CONTRACT_ZKAS_DB_NAME, SMART_CONTRACT_MONOTREE_DB_NAME].contains(&db_name.as_str()) {
156        error!(
157            target: "runtime::db::{lt}",
158            "[WASM] [{cid}] {lt}(): Attempted to lookup special db ({db_name})"
159        );
160        return darkfi_sdk::error::CALLER_ACCESS_DENIED
161    }
162
163    // Fetch the appropriate db
164    let tree_handle = if local {
165        let tree_handle = read_cid.hash_state_id(&db_name);
166
167        // Acquire the tx-local state
168        let mut db = env.tx_local.lock();
169
170        // If the caller is allowed to write, initialize the tx-local db
171        if read_cid == cid {
172            // Should be safe to unwrap here.
173            let db_cid = db.get_mut(&cid).unwrap();
174            db_cid.entry(tree_handle).or_default();
175        }
176
177        let Some(db_cid) = db.get(&read_cid) else {
178            // DB non-existent
179            return darkfi_sdk::error::DB_LOOKUP_FAILED
180        };
181
182        // Now check if the contract's db contains the db_name tree
183        if !db_cid.contains_key(&tree_handle) {
184            return darkfi_sdk::error::DB_LOOKUP_FAILED
185        }
186
187        // If it does, we can return the handle
188        tree_handle
189    } else {
190        // This takes lock of the blockchain overlay reference in the wasm env
191        let contracts = &env.blockchain.lock().unwrap().contracts;
192
193        // Lookup contract state
194        match contracts.lookup(&read_cid, &db_name) {
195            Ok(v) => v,
196            Err(_) => return darkfi_sdk::error::DB_LOOKUP_FAILED,
197        }
198    };
199
200    // Create the DbHandle
201    let db_handle = DbHandle::new(read_cid, tree_handle);
202    let mut db_handles =
203        if local { env.local_db_handles.borrow_mut() } else { env.db_handles.borrow_mut() };
204
205    // Make sure we don't duplicate the DbHandle in the vec
206    if let Some(index) = db_handles.iter().position(|x| x == &db_handle) {
207        return index as i64
208    }
209
210    // Push the new DbHandle to the Vec of opened DbHandles
211    match db_handles.len().try_into() {
212        Ok(db_handle_idx) => {
213            db_handles.push(db_handle);
214            db_handle_idx
215        }
216        Err(_) => {
217            error!(
218                target: "runtime::db::{lt}",
219                "[WASM] [{cid}] {lt}(): Too many open DbHandles",
220            );
221            darkfi_sdk::error::DB_LOOKUP_FAILED
222        }
223    }
224}