darkfi/runtime/import/db/
db_init.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/// Create a new on-chain database instance for the calling contract.
34/// When created, push it to the list of db_handles.
35///
36/// This function expects to receive a pointer from which a `ContractId`
37/// and the `db_name` will be read.
38///
39/// This function should only be allowed in `ContractSection::Deploy`, as
40/// that is called when a contract is being (re)deployed and databases have
41/// to be created.
42///
43/// ## Permissions
44/// * `ContractSection::Deploy`
45pub(crate) fn db_init(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
46    let (env, mut store) = ctx.data_and_store_mut();
47    let cid = env.contract_id;
48
49    // Enforce function ACL
50    if let Err(e) = acl_allow(env, &[ContractSection::Deploy]) {
51        error!(
52            target: "runtime::db::db_init",
53            "[WASM] [{cid}] db_init(): Called in unauthorized section: {e}",
54        );
55        return darkfi_sdk::error::CALLER_ACCESS_DENIED
56    }
57
58    // Subtract used gas.
59    // TODO: There should probably be an additional fee to open a new sled tree.
60    env.subtract_gas(&mut store, 1);
61
62    // Get the wasm memory reader
63    let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
64        Ok(v) => v,
65        Err(e) => {
66            error!(
67                target: "runtime::db::db_init",
68                "[WASM] [{cid}] db_init(): Failed to read WASM memory: {e}",
69            );
70            return darkfi_sdk::error::DB_INIT_FAILED
71        }
72    };
73
74    // This takes lock of the blockchain overlay reference in the wasm env
75    let contracts = &env.blockchain.lock().unwrap().contracts;
76
77    // Deserialize the Contract ID from the reader
78    let read_cid: ContractId = match Decodable::decode(&mut buf_reader) {
79        Ok(v) => v,
80        Err(e) => {
81            error!(
82                target: "runtime::db::db_init",
83                "[WASM] [{cid}] db_init(): Failed decoding ContractId: {e}",
84            );
85            return darkfi_sdk::error::DB_INIT_FAILED
86        }
87    };
88
89    // Deserialize the db name to read from the reader
90    let db_name: String = match Decodable::decode(&mut buf_reader) {
91        Ok(v) => v,
92        Err(e) => {
93            error!(
94                target: "runtime::db::db_init",
95                "[WASM] [{cid}] db_init(): Failed decoding db_name: {e}",
96            );
97            return darkfi_sdk::error::DB_INIT_FAILED
98        }
99    };
100
101    // Make sure we've read the entire buffer
102    if buf_reader.position() != ptr_len as u64 {
103        error!(
104            target: "runtime::db::db_init",
105            "[WASM] [{cid}] db_init(): Trailing bytes in argument stream",
106        );
107        return darkfi_sdk::error::DB_INIT_FAILED
108    }
109
110    // We cannot allow initializing the special zkas db or monotree db
111    if [SMART_CONTRACT_ZKAS_DB_NAME, SMART_CONTRACT_MONOTREE_DB_NAME].contains(&db_name.as_str()) {
112        error!(
113            target: "runtime::db::db_init",
114            "[WASM] [{cid}] db_init(): Attempted to init special db ({db_name})",
115        );
116        return darkfi_sdk::error::CALLER_ACCESS_DENIED
117    }
118
119    // Nor can we allow another contract to initialize a db for someone else:
120    if cid != read_cid {
121        error!(
122            target: "runtime::db::db_init",
123            "[WASM] [{cid}] db_init(): Unauthorized ContractId",
124        );
125        return darkfi_sdk::error::CALLER_ACCESS_DENIED
126    }
127
128    // Now try to initialize the tree. If this returns an error,
129    // it usually means that this DB was already initialized.
130    // An alternative error might happen if something in sled fails,
131    // for this we should take care to stop the node or do something to
132    // be able to gracefully recover.
133    // (src/blockchain/contract_store.rs holds this init() function)
134    let tree_handle = match contracts.init(&read_cid, &db_name) {
135        Ok(v) => v,
136        Err(e) => {
137            error!(
138                target: "runtime::db::db_init",
139                "[WASM] [{cid}] db_init(): Failed to init db: {e}",
140            );
141            return darkfi_sdk::error::DB_INIT_FAILED
142        }
143    };
144
145    // Create the DbHandle
146    let db_handle = DbHandle::new(read_cid, tree_handle);
147    let mut db_handles = env.db_handles.borrow_mut();
148
149    // Make sure we don't duplicate the DbHandle in the vec
150    if let Some(index) = db_handles.iter().position(|x| x == &db_handle) {
151        return index as i64
152    }
153
154    // Push the new DbHandle into the Vec of opened DbHandle
155    match db_handles.len().try_into() {
156        Ok(db_handle_idx) => {
157            db_handles.push(db_handle);
158            db_handle_idx
159        }
160        Err(_) => {
161            error!(
162                target: "runtime::db::db_init",
163                "[WASM] [{cid}] db_init(): Too many open DbHandles",
164            );
165            darkfi_sdk::error::DB_INIT_FAILED
166        }
167    }
168}