darkfi/runtime/import/db/
zkas_db_set.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::wasm;
20use darkfi_serial::{deserialize, serialize, Decodable};
21use tracing::{debug, error, info};
22use wasmer::{FunctionEnvMut, WasmPtr};
23
24use crate::{
25    runtime::{
26        import::{acl::acl_allow, util::wasm_mem_read},
27        vm_runtime::{ContractSection, Env},
28    },
29    zk::{empty_witnesses, VerifyingKey, ZkCircuit},
30    zkas::ZkBinary,
31};
32
33/// Given a zkas circuit, create a VerifyingKey and insert them both into
34/// the on-chain db.
35///
36/// Returns `SUCCESS` on success, otherwise returns an error code.
37///
38/// ## Permissions
39/// * `ContractSection::Deploy`
40pub(crate) fn zkas_db_set(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
41    let (env, mut store) = ctx.data_and_store_mut();
42    let cid = env.contract_id;
43
44    // Enforce function ACL
45    if let Err(e) = acl_allow(env, &[ContractSection::Deploy]) {
46        error!(
47            target: "runtime::db::zkas_db_set",
48            "[WASM] [{cid}] zkas_db_set(): Called in unauthorized section: {e}",
49        );
50        return darkfi_sdk::error::CALLER_ACCESS_DENIED
51    }
52
53    // Get the wasm memory reader
54    let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
55        Ok(v) => v,
56        Err(e) => {
57            error!(
58                target: "runtime::db::zkas_db_set",
59                "[WASM] [{cid}] zkas_db_set(): Failed to read wasm memory: {e}",
60            );
61            return darkfi_sdk::error::DB_SET_FAILED
62        }
63    };
64
65    // Deserialize the ZkBinary bytes from the buffer
66    let zkbin_bytes: Vec<u8> = match Decodable::decode(&mut buf_reader) {
67        Ok(zkbin) => zkbin,
68        Err(e) => {
69            error!(
70                target: "runtime::db::zkas_db_set",
71                "[WASM] [{cid}] zkas_db_set(): Could not deserialize bytes from buffer: {e}"
72            );
73            return darkfi_sdk::error::DB_SET_FAILED
74        }
75    };
76
77    // Make sure there are no trailing bytes in the buffer.
78    if buf_reader.position() != ptr_len as u64 {
79        error!(
80            target: "runtime::db::zkas_db_set",
81            "[WASM] [{cid}] zkas_db_set(): Trailing bytes in argument stream",
82        );
83        return darkfi_sdk::error::DB_SET_FAILED
84    }
85
86    // Validate the bytes by decoding them into the ZkBinary format
87    let zkbin = match ZkBinary::decode(&zkbin_bytes, false) {
88        Ok(zkbin) => zkbin,
89        Err(e) => {
90            error!(
91                target: "runtime::db::zkas_db_set",
92                "[WASM] [{cid}] zkas_db_set(): Invalid zkas bincode passed to function: {e}"
93            );
94            return darkfi_sdk::error::DB_SET_FAILED
95        }
96    };
97
98    // Subtract used gas. We count 100 gas per opcode, witness, and literal.
99    // This is likely bad.
100    // TODO: This should be better-priced.
101    let gas_cost =
102        (zkbin.literals.len() + zkbin.witnesses.len() + zkbin.opcodes.len()) as u64 * 100;
103    env.subtract_gas(&mut store, gas_cost);
104
105    // Because of `Runtime::Deploy`, we should be sure that the zkas db is index zero.
106    let db_handles = env.db_handles.borrow();
107    let db_handle = &db_handles[0];
108    // Redundant check
109    if db_handle.contract_id != cid {
110        error!(
111            target: "runtime::db::zkas_db_set",
112            "[WASM] [{cid}] zkas_db_set(): Internal error, zkas db at index 0 incorrect"
113        );
114        return darkfi_sdk::error::DB_SET_FAILED
115    }
116
117    // Check if there is existing bincode and compare it. Return DB_SUCCESS if
118    // they're the same. The assumption should be that VerifyingKey was generated
119    // already so we can skip things after this guard.
120    match env
121        .blockchain
122        .lock()
123        .unwrap()
124        .overlay
125        .lock()
126        .unwrap()
127        .get(&db_handle.tree, &serialize(&zkbin.namespace))
128    {
129        Ok(v) => {
130            if let Some(bytes) = v {
131                // We allow a panic here because this db should never be corrupted in this way.
132                let (existing_zkbin, _): (Vec<u8>, Vec<u8>) =
133                    deserialize(&bytes).expect("deserialize tuple");
134
135                if existing_zkbin == zkbin_bytes {
136                    debug!(
137                        target: "runtime::db::zkas_db_set",
138                        "[WASM] [{cid}] zkas_db_set(): Existing zkas bincode is the same. Skipping."
139                    );
140                    return wasm::entrypoint::SUCCESS
141                }
142            }
143        }
144        Err(e) => {
145            error!(
146                target: "runtime::db::zkas_db_set",
147                "[WASM] [{cid}] zkas_db_set(): Internal error getting from tree: {e}"
148            );
149            return darkfi_sdk::error::DB_SET_FAILED
150        }
151    };
152
153    // We didn't find any existing bincode, so let's create a new VerifyingKey and write it all.
154    info!(
155        target: "runtime::db::zkas_db_set",
156        "[WASM] [{cid}] zkas_db_set(): Creating VerifyingKey for {} zkas circuit",
157        zkbin.namespace,
158    );
159
160    let witnesses = match empty_witnesses(&zkbin) {
161        Ok(w) => w,
162        Err(e) => {
163            error!(
164                target: "runtime::db::zkas_db_set",
165                "[WASM] [{cid}] zkas_db_set(): Failed to create empty witnesses: {e}"
166            );
167            return darkfi_sdk::error::DB_SET_FAILED
168        }
169    };
170
171    // Construct the circuit and build the VerifyingKey
172    let circuit = ZkCircuit::new(witnesses, &zkbin);
173    let vk = VerifyingKey::build(zkbin.k, &circuit);
174    let mut vk_buf = vec![];
175    if let Err(e) = vk.write(&mut vk_buf) {
176        error!(
177            target: "runtime::db::zkas_db_set",
178            "[WASM] [{cid}] zkas_db_set(): Failed to serialize VerifyingKey: {e}"
179        );
180        return darkfi_sdk::error::DB_SET_FAILED
181    }
182
183    // Insert the key-value pair into the database.
184    let key = serialize(&zkbin.namespace);
185    let value = serialize(&(zkbin_bytes, vk_buf));
186    if env
187        .blockchain
188        .lock()
189        .unwrap()
190        .overlay
191        .lock()
192        .unwrap()
193        .insert(&db_handle.tree, &key, &value)
194        .is_err()
195    {
196        error!(
197            target: "runtime::db::zkas_db_set",
198            "[WASM] [{cid}] zkas_db_set(): Couldn't insert to db_handle tree"
199        );
200        return darkfi_sdk::error::DB_SET_FAILED
201    }
202    drop(db_handles);
203
204    // Subtract used gas. Here we count the bytes written into the db.
205    env.subtract_gas(&mut store, (key.len() + value.len()) as u64);
206
207    wasm::entrypoint::SUCCESS
208}