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}