darkfi/runtime/import/
merkle.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 std::io::Cursor;
20
21use darkfi_sdk::{
22    crypto::{pasta_prelude::Field, MerkleNode, MerkleTree},
23    hex::AsHex,
24    pasta::pallas,
25    wasm,
26};
27use darkfi_serial::{serialize, Decodable, Encodable, WriteExt};
28use tracing::{debug, error};
29use wasmer::{FunctionEnvMut, WasmPtr};
30
31use crate::runtime::{
32    import::{acl::acl_allow, util::wasm_mem_read},
33    vm_runtime::{ContractSection, Env},
34};
35
36/// Add data to an on-chain Merkle tree.
37///
38/// Expects:
39/// * `db_info`: Handle where the Merkle tree is stored
40/// * `db_roots`: Handle where all new Merkle roots are stored
41/// * `root_key`: Serialized key pointing to latest root in `db_info`
42/// * `tree_key`: Serialized key pointing to the Merkle tree in `db_info`
43/// * `coins`: Items we want to add to the Merkle tree
44///
45/// ## Permissions
46/// * `ContractSection::Update`
47pub(crate) fn merkle_add(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
48    merkle_add_internal(ctx, ptr, ptr_len, false)
49}
50
51/// Add data to a tx-local Merkle tree.
52///
53/// Expects:
54/// * `db_info`: Handle where the Merkle tree is stored
55/// * `db_roots`: Handle where all new Merkle roots are stored
56/// * `root_key`: Serialized key pointing to latest root in `db_info`
57/// * `tree_key`: Serialized key pointing to the Merkle tree in `db_info`
58/// * `coins`: Items we want to add to the Merkle tree
59///
60/// ## Permissions
61/// * `ContractSection::Update`
62pub(crate) fn merkle_add_local(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
63    merkle_add_internal(ctx, ptr, ptr_len, true)
64}
65
66/// Internal function for `merkle_add` which branches to either on-chain
67/// or transaction-local.
68pub(crate) fn merkle_add_internal(
69    mut ctx: FunctionEnvMut<Env>,
70    ptr: WasmPtr<u8>,
71    ptr_len: u32,
72    local: bool,
73) -> i64 {
74    let lt = if local { "merkle_add_local" } else { "merkle_add" };
75    let (env, mut store) = ctx.data_and_store_mut();
76    let cid = env.contract_id;
77
78    // Enforce function ACL
79    if let Err(e) = acl_allow(env, &[ContractSection::Update]) {
80        error!(
81            target: "runtime::merkle::{lt}",
82            "[WASM] [{cid}] {lt}(): Called in unauthorized section: {e}",
83        );
84        return darkfi_sdk::error::CALLER_ACCESS_DENIED
85    }
86
87    // Subtract used gas. 1 for opcode, 33 for value_data.len().
88    env.subtract_gas(&mut store, 34);
89
90    // Get the wasm memory reader
91    let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
92        Ok(v) => v,
93        Err(e) => {
94            error!(
95                target: "runtime::merkle::{lt}",
96                "[WASM] [{cid}] {lt}(): Failed to read wasm memory: {e}",
97            );
98            return darkfi_sdk::error::INTERNAL_ERROR
99        }
100    };
101
102    // The buffer should deserialize intto:
103    // - db_info (DbHandle)
104    // - db_roots (DbHandle)
105    // - root_key (Vec<u8>)
106    // - tree_key (Vec<u8>)
107    // - coins (Vec<MerkleNode>)
108
109    let db_info_index: u32 = match Decodable::decode(&mut buf_reader) {
110        Ok(v) => v,
111        Err(e) => {
112            error!(
113                target: "runtime::merkle::{lt}",
114                "[WASM] [{cid}] {lt}(): Failed to decode db_info DbHandle: {e}",
115            );
116            return darkfi_sdk::error::INTERNAL_ERROR
117        }
118    };
119
120    let db_roots_index: u32 = match Decodable::decode(&mut buf_reader) {
121        Ok(v) => v,
122        Err(e) => {
123            error!(
124                target: "runtime::merkle::{lt}",
125                "[WASM] [{cid}] {lt}(): Failed to decode db_roots DbHandle: {e}",
126            );
127            return darkfi_sdk::error::INTERNAL_ERROR
128        }
129    };
130
131    // Fetch the required db handles
132    let db_info_index = db_info_index as usize;
133    let db_roots_index = db_roots_index as usize;
134    let db_handles = if local { env.local_db_handles.borrow() } else { env.db_handles.borrow() };
135    let n_dbs = db_handles.len();
136
137    if n_dbs <= db_info_index || n_dbs <= db_roots_index {
138        error!(
139            target: "runtime::merkle::{lt}",
140            "[WASM] [{cid}] {lt}(): Requested DbHandle that is out of bounds",
141        );
142        return darkfi_sdk::error::INTERNAL_ERROR
143    }
144
145    let db_info = &db_handles[db_info_index];
146    let db_roots = &db_handles[db_roots_index];
147
148    // Make sure that the contract owns the dbs it wants to write to
149    if db_info.contract_id != cid || db_roots.contract_id != cid {
150        error!(
151            target: "runtime::merkle::{lt}",
152            "[WASM] [{cid}] {lt}(): Unauthorized write to DbHandle",
153        );
154        return darkfi_sdk::error::CALLER_ACCESS_DENIED
155    }
156
157    // This key represents the key in db_info where the latest root is
158    let root_key: Vec<u8> = match Decodable::decode(&mut buf_reader) {
159        Ok(v) => v,
160        Err(e) => {
161            error!(
162                target: "runtime::merkle::{lt}",
163                "[WASM] [{cid}] {lt}(): Failed to decode root_key Vec: {e}",
164            );
165            return darkfi_sdk::error::INTERNAL_ERROR
166        }
167    };
168
169    // This key represents the key in db_info where the Merkle tree is
170    let tree_key: Vec<u8> = match Decodable::decode(&mut buf_reader) {
171        Ok(v) => v,
172        Err(e) => {
173            error!(
174                target: "runtime::merkle::{lt}",
175                "[WASM] [{cid}] {lt}(): Failed to decode tree_key Vec: {e}",
176            );
177            return darkfi_sdk::error::INTERNAL_ERROR
178        }
179    };
180
181    // Coins represent the leaf(s) we're adding to the Merkle tree
182    let coins: Vec<MerkleNode> = match Decodable::decode(&mut buf_reader) {
183        Ok(v) => v,
184        Err(e) => {
185            error!(
186                target: "runtime::merkle::{lt}",
187                "[WASM] [{cid}] {lt}(): Failed to decode Vec<MerkleNode>: {e}",
188            );
189            return darkfi_sdk::error::INTERNAL_ERROR
190        }
191    };
192
193    // Make sure we've read the entire buffer
194    if buf_reader.position() != ptr_len as u64 {
195        error!(
196            target: "runtime::merkle::{lt}",
197            "[WASM] [{cid}] {lt}(): Trailing bytes in argument stream",
198        );
199        return darkfi_sdk::error::INTERNAL_ERROR
200    }
201
202    // Even with tx-local, we will lock the blockchain db to make sure
203    // it does not change for any reason during this execution.
204    let blockchain = env.blockchain.lock().unwrap();
205    let mut overlay = blockchain.overlay.lock().unwrap();
206    let mut tx_local_db = env.tx_local.lock();
207
208    // Read the current Merkle tree.
209    let tree_bytes = if local {
210        let Some(db_cid) = tx_local_db.get_mut(&db_info.contract_id) else {
211            error!(
212                target: "runtime::db::{lt}",
213                "[WASM] [{cid}] {lt}(): Could not find db for {}",
214                db_info.contract_id,
215            );
216            return darkfi_sdk::error::INTERNAL_ERROR
217        };
218
219        // Fetch or initialize this db tree
220        let tree = db_cid.entry(db_info.tree).or_default();
221
222        match tree.get(&tree_key) {
223            Some(v) => Some(v).cloned(),
224            None => {
225                // If our tx-local db does not contain the Merkle tree,
226                // initialize it with a "zero" leaf.
227                let mut merkle_tree = MerkleTree::new(1);
228                merkle_tree.append(MerkleNode::from(pallas::Base::ZERO));
229                let mut merkle_tree_data = vec![];
230                merkle_tree_data.write_u32(0).unwrap();
231                merkle_tree.encode(&mut merkle_tree_data).unwrap();
232                Some(merkle_tree_data)
233            }
234        }
235    } else {
236        match overlay.get(&db_info.tree, &tree_key) {
237            Ok(v) => v.map(|iv| iv.to_vec()),
238            Err(e) => {
239                error!(
240                    target: "runtime::merkle::{lt}",
241                    "[WASM] [{cid}] {lt}(): Error getting from sled tree: {e}",
242                );
243                return darkfi_sdk::error::INTERNAL_ERROR
244            }
245        }
246    };
247
248    let Some(tree_bytes) = tree_bytes else {
249        error!(
250            target: "runtime::merkle::{lt}",
251            "[WASM] [{cid}] {lt}(): Merkle tree k/v is empty",
252        );
253        return darkfi_sdk::error::INTERNAL_ERROR
254    };
255
256    // Deserialize the tree
257    debug!(target: "runtime::merkle::{lt}", "Serialized tree: {} bytes", tree_bytes.len());
258    debug!(target: "runtime::merkle::{lt}", "{}", tree_bytes.hex());
259
260    let mut decoder = Cursor::new(&tree_bytes);
261    let set_size: u32 = match Decodable::decode(&mut decoder) {
262        Ok(v) => v,
263        Err(e) => {
264            error!(
265                target: "runtime::merkle::{lt}",
266                "[WASM] [{cid}] {lt}(): Unable to decode set size: {e}",
267            );
268            return darkfi_sdk::error::INTERNAL_ERROR
269        }
270    };
271
272    let mut merkle_tree: MerkleTree = match Decodable::decode(&mut decoder) {
273        Ok(v) => v,
274        Err(e) => {
275            error!(
276                target: "runtime::merkle::{lt}",
277                "[WASM] [{cid}] {lt}(): Unable to deserialize Merkle tree: {e}",
278            );
279            return darkfi_sdk::error::INTERNAL_ERROR
280        }
281    };
282
283    // Here we add the new coins into the tree.
284    let coins_len = coins.len();
285    for coin in coins {
286        merkle_tree.append(coin);
287    }
288
289    // And we serialize the tree back to bytes
290    let mut merkle_tree_data = vec![];
291    if merkle_tree_data.write_u32(set_size + coins_len as u32).is_err() ||
292        merkle_tree.encode(&mut merkle_tree_data).is_err()
293    {
294        error!(
295            target: "runtime::merkle::{lt}",
296            "[WASM] [{cid}] {lt}(): Could not serialize modified Merkle tree",
297        );
298        return darkfi_sdk::error::INTERNAL_ERROR
299    }
300
301    // Apply changes
302    if local {
303        // We unwrap here because we already know the databases exist
304        // from when we fetched the tree.
305        let db_cid = tx_local_db.get_mut(&db_info.contract_id).unwrap();
306        let tree = db_cid.get_mut(&db_info.tree).unwrap();
307        tree.insert(tree_key, merkle_tree_data);
308    } else if let Err(e) = overlay.insert(&db_info.tree, &tree_key, &merkle_tree_data) {
309        error!(
310            target: "runtime::merkle::{lt}",
311            "[WASM] [{cid}] {lt}(): Could not insert tree to db_info: {e}",
312        );
313        return darkfi_sdk::error::INTERNAL_ERROR
314    }
315
316    // Here we add the Merkle root to our set of roots
317    // Since each update to the tree is atomic, we only need to add the last
318    // known root.
319    let Some(latest_root) = merkle_tree.root(0) else {
320        error!(
321            target: "runtime::merkle::{lt}",
322            "[WASM] [{cid}] {lt}(): Unable to read Merkle tree root",
323        );
324        return darkfi_sdk::error::INTERNAL_ERROR
325    };
326
327    debug!(
328        target: "runtime::merkle::{lt}",
329        "[WASM] [{cid}] {lt}(): Appending Merkle root to db: {latest_root:?}",
330    );
331
332    let latest_root_data = serialize(&latest_root);
333    assert_eq!(latest_root_data.len(), 32);
334
335    let mut value_data = Vec::with_capacity(32 + 1);
336    env.tx_hash.inner().encode(&mut value_data).expect("Unable to serialize tx_hash");
337    env.call_idx.encode(&mut value_data).expect("Unable to serialize call_idx");
338    assert_eq!(value_data.len(), 32 + 1);
339
340    if local {
341        // We unwrap here because we already know the databases exist
342        // from when we fetched the tree.
343        let db_cid = tx_local_db.get_mut(&db_info.contract_id).unwrap();
344
345        let info_tree = db_cid.get_mut(&db_info.tree).unwrap();
346        info_tree.insert(root_key, latest_root_data.clone());
347
348        let roots_tree = db_cid.entry(db_roots.tree).or_default();
349        roots_tree.insert(latest_root_data, value_data);
350    } else {
351        if let Err(e) = overlay.insert(&db_roots.tree, &latest_root_data, &value_data) {
352            error!(
353                target: "runtime::merkle::{lt}",
354                "[WASM] [{cid}] {lt}(): Could not insert to db_roots tree: {e}",
355            );
356            return darkfi_sdk::error::INTERNAL_ERROR
357        }
358
359        if let Err(e) = overlay.insert(&db_info.tree, &root_key, &latest_root_data) {
360            error!(
361                target: "runtime::merkle::{lt}",
362                "[WASM] [{cid}] {lt}(): Could not insert latest root to db_info: {e}",
363            );
364            return darkfi_sdk::error::INTERNAL_ERROR
365        }
366    }
367
368    // Subtract used gas.
369    drop(tx_local_db);
370    drop(overlay);
371    drop(blockchain);
372    drop(db_handles);
373    let spent_gas = coins_len * 32;
374    env.subtract_gas(&mut store, spent_gas as u64);
375
376    wasm::entrypoint::SUCCESS
377}