darkfi/runtime/import/db/
db_get.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_serial::Decodable;
20use tracing::{debug, error};
21use wasmer::{FunctionEnvMut, WasmPtr};
22
23use crate::runtime::{
24    import::{acl::acl_allow, util::wasm_mem_read},
25    vm_runtime::{ContractSection, Env},
26};
27
28/// Reads a value by key from the on-chain key-value store.
29///
30/// On success, returns the length of the `objects` Vector in the environment.
31/// Otherwise, returns an error code.
32///
33/// ## Permissions
34/// * `ContractSection::Deploy`
35/// * `ContractSection::Metadata`
36/// * `ContractSection::Exec`
37pub(crate) fn db_get(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
38    db_get_internal(ctx, ptr, ptr_len, false)
39}
40
41/// Reads a value by key from the tx-local key-value store.
42///
43/// On success, returns the length of the `objects` Vector in the environment.
44/// Otherwise, returns an error code.
45///
46/// ## Permissions
47/// * `ContractSection::Deploy`
48/// * `ContractSection::Metadata`
49/// * `ContractSection::Exec`
50pub(crate) fn db_get_local(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
51    db_get_internal(ctx, ptr, ptr_len, true)
52}
53
54/// Internal `db_get` function which branches to either on-chain or tx-local.
55///
56/// ## Permissions
57/// * `ContractSection::Deploy`
58/// * `ContractSection::Metadata`
59/// * `ContractSection::Exec`
60pub(crate) fn db_get_internal(
61    mut ctx: FunctionEnvMut<Env>,
62    ptr: WasmPtr<u8>,
63    ptr_len: u32,
64    local: bool,
65) -> i64 {
66    let lt = if local { "db_get_local" } else { "db_get" };
67    let (env, mut store) = ctx.data_and_store_mut();
68    let cid = env.contract_id;
69
70    // Enforce function ACL
71    if let Err(e) =
72        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
73    {
74        error!(
75            target: "runtime::db::{lt}",
76            "[WASM] [{cid}] {lt}(): Called in unauthorized section: {e}",
77        );
78        return darkfi_sdk::error::CALLER_ACCESS_DENIED
79    }
80
81    // Subtract used gas.
82    env.subtract_gas(&mut store, 1);
83
84    // Get the WASM memory reader
85    let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
86        Ok(v) => v,
87        Err(e) => {
88            error!(
89                target: "runtime::db::{lt}",
90                "[WASM] [{cid}] {lt}(): Failed to read wasm memory: {e}",
91            );
92            return darkfi_sdk::error::DB_GET_FAILED
93        }
94    };
95
96    // Decode DbHandle index
97    let db_handle_index: u32 = match Decodable::decode(&mut buf_reader) {
98        Ok(v) => v,
99        Err(e) => {
100            error!(
101                target: "runtime::db::{lt}",
102                "[WASM] [{cid}] {lt}(): Failed to decode DbHandle: {e}",
103            );
104            return darkfi_sdk::error::DB_GET_FAILED
105        }
106    };
107
108    let db_handle_index = db_handle_index as usize;
109
110    // Decode key for key-value pair that we wish to retrieve
111    let key: Vec<u8> = match Decodable::decode(&mut buf_reader) {
112        Ok(v) => v,
113        Err(e) => {
114            error!(
115                target: "runtime::db::db_get",
116                "[WASM] [{cid}] db_get(): Failed to decode key from vec: {e}",
117            );
118            return darkfi_sdk::error::DB_GET_FAILED
119        }
120    };
121
122    // Make sure there are no trailing bytes in the buffer.
123    if buf_reader.position() != ptr_len as u64 {
124        error!(
125            target: "runtime::db::{lt}",
126            "[WASM] [{cid}] {lt}(): Trailing bytes in argument stream",
127        );
128        return darkfi_sdk::error::DB_GET_FAILED
129    }
130
131    // Fetch requested db handles
132    let db_handles = if local { env.local_db_handles.borrow() } else { env.db_handles.borrow() };
133
134    // Ensure that the index is within bounds
135    if db_handles.len() <= db_handle_index {
136        error!(
137            target: "runtime::db::{lt}",
138            "[WASM] [{cid}] {lt}(): Requested DbHandle that is out of bounds",
139        );
140        return darkfi_sdk::error::DB_GET_FAILED
141    }
142
143    // Get DbHandle using db_handle_index
144    let db_handle = &db_handles[db_handle_index];
145
146    // Retrieve data using the `key`
147    let ret: Option<Vec<u8>> = if local {
148        // tx-local db
149        let db = env.tx_local.lock();
150        let Some(db_cid) = db.get(&db_handle.contract_id) else {
151            error!(
152                target: "runtime::db::{lt}",
153                "[WASM] [{cid}] {lt}(): Could not find db for {}",
154                db_handle.contract_id,
155            );
156            return darkfi_sdk::error::DB_GET_FAILED
157        };
158
159        let Some(tree) = db_cid.get(&db_handle.tree) else {
160            error!(
161                target: "runtime::db::{lt}",
162                "[WASM] [{cid}] {lt}(): Could not find db tree for {}",
163                db_handle.contract_id,
164            );
165            return darkfi_sdk::error::DB_GET_FAILED
166        };
167
168        tree.get(&key).cloned()
169    } else {
170        match env.blockchain.lock().unwrap().overlay.lock().unwrap().get(&db_handle.tree, &key) {
171            Ok(v) => v.map(|iv| iv.to_vec()),
172            Err(e) => {
173                error!(
174                    target: "runtime::db::{lt}",
175                    "[WASM] [{cid}] {lt}(): Internal error getting from tree: {e}",
176                );
177                return darkfi_sdk::error::DB_GET_FAILED
178            }
179        }
180    };
181    drop(db_handles);
182
183    // Return special error if the data is empty
184    let Some(return_data) = ret else {
185        debug!(
186            target: "runtime::db::{lt}",
187            "[WASM] [{cid}] {lt}(): Return data is empty",
188        );
189        return darkfi_sdk::error::DB_GET_EMPTY
190    };
191
192    if return_data.len() > u32::MAX as usize {
193        return darkfi_sdk::error::DATA_TOO_LARGE
194    }
195
196    // Subtract used gas. Here we count the length of the data read from db.
197    env.subtract_gas(&mut store, return_data.len() as u64);
198
199    // Copy the data (Vec<u8>) to the VM by pushing it to the objects Vector.
200    let mut objects = env.objects.borrow_mut();
201    if objects.len() == u32::MAX as usize {
202        return darkfi_sdk::error::DATA_TOO_LARGE
203    }
204
205    // Return the length of the objects Vector.
206    // This is the location of the data that was retrieved and pushed
207    objects.push(return_data.to_vec());
208    (objects.len() - 1) as i64
209}